mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-07-29 08:47:45 +00:00
Compare commits
1 Commits
3452
...
util-lua-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ed9d5893b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -73,7 +73,6 @@ buck-out/
|
||||
# Build artifacts
|
||||
*.jsbundle
|
||||
*.framework
|
||||
android/app/debug
|
||||
android/app/release
|
||||
|
||||
# precommit-hook
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.view.KeyEvent;
|
||||
import org.jitsi.meet.sdk.JitsiMeet;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
|
||||
import org.jitsi.meet.sdk.JitsiMeetUserInfo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
@@ -107,7 +106,7 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
//
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
|
||||
&& canRequestOverlayPermission()) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
|
||||
@@ -169,7 +169,7 @@ ext {
|
||||
glideVersion = "4.7.1" // keep in sync with react-native-fast-image
|
||||
|
||||
// Libre build
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "true").toBoolean()
|
||||
}
|
||||
|
||||
// If Android SDK is not installed, accept its license so that it
|
||||
|
||||
@@ -14,7 +14,6 @@ if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
|
||||
exit 2
|
||||
fi
|
||||
else
|
||||
adb reverse tcp:8081 tcp:8081
|
||||
CMD="${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command"
|
||||
if [[ `uname` == "Darwin" ]]; then
|
||||
open -g "${CMD}" || echo "Can't start packager automatically"
|
||||
|
||||
@@ -143,11 +143,6 @@ public class JitsiMeetActivity extends FragmentActivity
|
||||
// Activity lifecycle methods
|
||||
//
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
JitsiMeetActivityDelegate.onActivityResult(this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
JitsiMeetActivityDelegate.onBackPressed();
|
||||
|
||||
@@ -54,11 +54,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
*/
|
||||
private Bundle colorScheme;
|
||||
|
||||
/**
|
||||
* Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
|
||||
*/
|
||||
private Bundle featureFlags;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to join the conference with audio / video muted or to start in audio
|
||||
* only mode respectively.
|
||||
@@ -68,9 +63,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
private Boolean videoMuted;
|
||||
|
||||
/**
|
||||
* USer information, to be used when no token is specified.
|
||||
* Set to {@code true} to enable the welcome page. Typically SDK users won't need this enabled
|
||||
* since the host application decides which meeting to join.
|
||||
*/
|
||||
private JitsiMeetUserInfo userInfo;
|
||||
private Boolean welcomePageEnabled;
|
||||
|
||||
/**
|
||||
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
|
||||
@@ -82,16 +78,14 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
private String token;
|
||||
|
||||
private Bundle colorScheme;
|
||||
private Bundle featureFlags;
|
||||
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
private JitsiMeetUserInfo userInfo;
|
||||
private Boolean welcomePageEnabled;
|
||||
|
||||
public Builder() {
|
||||
featureFlags = new Bundle();
|
||||
}
|
||||
|
||||
/**\
|
||||
@@ -192,31 +186,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setWelcomePageEnabled(boolean enabled) {
|
||||
this.featureFlags.putBoolean("welcomepage.enabled", enabled);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFeatureFlag(String flag, boolean value) {
|
||||
this.featureFlags.putBoolean(flag, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFeatureFlag(String flag, String value) {
|
||||
this.featureFlags.putString(flag, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFeatureFlag(String flag, int value) {
|
||||
this.featureFlags.putInt(flag, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUserInfo(JitsiMeetUserInfo userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
this.welcomePageEnabled = enabled;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -234,11 +204,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
options.subject = this.subject;
|
||||
options.token = this.token;
|
||||
options.colorScheme = this.colorScheme;
|
||||
options.featureFlags = this.featureFlags;
|
||||
options.audioMuted = this.audioMuted;
|
||||
options.audioOnly = this.audioOnly;
|
||||
options.videoMuted = this.videoMuted;
|
||||
options.userInfo = this.userInfo;
|
||||
options.welcomePageEnabled = this.welcomePageEnabled;
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -252,30 +221,30 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
subject = in.readString();
|
||||
token = in.readString();
|
||||
colorScheme = in.readBundle();
|
||||
featureFlags = in.readBundle();
|
||||
userInfo = new JitsiMeetUserInfo(in.readBundle());
|
||||
byte tmpAudioMuted = in.readByte();
|
||||
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
|
||||
byte tmpAudioOnly = in.readByte();
|
||||
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
|
||||
byte tmpVideoMuted = in.readByte();
|
||||
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
|
||||
byte tmpWelcomePageEnabled = in.readByte();
|
||||
welcomePageEnabled = tmpWelcomePageEnabled == 0 ? null : tmpWelcomePageEnabled == 1;
|
||||
}
|
||||
|
||||
Bundle asProps() {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
// Android always has the PiP flag set by default.
|
||||
if (!featureFlags.containsKey("pip.enabled")) {
|
||||
featureFlags.putBoolean("pip.enabled", true);
|
||||
}
|
||||
|
||||
props.putBundle("flags", featureFlags);
|
||||
|
||||
if (colorScheme != null) {
|
||||
props.putBundle("colorScheme", colorScheme);
|
||||
}
|
||||
|
||||
if (welcomePageEnabled != null) {
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
}
|
||||
|
||||
// TODO: get rid of this.
|
||||
props.putBoolean("pictureInPictureEnabled", true);
|
||||
|
||||
Bundle config = new Bundle();
|
||||
|
||||
if (audioMuted != null) {
|
||||
@@ -309,10 +278,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
if (token == null && userInfo != null) {
|
||||
props.putBundle("userInfo", userInfo.asBundle());
|
||||
}
|
||||
|
||||
urlProps.putBundle("config", config);
|
||||
props.putBundle("url", urlProps);
|
||||
|
||||
@@ -340,11 +305,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
dest.writeString(subject);
|
||||
dest.writeString(token);
|
||||
dest.writeBundle(colorScheme);
|
||||
dest.writeBundle(featureFlags);
|
||||
dest.writeBundle(userInfo.asBundle());
|
||||
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
||||
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
|
||||
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
|
||||
dest.writeByte((byte) (welcomePageEnabled == null ? 0 : welcomePageEnabled ? 1 : 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,6 +26,8 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* This class represents user information to be passed to {@link JitsiMeetConferenceOptions} for
|
||||
* identifying a user.
|
||||
*/
|
||||
public class JitsiMeetUserInfo {
|
||||
/**
|
||||
* User's display name.
|
||||
*/
|
||||
private String displayName;
|
||||
|
||||
/**
|
||||
* User's email address.
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* User's avatar URL.
|
||||
*/
|
||||
private URL avatar;
|
||||
|
||||
public JitsiMeetUserInfo() {}
|
||||
|
||||
public JitsiMeetUserInfo(Bundle b) {
|
||||
super();
|
||||
|
||||
if (b.containsKey("displayName")) {
|
||||
displayName = b.getString("displayName");
|
||||
}
|
||||
|
||||
if (b.containsKey("email")) {
|
||||
email = b.getString("email");
|
||||
}
|
||||
|
||||
if (b.containsKey("avatarURL")) {
|
||||
String avatarURL = b.getString("avatarURL");
|
||||
try {
|
||||
avatar = new URL(avatarURL);
|
||||
} catch (MalformedURLException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public URL getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(URL avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
Bundle asBundle() {
|
||||
Bundle b = new Bundle();
|
||||
|
||||
if (displayName != null) {
|
||||
b.putString("displayName", displayName);
|
||||
}
|
||||
|
||||
if (email != null) {
|
||||
b.putString("email", email);
|
||||
}
|
||||
|
||||
if (avatar != null) {
|
||||
b.putString("avatarURL", avatar.toString());
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||
|
||||
import {
|
||||
createDeviceChangedEvent,
|
||||
createStartSilentEvent,
|
||||
createScreenSharingEvent,
|
||||
createStreamSwitchDelayEvent,
|
||||
createTrackMutedEvent,
|
||||
@@ -86,8 +85,6 @@ import {
|
||||
localParticipantConnectionStatusChanged,
|
||||
localParticipantRoleChanged,
|
||||
participantConnectionStatusChanged,
|
||||
participantKicked,
|
||||
participantMutedUs,
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
@@ -105,7 +102,6 @@ import {
|
||||
getLocationContextRoot,
|
||||
getJitsiMeetGlobalNS
|
||||
} from './react/features/base/util';
|
||||
import { notifyKickedOut } from './react/features/conference';
|
||||
import { addMessage } from './react/features/chat';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
@@ -733,7 +729,6 @@ export default {
|
||||
// based on preferred devices, loose label matching can be done in
|
||||
// cases where the exact ID match is no longer available, such as
|
||||
// when the camera device has switched USB ports.
|
||||
// when in startSilent mode we want to start with audio muted
|
||||
this._initDeviceList()
|
||||
.catch(error => logger.warn(
|
||||
'initial device list initialization failed', error))
|
||||
@@ -741,7 +736,7 @@ export default {
|
||||
options.roomName, {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: config.startWithAudioMuted || config.startSilent,
|
||||
startWithAudioMuted: config.startWithAudioMuted,
|
||||
startWithVideoMuted: config.startWithVideoMuted
|
||||
}))
|
||||
.then(([ tracks, con ]) => {
|
||||
@@ -798,7 +793,6 @@ export default {
|
||||
}
|
||||
|
||||
if (config.startSilent) {
|
||||
sendAnalytics(createStartSilentEvent());
|
||||
APP.store.dispatch(showNotification({
|
||||
descriptionKey: 'notify.startSilentDescription',
|
||||
titleKey: 'notify.startSilentTitle'
|
||||
@@ -1861,12 +1855,6 @@ export default {
|
||||
APP.UI.setAudioLevel(id, newLvl);
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, (_, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
APP.store.dispatch(participantMutedUs(participantThatMutedUs));
|
||||
}
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
|
||||
APP.UI.showToolbar(6000);
|
||||
});
|
||||
@@ -1967,17 +1955,13 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.KICKED, participant => {
|
||||
room.on(JitsiConferenceEvents.KICKED, () => {
|
||||
APP.UI.hideStats();
|
||||
APP.store.dispatch(notifyKickedOut(participant));
|
||||
APP.UI.notifyKicked();
|
||||
|
||||
// FIXME close
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.PARTICIPANT_KICKED, (kicker, kicked) => {
|
||||
APP.store.dispatch(participantKicked(kicker, kicked));
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.SUSPEND_DETECTED, () => {
|
||||
APP.store.dispatch(suspendDetected());
|
||||
|
||||
@@ -2273,8 +2257,7 @@ export default {
|
||||
APP.keyboardshortcut.init();
|
||||
|
||||
if (config.requireDisplayName
|
||||
&& !APP.conference.getLocalDisplayName()
|
||||
&& !this._room.isHidden()) {
|
||||
&& !APP.conference.getLocalDisplayName()) {
|
||||
APP.UI.promptDisplayName();
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,9 @@
|
||||
}
|
||||
}
|
||||
i.disabled, .disabled i {
|
||||
cursor: initial !important;
|
||||
color: #fff !important;
|
||||
background-color: #a4b8d1 !important;
|
||||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
|
||||
.icon-mic-disabled, .icon-microphone, .icon-camera-disabled, .icon-camera {
|
||||
|
||||
@@ -23,9 +23,14 @@
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
#largeVideoBackground {
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
&.fit-full-height #largeVideoBackground {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fit-full-width #largeVideoBackground {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
#largeVideoBackgroundContainer {
|
||||
@@ -541,17 +546,13 @@
|
||||
#videoNotAvailableScreen {
|
||||
text-align: center;
|
||||
#avatarContainer {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 50vh;
|
||||
display:inline-block;
|
||||
margin-top: 25vh;
|
||||
overflow: hidden;
|
||||
width: 50vh;
|
||||
|
||||
#avatar {
|
||||
border-radius: 50%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,11 +192,6 @@ The `command` parameter is String object with the name of the command. The follo
|
||||
api.executeCommand('displayName', 'New Nickname');
|
||||
```
|
||||
|
||||
* **password** - Sets the password for the room. This command requires one argument - the password name to be set.
|
||||
```javascript
|
||||
api.executeCommand('password', 'The Password');
|
||||
```
|
||||
|
||||
* **subject** - Sets the subject of the conference. This command requires one argument - the new subject to be set.
|
||||
```javascript
|
||||
api.executeCommand('subject', 'New Conference Subject');
|
||||
@@ -394,8 +389,6 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **passwordRequired** - event notifications fired when failing to join a room because it has a password.
|
||||
|
||||
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
|
||||
@@ -150,8 +150,8 @@ PODS:
|
||||
- React/RCTBlob
|
||||
- RNCAsyncStorage (1.3.4):
|
||||
- React
|
||||
- RNGoogleSignin (2.0.0):
|
||||
- GoogleSignIn (~> 4.4.0)
|
||||
- RNGoogleSignin (1.0.2):
|
||||
- GoogleSignIn
|
||||
- React
|
||||
- RNSound (0.10.12):
|
||||
- React/Core
|
||||
@@ -291,7 +291,7 @@ SPEC CHECKSUMS:
|
||||
react-native-webrtc: 90a847d19deb2d7323fef8cc89ca12b8995fbc90
|
||||
react-native-webview: a95842e3f351a6d2c8bc8bcc9eab689c7e7e5ad4
|
||||
RNCAsyncStorage: 8e31405a9f12fbf42c2bb330e4560bfd79c18323
|
||||
RNGoogleSignin: d030c6c6591db24c3cee649f64c7babf0a1699a0
|
||||
RNGoogleSignin: 361174d9a3090d295b06257162b560d8efc8a6ed
|
||||
RNSound: e157320f503bdd4f4ee6d8542e948d54f90c3c3a
|
||||
RNVectorIcons: d819334932bcda3332deb3d2c8ea4d069e0b98f9
|
||||
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
|
||||
@@ -300,4 +300,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: b55338cc43312051ed83f8d9c6aadbd8c9402e6a
|
||||
|
||||
COCOAPODS: 1.7.2
|
||||
COCOAPODS: 1.6.1
|
||||
|
||||
@@ -46,12 +46,6 @@
|
||||
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
|
||||
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
|
||||
builder.welcomePageEnabled = YES;
|
||||
|
||||
// Apple rejected our app because they claim requiring a
|
||||
// Dropbox account for recording is not acceptable.
|
||||
#if DEBUG
|
||||
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
|
||||
#endif
|
||||
}];
|
||||
|
||||
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
|
||||
@@ -96,10 +96,4 @@
|
||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data];
|
||||
}
|
||||
|
||||
#if 0
|
||||
- (void)enterPictureInPicture:(NSDictionary *)data {
|
||||
[self _onJitsiMeetViewDelegateEvent:@"ENTER_PICTURE_IN_PICTURE" withData:data];
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; };
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
|
||||
DE762DB422AFDE76000DEBD6 /* JitsiMeetUserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DE762DB622AFDE8D000DEBD6 /* JitsiMeetUserInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DE762DB522AFDE8D000DEBD6 /* JitsiMeetUserInfo.m */; };
|
||||
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */; };
|
||||
DEAFA779229EAD520033A7FA /* RNRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAFA778229EAD520033A7FA /* RNRootView.m */; };
|
||||
@@ -96,9 +94,6 @@
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetUserInfo.h; sourceTree = "<group>"; };
|
||||
DE762DB522AFDE8D000DEBD6 /* JitsiMeetUserInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetUserInfo.m; sourceTree = "<group>"; };
|
||||
DE762DB722AFE166000DEBD6 /* JitsiMeetUserInfo+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetUserInfo+Private.h"; sourceTree = "<group>"; };
|
||||
DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetConferenceOptions.h; sourceTree = "<group>"; };
|
||||
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetConferenceOptions.m; sourceTree = "<group>"; };
|
||||
DEAD3228220C734300E93636 /* JitsiMeetConferenceOptions+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetConferenceOptions+Private.h"; sourceTree = "<group>"; };
|
||||
@@ -179,9 +174,6 @@
|
||||
DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */,
|
||||
DEAD3228220C734300E93636 /* JitsiMeetConferenceOptions+Private.h */,
|
||||
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */,
|
||||
DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */,
|
||||
DE762DB722AFE166000DEBD6 /* JitsiMeetUserInfo+Private.h */,
|
||||
DE762DB522AFDE8D000DEBD6 /* JitsiMeetUserInfo.m */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
DEAFA777229EAD3B0033A7FA /* RNRootView.h */,
|
||||
@@ -267,7 +259,6 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DE762DB422AFDE76000DEBD6 /* JitsiMeetUserInfo.h in Headers */,
|
||||
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
@@ -493,7 +484,6 @@
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
DEAFA779229EAD520033A7FA /* RNRootView.m in Sources */,
|
||||
DE762DB622AFDE8D000DEBD6 /* JitsiMeetUserInfo.m in Sources */,
|
||||
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */,
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "JitsiMeetUserInfo.h"
|
||||
|
||||
|
||||
@interface JitsiMeetConferenceOptionsBuilder : NSObject
|
||||
|
||||
/**
|
||||
@@ -44,11 +41,6 @@
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
||||
|
||||
/**
|
||||
* Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
|
||||
*/
|
||||
@property (nonatomic, readonly, nonnull) NSDictionary *featureFlags;
|
||||
|
||||
/**
|
||||
* Set to YES to join the conference with audio / video muted or to start in audio
|
||||
* only mode respectively.
|
||||
@@ -63,14 +55,6 @@
|
||||
*/
|
||||
@property (nonatomic) BOOL welcomePageEnabled;
|
||||
|
||||
/**
|
||||
* Information about the local user. It will be used in absence of a token.
|
||||
*/
|
||||
@property (nonatomic, nullable) JitsiMeetUserInfo *userInfo;
|
||||
|
||||
- (void)setFeatureFlag:(NSString *_Nonnull)flag withBoolean:(BOOL)value;
|
||||
- (void)setFeatureFlag:(NSString *_Nonnull)flag withValue:(id _Nonnull)value;
|
||||
|
||||
@end
|
||||
|
||||
@interface JitsiMeetConferenceOptions : NSObject
|
||||
@@ -82,7 +66,6 @@
|
||||
@property (nonatomic, copy, nullable, readonly) NSString *token;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
||||
@property (nonatomic, readonly, nonnull) NSDictionary *featureFlags;
|
||||
|
||||
@property (nonatomic, readonly) BOOL audioOnly;
|
||||
@property (nonatomic, readonly) BOOL audioMuted;
|
||||
@@ -90,8 +73,6 @@
|
||||
|
||||
@property (nonatomic, readonly) BOOL welcomePageEnabled;
|
||||
|
||||
@property (nonatomic, nullable) JitsiMeetUserInfo *userInfo;
|
||||
|
||||
+ (instancetype _Nonnull)fromBuilder:(void (^_Nonnull)(JitsiMeetConferenceOptionsBuilder *_Nonnull))initBlock;
|
||||
- (instancetype _Nonnull)init NS_UNAVAILABLE;
|
||||
|
||||
|
||||
@@ -17,19 +17,12 @@
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import "JitsiMeetConferenceOptions+Private.h"
|
||||
#import "JitsiMeetUserInfo+Private.h"
|
||||
|
||||
/**
|
||||
* Backwards compatibility: turn the boolean property into a feature flag.
|
||||
*/
|
||||
static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
|
||||
|
||||
@implementation JitsiMeetConferenceOptionsBuilder {
|
||||
NSNumber *_audioOnly;
|
||||
NSNumber *_audioMuted;
|
||||
NSNumber *_videoMuted;
|
||||
NSMutableDictionary *_featureFlags;
|
||||
NSNumber *_welcomePageEnabled;
|
||||
}
|
||||
|
||||
@dynamic audioOnly;
|
||||
@@ -45,26 +38,17 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
_token = nil;
|
||||
|
||||
_colorScheme = nil;
|
||||
_featureFlags = [[NSMutableDictionary alloc] init];
|
||||
|
||||
_audioOnly = nil;
|
||||
_audioMuted = nil;
|
||||
_videoMuted = nil;
|
||||
|
||||
_userInfo = nil;
|
||||
_welcomePageEnabled = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFeatureFlag:(NSString *)flag withBoolean:(BOOL)value {
|
||||
[self setFeatureFlag:flag withValue:[NSNumber numberWithBool:value]];
|
||||
}
|
||||
|
||||
- (void)setFeatureFlag:(NSString *)flag withValue:(id)value {
|
||||
_featureFlags[flag] = value;
|
||||
}
|
||||
|
||||
#pragma mark - Dynamic properties
|
||||
|
||||
- (void)setAudioOnly:(BOOL)audioOnly {
|
||||
@@ -92,14 +76,11 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
}
|
||||
|
||||
- (void)setWelcomePageEnabled:(BOOL)welcomePageEnabled {
|
||||
[self setFeatureFlag:WelcomePageEnabledFeatureFlag
|
||||
withBoolean:welcomePageEnabled];
|
||||
_welcomePageEnabled = [NSNumber numberWithBool:welcomePageEnabled];
|
||||
}
|
||||
|
||||
- (BOOL)welcomePageEnabled {
|
||||
NSNumber *n = _featureFlags[WelcomePageEnabledFeatureFlag];
|
||||
|
||||
return n != nil ? [n boolValue] : NO;
|
||||
return _welcomePageEnabled && [_welcomePageEnabled boolValue];
|
||||
}
|
||||
|
||||
#pragma mark - Private API
|
||||
@@ -116,13 +97,17 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
return _videoMuted;
|
||||
}
|
||||
|
||||
- (NSNumber *)getWelcomePageEnabled {
|
||||
return _welcomePageEnabled;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiMeetConferenceOptions {
|
||||
NSNumber *_audioOnly;
|
||||
NSNumber *_audioMuted;
|
||||
NSNumber *_videoMuted;
|
||||
NSDictionary *_featureFlags;
|
||||
NSNumber *_welcomePageEnabled;
|
||||
}
|
||||
|
||||
@dynamic audioOnly;
|
||||
@@ -145,9 +130,7 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
}
|
||||
|
||||
- (BOOL)welcomePageEnabled {
|
||||
NSNumber *n = _featureFlags[WelcomePageEnabledFeatureFlag];
|
||||
|
||||
return n != nil ? [n boolValue] : NO;
|
||||
return _welcomePageEnabled && [_welcomePageEnabled boolValue];
|
||||
}
|
||||
|
||||
#pragma mark - Internal initializer
|
||||
@@ -165,9 +148,7 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
_audioMuted = [builder getAudioMuted];
|
||||
_videoMuted = [builder getVideoMuted];
|
||||
|
||||
_featureFlags = [NSDictionary dictionaryWithDictionary:builder.featureFlags];
|
||||
|
||||
_userInfo = builder.userInfo;
|
||||
_welcomePageEnabled = [builder getWelcomePageEnabled];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -186,12 +167,14 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
- (NSDictionary *)asProps {
|
||||
NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
|
||||
|
||||
props[@"flags"] = [NSMutableDictionary dictionaryWithDictionary:_featureFlags];
|
||||
|
||||
if (_colorScheme != nil) {
|
||||
props[@"colorScheme"] = self.colorScheme;
|
||||
}
|
||||
|
||||
if (_welcomePageEnabled != nil) {
|
||||
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
|
||||
}
|
||||
|
||||
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
|
||||
if (_audioOnly != nil) {
|
||||
config[@"startAudioOnly"] = @(self.audioOnly);
|
||||
@@ -225,10 +208,6 @@ static NSString *const WelcomePageEnabledFeatureFlag = @"welcomepage.enabled";
|
||||
urlProps[@"jwt"] = _token;
|
||||
}
|
||||
|
||||
if (_token == nil && _userInfo != nil) {
|
||||
props[@"userInfo"] = [self.userInfo asDict];
|
||||
}
|
||||
|
||||
urlProps[@"config"] = config;
|
||||
props[@"url"] = urlProps;
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "JitsiMeetUserInfo.h"
|
||||
|
||||
@interface JitsiMeetUserInfo ()
|
||||
|
||||
- (NSMutableDictionary *)asDict;
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface JitsiMeetUserInfo : NSObject
|
||||
|
||||
/**
|
||||
* User display name.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *displayName;
|
||||
/**
|
||||
* User e-mail.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *email;
|
||||
/**
|
||||
* URL for the user avatar.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSURL *avatar;
|
||||
|
||||
- (instancetype _Nullable)initWithDisplayName:(NSString *_Nullable)displayName
|
||||
andEmail:(NSString *_Nullable)email
|
||||
andAvatar:(NSURL *_Nullable) avatar;
|
||||
|
||||
@end
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "JitsiMeetUserInfo+Private.h"
|
||||
|
||||
@implementation JitsiMeetUserInfo
|
||||
|
||||
- (instancetype)initWithDisplayName:(NSString *)displayName
|
||||
andEmail:(NSString *)email
|
||||
andAvatar:(NSURL *_Nullable) avatar {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.displayName = displayName;
|
||||
self.email = email;
|
||||
self.avatar = avatar;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)asDict {
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if (self.displayName != nil) {
|
||||
dict[@"displayName"] = self.displayName;
|
||||
}
|
||||
|
||||
if (self.email != nil) {
|
||||
dict[@"email"] = self.email;
|
||||
}
|
||||
|
||||
if (self.avatar != nil) {
|
||||
NSString *avatarURL = [self.avatar absoluteString];
|
||||
if (avatarURL != nil) {
|
||||
dict[@"avatarURL"] = avatarURL;
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -24,12 +24,6 @@
|
||||
#import "RNRootView.h"
|
||||
|
||||
|
||||
/**
|
||||
* Backwards compatibility: turn the boolean prop into a feature flag.
|
||||
*/
|
||||
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
|
||||
|
||||
@implementation JitsiMeetView {
|
||||
/**
|
||||
* The unique identifier of this `JitsiMeetView` within the process for the
|
||||
@@ -128,16 +122,12 @@ static void initializeViewsMap() {
|
||||
- (void)setProps:(NSDictionary *_Nonnull)newProps {
|
||||
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
|
||||
|
||||
// Set the PiP flag if it wasn't manually set.
|
||||
NSMutableDictionary *featureFlags = props[@"flags"];
|
||||
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
|
||||
featureFlags[PiPEnabledFeatureFlag]
|
||||
= [NSNumber numberWithBool:
|
||||
self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]];
|
||||
}
|
||||
|
||||
props[@"externalAPIScope"] = externalAPIScope;
|
||||
|
||||
// TODO: put this in some 'flags' field
|
||||
props[@"pictureInPictureEnabled"]
|
||||
= @(self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]);
|
||||
|
||||
// This method is supposed to be imperative i.e. a second
|
||||
// invocation with one and the same URL is expected to join the respective
|
||||
// conference again if the first invocation was followed by leaving the
|
||||
|
||||
@@ -74,12 +74,6 @@ RCT_EXPORT_METHOD(endCall:(NSString *)callUUID
|
||||
#endif
|
||||
|
||||
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||
|
||||
if (!callUUID_) {
|
||||
reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
|
||||
return;
|
||||
}
|
||||
|
||||
CXEndCallAction *action
|
||||
= [[CXEndCallAction alloc] initWithCallUUID:callUUID_];
|
||||
[self requestTransaction:[[CXTransaction alloc] initWithAction:action]
|
||||
@@ -97,12 +91,6 @@ RCT_EXPORT_METHOD(setMuted:(NSString *)callUUID
|
||||
#endif
|
||||
|
||||
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||
|
||||
if (!callUUID_) {
|
||||
reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
|
||||
return;
|
||||
}
|
||||
|
||||
CXSetMutedCallAction *action
|
||||
= [[CXSetMutedCallAction alloc] initWithCallUUID:callUUID_ muted:muted];
|
||||
[self requestTransaction:[[CXTransaction alloc] initWithAction:action]
|
||||
@@ -135,13 +123,6 @@ RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
|
||||
NSLog(@"[RNCallKit][startCall] callUUID = %@", callUUID);
|
||||
#endif
|
||||
|
||||
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||
|
||||
if (!callUUID_) {
|
||||
reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't start a new call if there's an active call for the specified
|
||||
// callUUID. JitsiMeetView was configured for an incoming call.
|
||||
if ([JMCallKitProxy hasActiveCallForUUID:callUUID]) {
|
||||
@@ -151,6 +132,7 @@ RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
|
||||
|
||||
CXHandle *handle_
|
||||
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
||||
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||
CXStartCallAction *action
|
||||
= [[CXStartCallAction alloc] initWithCallUUID:callUUID_
|
||||
handle:handle_];
|
||||
@@ -164,12 +146,6 @@ RCT_EXPORT_METHOD(reportCallFailed:(NSString *)callUUID
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||
|
||||
if (!callUUID_) {
|
||||
reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
|
||||
return;
|
||||
}
|
||||
|
||||
[JMCallKitProxy reportCallWith:callUUID_
|
||||
endedAt:nil
|
||||
reason:CXCallEndedReasonFailed];
|
||||
@@ -181,12 +157,6 @@ RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)callUUID
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||
|
||||
if (!callUUID_) {
|
||||
reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
|
||||
return;
|
||||
}
|
||||
|
||||
[JMCallKitProxy reportOutgoingCallWith:callUUID_
|
||||
connectedAt:nil];
|
||||
resolve(nil);
|
||||
@@ -205,12 +175,6 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
|
||||
#endif
|
||||
|
||||
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||
|
||||
if (!callUUID_) {
|
||||
reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *displayName = options[@"displayName"];
|
||||
BOOL hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
|
||||
|
||||
|
||||
@@ -37,15 +37,6 @@ public class PiPViewCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
public enum Position {
|
||||
case lowerRightCorner
|
||||
case upperRightCorner
|
||||
case lowerLeftCorner
|
||||
case upperLeftCorner
|
||||
}
|
||||
|
||||
public var initialPositionInSuperview = Position.lowerRightCorner
|
||||
|
||||
/// The size ratio of the view when in PiP mode
|
||||
public var pipSizeRatio: CGFloat = {
|
||||
let deviceIdiom = UIScreen.main.traitCollection.userInterfaceIdiom
|
||||
@@ -214,21 +205,9 @@ public class PiPViewCoordinator {
|
||||
let adjustedBounds = bounds.inset(by: dragBoundInsets)
|
||||
let size = CGSize(width: bounds.size.width * pipSizeRatio,
|
||||
height: bounds.size.height * pipSizeRatio)
|
||||
let origin = initialPositionFor(pipSize: size, bounds: adjustedBounds)
|
||||
return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
private func initialPositionFor(pipSize size: CGSize, bounds: CGRect) -> CGPoint {
|
||||
switch initialPositionInSuperview {
|
||||
case .lowerLeftCorner:
|
||||
return CGPoint(x: bounds.minX, y: bounds.maxY - size.height)
|
||||
case .lowerRightCorner:
|
||||
return CGPoint(x: bounds.maxX - size.width, y: bounds.maxY - size.height)
|
||||
case .upperLeftCorner:
|
||||
return CGPoint(x: bounds.minX, y: bounds.minY)
|
||||
case .upperRightCorner:
|
||||
return CGPoint(x: bounds.maxX - size.width, y: bounds.minY)
|
||||
}
|
||||
let x: CGFloat = adjustedBounds.maxX - size.width
|
||||
let y: CGFloat = adjustedBounds.maxY - size.height
|
||||
return CGRect(x: x, y: y, width: size.width, height: size.height)
|
||||
}
|
||||
|
||||
// MARK: - Animation helpers
|
||||
|
||||
@@ -121,7 +121,6 @@
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "You need the __app__ mobile app to join this meeting on your phone.",
|
||||
"description": "Nothing happened? We tried launching your meeting in the __app__ desktop app. Try again or launch it in the __app__ web app.",
|
||||
"descriptionWithoutWeb": "Nothing happened? We tried launching your meeting in the __app__ desktop app.",
|
||||
"downloadApp": "Download the app",
|
||||
"launchWebButton": "Launch in web",
|
||||
"openApp": "Continue to the app",
|
||||
@@ -177,10 +176,10 @@
|
||||
"defaultError": "There was some kind of error",
|
||||
"detectext": "Error when trying to detect desktopsharing extension.",
|
||||
"dismiss": "Dismiss",
|
||||
"displayNameRequired": "Hi! What’s your name?",
|
||||
"displayNameRequired": "Display name is required",
|
||||
"done": "Done",
|
||||
"doNotShowMessageAgain": "Don't show this message again",
|
||||
"enterDisplayName": "Please enter your name here",
|
||||
"enterDisplayName": "Please enter your display name",
|
||||
"error": "Error",
|
||||
"externalInstallationMsg": "You need to install our desktop sharing extension.",
|
||||
"externalInstallationTitle": "Extension required",
|
||||
@@ -197,11 +196,11 @@
|
||||
"internalError": "Oops! Something went wrong. The following error occurred: __error__",
|
||||
"internalErrorTitle": "Internal error",
|
||||
"joinAgain": "Join again",
|
||||
"kickMessage": "You can contact __participantDisplayName__ for more details.",
|
||||
"kickMessage": "Ouch! You have been kicked out of the meet!",
|
||||
"kickParticipantButton": "Kick",
|
||||
"kickParticipantDialog": "Are you sure you want to kick this participant?",
|
||||
"kickParticipantTitle": "Kick this member?",
|
||||
"kickTitle": "Ouch! __participantDisplayName__ kicked you out of the meeting",
|
||||
"kickTitle": "Kicked from meeting",
|
||||
"liveStreaming": "Live Streaming",
|
||||
"liveStreamingDisabledForGuestTooltip": "Guests can't start live streaming.",
|
||||
"liveStreamingDisabledTooltip": "Start live stream disabled.",
|
||||
@@ -214,8 +213,8 @@
|
||||
"maxUsersLimitReachedTitle": "Maximum members limit reached",
|
||||
"micConstraintFailedError": "Your microphone does not satisfy some of the required constraints.",
|
||||
"micNotFoundError": "Microphone was not found.",
|
||||
"micNotSendingData": "Go to your computer's settings to unmute your mic and adjust its level",
|
||||
"micNotSendingDataTitle": "Your mic is muted by your system settings",
|
||||
"micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to reload the application.",
|
||||
"micNotSendingDataTitle": "Unable to access microphone",
|
||||
"micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
|
||||
"micUnknownError": "Cannot use microphone for an unknown reason.",
|
||||
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
@@ -356,11 +355,12 @@
|
||||
"dialInTollFree": "Toll Free",
|
||||
"genericError": "Whoops, something went wrong.",
|
||||
"inviteLiveStream": "To view the live stream of this meeting, click this link: __url__",
|
||||
"invitePhone": "To join by phone instead, tap this: __number__,,__conferenceID__#\n",
|
||||
"invitePhoneAlternatives": "Looking for a different dial-in number?\nSee meeting dial-in numbers: __url__\n\n\nIf also dialing-in through a room phone, join without connecting to audio: __silentUrl__",
|
||||
"invitePhone": "One tap audio Dial In: __number__,,__conferenceID__#",
|
||||
"invitePhoneAlternatives": "Looking for a different dial in number? Please see: __url__",
|
||||
"inviteURLFirstPartGeneral": "You are invited to join a meeting.",
|
||||
"inviteURLFirstPartPersonal": "__name__ is inviting you to a meeting.\n",
|
||||
"inviteURLSecondPart": "\nJoin the meeting:\n__url__\n",
|
||||
"inviteURLFirstPartPersonal": "__name__ is inviting you to a meeting.",
|
||||
"inviteURLSecondPart": "\n__moreInfo__\nJoin meeting: __url__\n",
|
||||
"inviteURLMoreInfo": "Meeting ID: __conferenceID__#\n",
|
||||
"liveStreamURL": "Live stream:",
|
||||
"moreNumbers": "More numbers",
|
||||
"noNumbers": "No dial-in numbers.",
|
||||
@@ -472,13 +472,10 @@
|
||||
"focus": "Conference focus",
|
||||
"focusFail": "__component__ not available - retry in __ms__ sec",
|
||||
"grantedTo": "Moderator rights granted to __to__!",
|
||||
"kickParticipant": "__kicked__ was kicked by __kicker__",
|
||||
"me": "Me",
|
||||
"moderator": "Moderator rights granted!",
|
||||
"muted": "You have started the conversation muted.",
|
||||
"mutedTitle": "You're muted!",
|
||||
"mutedRemotelyTitle": "You have been muted by __participantDisplayName__!",
|
||||
"mutedRemotelyDescription": "You can always unmute when you're ready to speak. Mute back when you're done to keep noise away from the meeting.",
|
||||
"raisedHand": "__name__ would like to speak.",
|
||||
"somebody": "Somebody",
|
||||
"startSilentTitle": "You joined with no audio output!",
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
createApiEvent,
|
||||
sendAnalytics
|
||||
} from '../../react/features/analytics';
|
||||
import { setPassword, setSubject } from '../../react/features/base/conference';
|
||||
import { setSubject } from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import { toggleTileView } from '../../react/features/video-layout';
|
||||
@@ -65,28 +65,6 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('display.name.changed'));
|
||||
APP.conference.changeLocalDisplayName(displayName);
|
||||
},
|
||||
'password': password => {
|
||||
const { conference, passwordRequired }
|
||||
= APP.store.getState()['features/base/conference'];
|
||||
|
||||
if (passwordRequired) {
|
||||
sendAnalytics(createApiEvent('submit.password'));
|
||||
|
||||
APP.store.dispatch(setPassword(
|
||||
passwordRequired,
|
||||
passwordRequired.join,
|
||||
password
|
||||
));
|
||||
} else {
|
||||
sendAnalytics(createApiEvent('password.changed'));
|
||||
|
||||
APP.store.dispatch(setPassword(
|
||||
conference,
|
||||
conference.lock,
|
||||
password
|
||||
));
|
||||
}
|
||||
},
|
||||
'proxy-connection-event': event => {
|
||||
APP.conference.onProxyConnectionEvent(event);
|
||||
},
|
||||
@@ -649,16 +627,6 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application of the current meeting requiring a password
|
||||
* to join.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyOnPasswordRequired() {
|
||||
this._sendEvent({ name: 'password-required' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the screen sharing
|
||||
* has been turned on/off.
|
||||
|
||||
2
modules/API/external/external_api.js
vendored
2
modules/API/external/external_api.js
vendored
@@ -33,7 +33,6 @@ const commands = {
|
||||
displayName: 'display-name',
|
||||
email: 'email',
|
||||
hangup: 'video-hangup',
|
||||
password: 'password',
|
||||
subject: 'subject',
|
||||
submitFeedback: 'submit-feedback',
|
||||
toggleAudio: 'toggle-audio',
|
||||
@@ -64,7 +63,6 @@ const events = {
|
||||
'outgoing-message': 'outgoingMessage',
|
||||
'participant-joined': 'participantJoined',
|
||||
'participant-left': 'participantLeft',
|
||||
'password-required': 'passwordRequired',
|
||||
'proxy-connection-event': 'proxyConnectionEvent',
|
||||
'video-ready-to-close': 'readyToClose',
|
||||
'video-conference-joined': 'videoConferenceJoined',
|
||||
|
||||
@@ -99,6 +99,17 @@ UI.notifyReservationError = function(code, msg) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify user that he has been kicked from the server.
|
||||
*/
|
||||
UI.notifyKicked = function() {
|
||||
messageHandler.showError({
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.kickMessage',
|
||||
titleKey: 'dialog.kickTitle'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify user that conference was destroyed.
|
||||
* @param reason {string} the reason text
|
||||
@@ -720,6 +731,24 @@ UI.showExtensionInlineInstallationDialog = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows error dialog that informs the user that no data is received from the
|
||||
* device.
|
||||
*
|
||||
* @param {boolean} isAudioTrack - Whether or not the dialog is for an audio
|
||||
* track error.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.showTrackNotWorkingDialog = function(isAudioTrack) {
|
||||
messageHandler.showError({
|
||||
descriptionKey: isAudioTrack
|
||||
? 'dialog.micNotSendingData' : 'dialog.cameraNotSendingData',
|
||||
titleKey: isAudioTrack
|
||||
? 'dialog.micNotSendingDataTitle'
|
||||
: 'dialog.cameraNotSendingDataTitle'
|
||||
});
|
||||
};
|
||||
|
||||
UI.updateDevicesAvailability = function(id, devices) {
|
||||
VideoLayout.setDeviceAvailabilityIcons(id, devices);
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ export default function SharedVideoThumb(participant, videoType, VideoLayout) {
|
||||
this.updateDisplayName();
|
||||
|
||||
this.container.onclick = this._onContainerClick;
|
||||
this.container.ondblclick = this._onContainerDoubleClick;
|
||||
}
|
||||
SharedVideoThumb.prototype = Object.create(SmallVideo.prototype);
|
||||
SharedVideoThumb.prototype.constructor = SharedVideoThumb;
|
||||
|
||||
@@ -62,6 +62,7 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
||||
this.updateIndicators();
|
||||
|
||||
this.container.onclick = this._onContainerClick;
|
||||
this.container.ondblclick = this._onContainerDoubleClick;
|
||||
}
|
||||
|
||||
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
||||
|
||||
@@ -89,6 +89,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
|
||||
this._stopRemoteControl = this._stopRemoteControl.bind(this);
|
||||
|
||||
this.container.onclick = this._onContainerClick;
|
||||
this.container.ondblclick = this._onContainerDoubleClick;
|
||||
}
|
||||
|
||||
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
|
||||
@@ -164,10 +165,7 @@ RemoteVideo.prototype._generatePopupContent = function() {
|
||||
|
||||
const initialVolumeValue
|
||||
= this._audioStreamElement && this._audioStreamElement.volume;
|
||||
|
||||
// hide volume when in silent mode
|
||||
const onVolumeChange = APP.store.getState()['features/base/config'].startSilent
|
||||
? undefined : this._setAudioVolume;
|
||||
const onVolumeChange = this._setAudioVolume;
|
||||
const { isModerator } = APP.conference;
|
||||
const participantID = this.id;
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ function SmallVideo(VideoLayout) {
|
||||
this.updateView = this.updateView.bind(this);
|
||||
|
||||
this._onContainerClick = this._onContainerClick.bind(this);
|
||||
this._onContainerDoubleClick = this._onContainerDoubleClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -858,6 +859,20 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback invoked when the thumbnail is double clicked. Will pin the
|
||||
* participant if in tile view.
|
||||
*
|
||||
* @param {MouseEvent} event - The click event to intercept.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype._onContainerDoubleClick = function(event) {
|
||||
if (this._pinningRequiresDoubleClick() && this._shouldTriggerPin(event)) {
|
||||
APP.store.dispatch(pinParticipant(this.id));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback invoked when the thumbnail is clicked and potentially trigger
|
||||
* pinning of the participant.
|
||||
@@ -867,7 +882,8 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype._onContainerClick = function(event) {
|
||||
const triggerPin = this._shouldTriggerPin(event);
|
||||
const triggerPin = this._shouldTriggerPin(event)
|
||||
&& !this._pinningRequiresDoubleClick();
|
||||
|
||||
if (event.stopPropagation && triggerPin) {
|
||||
event.stopPropagation();
|
||||
@@ -918,6 +934,17 @@ SmallVideo.prototype.togglePin = function() {
|
||||
APP.store.dispatch(pinParticipant(participantIdToPin));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether or not clicking to pin the participant needs to be a double
|
||||
* click instead of a single click.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
SmallVideo.prototype._pinningRequiresDoubleClick = function() {
|
||||
return shouldDisplayTileView(APP.store.getState());
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the React element responsible for showing connection status, dominant
|
||||
* speaker, and raised hand icons.
|
||||
|
||||
53
package-lock.json
generated
53
package-lock.json
generated
@@ -2840,7 +2840,7 @@
|
||||
"blueimp-md5": "^2.10.0",
|
||||
"json3": "^3.3.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f1"
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": {
|
||||
@@ -6750,8 +6750,7 @@
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -6769,13 +6768,11 @@
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -6788,18 +6785,15 @@
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -6902,8 +6896,7 @@
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -6913,7 +6906,6 @@
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -6926,20 +6918,17 @@
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -6956,7 +6945,6 @@
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -7029,8 +7017,7 @@
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -7040,7 +7027,6 @@
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -7116,8 +7102,7 @@
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -7147,7 +7132,6 @@
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -7165,7 +7149,6 @@
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -7204,13 +7187,11 @@
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8946,8 +8927,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#15e09476cdae1dee8f633c628681449be26d12be",
|
||||
"from": "github:jitsi/lib-jitsi-meet#15e09476cdae1dee8f633c628681449be26d12be",
|
||||
"version": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"from": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "0.1.14",
|
||||
"@jitsi/sdp-simulcast": "0.2.1",
|
||||
@@ -12219,9 +12200,9 @@
|
||||
"integrity": "sha512-kEzgZxbbXYhy27u5GnhrKitn+XDBFAHSDUJdYC6llMi5cDPjgcqhOAQABj0K+ga5pn+/xPZLmD882rrUGiwVVA=="
|
||||
},
|
||||
"react-native-google-signin": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-google-signin/-/react-native-google-signin-2.0.0.tgz",
|
||||
"integrity": "sha512-9loM4lcCIdbco5BnmNio7yGaXQKCpCaY1VRmYiTSvC5NjuSf6Ui6jZRee46p/YdaU4yRnS3u5Vct6Psrvr0HNg=="
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-google-signin/-/react-native-google-signin-1.0.2.tgz",
|
||||
"integrity": "sha512-4HPPSecI29gX0Pu7h2E7ZYXnKO4r+6eh5f+Unm67liE1RfvCQfOqoDliPbK96Mb/91VgHwqyxi0sUEC4j54/AQ=="
|
||||
},
|
||||
"react-native-immersive": {
|
||||
"version": "2.0.0",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"js-utils": "github:jitsi/js-utils#73a67a7a60d52f8e895f50939c8fcbd1f20fe7b5",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#15e09476cdae1dee8f633c628681449be26d12be",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.11",
|
||||
"moment": "2.19.4",
|
||||
@@ -68,7 +68,7 @@
|
||||
"react-native-calendar-events": "1.6.4",
|
||||
"react-native-callstats": "3.58.2",
|
||||
"react-native-fast-image": "5.1.1",
|
||||
"react-native-google-signin": "2.0.0",
|
||||
"react-native-google-signin": "1.0.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-linear-gradient": "2.5.3",
|
||||
|
||||
@@ -551,18 +551,6 @@ export function createStartAudioOnlyEvent(audioOnly) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates the "start silent" configuration.
|
||||
*
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createStartSilentEvent() {
|
||||
return {
|
||||
action: 'start.silent'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates the "start muted" configuration.
|
||||
*
|
||||
|
||||
@@ -6,14 +6,12 @@ import '../../analytics';
|
||||
import '../../authentication';
|
||||
import { setColorScheme } from '../../base/color-scheme';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import { updateFlags } from '../../base/flags';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
import {
|
||||
AspectRatioDetector,
|
||||
ReducedUIDetector
|
||||
} from '../../base/responsive-ui';
|
||||
import { updateSettings } from '../../base/settings';
|
||||
import '../../google-api';
|
||||
import '../../mobile/audio-mode';
|
||||
import '../../mobile/background';
|
||||
@@ -49,14 +47,18 @@ type Props = AbstractAppProps & {
|
||||
externalAPIScope: string,
|
||||
|
||||
/**
|
||||
* An object with the feature flags.
|
||||
* Whether Picture-in-Picture is enabled. If {@code true}, a toolbar button
|
||||
* is rendered in the {@link Conference} view to afford entering
|
||||
* Picture-in-Picture.
|
||||
*/
|
||||
flags: Object,
|
||||
pictureInPictureEnabled: boolean,
|
||||
|
||||
/**
|
||||
* An object with user information (display name, email, avatar URL).
|
||||
* Whether the Welcome page is enabled. If {@code true}, the Welcome page is
|
||||
* rendered when the {@link App} is not at a location (URL) identifying
|
||||
* a Jitsi Meet conference/room.
|
||||
*/
|
||||
userInfo: ?Object
|
||||
welcomePageEnabled: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -94,12 +96,9 @@ export class App extends AbstractApp {
|
||||
super.componentDidMount();
|
||||
|
||||
this._init.then(() => {
|
||||
// We set these early enough so then we avoid any unnecessary re-renders.
|
||||
const { dispatch } = this.state.store;
|
||||
|
||||
dispatch(setColorScheme(this.props.colorScheme));
|
||||
dispatch(updateFlags(this.props.flags));
|
||||
dispatch(updateSettings(this.props.userInfo || {}));
|
||||
// We set the color scheme early enough so then we avoid any
|
||||
// unnecessary re-renders.
|
||||
this.state.store.dispatch(setColorScheme(this.props.colorScheme));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import { generateRoomWithoutSeparator } from 'js-utils/random';
|
||||
import type { Component } from 'react';
|
||||
|
||||
import { isRoomValid } from '../base/conference';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { Platform } from '../base/react';
|
||||
import { toState } from '../base/redux';
|
||||
import { isSupportedBrowser } from '../base/environment';
|
||||
import { Conference } from '../conference';
|
||||
import { getDeepLinkingPage } from '../deep-linking';
|
||||
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
|
||||
@@ -39,109 +40,69 @@ export type Route = {
|
||||
*/
|
||||
export function _getRouteToRender(stateful: Function | Object): Promise<Route> {
|
||||
const state = toState(stateful);
|
||||
|
||||
if (navigator.product === 'ReactNative') {
|
||||
return _getMobileRoute(state);
|
||||
}
|
||||
|
||||
return _getWebConferenceRoute(state) || _getWebWelcomePageRoute(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Route} to display on the React Native app.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Promise<Route>}
|
||||
*/
|
||||
function _getMobileRoute(state): Promise<Route> {
|
||||
const route = _getEmptyRoute();
|
||||
|
||||
if (isRoomValid(state['features/base/conference'].room)) {
|
||||
route.component = Conference;
|
||||
} else if (isWelcomePageAppEnabled(state)) {
|
||||
route.component = WelcomePage;
|
||||
} else {
|
||||
route.component = BlankPage;
|
||||
}
|
||||
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Route} to display when trying to access a conference if
|
||||
* a valid conference is being joined.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Promise<Route>|undefined}
|
||||
*/
|
||||
function _getWebConferenceRoute(state): ?Promise<Route> {
|
||||
if (!isRoomValid(state['features/base/conference'].room)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const route = _getEmptyRoute();
|
||||
|
||||
// Update the location if it doesn't match. This happens when a room is
|
||||
// joined from the welcome page. The reason for doing this instead of using
|
||||
// the history API is that we want to load the config.js which takes the
|
||||
// room into account.
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
if (window.location.href !== locationURL.href) {
|
||||
route.href = locationURL.href;
|
||||
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
return getDeepLinkingPage(state)
|
||||
.then(deepLinkComponent => {
|
||||
if (deepLinkComponent) {
|
||||
route.component = deepLinkComponent;
|
||||
} else if (isSupportedBrowser()) {
|
||||
route.component = Conference;
|
||||
} else {
|
||||
route.component = UnsupportedDesktopBrowser;
|
||||
}
|
||||
|
||||
return route;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Route} to display when trying to access the welcome page.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Promise<Route>}
|
||||
*/
|
||||
function _getWebWelcomePageRoute(state): Promise<Route> {
|
||||
const route = _getEmptyRoute();
|
||||
|
||||
if (isWelcomePageUserEnabled(state)) {
|
||||
if (isSupportedBrowser()) {
|
||||
route.component = WelcomePage;
|
||||
} else {
|
||||
route.component = UnsupportedDesktopBrowser;
|
||||
}
|
||||
} else {
|
||||
// Web: if the welcome page is disabled, go directly to a random room.
|
||||
|
||||
let href = window.location.href;
|
||||
|
||||
href.endsWith('/') || (href += '/');
|
||||
route.href = href + generateRoomWithoutSeparator();
|
||||
}
|
||||
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default {@code Route}.
|
||||
*
|
||||
* @returns {Route}
|
||||
*/
|
||||
function _getEmptyRoute(): Route {
|
||||
return {
|
||||
const { room } = state['features/base/conference'];
|
||||
const isMobileApp = navigator.product === 'ReactNative';
|
||||
const isMobileBrowser
|
||||
= !isMobileApp && (Platform.OS === 'android' || Platform.OS === 'ios');
|
||||
const route: Route = {
|
||||
component: BlankPage,
|
||||
href: undefined
|
||||
};
|
||||
|
||||
return new Promise(resolve => {
|
||||
// First, check if the current endpoint supports WebRTC. We are
|
||||
// intentionally not performing the check for mobile browsers because:
|
||||
// - the WelcomePage is mobile ready;
|
||||
// - if the URL points to a conference, getDeepLinkingPage will take
|
||||
// care of it.
|
||||
if (!isMobileBrowser && !JitsiMeetJS.isWebRtcSupported()) {
|
||||
route.component = UnsupportedDesktopBrowser;
|
||||
resolve(route);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRoomValid(room)) {
|
||||
if (isMobileApp) {
|
||||
route.component = Conference;
|
||||
resolve(route);
|
||||
} else {
|
||||
// Update the location if it doesn't match. This happens when a
|
||||
// room is joined from the welcome page. The reason for doing
|
||||
// this instead of using the history API is that we want to load
|
||||
// the config.js which takes the room into account.
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
if (window.location.href !== locationURL.href) {
|
||||
route.href = locationURL.href;
|
||||
resolve(route);
|
||||
} else {
|
||||
// Maybe show deep-linking, otherwise go to Conference.
|
||||
getDeepLinkingPage(state).then(component => {
|
||||
route.component = component || Conference;
|
||||
resolve(route);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isWelcomePageUserEnabled(state)) {
|
||||
// Web: if the welcome page is disabled, go directly to a random
|
||||
// room.
|
||||
|
||||
let href = window.location.href;
|
||||
|
||||
href.endsWith('/') || (href += '/');
|
||||
route.href = href + generateRoomWithoutSeparator();
|
||||
} else if (isWelcomePageAppEnabled(state)) {
|
||||
// Mobile: only go to the welcome page if enabled.
|
||||
|
||||
route.component = WelcomePage;
|
||||
}
|
||||
|
||||
resolve(route);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
dominantSpeakerChanged,
|
||||
getNormalizedDisplayName,
|
||||
participantConnectionStatusChanged,
|
||||
participantKicked,
|
||||
participantMutedUs,
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
@@ -91,11 +89,7 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.KICKED,
|
||||
(...args) => dispatch(kickedOut(conference, ...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PARTICIPANT_KICKED,
|
||||
(kicker, kicked) => dispatch(participantKicked(kicker, kicked)));
|
||||
() => dispatch(kickedOut(conference)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
||||
@@ -135,14 +129,6 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
JitsiConferenceEvents.TRACK_REMOVED,
|
||||
t => t && !t.isLocal() && dispatch(trackRemoved(t)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_MUTE_CHANGED,
|
||||
(_, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
dispatch(participantMutedUs(participantThatMutedUs));
|
||||
}
|
||||
});
|
||||
|
||||
// Dispatches into features/base/participants follow:
|
||||
conference.on(
|
||||
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
|
||||
@@ -446,19 +432,15 @@ export function dataChannelOpened() {
|
||||
*
|
||||
* @param {JitsiConference} conference - The {@link JitsiConference} instance
|
||||
* for which the event is being signaled.
|
||||
* @param {JitsiParticipant} participant - The {@link JitsiParticipant}
|
||||
* instance which initiated the kick event.
|
||||
* @returns {{
|
||||
* type: KICKED_OUT,
|
||||
* conference: JitsiConference,
|
||||
* participant: JitsiParticipant
|
||||
* conference: JitsiConference
|
||||
* }}
|
||||
*/
|
||||
export function kickedOut(conference: Object, participant: Object) {
|
||||
export function kickedOut(conference: Object) {
|
||||
return {
|
||||
type: KICKED_OUT,
|
||||
conference,
|
||||
participant
|
||||
conference
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,14 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
microphone: {
|
||||
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.micConstraintFailedError',
|
||||
[JitsiTrackErrors.GENERAL]: 'dialog.micUnknownError',
|
||||
[JitsiTrackErrors.NO_DATA_FROM_SOURCE]: 'dialog.micNotSendingData',
|
||||
[JitsiTrackErrors.NOT_FOUND]: 'dialog.micNotFoundError',
|
||||
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.micPermissionDeniedError'
|
||||
},
|
||||
camera: {
|
||||
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.cameraConstraintFailedError',
|
||||
[JitsiTrackErrors.GENERAL]: 'dialog.cameraUnknownError',
|
||||
[JitsiTrackErrors.NO_DATA_FROM_SOURCE]: 'dialog.cameraNotSendingData',
|
||||
[JitsiTrackErrors.NOT_FOUND]: 'dialog.cameraNotFoundError',
|
||||
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.cameraPermissionDeniedError',
|
||||
[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]: 'dialog.cameraUnsupportedResolutionError'
|
||||
|
||||
@@ -61,9 +61,7 @@ class BottomSheet extends PureComponent<Props> {
|
||||
styles.sheetItemContainer,
|
||||
_styles.sheet
|
||||
] }>
|
||||
<ScrollView
|
||||
bounces = { false }
|
||||
showsVerticalScrollIndicator = { false }>
|
||||
<ScrollView bounces = { false }>
|
||||
{ this._getWrappedContent() }
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { Platform } from '../react';
|
||||
|
||||
import { isBlacklistedEnvironment } from './isBlacklistedEnvironment';
|
||||
|
||||
/**
|
||||
* Returns whether or not the current browser should allow the app to display.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isSupportedBrowser() {
|
||||
if (navigator.product === 'ReactNative' || isBlacklistedEnvironment()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are intentionally allow mobile browsers because:
|
||||
// - the WelcomePage is mobile ready;
|
||||
// - if the URL points to a conference then deep-linking will take
|
||||
// care of it.
|
||||
return Platform.OS === 'android'
|
||||
|| Platform.OS === 'ios'
|
||||
|| JitsiMeetJS.isWebRtcSupported();
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './environment';
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Returns whether or not the current browser is supported for showing meeting
|
||||
* based on any custom overrides. This file should be overridden with branding
|
||||
* as needed to fit deployment needs.
|
||||
*
|
||||
* @returns {boolean} True the browser is unsupported due to being blacklisted
|
||||
* by the logic within this function.
|
||||
*/
|
||||
export function isBlacklistedEnvironment() {
|
||||
return false;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* The type of Redux action which updates the feature flags.
|
||||
*
|
||||
* {
|
||||
* type: UPDATE_FLAGS,
|
||||
* flags: Object
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export const UPDATE_FLAGS = 'UPDATE_FLAGS';
|
||||
@@ -1,19 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { UPDATE_FLAGS } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Updates the current features flags with the given ones. They will be merged.
|
||||
*
|
||||
* @param {Object} flags - The new flags object.
|
||||
* @returns {{
|
||||
* type: UPDATE_FLAGS,
|
||||
* flags: Object
|
||||
* }}
|
||||
*/
|
||||
export function updateFlags(flags: Object) {
|
||||
return {
|
||||
type: UPDATE_FLAGS,
|
||||
flags
|
||||
};
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Flag indicating if calendar integration should be enabled.
|
||||
* Default: enabled (true) on Android, auto-detected on iOS.
|
||||
*/
|
||||
export const CALENDAR_ENABLED = 'calendar.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if chat should be enabled.
|
||||
* Default: enabled (true).
|
||||
*/
|
||||
export const CHAT_ENABLED = 'chat.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if recording should be enabled in iOS.
|
||||
* Default: disabled (false).
|
||||
*/
|
||||
export const IOS_RECORDING_ENABLED = 'ios.recording.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if Picture-in-Picture should be enabled.
|
||||
* Default: auto-detected.
|
||||
*/
|
||||
export const PIP_ENABLED = 'pip.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the welcome page should be enabled.
|
||||
* Default: disabled (false).
|
||||
*/
|
||||
export const WELCOME_PAGE_ENABLED = 'welcomepage.enabled';
|
||||
@@ -1,32 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { getAppProp } from '../app';
|
||||
import { toState } from '../redux';
|
||||
|
||||
/**
|
||||
* Gets the value of a specific feature flag.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store or {@code getState}
|
||||
* function.
|
||||
* @param {string} flag - The name of the React {@code Component} prop of
|
||||
* the currently mounted {@code App} to get.
|
||||
* @param {*} defaultValue - A default value for the flag, in case it's not defined.
|
||||
* @returns {*} The value of the specified React {@code Compoennt} prop of the
|
||||
* currently mounted {@code App}.
|
||||
*/
|
||||
export function getFeatureFlag(stateful: Function | Object, flag: string, defaultValue: any) {
|
||||
const state = toState(stateful)['features/base/flags'];
|
||||
|
||||
if (state) {
|
||||
const value = state[flag];
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe the value hasn't made it to the redux store yet, check the app props.
|
||||
const flags = getAppProp(stateful, 'flags') || {};
|
||||
|
||||
return flags[flag] || defaultValue;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
import './reducer';
|
||||
@@ -1,33 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ReducerRegistry } from '../redux';
|
||||
|
||||
import { UPDATE_FLAGS } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Default state value for the feature flags.
|
||||
*/
|
||||
const DEFAULT_STATE = {};
|
||||
|
||||
/**
|
||||
* Reduces redux actions which handle feature flags.
|
||||
*
|
||||
* @param {State} state - The current redux state.
|
||||
* @param {Action} action - The redux action to reduce.
|
||||
* @param {string} action.type - The type of the redux action to reduce.
|
||||
* @returns {State} The next redux state that is the result of reducing the
|
||||
* specified action.
|
||||
*/
|
||||
ReducerRegistry.register('features/base/flags', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_FLAGS: {
|
||||
const newState = _.merge({}, state, action.flags);
|
||||
|
||||
return _.isEqual(state, newState) ? state : newState;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
@@ -17,11 +17,7 @@ import {
|
||||
PARTICIPANT_UPDATED,
|
||||
PIN_PARTICIPANT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
getParticipantDisplayName
|
||||
} from './functions';
|
||||
import { getLocalParticipant, getNormalizedDisplayName } from './functions';
|
||||
|
||||
/**
|
||||
* Create an action for when dominant speaker changes.
|
||||
@@ -386,51 +382,6 @@ export function participantUpdated(participant = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal that a participant has muted us.
|
||||
*
|
||||
* @param {JitsiParticipant} participant - Information about participant.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function participantMutedUs(participant) {
|
||||
return (dispatch, getState) => {
|
||||
if (!participant) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
descriptionKey: 'notify.mutedRemotelyDescription',
|
||||
titleKey: 'notify.mutedRemotelyTitle',
|
||||
titleArguments: {
|
||||
participantDisplayName:
|
||||
getParticipantDisplayName(getState, participant.getId())
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal that a participant had been kicked.
|
||||
*
|
||||
* @param {JitsiParticipant} kicker - Information about participant performing the kick.
|
||||
* @param {JitsiParticipant} kicked - Information about participant that was kicked.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function participantKicked(kicker, kicked) {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch(showNotification({
|
||||
titleArguments: {
|
||||
kicked:
|
||||
getParticipantDisplayName(getState, kicked.getId()),
|
||||
kicker:
|
||||
getParticipantDisplayName(getState, kicker.getId())
|
||||
},
|
||||
titleKey: 'notify.kickParticipant'
|
||||
}, NOTIFICATION_TIMEOUT * 2)); // leave more time for this
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action which pins a conference participant.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
|
||||
@@ -43,16 +43,6 @@ export const TRACK_CREATE_CANCELED = 'TRACK_CREATE_CANCELED';
|
||||
*/
|
||||
export const TRACK_CREATE_ERROR = 'TRACK_CREATE_ERROR';
|
||||
|
||||
/**
|
||||
* The type of redux action dispatched when a track has triggered no data from source event.
|
||||
*
|
||||
* {
|
||||
* type: TRACK_NO_DATA_FROM_SOURCE,
|
||||
* track: Track
|
||||
* }
|
||||
*/
|
||||
export const TRACK_NO_DATA_FROM_SOURCE = 'TRACK_NO_DATA_FROM_SOURCE';
|
||||
|
||||
/**
|
||||
* The type of redux action dispatched when a track has been (locally or
|
||||
* remotely) removed from the conference.
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { showErrorNotification, showNotification } from '../../notifications';
|
||||
import {
|
||||
CAMERA_FACING_MODE,
|
||||
MEDIA_TYPE,
|
||||
@@ -18,12 +17,11 @@ import {
|
||||
TRACK_ADDED,
|
||||
TRACK_CREATE_CANCELED,
|
||||
TRACK_CREATE_ERROR,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED,
|
||||
TRACK_WILL_CREATE
|
||||
} from './actionTypes';
|
||||
import { createLocalTracksF, getLocalTrack, getLocalTracks, getTrackByJitsiTrack } from './functions';
|
||||
import { createLocalTracksF, getLocalTrack, getLocalTracks } from './functions';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -191,55 +189,6 @@ export function destroyLocalTracks() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the passed JitsiLocalTrack has triggered a no data from source event.
|
||||
*
|
||||
* @param {JitsiLocalTrack} track - The track.
|
||||
* @returns {{
|
||||
* type: TRACK_NO_DATA_FROM_SOURCE,
|
||||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function noDataFromSource(track) {
|
||||
return {
|
||||
type: TRACK_NO_DATA_FROM_SOURCE,
|
||||
track
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a no data from source video error if needed.
|
||||
*
|
||||
* @param {JitsiLocalTrack} jitsiTrack - The track.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function showNoDataFromSourceVideoError(jitsiTrack) {
|
||||
return (dispatch, getState) => {
|
||||
let notificationInfo;
|
||||
|
||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
|
||||
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (track.isReceivingData) {
|
||||
notificationInfo = undefined;
|
||||
} else {
|
||||
const notificationAction = showErrorNotification({
|
||||
descriptionKey: 'dialog.cameraNotSendingData',
|
||||
titleKey: 'dialog.cameraNotSendingDataTitle'
|
||||
});
|
||||
|
||||
dispatch(notificationAction);
|
||||
notificationInfo = {
|
||||
uid: notificationAction.uid
|
||||
};
|
||||
}
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, notificationInfo));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the local participant is ending screensharing or beginning the
|
||||
* screensharing flow.
|
||||
@@ -339,8 +288,7 @@ export function trackAdded(track) {
|
||||
|
||||
// participantId
|
||||
const local = track.isLocal();
|
||||
const mediaType = track.getType();
|
||||
let isReceivingData, noDataFromSourceNotificationInfo, participantId;
|
||||
let participantId;
|
||||
|
||||
if (local) {
|
||||
const participant = getLocalParticipant(getState);
|
||||
@@ -348,40 +296,18 @@ export function trackAdded(track) {
|
||||
if (participant) {
|
||||
participantId = participant.id;
|
||||
}
|
||||
|
||||
isReceivingData = track.isReceivingData();
|
||||
track.on(JitsiTrackEvents.NO_DATA_FROM_SOURCE, () => dispatch(noDataFromSource({ jitsiTrack: track })));
|
||||
if (!isReceivingData) {
|
||||
if (mediaType === MEDIA_TYPE.AUDIO) {
|
||||
const notificationAction = showNotification({
|
||||
descriptionKey: 'dialog.micNotSendingData',
|
||||
titleKey: 'dialog.micNotSendingDataTitle'
|
||||
});
|
||||
|
||||
dispatch(notificationAction);
|
||||
noDataFromSourceNotificationInfo = { uid: notificationAction.uid };
|
||||
} else {
|
||||
const timeout = setTimeout(() => dispatch(showNoDataFromSourceVideoError(track)), 5000);
|
||||
|
||||
noDataFromSourceNotificationInfo = { timeout };
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
participantId = track.getParticipantId();
|
||||
isReceivingData = true;
|
||||
}
|
||||
|
||||
return dispatch({
|
||||
type: TRACK_ADDED,
|
||||
track: {
|
||||
jitsiTrack: track,
|
||||
isReceivingData,
|
||||
local,
|
||||
mediaType,
|
||||
mediaType: track.getType(),
|
||||
mirror: _shouldMirror(track),
|
||||
muted: track.isMuted(),
|
||||
noDataFromSourceNotificationInfo,
|
||||
participantId,
|
||||
videoStarted: false,
|
||||
videoType: track.videoType
|
||||
@@ -410,26 +336,6 @@ export function trackMutedChanged(track) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a track's no data from source notification information changes.
|
||||
*
|
||||
* @param {JitsiLocalTrack} track - JitsiTrack instance.
|
||||
* @param {Object} noDataFromSourceNotificationInfo - Information about no data from source notification.
|
||||
* @returns {{
|
||||
* type: TRACK_UPDATED,
|
||||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackNoDataFromSourceNotificationInfoChanged(track, noDataFromSourceNotificationInfo) {
|
||||
return {
|
||||
type: TRACK_UPDATED,
|
||||
track: {
|
||||
jitsiTrack: track,
|
||||
noDataFromSourceNotificationInfo
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a track has been signaled for removal from the
|
||||
* conference.
|
||||
@@ -443,7 +349,6 @@ export function trackNoDataFromSourceNotificationInfoChanged(track, noDataFromSo
|
||||
export function trackRemoved(track) {
|
||||
track.removeAllListeners(JitsiTrackEvents.TRACK_MUTE_CHANGED);
|
||||
track.removeAllListeners(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED);
|
||||
track.removeAllListeners(JitsiTrackEvents.NO_DATA_FROM_SOURCE);
|
||||
|
||||
return {
|
||||
type: TRACK_REMOVED,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* global APP */
|
||||
|
||||
import JitsiMeetJS, { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import JitsiMeetJS, { JitsiTrackErrors, JitsiTrackEvents }
|
||||
from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
@@ -76,6 +77,21 @@ export function createLocalTracksF(
|
||||
resolution
|
||||
},
|
||||
firePermissionPromptIsShownEvent)
|
||||
.then(tracks => {
|
||||
// TODO JitsiTrackEvents.NO_DATA_FROM_SOURCE should probably be
|
||||
// dispatched in the redux store here and then
|
||||
// APP.UI.showTrackNotWorkingDialog should be in a middleware
|
||||
// somewhere else.
|
||||
if (typeof APP !== 'undefined') {
|
||||
tracks.forEach(track =>
|
||||
track.on(
|
||||
JitsiTrackEvents.NO_DATA_FROM_SOURCE,
|
||||
APP.UI.showTrackNotWorkingDialog.bind(
|
||||
null, track.isAudioTrack())));
|
||||
}
|
||||
|
||||
return tracks;
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('Failed to create local tracks', options.devices, err);
|
||||
|
||||
|
||||
@@ -9,22 +9,15 @@ import {
|
||||
TOGGLE_CAMERA_FACING_MODE,
|
||||
toggleCameraFacingMode
|
||||
} from '../media';
|
||||
import { hideNotification } from '../../notifications';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import {
|
||||
createLocalTracksA,
|
||||
showNoDataFromSourceVideoError,
|
||||
trackNoDataFromSourceNotificationInfoChanged
|
||||
} from './actions';
|
||||
import { createLocalTracksA } from './actions';
|
||||
import {
|
||||
TOGGLE_SCREENSHARING,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED
|
||||
} from './actionTypes';
|
||||
import { getLocalTrack, getTrackByJitsiTrack, setTrackMuted } from './functions';
|
||||
import { getLocalTrack, setTrackMuted } from './functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
@@ -38,17 +31,6 @@ declare var APP: Object;
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TRACK_NO_DATA_FROM_SOURCE: {
|
||||
const result = next(action);
|
||||
|
||||
_handleNoDataFromSourceErrors(store, action);
|
||||
|
||||
return result;
|
||||
}
|
||||
case TRACK_REMOVED: {
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
break;
|
||||
}
|
||||
case SET_AUDIO_MUTED:
|
||||
_setMuted(store, action, MEDIA_TYPE.AUDIO);
|
||||
break;
|
||||
@@ -139,53 +121,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles no data from source errors.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is
|
||||
* dispatched.
|
||||
* @param {Action} action - The redux action dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleNoDataFromSourceErrors(store, action) {
|
||||
const { getState, dispatch } = store;
|
||||
|
||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], action.track.jitsiTrack);
|
||||
|
||||
if (!track || !track.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { jitsiTrack } = track;
|
||||
|
||||
if (track.mediaType === MEDIA_TYPE.AUDIO && track.isReceivingData) {
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
}
|
||||
|
||||
if (track.mediaType === MEDIA_TYPE.VIDEO) {
|
||||
const { noDataFromSourceNotificationInfo = {} } = track;
|
||||
|
||||
if (track.isReceivingData) {
|
||||
if (noDataFromSourceNotificationInfo.timeout) {
|
||||
clearTimeout(noDataFromSourceNotificationInfo.timeout);
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
||||
}
|
||||
|
||||
// try to remove the notification if there is one.
|
||||
_removeNoDataFromSourceNotification(store, action.track);
|
||||
} else {
|
||||
if (noDataFromSourceNotificationInfo.timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => dispatch(showNoDataFromSourceVideoError(jitsiTrack)), 5000);
|
||||
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, { timeout }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local track associated with a specific {@code MEDIA_TYPE} in a
|
||||
* specific redux store.
|
||||
@@ -214,23 +149,6 @@ function _getLocalTrack(
|
||||
includePending));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the no data from source notification associated with the JitsiTrack if displayed.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {Track} track - The redux action dispatched in the specified store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _removeNoDataFromSourceNotification({ getState, dispatch }, track) {
|
||||
const t = getTrackByJitsiTrack(getState()['features/base/tracks'], track.jitsiTrack);
|
||||
const { jitsiTrack, noDataFromSourceNotificationInfo = {} } = t || {};
|
||||
|
||||
if (noDataFromSourceNotificationInfo && noDataFromSourceNotificationInfo.uid) {
|
||||
dispatch(hideNotification(noDataFromSourceNotificationInfo.uid));
|
||||
dispatch(trackNoDataFromSourceNotificationInfoChanged(jitsiTrack, undefined));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes or unmutes a local track with a specific media type.
|
||||
*
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
TRACK_ADDED,
|
||||
TRACK_CREATE_CANCELED,
|
||||
TRACK_CREATE_ERROR,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED,
|
||||
TRACK_WILL_CREATE
|
||||
@@ -76,21 +75,6 @@ function track(state, action) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TRACK_NO_DATA_FROM_SOURCE: {
|
||||
const t = action.track;
|
||||
|
||||
if (state.jitsiTrack === t.jitsiTrack) {
|
||||
const isReceivingData = t.jitsiTrack.isReceivingData();
|
||||
|
||||
if (state.isReceivingData !== isReceivingData) {
|
||||
return {
|
||||
...state,
|
||||
isReceivingData
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -102,7 +86,6 @@ function track(state, action) {
|
||||
ReducerRegistry.register('features/base/tracks', (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case PARTICIPANT_ID_CHANGED:
|
||||
case TRACK_NO_DATA_FROM_SOURCE:
|
||||
case TRACK_UPDATED:
|
||||
return state.map(t => track(t, action));
|
||||
|
||||
|
||||
@@ -30,19 +30,17 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
*/
|
||||
export function bootstrapCalendarIntegration(): Function {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
if (!isCalendarEnabled(state)) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
const {
|
||||
googleApiApplicationClientID
|
||||
} = state['features/base/config'];
|
||||
} = getState()['features/base/config'];
|
||||
const {
|
||||
integrationReady,
|
||||
integrationType
|
||||
} = state['features/calendar-sync'];
|
||||
} = getState()['features/calendar-sync'];
|
||||
|
||||
if (!isCalendarEnabled()) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AbstractPage } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
import styles from './styles';
|
||||
|
||||
import CalendarListContent from './CalendarListContent';
|
||||
@@ -137,4 +138,6 @@ function _mapStateToProps(state: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(CalendarList));
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(CalendarList))
|
||||
: undefined;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
|
||||
import { refreshCalendar } from '../actions';
|
||||
import { ERRORS } from '../constants';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import CalendarListContent from './CalendarListContent';
|
||||
|
||||
@@ -256,4 +257,6 @@ function _mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(CalendarList));
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(CalendarList))
|
||||
: undefined;
|
||||
|
||||
@@ -13,6 +13,7 @@ import { NavigateSectionList } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
|
||||
import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
|
||||
/**
|
||||
@@ -270,4 +271,6 @@ function _mapStateToProps(state: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(CalendarListContent));
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(CalendarListContent))
|
||||
: undefined;
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { MeetingsList } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
|
||||
import AddMeetingUrlButton from './AddMeetingUrlButton';
|
||||
import JoinButton from './JoinButton';
|
||||
|
||||
@@ -170,4 +172,6 @@ function _mapStateToProps(state: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(CalendarListContent);
|
||||
export default isCalendarEnabled()
|
||||
? connect(_mapStateToProps)(CalendarListContent)
|
||||
: undefined;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getLocalizedDateFormatter, translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
|
||||
|
||||
import { isCalendarEnabled } from '../functions';
|
||||
import styles from './styles';
|
||||
|
||||
const ALERT_MILLISECONDS = 5 * 60 * 1000;
|
||||
@@ -293,4 +294,6 @@ function _mapStateToProps(state: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ConferenceNotification));
|
||||
export default isCalendarEnabled()
|
||||
? translate(connect(_mapStateToProps)(ConferenceNotification))
|
||||
: undefined;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { NativeModules, Platform } from 'react-native';
|
||||
import RNCalendarEvents from 'react-native-calendar-events';
|
||||
import type { Store } from 'redux';
|
||||
|
||||
import { CALENDAR_ENABLED, getFeatureFlag } from '../base/flags';
|
||||
import { getShareInfoText } from '../invite';
|
||||
|
||||
import { setCalendarAuthorization } from './actions';
|
||||
@@ -55,20 +54,12 @@ export function addLinkToCalendarEntry(
|
||||
* Determines whether the calendar feature is enabled by the app. For
|
||||
* example, Apple through its App Store requires
|
||||
* {@code NSCalendarsUsageDescription} in the app's Info.plist or App Store
|
||||
* rejects the app. It could also be disabled with a feature flag.
|
||||
* rejects the app.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store or {@code getState}
|
||||
* function.
|
||||
* @returns {boolean} If the app has enabled the calendar feature, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
export function isCalendarEnabled(stateful: Function | Object) {
|
||||
const flag = getFeatureFlag(stateful, CALENDAR_ENABLED);
|
||||
|
||||
if (typeof flag !== 'undefined') {
|
||||
return flag;
|
||||
}
|
||||
|
||||
export function isCalendarEnabled() {
|
||||
const { calendarEnabled = true } = NativeModules.AppInfo;
|
||||
|
||||
return calendarEnabled;
|
||||
|
||||
@@ -16,26 +16,22 @@ import {
|
||||
import { _updateCalendarEntries } from './functions';
|
||||
import { googleCalendarApi } from './web/googleCalendar';
|
||||
import { microsoftCalendarApi } from './web/microsoftCalendar';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
declare var config: Object;
|
||||
|
||||
/**
|
||||
* Determines whether the calendar feature is enabled by the web.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store or {@code getState}
|
||||
* function.
|
||||
* @returns {boolean} If the app has enabled the calendar feature, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
export function isCalendarEnabled(stateful: Function | Object) {
|
||||
const {
|
||||
enableCalendarIntegration,
|
||||
googleApiApplicationClientID,
|
||||
microsoftApiApplicationClientID
|
||||
} = toState(stateful)['features/base/config'] || {};
|
||||
|
||||
return Boolean(enableCalendarIntegration && (googleApiApplicationClientID || microsoftApiApplicationClientID));
|
||||
export function isCalendarEnabled() {
|
||||
return Boolean(
|
||||
config.enableCalendarIntegration
|
||||
&& (config.googleApiApplicationClientID
|
||||
|| config.microsoftApiApplicationClientID));
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
@@ -9,55 +9,51 @@ import { setCalendarAuthorization } from './actions';
|
||||
import { REFRESH_CALENDAR } from './actionTypes';
|
||||
import { _fetchCalendarEntries, isCalendarEnabled } from './functions';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { getState } = store;
|
||||
isCalendarEnabled()
|
||||
&& MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case ADD_KNOWN_DOMAINS: {
|
||||
// XXX Fetch new calendar entries only when an actual domain has
|
||||
// become known.
|
||||
const { getState } = store;
|
||||
const oldValue = getState()['features/base/known-domains'];
|
||||
const result = next(action);
|
||||
const newValue = getState()['features/base/known-domains'];
|
||||
|
||||
equals(oldValue, newValue)
|
||||
|| _fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case APP_STATE_CHANGED: {
|
||||
const result = next(action);
|
||||
|
||||
_maybeClearAccessStatus(store, action);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
|
||||
_fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case REFRESH_CALENDAR: {
|
||||
const result = next(action);
|
||||
|
||||
_fetchCalendarEntries(
|
||||
store, action.isInteractive, action.forcePermission);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCalendarEnabled(getState)) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case ADD_KNOWN_DOMAINS: {
|
||||
// XXX Fetch new calendar entries only when an actual domain has
|
||||
// become known.
|
||||
const oldValue = getState()['features/base/known-domains'];
|
||||
const result = next(action);
|
||||
const newValue = getState()['features/base/known-domains'];
|
||||
|
||||
equals(oldValue, newValue)
|
||||
|| _fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case APP_STATE_CHANGED: {
|
||||
const result = next(action);
|
||||
|
||||
_maybeClearAccessStatus(store, action);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
|
||||
_fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case REFRESH_CALENDAR: {
|
||||
const result = next(action);
|
||||
|
||||
_fetchCalendarEntries(
|
||||
store, action.isInteractive, action.forcePermission);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Clears the calendar access status when the app comes back from the
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
SET_CALENDAR_PROFILE_EMAIL,
|
||||
SET_LOADING_CALENDAR_EVENTS
|
||||
} from './actionTypes';
|
||||
import { isCalendarEnabled } from './functions';
|
||||
|
||||
/**
|
||||
* The default state of the calendar feature.
|
||||
@@ -43,50 +44,52 @@ const STORE_NAME = 'features/calendar-sync';
|
||||
* runtime value to see if we need to re-request the calendar permission from
|
||||
* the user.
|
||||
*/
|
||||
PersistenceRegistry.register(STORE_NAME, {
|
||||
integrationType: true,
|
||||
msAuthState: true
|
||||
});
|
||||
isCalendarEnabled()
|
||||
&& PersistenceRegistry.register(STORE_NAME, {
|
||||
integrationType: true,
|
||||
msAuthState: true
|
||||
});
|
||||
|
||||
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CLEAR_CALENDAR_INTEGRATION:
|
||||
return DEFAULT_STATE;
|
||||
isCalendarEnabled()
|
||||
&& ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CLEAR_CALENDAR_INTEGRATION:
|
||||
return DEFAULT_STATE;
|
||||
|
||||
case SET_CALENDAR_AUTH_STATE: {
|
||||
if (!action.msAuthState) {
|
||||
// received request to delete the state
|
||||
return set(state, 'msAuthState', undefined);
|
||||
case SET_CALENDAR_AUTH_STATE: {
|
||||
if (!action.msAuthState) {
|
||||
// received request to delete the state
|
||||
return set(state, 'msAuthState', undefined);
|
||||
}
|
||||
|
||||
return set(state, 'msAuthState', {
|
||||
...state.msAuthState,
|
||||
...action.msAuthState
|
||||
});
|
||||
}
|
||||
|
||||
return set(state, 'msAuthState', {
|
||||
...state.msAuthState,
|
||||
...action.msAuthState
|
||||
});
|
||||
}
|
||||
case SET_CALENDAR_AUTHORIZATION:
|
||||
return set(state, 'authorization', action.authorization);
|
||||
|
||||
case SET_CALENDAR_AUTHORIZATION:
|
||||
return set(state, 'authorization', action.authorization);
|
||||
case SET_CALENDAR_ERROR:
|
||||
return set(state, 'error', action.error);
|
||||
|
||||
case SET_CALENDAR_ERROR:
|
||||
return set(state, 'error', action.error);
|
||||
case SET_CALENDAR_EVENTS:
|
||||
return set(state, 'events', action.events);
|
||||
|
||||
case SET_CALENDAR_EVENTS:
|
||||
return set(state, 'events', action.events);
|
||||
case SET_CALENDAR_INTEGRATION:
|
||||
return {
|
||||
...state,
|
||||
integrationReady: action.integrationReady,
|
||||
integrationType: action.integrationType
|
||||
};
|
||||
|
||||
case SET_CALENDAR_INTEGRATION:
|
||||
return {
|
||||
...state,
|
||||
integrationReady: action.integrationReady,
|
||||
integrationType: action.integrationType
|
||||
};
|
||||
case SET_CALENDAR_PROFILE_EMAIL:
|
||||
return set(state, 'profileEmail', action.email);
|
||||
|
||||
case SET_CALENDAR_PROFILE_EMAIL:
|
||||
return set(state, 'profileEmail', action.email);
|
||||
case SET_LOADING_CALENDAR_EVENTS:
|
||||
return set(state, 'isLoadingEvents', action.isLoadingEvents);
|
||||
}
|
||||
|
||||
case SET_LOADING_CALENDAR_EVENTS:
|
||||
return set(state, 'isLoadingEvents', action.isLoadingEvents);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
return state;
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ function _mapDispatchToProps(dispatch: Function) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays a display name prompt.
|
||||
* Displays a diaply name prompt.
|
||||
*
|
||||
* @param {Function} onPostSubmit - The function to invoke after a
|
||||
* succesfulsetting of the display name.
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
openDialog
|
||||
} from '../base/dialog';
|
||||
import { getParticipantDisplayName } from '../base/participants';
|
||||
|
||||
/**
|
||||
* Notify that we've been kicked out of the conference.
|
||||
*
|
||||
* @param {JitsiParticipant} participant - The {@link JitsiParticipant}
|
||||
* instance which initiated the kick event.
|
||||
* @param {?Function} submit - The function to execute after submiting the dialog.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function notifyKickedOut(participant: Object, submit: ?Function) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: 'dialog.kickTitle',
|
||||
params: {
|
||||
participantDisplayName: getParticipantDisplayName(getState, participant.getId())
|
||||
}
|
||||
},
|
||||
onSubmit: submit
|
||||
}));
|
||||
};
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
NOTIFICATION_TYPE,
|
||||
showNotification
|
||||
} from '../notifications';
|
||||
import { getParticipantDisplayName } from '../base/participants';
|
||||
|
||||
/**
|
||||
* Notify that we've been kicked out of the conference.
|
||||
*
|
||||
* @param {JitsiParticipant} participant - The {@link JitsiParticipant}
|
||||
* instance which initiated the kick event.
|
||||
* @param {?Function} _ - Used only in native code.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function notifyKickedOut(participant: Object, _: ?Function) { // eslint-disable-line no-unused-vars
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const args = {
|
||||
participantDisplayName:
|
||||
getParticipantDisplayName(getState, participant.getDisplayName())
|
||||
};
|
||||
|
||||
dispatch(showNotification({
|
||||
appearance: NOTIFICATION_TYPE.ERROR,
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.kickMessage',
|
||||
descriptionArguments: args,
|
||||
titleKey: 'dialog.kickTitle',
|
||||
titleArguments: args
|
||||
}));
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import React from 'react';
|
||||
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
||||
|
||||
import { appNavigate } from '../../../app';
|
||||
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { getAppProp } from '../../../base/app';
|
||||
import { getParticipantCount } from '../../../base/participants';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
makeAspectRatioAware
|
||||
} from '../../../base/responsive-ui';
|
||||
import { TestConnectionInfo } from '../../../base/testing';
|
||||
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
|
||||
import { ConferenceNotification } from '../../../calendar-sync';
|
||||
import { Chat } from '../../../chat';
|
||||
import { DisplayNameLabel } from '../../../display-name';
|
||||
import {
|
||||
@@ -42,13 +42,6 @@ import type { AbstractProps } from '../AbstractConference';
|
||||
*/
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Wherther the calendar feature is enabled or not.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_calendarEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines that we are still connecting to the
|
||||
* conference which includes establishing the XMPP connection and then
|
||||
@@ -338,10 +331,10 @@ class Conference extends AbstractConference<Props, *> {
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderConferenceNotification() {
|
||||
const { _calendarEnabled, _reducedUI } = this.props;
|
||||
|
||||
// XXX If the calendar feature is disabled on a platform, then we don't
|
||||
// have its components exported so an undefined check is necessary.
|
||||
return (
|
||||
_calendarEnabled && !_reducedUI
|
||||
!this.props._reducedUI && ConferenceNotification
|
||||
? <ConferenceNotification />
|
||||
: undefined);
|
||||
}
|
||||
@@ -424,14 +417,6 @@ function _mapStateToProps(state) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
|
||||
/**
|
||||
* Wherther the calendar feature is enabled or not.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
|
||||
/**
|
||||
* The indicator which determines that we are still connecting to the
|
||||
* conference which includes establishing the XMPP connection and then
|
||||
@@ -467,7 +452,7 @@ function _mapStateToProps(state) {
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||
_pictureInPictureEnabled: getAppProp(state, 'pictureInPictureEnabled'),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
export * from './actions';
|
||||
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import { notifyKickedOut } from './actions';
|
||||
|
||||
import { appNavigate } from '../app';
|
||||
import {
|
||||
CONFERENCE_JOINED,
|
||||
@@ -43,15 +43,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case KICKED_OUT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(notifyKickedOut(
|
||||
action.participant,
|
||||
() => {
|
||||
dispatch(
|
||||
conferenceFailed(action.conference, JitsiConferenceEvents.KICKED));
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
));
|
||||
|
||||
dispatch(
|
||||
conferenceFailed(action.conference, JitsiConferenceEvents.KICKED));
|
||||
dispatch(appNavigate(undefined));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { connect } from '../../base/redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
|
||||
import { isSupportedBrowser } from '../../base/environment';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import {
|
||||
@@ -108,12 +107,8 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
||||
</h1>
|
||||
<p className = 'description'>
|
||||
{
|
||||
t(
|
||||
`${_TNS}.${isSupportedBrowser()
|
||||
? 'description'
|
||||
: 'descriptionWithoutWeb'}`,
|
||||
{ app: NATIVE_APP_NAME }
|
||||
)
|
||||
t(`${_TNS}.description`,
|
||||
{ app: NATIVE_APP_NAME })
|
||||
}
|
||||
</p>
|
||||
<div className = 'buttons'>
|
||||
@@ -123,12 +118,9 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
||||
onClick = { this._onTryAgain }>
|
||||
{ t(`${_TNS}.tryAgainButton`) }
|
||||
</Button>
|
||||
{
|
||||
isSupportedBrowser()
|
||||
&& <Button onClick = { this._onLaunchWeb }>
|
||||
{ t(`${_TNS}.launchWebButton`) }
|
||||
</Button>
|
||||
}
|
||||
<Button onClick = { this._onLaunchWeb }>
|
||||
{ t(`${_TNS}.launchWebButton`) }
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@ export function getDeepLinkingPage(state) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return _openDesktopApp(state).then(
|
||||
return _openDesktopApp().then(
|
||||
// eslint-disable-next-line no-confusing-arrow
|
||||
result => result ? DeepLinkingDesktopPage : undefined);
|
||||
}
|
||||
@@ -83,10 +83,9 @@ export function getDeepLinkingPage(state) {
|
||||
/**
|
||||
* Opens the desktop app.
|
||||
*
|
||||
* @param {Object} state - Object containing current redux state.
|
||||
* @returns {Promise<boolean>} - Resolves with true if the attempt to open the desktop app was successful and resolves
|
||||
* with false otherwise.
|
||||
*/
|
||||
export function openDesktopApp(state) {
|
||||
return _openDesktopApp(state);
|
||||
export function openDesktopApp() {
|
||||
return _openDesktopApp();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { openDesktopApp } from './functions';
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case OPEN_DESKTOP_APP:
|
||||
openDesktopApp(store.getState());
|
||||
openDesktopApp();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Opens the desktop app.
|
||||
*
|
||||
* @param {Object} state - Object containing current redux state.
|
||||
* @returns {Promise<boolean>} - Resolves with true if the attempt to open the desktop app was successful and resolves
|
||||
* with false otherwise.
|
||||
*/
|
||||
export function _openDesktopApp(state: Object) { // eslint-disable-line no-unused-vars
|
||||
export function _openDesktopApp() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
@@ -3,5 +3,3 @@
|
||||
export * from './actions';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { hideDialog, isDialogOpen } from '../base/dialog';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { SETTINGS_UPDATED } from '../base/settings';
|
||||
import { DisplayNamePrompt } from './components';
|
||||
|
||||
/**
|
||||
* Middleware that captures actions related to display name setting.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
switch (action.type) {
|
||||
case SETTINGS_UPDATED: {
|
||||
if (action.settings.displayName
|
||||
&& isDialogOpen(getState, DisplayNamePrompt)) {
|
||||
dispatch(hideDialog(DisplayNamePrompt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { CONFERENCE_FAILED } from '../base/conference';
|
||||
import { NOTIFY_CAMERA_ERROR, NOTIFY_MIC_ERROR } from '../base/devices';
|
||||
import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -14,14 +12,6 @@ declare var APP: Object;
|
||||
*/
|
||||
MiddlewareRegistry.register((/* store */) => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED: {
|
||||
if (action.conference
|
||||
&& action.error.name === JitsiConferenceErrors.PASSWORD_REQUIRED) {
|
||||
APP.API.notifyOnPasswordRequired();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NOTIFY_CAMERA_ERROR:
|
||||
if (action.error) {
|
||||
APP.API.notifyOnCameraError(
|
||||
|
||||
@@ -35,15 +35,6 @@ class GoogleApi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current tokens.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getTokens(): Promise<*> {
|
||||
return GoogleSignin.getTokens();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the available YouTube streams the user can use for live
|
||||
* streaming.
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
SafeAreaView,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
@@ -150,41 +149,35 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
||||
headerLabelKey = 'inviteDialog.header'
|
||||
onPressBack = { this._onCloseAddPeopleDialog }
|
||||
onPressForward = { this._onInvite } />
|
||||
<KeyboardAvoidingView
|
||||
behavior = 'padding'
|
||||
style = { styles.avoidingView }>
|
||||
<SafeAreaView style = { styles.dialogWrapper }>
|
||||
<View
|
||||
style = { styles.searchFieldWrapper }>
|
||||
<View style = { styles.searchIconWrapper }>
|
||||
{ this.state.searchInprogress
|
||||
? <ActivityIndicator
|
||||
color = { DARK_GREY }
|
||||
size = 'small' />
|
||||
: <Icon
|
||||
name = { 'search' }
|
||||
style = { styles.searchIcon } />}
|
||||
</View>
|
||||
<TextInput
|
||||
autoCorrect = { false }
|
||||
autoFocus = { true }
|
||||
onChangeText = { this._onTypeQuery }
|
||||
placeholder = {
|
||||
this.props.t(`inviteDialog.${placeholderKey}`)
|
||||
}
|
||||
ref = { this._setFieldRef }
|
||||
style = { styles.searchField } />
|
||||
<SafeAreaView style = { styles.dialogWrapper }>
|
||||
<View
|
||||
style = { styles.searchFieldWrapper }>
|
||||
<View style = { styles.searchIconWrapper }>
|
||||
{ this.state.searchInprogress
|
||||
? <ActivityIndicator
|
||||
color = { DARK_GREY }
|
||||
size = 'small' />
|
||||
: <Icon
|
||||
name = { 'search' }
|
||||
style = { styles.searchIcon } />}
|
||||
</View>
|
||||
<FlatList
|
||||
ItemSeparatorComponent = { this._renderSeparator }
|
||||
data = { this.state.selectableItems }
|
||||
extraData = { inviteItems }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
keyboardShouldPersistTaps = 'always'
|
||||
renderItem = { this._renderItem }
|
||||
style = { styles.resultList } />
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
<TextInput
|
||||
autoCorrect = { false }
|
||||
onChangeText = { this._onTypeQuery }
|
||||
placeholder = {
|
||||
this.props.t(`inviteDialog.${placeholderKey}`)
|
||||
}
|
||||
ref = { this._setFieldRef }
|
||||
style = { styles.searchField } />
|
||||
</View>
|
||||
<FlatList
|
||||
ItemSeparatorComponent = { this._renderSeparator }
|
||||
data = { this.state.selectableItems }
|
||||
extraData = { inviteItems }
|
||||
keyExtractor = { this._keyExtractor }
|
||||
renderItem = { this._renderItem }
|
||||
style = { styles.resultList } />
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -272,7 +265,8 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
||||
const items: Array<*> = inviteItems.concat(item);
|
||||
|
||||
this.setState({
|
||||
inviteItems: _.sortBy(items, [ 'name', 'number' ])
|
||||
// $FlowExpectedError
|
||||
inviteItems: _.orderBy(items, [ 'name' ], [ 'asc' ])
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -322,10 +316,13 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
|
||||
}
|
||||
});
|
||||
|
||||
selectableItems = _.sortBy(selectableItems, [ 'name', 'number' ]);
|
||||
const items = this.state.inviteItems.concat(selectableItems);
|
||||
|
||||
// $FlowExpectedError
|
||||
selectableItems = _.orderBy(items, [ 'name' ], [ 'asc' ]);
|
||||
|
||||
this.setState({
|
||||
selectableItems: this.state.inviteItems.concat(selectableItems)
|
||||
selectableItems
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -17,10 +17,6 @@ export default {
|
||||
fontSize: 12
|
||||
},
|
||||
|
||||
avoidingView: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
dialogWrapper: {
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: ColorPalette.white,
|
||||
|
||||
@@ -309,13 +309,18 @@ class InfoDialog extends Component<Props, State> {
|
||||
_getTextToCopy() {
|
||||
const { _localParticipant, liveStreamViewURL, t } = this.props;
|
||||
const shouldDisplayDialIn = this._shouldDisplayDialIn();
|
||||
const moreInfo
|
||||
= shouldDisplayDialIn
|
||||
? t('info.inviteURLMoreInfo', { conferenceID: this.props.dialIn.conferenceID })
|
||||
: '';
|
||||
|
||||
let invite = _localParticipant && _localParticipant.name
|
||||
? t('info.inviteURLFirstPartPersonal', { name: _localParticipant.name })
|
||||
: t('info.inviteURLFirstPartGeneral');
|
||||
|
||||
invite += t('info.inviteURLSecondPart', {
|
||||
url: this.props._inviteURL
|
||||
url: this.props._inviteURL,
|
||||
moreInfo
|
||||
});
|
||||
|
||||
if (liveStreamViewURL) {
|
||||
@@ -332,8 +337,7 @@ class InfoDialog extends Component<Props, State> {
|
||||
conferenceID: this.props.dialIn.conferenceID
|
||||
});
|
||||
const moreNumbers = t('info.invitePhoneAlternatives', {
|
||||
url: this._getDialInfoPageURL(),
|
||||
silentUrl: `${this.props._inviteURL}#config.startSilent=true`
|
||||
url: this._getDialInfoPageURL()
|
||||
});
|
||||
|
||||
invite = `${invite}\n${dial}\n${moreNumbers}`;
|
||||
|
||||
@@ -145,16 +145,7 @@ function _electParticipantInLargeVideo(state) {
|
||||
// As a last resort, pick the last participant who joined the
|
||||
// conference (regardless of whether they are local or
|
||||
// remote).
|
||||
//
|
||||
// HOWEVER: We don't want to show poltergeist or other bot type participants on stage
|
||||
// automatically, because it's misleading (users may think they are already
|
||||
// joined and maybe speaking).
|
||||
for (let i = participants.length; i > 0 && !participant; i--) {
|
||||
const p = participants[i - 1];
|
||||
|
||||
!p.botType && (participant = p);
|
||||
}
|
||||
|
||||
participant = participants[participants.length - 1];
|
||||
id = participant && participant.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,18 @@ export const ORIENTATION = {
|
||||
PORTRAIT: 'portrait'
|
||||
};
|
||||
|
||||
/**
|
||||
* A mapping of orientations to a class that should fit the
|
||||
* {@code LargeVideoBackground} into its container.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
const ORIENTATION_TO_CLASS = {
|
||||
[ORIENTATION.LANDSCAPE]: 'fit-full-width',
|
||||
[ORIENTATION.PORTRAIT]: 'fit-full-height'
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link LargeVideoBackgroundCanvas}.
|
||||
@@ -138,11 +150,14 @@ export class LargeVideoBackground extends Component<Props> {
|
||||
const {
|
||||
hidden,
|
||||
mirror,
|
||||
orientationFit,
|
||||
showLocalProblemFilter,
|
||||
showRemoteProblemFilter
|
||||
} = this.props;
|
||||
const orientationClass = orientationFit
|
||||
? ORIENTATION_TO_CLASS[orientationFit] : '';
|
||||
const classNames = `large-video-background ${mirror ? 'flip-x' : ''} ${
|
||||
hidden ? 'invisible' : ''} ${
|
||||
hidden ? 'invisible' : ''} ${orientationClass} ${
|
||||
showLocalProblemFilter ? 'videoProblemFilter' : ''} ${
|
||||
showRemoteProblemFilter ? 'remoteVideoProblemFilter' : ''}`;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { PIP_ENABLED, getFeatureFlag } from '../../base/flags';
|
||||
import { getAppProp } from '../../base/app';
|
||||
import { Platform } from '../../base/react';
|
||||
|
||||
import { ENTER_PICTURE_IN_PICTURE } from './actionTypes';
|
||||
@@ -25,7 +25,7 @@ export function enterPictureInPicture() {
|
||||
// XXX At the time of this writing this action can only be dispatched by
|
||||
// the button which is on the conference view, which means that it's
|
||||
// fine to enter PiP mode.
|
||||
if (getFeatureFlag(getState, PIP_ENABLED)) {
|
||||
if (getAppProp(getState, 'pictureInPictureEnabled')) {
|
||||
const { PictureInPicture } = NativeModules;
|
||||
const p
|
||||
= Platform.OS === 'android'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { getAppProp } from '../../../base/app';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton } from '../../../base/toolbox';
|
||||
@@ -62,7 +62,7 @@ class PictureInPictureButton extends AbstractButton<Props, *> {
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
return {
|
||||
_enabled: Boolean(getFeatureFlag(state, PIP_ENABLED))
|
||||
_enabled: Boolean(getAppProp(state, 'pictureInPictureEnabled'))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -119,22 +119,13 @@ class StartLiveStreamDialog extends AbstractStartLiveStreamDialog<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onUserChanged(response) {
|
||||
if (response) {
|
||||
googleApi.getTokens()
|
||||
.then(tokens => {
|
||||
googleApi.getYouTubeLiveStreams(tokens.accessToken)
|
||||
.then(broadcasts => {
|
||||
this.setState({
|
||||
broadcasts
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
broadcasts: undefined,
|
||||
streamKey: undefined
|
||||
});
|
||||
if (response && response.accessToken) {
|
||||
googleApi.getYouTubeLiveStreams(response.accessToken)
|
||||
.then(broadcasts => {
|
||||
this.setState({
|
||||
broadcasts
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
broadcasts: undefined,
|
||||
|
||||
@@ -285,7 +285,7 @@ function _mapStateToProps(state) {
|
||||
googleApiApplicationClientID,
|
||||
microsoftApiApplicationClientID
|
||||
} = state['features/base/config'];
|
||||
const calendarEnabled = isCalendarEnabled(state);
|
||||
const calendarEnabled = isCalendarEnabled();
|
||||
|
||||
return {
|
||||
_appName: interfaceConfig.APP_NAME,
|
||||
|
||||
@@ -135,7 +135,7 @@ function _mapStateToProps(state) {
|
||||
const showProfileSettings
|
||||
= configuredTabs.includes('profile') && jwt.isGuest;
|
||||
const showCalendarSettings
|
||||
= configuredTabs.includes('calendar') && isCalendarEnabled(state);
|
||||
= configuredTabs.includes('calendar') && isCalendarEnabled();
|
||||
const tabs = [];
|
||||
|
||||
if (showDeviceSettings) {
|
||||
|
||||
@@ -27,11 +27,6 @@ type Props = AbstractButtonProps & {
|
||||
*/
|
||||
_audioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether the button is disabled.
|
||||
*/
|
||||
_disabled: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
@@ -133,15 +128,6 @@ class AudioMuteButton extends AbstractAudioMuteButton<Props, *> {
|
||||
typeof APP === 'undefined'
|
||||
|| APP.UI.emitEvent(UIEvents.AUDIO_MUTED, audioMuted, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean value indicating if this button is disabled or not.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,8 +144,7 @@ function _mapStateToProps(state): Object {
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
return {
|
||||
_audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO),
|
||||
_disabled: state['features/base/config'].startSilent
|
||||
_audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { setAudioOnly } from '../../base/conference';
|
||||
import { translate } from '../../base/i18n';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
@@ -92,6 +91,17 @@ class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
|
||||
|| APP.keyboardshortcut.unregisterShortcut('V');
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this button should be disabled or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return this.props._audioOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if video is currently muted ot nor.
|
||||
*
|
||||
@@ -132,11 +142,6 @@ class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
|
||||
*/
|
||||
_setVideoMuted(videoMuted: boolean) {
|
||||
sendAnalytics(createToolbarEvent(VIDEO_MUTE, { enable: videoMuted }));
|
||||
if (this.props._audioOnly) {
|
||||
this.props.dispatch(
|
||||
setAudioOnly(false, /* ensureTrack */ true));
|
||||
}
|
||||
|
||||
this.props.dispatch(
|
||||
setVideoMuted(
|
||||
videoMuted,
|
||||
|
||||
@@ -4,8 +4,10 @@ import React, { Component } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { BottomSheet, hideDialog } from '../../../base/dialog';
|
||||
import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import {
|
||||
BottomSheet,
|
||||
hideDialog
|
||||
} from '../../../base/dialog';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { InfoDialogButton, InviteButton } from '../../../invite';
|
||||
@@ -19,6 +21,8 @@ import AudioOnlyButton from './AudioOnlyButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ToggleCameraButton from './ToggleCameraButton';
|
||||
|
||||
declare var __DEV__;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link OverflowMenu}.
|
||||
*/
|
||||
@@ -29,16 +33,6 @@ type Props = {
|
||||
*/
|
||||
_bottomSheetStyles: StyleType,
|
||||
|
||||
/**
|
||||
* Whether the chat feature has been enabled. The meeting info button will be displayed in its place when disabled.
|
||||
*/
|
||||
_chatEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether the recoding button should be enabled or not.
|
||||
*/
|
||||
_recordingEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Used for hiding the dialog when the selection was completed.
|
||||
*/
|
||||
@@ -92,16 +86,17 @@ class OverflowMenu extends Component<Props> {
|
||||
<RoomLockButton { ...buttonProps } />
|
||||
<ClosedCaptionButton { ...buttonProps } />
|
||||
{
|
||||
this.props._recordingEnabled
|
||||
|
||||
// Apple rejected our app because they claim requiring a
|
||||
// Dropbox account for recording is not acceptable.
|
||||
// Ddisable it until we can find a way around it.
|
||||
(__DEV__ || Platform.OS !== 'ios')
|
||||
&& <RecordButton { ...buttonProps } />
|
||||
}
|
||||
<LiveStreamButton { ...buttonProps } />
|
||||
<TileViewButton { ...buttonProps } />
|
||||
<InviteButton { ...buttonProps } />
|
||||
{
|
||||
this.props._chatEnabled
|
||||
&& <InfoDialogButton { ...buttonProps } />
|
||||
}
|
||||
<InfoDialogButton { ...buttonProps } />
|
||||
<RaiseHandButton { ...buttonProps } />
|
||||
</BottomSheet>
|
||||
);
|
||||
@@ -126,17 +121,13 @@ class OverflowMenu extends Component<Props> {
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _bottomSheetStyles: StyleType,
|
||||
* _chatEnabled: boolean,
|
||||
* _recordingEnabled: boolean
|
||||
* _bottomSheetStyles: StyleType
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_bottomSheetStyles:
|
||||
ColorSchemeRegistry.get(state, 'BottomSheet'),
|
||||
_chatEnabled: getFeatureFlag(state, CHAT_ENABLED, true),
|
||||
_recordingEnabled: Platform.OS !== 'ios' || getFeatureFlag(state, IOS_RECORDING_ENABLED)
|
||||
ColorSchemeRegistry.get(state, 'BottomSheet')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
import { Container } from '../../../base/react';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { ChatButton } from '../../../chat';
|
||||
import { InfoDialogButton } from '../../../invite';
|
||||
|
||||
import { isToolboxVisible } from '../../functions';
|
||||
import { HANGUP_BUTTON_SIZE } from '../../constants';
|
||||
@@ -43,11 +41,6 @@ const _BUTTON_SIZE_FACTOR = 0.85;
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether the chat feature has been enabled. The meeting info button will be displayed in its place when disabled.
|
||||
*/
|
||||
_chatEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
@@ -209,7 +202,7 @@ class Toolbox extends Component<Props, State> {
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderToolbar() {
|
||||
const { _chatEnabled, _styles } = this.props;
|
||||
const { _styles } = this.props;
|
||||
const buttonSize = this._calculateButtonSize();
|
||||
let { buttonStyles, toggledButtonStyles } = _styles;
|
||||
|
||||
@@ -246,20 +239,11 @@ class Toolbox extends Component<Props, State> {
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.toolbar }>
|
||||
{
|
||||
_chatEnabled
|
||||
&& <ChatButton
|
||||
styles = { buttonStyles }
|
||||
toggledStyles = {
|
||||
this._getChatButtonToggledStyle(toggledButtonStyles)
|
||||
} />
|
||||
}
|
||||
{
|
||||
!_chatEnabled
|
||||
&& <InfoDialogButton
|
||||
styles = { buttonStyles }
|
||||
toggledStyles = { toggledButtonStyles } />
|
||||
}
|
||||
<ChatButton
|
||||
styles = { buttonStyles }
|
||||
toggledStyles = {
|
||||
this._getChatButtonToggledStyle(toggledButtonStyles)
|
||||
} />
|
||||
<AudioMuteButton
|
||||
styles = { buttonStyles }
|
||||
toggledStyles = { toggledButtonStyles } />
|
||||
@@ -284,14 +268,12 @@ class Toolbox extends Component<Props, State> {
|
||||
* {@code Toolbox} props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _chatEnabled: boolean,
|
||||
* _styles: StyleType,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
return {
|
||||
_chatEnabled: getFeatureFlag(state, CHAT_ENABLED, true),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
|
||||
_visible: isToolboxVisible(state)
|
||||
};
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Platform } from '../../base/react';
|
||||
|
||||
import { CHROME, FIREFOX } from './browserLinks';
|
||||
import { CHROME, /* EDGE, */ FIREFOX, SAFARI } from './browserLinks';
|
||||
|
||||
/**
|
||||
* The namespace of the CSS styles of UnsupportedDesktopBrowser.
|
||||
@@ -50,11 +51,55 @@ class UnsupportedDesktopBrowser extends Component<Props> {
|
||||
href = { CHROME } >Chrome</a> and
|
||||
<a
|
||||
className = { `${_SNS}__link` }
|
||||
href = { FIREFOX }>Firefox</a>
|
||||
href = { FIREFOX }>Firefox</a>
|
||||
{
|
||||
this._renderOSSpecificBrowserDownloadLink()
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Depending on the platform returns the link to Safari browser.
|
||||
*
|
||||
* @returns {ReactElement|null}
|
||||
* @private
|
||||
*/
|
||||
_renderOSSpecificBrowserDownloadLink() {
|
||||
let link;
|
||||
let text;
|
||||
|
||||
switch (Platform.OS) {
|
||||
case 'macos':
|
||||
link = SAFARI;
|
||||
text = 'Safari';
|
||||
break;
|
||||
|
||||
/*
|
||||
case 'windows':
|
||||
link = EDGE;
|
||||
text = 'Edge';
|
||||
break;
|
||||
*/
|
||||
}
|
||||
if (typeof link !== 'undefined') {
|
||||
return (
|
||||
<span>
|
||||
or
|
||||
<a
|
||||
className = { `${_SNS}__link` }
|
||||
href = { link }>
|
||||
{
|
||||
text
|
||||
}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(UnsupportedDesktopBrowser);
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { Dispatch } from 'redux';
|
||||
|
||||
import { createWelcomePageEvent, sendAnalytics } from '../../analytics';
|
||||
import { appNavigate } from '../../app';
|
||||
import { isCalendarEnabled } from '../../calendar-sync';
|
||||
import { isRoomValid } from '../../base/conference';
|
||||
|
||||
/**
|
||||
@@ -14,11 +13,6 @@ import { isRoomValid } from '../../base/conference';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether the calendar functionality is enabled or not.
|
||||
*/
|
||||
_calendarEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Room name to join to.
|
||||
*/
|
||||
@@ -243,14 +237,12 @@ export class AbstractWelcomePage extends Component<Props, *> {
|
||||
* @param {Object} state - The redux state.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* _calendarEnabled: boolean,
|
||||
* _room: string,
|
||||
* _settings: Object
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object) {
|
||||
return {
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
_room: state['features/base/conference'].room,
|
||||
_settings: state['features/base/settings']
|
||||
};
|
||||
|
||||
@@ -225,11 +225,11 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { _calendarEnabled, t } = this.props;
|
||||
const { t } = this.props;
|
||||
|
||||
const tabs = [];
|
||||
|
||||
if (_calendarEnabled) {
|
||||
if (CalendarList) {
|
||||
tabs.push({
|
||||
label: t('welcomepage.calendar'),
|
||||
content: <CalendarList />
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { Component } from 'react';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { PagedList } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { CalendarList, isCalendarEnabled } from '../../calendar-sync';
|
||||
import { CalendarList } from '../../calendar-sync';
|
||||
import { RecentList } from '../../recent-list';
|
||||
|
||||
import { setWelcomePageListsDefaultPage } from '../actions';
|
||||
@@ -15,11 +15,6 @@ import { setWelcomePageListsDefaultPage } from '../actions';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether the calendar functionality is enabled or not.
|
||||
*/
|
||||
_calendarEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The stored default page index.
|
||||
*/
|
||||
@@ -45,6 +40,19 @@ type Props = {
|
||||
* Implements the lists displayed on the mobile welcome screen.
|
||||
*/
|
||||
class WelcomePageLists extends Component<Props> {
|
||||
/**
|
||||
* The pages to be rendered.
|
||||
*
|
||||
* Note: An element's {@code component} may be {@code undefined} if a
|
||||
* feature (such as Calendar) is disabled, and that means that the page must
|
||||
* not be rendered.
|
||||
*/
|
||||
pages: Array<{
|
||||
component: ?Object,
|
||||
icon: string | number,
|
||||
title: string
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code WelcomePageLists} instance.
|
||||
*
|
||||
@@ -53,6 +61,21 @@ class WelcomePageLists extends Component<Props> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { t } = props;
|
||||
|
||||
this.pages = [
|
||||
{
|
||||
component: RecentList,
|
||||
icon: 'restore',
|
||||
title: t('welcomepage.recentList')
|
||||
},
|
||||
{
|
||||
component: CalendarList,
|
||||
icon: 'event_note',
|
||||
title: t('welcomepage.calendar')
|
||||
}
|
||||
];
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSelectPage = this._onSelectPage.bind(this);
|
||||
}
|
||||
@@ -63,36 +86,18 @@ class WelcomePageLists extends Component<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _calendarEnabled, _defaultPage, t } = this.props;
|
||||
const { _defaultPage } = this.props;
|
||||
|
||||
if (typeof _defaultPage === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pages = [
|
||||
{
|
||||
component: RecentList,
|
||||
icon: 'restore',
|
||||
title: t('welcomepage.recentList')
|
||||
}
|
||||
];
|
||||
|
||||
if (_calendarEnabled) {
|
||||
pages.push(
|
||||
{
|
||||
component: CalendarList,
|
||||
icon: 'event_note',
|
||||
title: t('welcomepage.calendar')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PagedList
|
||||
defaultPage = { _defaultPage }
|
||||
disabled = { this.props.disabled }
|
||||
onSelectPage = { this._onSelectPage }
|
||||
pages = { pages } />
|
||||
pages = { this.pages } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,7 +122,6 @@ class WelcomePageLists extends Component<Props> {
|
||||
* @param {Object} state - The redux state.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* _calendarEnabled: boolean,
|
||||
* _defaultPage: number
|
||||
* }}
|
||||
*/
|
||||
@@ -131,7 +135,6 @@ function _mapStateToProps(state: Object) {
|
||||
}
|
||||
|
||||
return {
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
_defaultPage: defaultPage
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { WELCOME_PAGE_ENABLED, getFeatureFlag } from '../base/flags';
|
||||
import { getAppProp } from '../base/app';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -24,7 +24,7 @@ export function isWelcomePageAppEnabled(stateful: Function | Object) {
|
||||
// - Enabling/disabling the Welcome page on Web historically
|
||||
// automatically redirects to a random room and that does not make sense
|
||||
// on mobile (right now).
|
||||
return Boolean(getFeatureFlag(stateful, WELCOME_PAGE_ENABLED));
|
||||
return Boolean(getAppProp(stateful, 'welcomePageEnabled'));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -39,6 +39,25 @@ local function room_jid_match_rewrite(room_jid)
|
||||
return room_jid
|
||||
end
|
||||
|
||||
-- Utility function to check and convert a room JID from real [foo]room1@muc.example.com to virtual room1@muc.foo.example.com
|
||||
local function room_jid_match_rewrite_from_internal(room_jid)
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
if host ~= muc_domain or not node then
|
||||
module:log("debug", "No need to rewrite %s (not from the MUC host)", room_jid);
|
||||
|
||||
return room_jid;
|
||||
end
|
||||
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$");
|
||||
if not (target_node and target_subdomain) then
|
||||
module:log("debug", "Not rewriting... unexpected node format: %s", node);
|
||||
return room_jid;
|
||||
end
|
||||
-- Ok, rewrite room_jid address to pretty format
|
||||
local new_node, new_host, new_resource = target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource;
|
||||
room_jid = jid.join(new_node, new_host, new_resource);
|
||||
module:log("debug", "Rewrote to %s", room_jid);
|
||||
return room_jid
|
||||
end
|
||||
|
||||
--- Finds and returns room by its jid
|
||||
-- @param room_jid the room jid to search in the muc component
|
||||
@@ -194,5 +213,6 @@ return {
|
||||
wrap_async_run = wrap_async_run;
|
||||
async_handler_wrapper = async_handler_wrapper;
|
||||
room_jid_match_rewrite = room_jid_match_rewrite;
|
||||
room_jid_match_rewrite_from_internal = room_jid_match_rewrite_from_internal
|
||||
update_presence_identity = update_presence_identity;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user