Compare commits

..

5 Commits

Author SHA1 Message Date
paweldomas
4d60a4e3a6 ref(base/session): createSession on APP_WILL_NAVIGATE 2019-05-08 14:48:53 -05:00
paweldomas
160380dac0 ref(base/session): create tracks and connect on SET_ROOM 2019-05-08 14:46:18 -05:00
paweldomas
9dbba7b119 ref(base/session): move endAllSessions to APP_WILL_NAVIGATE 2019-05-08 14:39:18 -05:00
paweldomas
7101f90b6e feat: add base/session 2019-05-06 16:06:56 -05:00
paweldomas
621ee7b447 ref(base/connection/actions.native): JitsiConnection.connect returns void
Do not return anything from JitsiConnection.connect, because it's not
a promise and returns void. Doing so is confusing to the reader.
2019-05-06 16:06:56 -05:00
76 changed files with 764 additions and 1061 deletions

View File

@@ -2,6 +2,5 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:navigationBarColor">#1081B2</item>
</style>
</resources>

View File

@@ -62,8 +62,6 @@ dependencies {
implementation project(':react-native-sound')
implementation project(':react-native-vector-icons')
implementation project(':react-native-webrtc')
implementation project(':react-native-webview')
implementation project(':@react-native-community_async-storage')
testImplementation 'junit:junit:4.12'
}
@@ -207,7 +205,7 @@ publishing {
def groupId = it.moduleGroup
def artifactId = it.moduleName
if ((artifactId.startsWith('react-native-') || artifactId.startsWith('@react-native-community'))
if (artifactId.startsWith('react-native-')
&& groupId.equals('jitsi-meet')) {
groupId = rootProject.ext.moduleGroupId
}

View File

@@ -40,15 +40,6 @@ public class JitsiMeetActivityDelegate {
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* Tells whether or not the permissions request is currently in progress.
*
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
*/
static boolean arePermissionsBeingRequested() {
return permissionListener != null;
}
/**
* {@link Activity} lifecycle method which should be called from
* {@code Activity#onActivityResult} so we are notified about results of external intents

View File

@@ -123,7 +123,6 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
PictureInPictureModule.class);
if (pipModule != null
&& PictureInPictureModule.isPictureInPictureSupported()
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
&& this.url != null) {
try {
pipModule.enterPictureInPicture();

View File

@@ -147,8 +147,6 @@ class ReactInstanceManagerHolder {
new com.oblador.vectoricons.VectorIconsPackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
new com.reactnativecommunity.webview.RNCWebViewPackage(),
new com.rnimmersive.RNImmersivePackage(),
new com.zmxv.RNSound.RNSoundPackage(),
new ReactPackageAdapter() {

View File

@@ -3,8 +3,6 @@ rootProject.name = 'jitsi-meet'
include ':app', ':sdk'
include ':react-native-background-timer'
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
include ':react-native-calendar-events'
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
include ':react-native-fast-image'
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
include ':react-native-google-signin'
@@ -21,7 +19,5 @@ include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-webrtc'
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
include ':@react-native-community_async-storage'
project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android')
include ':react-native-calendar-events'
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')

View File

@@ -718,21 +718,13 @@ export default {
this.roomName = options.roomName;
return (
// Initialize the device list first. This way, when creating tracks
// 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.
this._initDeviceList()
.catch(error => logger.warn(
'initial device list initialization failed', error))
.then(() => this.createInitialLocalTracksAndConnect(
this.createInitialLocalTracksAndConnect(
options.roomName, {
startAudioOnly: config.startAudioOnly,
startScreenSharing: config.startScreenSharing,
startWithAudioMuted: config.startWithAudioMuted,
startWithVideoMuted: config.startWithVideoMuted
}))
})
.then(([ tracks, con ]) => {
tracks.forEach(track => {
if ((track.isAudioTrack() && this.isLocalAudioMuted())
@@ -777,10 +769,7 @@ export default {
this.setVideoMuteStatus(true);
}
// Initialize device list a second time to ensure device labels
// get populated in case of an initial gUM acceptance; otherwise
// they may remain as empty strings.
this._initDeviceList(true);
this._initDeviceList();
if (config.iAmRecorder) {
this.recorder = new Recorder();
@@ -2288,23 +2277,20 @@ export default {
},
/**
* Updates the list of current devices.
* @param {boolean} setDeviceListChangeHandler - Whether to add the deviceList change handlers.
* Inits list of current devices and event listener for device change.
* @private
* @returns {Promise}
*/
_initDeviceList(setDeviceListChangeHandler = false) {
_initDeviceList() {
const { mediaDevices } = JitsiMeetJS;
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
if (setDeviceListChangeHandler) {
this.deviceChangeListener = devices =>
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
mediaDevices.addEventListener(
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
this.deviceChangeListener);
}
this.deviceChangeListener = devices =>
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
mediaDevices.addEventListener(
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
this.deviceChangeListener);
const { dispatch } = APP.store;

View File

@@ -5,7 +5,8 @@ import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
import {
connectionEstablished,
connectionFailed
connectionFailed,
connectionWillConnect
} from './react/features/base/connection';
import {
isFatalJitsiConnectionError,
@@ -74,6 +75,7 @@ function checkForAttachParametersAndConnect(id, password, connection) {
function connect(id, password, roomName) {
const connectionConfig = Object.assign({}, config);
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
const { locationURL } = APP.store.getState()['features/base/connection'];
connectionConfig.bosh += `?room=${roomName}`;
@@ -83,6 +85,8 @@ function connect(id, password, roomName) {
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
connectionConfig);
APP.store.dispatch(connectionWillConnect(connection, locationURL));
return new Promise((resolve, reject) => {
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED,

View File

@@ -1,9 +1,6 @@
#sideToolbarContainer {
background-color: $newToolbarBackgroundColor;
box-sizing: border-box;
color: #FFF;
display: flex;
flex-direction: column;
/**
* Make the sidebar flush with the top of the toolbar. Take the size of
* the toolbar and subtract from 100%.
@@ -24,6 +21,20 @@
&.slideInExt {
left: 0;
}
.sideToolbarContainer__inner {
box-sizing: border-box;
color: #FFF;
display: flex;
flex-direction: column;
height: 100%;
width: $sidebarWidth;
}
}
#chat_container * {
-webkit-user-select: text;
user-select: text;
}
#chatconversation {
@@ -31,8 +42,9 @@
flex: 1;
font-size: 10pt;
line-height: 20px;
margin-top: $desktopAppDragBarHeight + 5px;
overflow: auto;
padding: 16px;
padding: 5px;
text-align: left;
width: $sidebarWidth;
word-wrap: break-word;
@@ -80,41 +92,26 @@
}
}
.chat-header {
background-color: $chatHeaderBackgroundColor;
height: 70px;
position: relative;
width: 100%;
.chat-close {
background: gray;
border: 3px solid rgba(255, 255, 255, 0.1);
border-radius: 100%;
color: white;
cursor:pointer;
height: 10px;
line-height: 10px;
padding: 4px;
position: absolute;
right: 5px;
text-align: center;
top: $desktopAppDragBarHeight;
width: 10px;
z-index: 1;
.chat-close {
align-items: center;
bottom: 8px;
color: white;
cursor: pointer;
display: flex;
font-size: 18px;
height: 40px;
justify-content: center;
line-height: 15px;
padding: 4px;
position: absolute;
right: 5px;
width: 40px;
&:hover {
color: rgba(255, 255, 255, 0.8);
}
}
}
#chat-input {
border-top: 1px solid $chatInputSeparatorColor;
background-color: $newToolbarBackgroundColor;
display: flex;
* {
background-color: transparent;
}
}
.remoteuser {
@@ -126,6 +123,7 @@
}
#usermsg {
background-color: $newToolbarBackgroundColor;
border: 0px none;
border-radius:0;
box-shadow: none;
@@ -147,35 +145,30 @@
}
#nickname {
position: absolute;
text-align: center;
color: #9d9d9d;
font-size: 18px;
margin-top: 30px;
top: 100px;
left: 5px;
right: 5px;
width: 95%;
}
.sideToolbarContainer {
* {
-webkit-user-select: text;
user-select: text;
}
#chat_container .display-name {
float: left;
padding-left: 5px;
font-weight: bold;
white-space: nowrap;
text-overflow: ellipsis;
width: 95%;
overflow: hidden;
}
.display-name {
float: left;
padding-left: 5px;
font-weight: bold;
white-space: nowrap;
text-overflow: ellipsis;
width: 95%;
overflow: hidden;
}
.timestamp {
float: right;
padding-right: 5px;
font-size: 11px;
}
#chat_container .timestamp {
float: right;
padding-right: 5px;
font-size: 11px;
}
.usermessage {
@@ -183,22 +176,33 @@
padding-left: 5px;
}
.chatArrow {
border-color:
transparent $newToolbarBackgroundColor transparent transparent;
border-style: solid;
border-width: 0 10px 10px 0;
left: -10px;
position: absolute;
}
.chatmessage {
background-color: $chatRemoteMessageBackgroundColor;
border-radius: 0px 6px 6px 6px;
background-color: $newToolbarBackgroundColor;
width: 93%;
margin-left: 9px;
margin-right: auto;
border-radius: 5px;
border-top-left-radius: 0px;
margin-top: 3px;
color: white;
padding-bottom: 3px;
position: relative;
&.localuser {
background-color: $chatLocalMessageBackgroundColor;
border-radius: 6px 0px 6px 6px;
&.localuser .display-name {
color: #4C9AFF
}
&.error {
border-radius: 0px;
.chatArrow,
.timestamp,
.display-name {
display: none;
@@ -227,6 +231,7 @@
}
#smileysarea {
background-color: $newToolbarBackgroundColor;
display: flex;
max-height: 150px;
min-height: 35px;
@@ -241,22 +246,14 @@
.smileys-panel {
bottom: 100%;
box-sizing: border-box;
height: auto;
max-height: 0;
height: 0;
overflow: hidden;
position: absolute;
transition: height 0.3s;
width: $sidebarWidth;
/**
* CSS transitions do not apply for auto dimensions. So to produce the css
* accordion effect for showing and hiding the smiley-panel, while allowing
* for variable panel, height, use a very large max-height and animate off
* of that.
*/
transition: max-height 0.3s;
&.show-smileys {
max-height: 500%;
height: 146px;
}
#smileysContainer {

View File

@@ -83,19 +83,11 @@ $modalMockAKInputBorder: 1px solid #f4f5f7;
$modalTextColor: #333;
/**
* Chat
*/
$chatHeaderBackgroundColor: rgba(42, 58, 75, 0.9);
$chatInputSeparatorColor: #A4B8D1;
$chatLocalMessageBackgroundColor: rgba(26, 108, 180, 1);
$chatRemoteMessageBackgroundColor: rgba(240, 243, 247, 0.15);
$sidebarWidth: 375px;
/**
* Misc.
*/
$borderRadius: 4px;
$defaultWatermarkLink: '../images/watermark.png';
$sidebarWidth: 220px;
$popoverMenuPadding: 13px;
$happySoftwareBackground: transparent;
$desktopAppDragBarHeight: 25px;

View File

@@ -185,7 +185,6 @@
font-size: 12px;
max-height: 100%;
overflow: auto;
padding: 15pt;
position: absolute;
transform: translateY(-50%);
top: 50%;

View File

@@ -14,9 +14,6 @@ end
target 'JitsiMeet' do
project 'sdk/sdk.xcodeproj'
# React Native and its dependencies
#
pod 'React', :path => '../node_modules/react-native', :subspecs => [
'Core',
'CxxBridge',
@@ -30,31 +27,33 @@ target 'JitsiMeet' do
'RCTWebSocket',
]
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
# React Native plugins
#
pod 'react-native-background-timer', :path => '../node_modules/react-native-background-timer'
pod 'react-native-calendar-events', :path => '../node_modules/react-native-calendar-events'
pod 'react-native-fast-image', :path => '../node_modules/react-native-fast-image'
pod 'react-native-keep-awake', :path => '../node_modules/react-native-keep-awake'
pod 'react-native-webview', :path => '../node_modules/react-native-webview'
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'BVLinearGradient', :path => '../node_modules/react-native-linear-gradient'
pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-community/async-storage'
pod 'RNGoogleSignin', :path => '../node_modules/react-native-google-signin'
pod 'RNSound', :path => '../node_modules/react-native-sound'
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity'
# Native pod dependencies
#
pod 'DoubleConversion',
:podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog',
:podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly',
:podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
pod 'Amplitude-iOS', '~> 4.0.4'
pod 'ObjectiveDropboxOfficial', '~> 3.9.4'
pod 'react-native-background-timer',
:path => '../node_modules/react-native-background-timer'
pod 'react-native-fast-image',
:path => '../node_modules/react-native-fast-image'
pod 'react-native-keep-awake',
:path => '../node_modules/react-native-keep-awake'
pod 'BVLinearGradient',
:path => '../node_modules/react-native-linear-gradient'
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'RNGoogleSignin',
:path => '../node_modules/react-native-google-signin'
pod 'RNSound', :path => '../node_modules/react-native-sound'
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity'
pod 'react-native-calendar-events',
:path => '../node_modules/react-native-calendar-events'
end
post_install do |installer|

View File

@@ -99,8 +99,6 @@ PODS:
- React
- react-native-webrtc (1.69.1):
- React
- react-native-webview (5.8.1):
- React
- React/Core (0.59.5):
- yoga (= 0.59.5.React)
- React/CxxBridge (0.59.5):
@@ -148,8 +146,6 @@ PODS:
- React/Core
- React/fishhook
- React/RCTBlob
- RNCAsyncStorage (1.3.4):
- React
- RNGoogleSignin (1.0.2):
- GoogleSignIn
- React
@@ -184,7 +180,6 @@ DEPENDENCIES:
- react-native-fast-image (from `../node_modules/react-native-fast-image`)
- react-native-keep-awake (from `../node_modules/react-native-keep-awake`)
- react-native-webrtc (from `../node_modules/react-native-webrtc`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React/Core (from `../node_modules/react-native`)
- React/CxxBridge (from `../node_modules/react-native`)
- React/DevSupport (from `../node_modules/react-native`)
@@ -195,7 +190,6 @@ DEPENDENCIES:
- React/RCTNetwork (from `../node_modules/react-native`)
- React/RCTText (from `../node_modules/react-native`)
- React/RCTWebSocket (from `../node_modules/react-native`)
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- RNGoogleSignin (from `../node_modules/react-native-google-signin`)
- RNSound (from `../node_modules/react-native-sound`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
@@ -245,10 +239,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-keep-awake"
react-native-webrtc:
:path: "../node_modules/react-native-webrtc"
react-native-webview:
:path: "../node_modules/react-native-webview"
RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage"
RNGoogleSignin:
:path: "../node_modules/react-native-google-signin"
RNSound:
@@ -289,8 +279,6 @@ SPEC CHECKSUMS:
react-native-fast-image: 47487b71169aea34868e7b38bf870b6b3f2157c5
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-webrtc: 90a847d19deb2d7323fef8cc89ca12b8995fbc90
react-native-webview: a95842e3f351a6d2c8bc8bcc9eab689c7e7e5ad4
RNCAsyncStorage: 8e31405a9f12fbf42c2bb330e4560bfd79c18323
RNGoogleSignin: 361174d9a3090d295b06257162b560d8efc8a6ed
RNSound: e157320f503bdd4f4ee6d8542e948d54f90c3c3a
RNVectorIcons: d819334932bcda3332deb3d2c8ea4d069e0b98f9
@@ -298,6 +286,6 @@ SPEC CHECKSUMS:
SDWebImage: 3f3f0c02f09798048c47a5ed0a13f17b063572d8
yoga: 2e571f113e8cbeb0eb752aeebc86c1bfe7a8200c
PODFILE CHECKSUM: b55338cc43312051ed83f8d9c6aadbd8c9402e6a
PODFILE CHECKSUM: 9e6bc935ea7d2974604572cc68938281a88cf35c
COCOAPODS: 1.6.1

View File

@@ -50,7 +50,7 @@
},
"chat": {
"error": "Error: your message \"__originalText__\" was not sent. Reason: __error__",
"messagebox": "Type a message",
"messagebox": "Enter text...",
"nickname": {
"popover": "Choose a nickname",
"title": "Enter a nickname to use chat"
@@ -351,7 +351,6 @@
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Sorry, dialing in is currently not supported.",
"dialInNumber": "Dial-in:",
"dialInSummaryError": "Error fetching dial-in info now. Please try again later.",
"dialInTollFree": "Toll Free",
"genericError": "Whoops, something went wrong.",
"inviteLiveStream": "To view the live stream of this meeting, click this link: __url__",
@@ -774,7 +773,6 @@
"enterRoomTitle": "Start a new meeting",
"go": "GO",
"join": "JOIN",
"info": "Info",
"privacy": "Privacy",
"recentList": "Recent",
"recentListDelete": "Delete",

View File

@@ -1,11 +1,6 @@
/* global APP, JitsiMeetJS */
import { getAudioOutputDeviceId } from '../../react/features/base/devices';
import {
getUserSelectedCameraDeviceId,
getUserSelectedMicDeviceId,
getUserSelectedOutputDeviceId
} from '../../react/features/base/settings';
/**
* Determines if currently selected audio output device should be changed after
@@ -31,7 +26,8 @@ function getNewAudioOutputDevice(newDevices) {
return 'default';
}
const preferredAudioOutputDeviceId = getUserSelectedOutputDeviceId(APP.store.getState());
const settings = APP.store.getState()['features/base/settings'];
const preferredAudioOutputDeviceId = settings.userSelectedAudioOutputDeviceId;
// if the preferred one is not the selected and is available in the new devices
// we want to use it as it was just added
@@ -53,7 +49,8 @@ function getNewAudioOutputDevice(newDevices) {
function getNewAudioInputDevice(newDevices, localAudio) {
const availableAudioInputDevices = newDevices.filter(
d => d.kind === 'audioinput');
const selectedAudioInputDeviceId = getUserSelectedMicDeviceId(APP.store.getState());
const settings = APP.store.getState()['features/base/settings'];
const selectedAudioInputDeviceId = settings.userSelectedMicDeviceId;
const selectedAudioInputDevice = availableAudioInputDevices.find(
d => d.deviceId === selectedAudioInputDeviceId);
@@ -91,7 +88,8 @@ function getNewAudioInputDevice(newDevices, localAudio) {
function getNewVideoInputDevice(newDevices, localVideo) {
const availableVideoInputDevices = newDevices.filter(
d => d.kind === 'videoinput');
const selectedVideoInputDeviceId = getUserSelectedCameraDeviceId(APP.store.getState());
const settings = APP.store.getState()['features/base/settings'];
const selectedVideoInputDeviceId = settings.userSelectedCameraDeviceId;
const selectedVideoInputDevice = availableVideoInputDevices.find(
d => d.deviceId === selectedVideoInputDeviceId);

24
package-lock.json generated
View File

@@ -2448,11 +2448,6 @@
"isomorphic-fetch": "^2.2.1"
}
},
"@react-native-community/async-storage": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.3.4.tgz",
"integrity": "sha512-fJmzL27x0BEjhmMXPnDPnUNCZK7bph+NBVCfAz9fzHzAamaiOkdUwuL3PvE4Oj4Kw4knP8ocw5VRDGorAidZ2g=="
},
"@react-native-community/cli": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-1.9.2.tgz",
@@ -12279,25 +12274,6 @@
"prop-types": "^15.5.10"
}
},
"react-native-webview": {
"version": "5.8.1",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-5.8.1.tgz",
"integrity": "sha512-b6pSvmjoiWtcz6YspggW02X+BRXJWuquHwkh37BRx1NMW1iwMZA31SnFQvTpPzWYYIb9WF/mRsy2nGtt9C6NIg==",
"requires": {
"escape-string-regexp": "1.0.5",
"invariant": "2.2.4"
},
"dependencies": {
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"requires": {
"loose-envify": "^1.0.0"
}
}
}
},
"react-node-resolver": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-node-resolver/-/react-node-resolver-1.0.1.tgz",

View File

@@ -34,7 +34,6 @@
"@atlaskit/toggle": "5.0.14",
"@atlaskit/tooltip": "12.1.13",
"@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-community/async-storage": "1.3.4",
"@webcomponents/url": "0.7.1",
"amplitude-js": "4.5.2",
"bc-css-flags": "3.0.0",
@@ -77,7 +76,6 @@
"react-native-vector-icons": "6.0.2",
"react-native-watch-connectivity": "0.2.0",
"react-native-webrtc": "github:jitsi/react-native-webrtc#4064c6f2db4f8b961daaaa8dafc6a896d7cfbc43",
"react-native-webview": "5.8.1",
"react-redux": "5.0.7",
"react-transition-group": "2.4.0",
"redux": "4.0.0",

View File

@@ -2,6 +2,7 @@
import type { Dispatch } from 'redux';
import { appWillNavigate } from '../base/app';
import { setRoom } from '../base/conference';
import {
configWillLoad,
@@ -58,6 +59,9 @@ export function appNavigate(uri: ?string) {
const { contextRoot, host, room } = location;
const locationURL = new URL(location.toString());
// XXX this looks like CONFIG_WILL_LOAD ?
dispatch(appWillNavigate(locationURL, room));
dispatch(configWillLoad(locationURL, room));
let protocol = location.protocol.toLowerCase();
@@ -81,7 +85,7 @@ export function appNavigate(uri: ?string) {
config = restoreConfig(baseURL);
if (!config) {
dispatch(loadConfigError(error, locationURL));
dispatch(loadConfigError(error, locationURL, room));
return;
}
@@ -92,7 +96,7 @@ export function appNavigate(uri: ?string) {
dispatch(setConfig(config));
dispatch(setRoom(room));
} else {
dispatch(loadConfigError(new Error('Config no longer needed!'), locationURL));
dispatch(loadConfigError(new Error('Config no longer needed!'), locationURL, room));
}
};
}

View File

@@ -113,6 +113,7 @@ export function cancelWaitForOwner() {
// clients/consumers need an event.
const { authRequired } = getState()['features/base/conference'];
// FIXME remove when external-api ported to base/session
authRequired && dispatch(conferenceLeft(authRequired));
dispatch(appNavigate(undefined));

View File

@@ -9,6 +9,13 @@
*/
export const APP_WILL_MOUNT = 'APP_WILL_MOUNT';
/**
* FIXME.
*
* @type {string}
*/
export const APP_WILL_NAVIGATE = 'APP_WILL_NAVIGATE';
/**
* The type of (redux) action which signals that a specific App will unmount (in
* React terms).

View File

@@ -2,7 +2,7 @@
import type { Dispatch } from 'redux';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
import { APP_WILL_MOUNT, APP_WILL_NAVIGATE, APP_WILL_UNMOUNT } from './actionTypes';
declare var APP;
@@ -33,6 +33,25 @@ export function appWillMount(app: Object) {
};
}
/**
* FIXME.
*
* @param {URL} locationURL - FIXME.
* @param {string} room - FIXME.
* @returns {{
* type: APP_WILL_NAVIGATE,
* locationURL: URL,
* room: ?string
* }}
*/
export function appWillNavigate(locationURL: URL, room: ?string) {
return {
type: APP_WILL_NAVIGATE,
locationURL,
room
};
}
/**
* Signals that a specific App will unmount (in the terms of React).
*

View File

@@ -38,17 +38,20 @@ export function configWillLoad(locationURL: URL, room: string) {
* loading of a configuration.
* @param {URL} locationURL - The URL of the location which necessitated the
* loading of a configuration.
* @param {string} room - The name of the conference room.
* @returns {{
* type: LOAD_CONFIG_ERROR,
* error: Error,
* locationURL: URL
* locationURL: URL,
* room: string
* }}
*/
export function loadConfigError(error: Error, locationURL: URL) {
export function loadConfigError(error: Error, locationURL: URL, room: ?string) {
return {
type: LOAD_CONFIG_ERROR,
error,
locationURL
locationURL,
room
};
}

View File

@@ -3,11 +3,6 @@
import _ from 'lodash';
import type { Dispatch } from 'redux';
import {
conferenceLeft,
conferenceWillLeave,
getCurrentConference
} from '../conference';
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
import { parseURIString } from '../util';
@@ -20,7 +15,7 @@ import {
} from './actionTypes';
import { JITSI_CONNECTION_URL_KEY } from './constants';
const logger = require('jitsi-meet-logger').getLogger(__filename);
import type JitsiConnection from 'lib-jitsi-meet/JitsiConnection';
/**
* The error structure passed to the {@link connectionFailed} action.
@@ -90,7 +85,7 @@ export function connect(id: ?string, password: ?string) {
connection[JITSI_CONNECTION_URL_KEY] = locationURL;
dispatch(_connectionWillConnect(connection));
dispatch(connectionWillConnect(connection, locationURL));
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
@@ -102,7 +97,7 @@ export function connect(id: ?string, password: ?string) {
JitsiConnectionEvents.CONNECTION_FAILED,
_onConnectionFailed);
return connection.connect({
connection.connect({
id,
password
});
@@ -258,16 +253,18 @@ export function connectionFailed(
*
* @param {JitsiConnection} connection - The {@code JitsiConnection} which will
* connect.
* @param {URL} locationURL - FIXME.
* @private
* @returns {{
* type: CONNECTION_WILL_CONNECT,
* connection: JitsiConnection
* }}
*/
function _connectionWillConnect(connection) {
export function connectionWillConnect(connection: JitsiConnection, locationURL: URL) {
return {
type: CONNECTION_WILL_CONNECT,
connection
connection,
locationURL
};
}
@@ -319,67 +316,6 @@ function _constructOptions(state) {
return options;
}
/**
* Closes connection.
*
* @returns {Function}
*/
export function disconnect() {
return (dispatch: Dispatch<any>, getState: Function): Promise<void> => {
const state = getState();
// The conference we have already joined or are joining.
const conference_ = getCurrentConference(state);
// Promise which completes when the conference has been left and the
// connection has been disconnected.
let promise;
// Leave the conference.
if (conference_) {
// In a fashion similar to JitsiConference's CONFERENCE_LEFT event
// (and the respective Redux action) which is fired after the
// conference has been left, notify the application about the
// intention to leave the conference.
dispatch(conferenceWillLeave(conference_));
promise
= conference_.leave()
.catch(error => {
logger.warn(
'JitsiConference.leave() rejected with:',
error);
// The library lib-jitsi-meet failed to make the
// JitsiConference leave. Which may be because
// JitsiConference thinks it has already left.
// Regardless of the failure reason, continue in
// jitsi-meet as if the leave has succeeded.
dispatch(conferenceLeft(conference_));
});
} else {
promise = Promise.resolve();
}
// Disconnect the connection.
const { connecting, connection } = state['features/base/connection'];
// The connection we have already connected or are connecting.
const connection_ = connection || connecting;
if (connection_) {
promise = promise.then(() => connection_.disconnect());
} else {
// FIXME: We have no connection! Fake a disconnect. Because of how the current disconnec is implemented
// (by doing the diconnect() in the Conference component unmount) we have lost the location URL already.
// Oh well, at least send the event.
promise.then(() => dispatch(_connectionDisconnected({}, '')));
}
return promise;
};
}
/**
* Sets the location URL of the application, connecton, conference, etc.
*

View File

@@ -12,6 +12,7 @@ import { configureInitialDevices } from '../devices';
export {
connectionEstablished,
connectionFailed,
connectionWillConnect,
setLocationURL
} from './actions.native';

View File

@@ -1,8 +1,5 @@
import JitsiMeetJS from '../lib-jitsi-meet';
import {
getUserSelectedOutputDeviceId,
updateSettings
} from '../settings';
import { updateSettings } from '../settings';
import {
ADD_PENDING_DEVICE_REQUEST,
@@ -94,7 +91,8 @@ export function configureInitialDevices() {
return updateSettingsPromise
.then(() => {
const userSelectedAudioOutputDeviceId = getUserSelectedOutputDeviceId(getState());
const { userSelectedAudioOutputDeviceId }
= getState()['features/base/settings'];
return setAudioOutputDeviceId(userSelectedAudioOutputDeviceId, dispatch)
.catch(ex => logger.warn(`Failed to set audio output device.

View File

@@ -67,34 +67,6 @@ export function getDeviceIdByLabel(state: Object, label: string, kind: string) {
}
}
/**
* Finds a device with a label that matches the passed id and returns its label.
*
* @param {Object} state - The redux state.
* @param {string} id - The device id.
* @param {string} kind - The type of the device. One of "audioInput",
* "audioOutput", and "videoInput". Also supported is all lowercase versions
* of the preceding types.
* @returns {string|undefined}
*/
export function getDeviceLabelById(state: Object, id: string, kind: string) {
const webrtcKindToJitsiKindTranslator = {
audioinput: 'audioInput',
audiooutput: 'audioOutput',
videoinput: 'videoInput'
};
const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind;
const device
= (state['features/base/devices'].availableDevices[kindToSearch] || [])
.find(d => d.deviceId === id);
if (device) {
return device.label;
}
}
/**
* Returns the devices set in the URL.
*
@@ -146,29 +118,24 @@ export function groupDevicesByKind(devices: Object[]): Object {
* @param {string} newId - New audio output device id.
* @param {Function} dispatch - The Redux dispatch function.
* @param {boolean} userSelection - Whether this is a user selection update.
* @param {?string} newLabel - New audio output device label to store.
* @returns {Promise}
*/
export function setAudioOutputDeviceId(
newId: string = 'default',
dispatch: Function,
userSelection: boolean = false,
newLabel: ?string): Promise<*> {
userSelection: boolean = false): Promise<*> {
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
.then(() => {
const newSettings = {
audioOutputDeviceId: newId,
userSelectedAudioOutputDeviceId: undefined,
userSelectedAudioOutputDeviceLabel: undefined
userSelectedAudioOutputDeviceId: undefined
};
if (userSelection) {
newSettings.userSelectedAudioOutputDeviceId = newId;
newSettings.userSelectedAudioOutputDeviceLabel = newLabel;
} else {
// a flow workaround, I needed to add 'userSelectedAudioOutputDeviceId: undefined'
delete newSettings.userSelectedAudioOutputDeviceId;
delete newSettings.userSelectedAudioOutputDeviceLabel;
}
return dispatch(updateSettings(newSettings));

View File

@@ -147,8 +147,7 @@ function _useDevice({ dispatch }, device) {
switch (device.kind) {
case 'videoinput': {
dispatch(updateSettings({
userSelectedCameraDeviceId: device.deviceId,
userSelectedCameraDeviceLabel: device.label
userSelectedCameraDeviceId: device.deviceId
}));
dispatch(setVideoInputDevice(device.deviceId));
@@ -156,8 +155,7 @@ function _useDevice({ dispatch }, device) {
}
case 'audioinput': {
dispatch(updateSettings({
userSelectedMicDeviceId: device.deviceId,
userSelectedMicDeviceLabel: device.label
userSelectedMicDeviceId: device.deviceId
}));
dispatch(setAudioInputDevice(device.deviceId));
@@ -167,8 +165,7 @@ function _useDevice({ dispatch }, device) {
setAudioOutputDeviceId(
device.deviceId,
dispatch,
true,
device.label)
true)
.then(() => logger.log('changed audio output device'))
.catch(err => {
logger.warn(

View File

@@ -1,52 +0,0 @@
// @flow
import React from 'react';
import { Text } from 'react-native';
import { translate } from '../../../i18n';
import { connect } from '../../../redux';
import { _abstractMapStateToProps } from '../../functions';
import { type Props as AbstractProps } from './BaseDialog';
import BaseSubmitDialog from './BaseSubmitDialog';
type Props = AbstractProps & {
/**
* Untranslated i18n key of the content to be displayed.
*
* NOTE: This dialog also adds support to Object type keys that will be
* translated using the provided params. See i18n function
* {@code translate(string, Object)} for more details.
*/
contentKey: string | { key: string, params: Object},
};
/**
* Implements an alert dialog, to simply show an error or a message, then disappear on dismiss.
*/
class AlertDialog extends BaseSubmitDialog<Props, *> {
/**
* Implements {@code BaseSubmitDialog._renderSubmittable}.
*
* @inheritdoc
*/
_renderSubmittable() {
const { _dialogStyles, contentKey, t } = this.props;
const content
= typeof contentKey === 'string'
? t(contentKey)
: this._renderHTML(t(contentKey.key, contentKey.params));
return (
<Text style = { _dialogStyles.text }>
{ content }
</Text>
);
}
_renderHTML: string => Object | string
}
export default translate(connect(_abstractMapStateToProps)(AlertDialog));

View File

@@ -4,7 +4,6 @@ export { default as BottomSheet } from './BottomSheet';
export { default as ConfirmDialog } from './ConfirmDialog';
export { default as CustomDialog } from './CustomDialog';
export { default as DialogContainer } from './DialogContainer';
export { default as AlertDialog } from './AlertDialog';
export { default as InputDialog } from './InputDialog';
export { default as CustomSubmitDialog } from './CustomSubmitDialog';

View File

@@ -2,8 +2,6 @@
import { Component } from 'react';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Describes audio element interface used in the base/media feature for audio
* playback.
@@ -12,7 +10,7 @@ export type AudioElement = {
currentTime: number,
pause: () => void,
play: () => void,
setSinkId?: string => Function,
setSinkId?: string => void,
stop: () => void
};
@@ -115,8 +113,7 @@ export default class AbstractAudio extends Component<Props> {
setSinkId(sinkId: string): void {
this._audioElementImpl
&& typeof this._audioElementImpl.setSinkId === 'function'
&& this._audioElementImpl.setSinkId(sinkId)
.catch(error => logger.error('Error setting sink', error));
&& this._audioElementImpl.setSinkId(sinkId);
}
/**

View File

@@ -1,66 +0,0 @@
// @flow
import React, { Component } from 'react';
import { translate } from '../../../i18n';
import BackButton from './BackButton';
import ForwardButton from './ForwardButton';
import Header from './Header';
import HeaderLabel from './HeaderLabel';
type Props = {
/**
* Boolean to set the forward button disabled.
*/
forwardDisabled: boolean,
/**
* The i18n key of the the forward button label.
*/
forwardLabelKey: ?string,
/**
* The i18n key of the header label (title)
*/
headerLabelKey: ?string,
/**
* Callback to be invoked on pressing the back button.
*/
onPressBack: ?Function,
/**
* Callback to be invoked on pressing the forward button.
*/
onPressForward: ?Function,
}
/**
* Implements a header with the standard navigation content.
*/
class HeaderWithNavigation extends Component<Props> {
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
const { onPressBack, onPressForward } = this.props;
return (
<Header>
{ onPressBack && <BackButton onPress = { onPressBack } /> }
<HeaderLabel labelKey = { this.props.headerLabelKey } />
{ onPressForward && <ForwardButton
disabled = { this.props.forwardDisabled }
labelKey = { this.props.forwardLabelKey }
onPress = { onPressForward } /> }
</Header>
);
}
}
export default translate(HeaderWithNavigation);

View File

@@ -5,11 +5,12 @@ import Swipeout from 'react-native-swipeout';
import { ColorPalette } from '../../../styles';
import Container from './Container';
import Text from './Text';
import styles from './styles';
import type { Item } from '../../Types';
import AvatarListItem from './AvatarListItem';
import Text from './Text';
import styles from './styles';
type Props = {
@@ -92,6 +93,24 @@ export default class NavigateSectionListItem extends Component<Props> {
return lines && lines.length ? lines.map(this._renderItemLine) : null;
}
/**
* Renders the secondary action label.
*
* @private
* @returns {React$Node}
*/
_renderSecondaryAction() {
const { secondaryAction } = this.props;
return (
<Container
onClick = { secondaryAction }
style = { styles.secondaryActionContainer }>
<Text style = { styles.secondaryActionLabel }>+</Text>
</Container>
);
}
/**
* Renders the content of this component.
*
@@ -119,12 +138,14 @@ export default class NavigateSectionListItem extends Component<Props> {
return (
<Swipeout
autoClose = { true }
backgroundColor = { ColorPalette.transparent }
right = { right }>
<AvatarListItem
item = { item }
onPress = { this.props.onPress } />
onPress = { this.props.onPress }>
{ this.props.secondaryAction
&& this._renderSecondaryAction() }
</AvatarListItem>
</Swipeout>
);
}

View File

@@ -8,7 +8,6 @@ export { default as Container } from './Container';
export { default as ForwardButton } from './ForwardButton';
export { default as Header } from './Header';
export { default as HeaderLabel } from './HeaderLabel';
export { default as HeaderWithNavigation } from './HeaderWithNavigation';
export { default as Image } from './Image';
export { default as Link } from './Link';
export { default as LoadingIndicator } from './LoadingIndicator';

View File

@@ -4,6 +4,7 @@ import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles';
const AVATAR_OPACITY = 0.4;
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
const SECONDARY_ACTION_BUTTON_SIZE = 30;
export const AVATAR_SIZE = 65;
export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
@@ -217,6 +218,21 @@ const SECTION_LIST_STYLES = {
color: OVERLAY_FONT_COLOR
},
secondaryActionContainer: {
alignItems: 'center',
backgroundColor: ColorPalette.blue,
borderRadius: 3,
height: SECONDARY_ACTION_BUTTON_SIZE,
justifyContent: 'center',
margin: BoxModel.margin * 0.5,
marginRight: BoxModel.margin,
width: SECONDARY_ACTION_BUTTON_SIZE
},
secondaryActionLabel: {
color: ColorPalette.white
},
touchableView: {
flexDirection: 'row'
}

View File

@@ -0,0 +1,86 @@
// @flow
import uuid from 'uuid';
import { toURLString } from '../util';
import type JitsiConference from 'lib-jitsi-meet/JitsiConference';
import type JitsiConnection from 'lib-jitsi-meet/JitsiConnection';
/**
* FIXME.
*/
export class Session {
_conference: ?JitsiConference;
_connection: ?JitsiConnection;
conferenceFailed: boolean;
id: string;
locationURL: URL;
room: string;
/**
* FIXME.
*
* @param {URL} locationURL - FIXME.
* @param {string} room - FIXME.
*/
constructor(locationURL: URL, room: string) {
this.locationURL = locationURL;
this.room = room;
this.id = uuid.v4().toUpperCase();
this.conferenceFailed = false;
}
/**
* FIXME.
*
* @param {JitsiConference} [conference] - FIXME.
*/
set conference(conference: ?JitsiConference) {
if (this._conference && conference && this._conference !== conference) {
throw new Error(`Attempt to reassign conference to ${this.toString()}`);
}
this._conference = conference;
}
/**
* FIXME.
*
* @returns {?JitsiConference}
*/
get conference(): ?JitsiConference {
return this._conference;
}
/**
* FIXME.
*
* @param {JitsiConnection} [connection] - FIXME.
*/
set connection(connection: ?JitsiConnection) {
if (this._connection && connection && this._connection !== connection) {
throw new Error(`Attempt to reassign connection to ${this.toString()}`);
}
this._connection = connection;
}
/**
* FIXME.
*
* @returns {?JitsiConnection}
*/
get connection() {
return this._connection;
}
/**
* FIXME.
*
* @returns {string}
*/
toString() {
return `Session[id=${this.id}, URL: ${toURLString(this.locationURL)} room: ${this.room}]`;
}
}

View File

@@ -0,0 +1,8 @@
export const SESSION_CREATED = 'SESSION_CREATED';
export const SESSION_STARTED = 'SESSION_STARTED';
export const SESSION_TERMINATED = 'SESSION_TERMINATED';
export const SESSION_FAILED = 'SESSION_FAILED';

View File

@@ -0,0 +1,135 @@
// @flow
import { conferenceLeft, conferenceWillLeave } from '../conference';
import { SESSION_CREATED, SESSION_FAILED, SESSION_STARTED, SESSION_TERMINATED } from './actionTypes';
import { Session } from './Session';
import type { Dispatch } from 'redux';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* FIXME.
*
* @returns {Function}
*/
export function endAllSessions() {
return (dispatch: Dispatch<any>, getState: Function) => {
const sessions = getState()['features/base/session'];
for (const session of sessions.values()) {
dispatch(endSession(session));
}
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {function(*): *}
*/
export function endSession(session: Session) {
return (dispatch: Dispatch<any>) => {
// The conference we have already joined or are joining.
const conference_ = session.conference;
// Promise which completes when the conference has been left and the connection has been disconnected.
let promise;
// Leave the conference.
if (conference_) {
// In a fashion similar to JitsiConference's CONFERENCE_LEFT event (and the respective Redux action) which
// is fired after the conference has been left, notify the application about the intention to leave
// the conference.
dispatch(conferenceWillLeave(conference_));
promise
= conference_.leave()
.catch(error => {
logger.warn('JitsiConference.leave() rejected with:', error);
// The library lib-jitsi-meet failed to make the JitsiConference leave. Which may be because
// JitsiConference thinks it has already left. Regardless of the failure reason, continue in
// jitsi-meet as if the leave has succeeded.
dispatch(conferenceLeft(conference_));
});
} else {
promise = Promise.resolve();
}
const connection_ = session.connection;
if (connection_) {
promise = promise.then(() => connection_.disconnect());
}
return promise;
};
}
/**
* FIXME.
*
* @param {URL} locationURL - FIXME.
* @param {string} room - FIXME.
* @returns {{
* type: SESSION_CREATED,
* session: Session
* }}
*/
export function createSession(locationURL: URL, room: string) {
return {
type: SESSION_CREATED,
session: new Session(locationURL, room)
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {{
* type: SESSION_FAILED,
* session: Session
* }}
*/
export function sessionFailed(session: Session) {
return {
type: SESSION_FAILED,
session
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {{
* session: Session,
* type: string
* }}
*/
export function sessionStarted(session: Session) {
return {
type: SESSION_STARTED,
session
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {{
* type: SESSION_TERMINATED,
* session: Session
* }}
*/
export function sessionTerminated(session: Session) {
return {
type: SESSION_TERMINATED,
session
};
}

View File

@@ -0,0 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './selectors';
import './middleware';
import './reducer';

View File

@@ -0,0 +1,164 @@
import { APP_WILL_NAVIGATE } from '../app';
import { CONFERENCE_FAILED, CONFERENCE_JOINED, CONFERENCE_LEFT, CONFERENCE_WILL_JOIN, SET_ROOM } from '../conference';
import { LOAD_CONFIG_ERROR } from '../config';
import { connect, CONNECTION_DISCONNECTED, CONNECTION_FAILED, CONNECTION_WILL_CONNECT } from '../connection';
import { MiddlewareRegistry } from '../redux';
import { createDesiredLocalTracks } from '../tracks';
import { toURLString } from '../util';
import { createSession, endAllSessions, endSession, sessionFailed, sessionStarted, sessionTerminated } from './actions';
import { SESSION_CREATED, SESSION_FAILED, SESSION_STARTED, SESSION_TERMINATED } from './actionTypes';
import { findSessionForConference, findSessionForConnection, findSessionForLocationURL } from './selectors';
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const result = next(action);
switch (action.type) {
case APP_WILL_NAVIGATE: {
const { locationURL, room } = action;
// Currently only one session is allowed at a time
dispatch(endAllSessions());
// Start a new session if there's a conference room name defined
room && room.length && dispatch(createSession(locationURL, room));
break;
}
case CONFERENCE_FAILED: {
const { conference, error } = action;
const { recoverable } = error;
const session = findSessionForConference(getState(), conference);
if (session) {
session.conference = null;
if (typeof recoverable === 'undefined' || recoverable === false) {
session.conferenceFailed = true;
if (session.connection) {
// The sessionFailed is expected to be dispatched from either CONNECTION_DISCONNECTED
// or CONNECTION_FAILED handler in this middleware. The purpose is to delay the SESSION_FAILED until
// the XMPP connection is properly disposed.
dispatch(endSession(session));
} else {
dispatch(sessionFailed(session));
}
}
} else {
console.error('CONFERENCE_FAILED - no session found');
}
break;
}
case CONFERENCE_LEFT: {
const { conference } = action;
const session = findSessionForConference(getState(), conference);
if (session) {
session.conference = null;
// If there's any existing connection wait for it to be closed first
session.connection || dispatch(sessionTerminated(session));
} else {
console.error('CONFERENCE_LEFT - no session found');
}
break;
}
case CONFERENCE_JOINED: {
const { conference } = action;
const session = findSessionForConference(getState(), conference);
if (session) {
dispatch(sessionStarted(session));
} else {
console.error('CONFERENCE_JOINED - no session found');
}
break;
}
case CONFERENCE_WILL_JOIN: {
const { conference } = action;
const { connection } = conference;
const session = findSessionForConnection(getState(), connection);
if (session) {
session.conference = conference;
} else {
console.error('CONFERENCE_WILL_JOIN - no session found');
}
break;
}
case LOAD_CONFIG_ERROR: {
const { locationURL, room } = action;
// There won't be a session if there's no room (it happen when the config is loaded for the welcome page).
if (room) {
const session = findSessionForLocationURL(getState(), locationURL);
if (session) {
dispatch(sessionFailed(session));
} else {
console.error(`LOAD_CONFIG_ERROR - no session found for: ${toURLString(locationURL)}`);
}
}
break;
}
case CONNECTION_DISCONNECTED: {
const { connection } = action;
const session = findSessionForConnection(getState(), connection);
if (session) {
session.connection = null;
dispatch(
session.conferenceFailed ? sessionFailed(session) : sessionTerminated(session));
} else {
console.error('CONNECTION_DISCONNECTED - no session found');
}
break;
}
case CONNECTION_FAILED: {
const { connection, error } = action;
const { recoverable } = error;
const session = findSessionForConnection(getState(), connection);
if (session) {
session.connection = null;
if (typeof recoverable === 'undefined' || recoverable === false) {
dispatch(sessionFailed(session));
}
} else {
console.error('CONNECTION_FAILED - no session found');
}
break;
}
case CONNECTION_WILL_CONNECT: {
const { connection, locationURL } = action;
const session = findSessionForLocationURL(getState(), locationURL);
if (session) {
session.connection = connection;
} else {
console.error(`CONNECTION_WILL_CONNECT - no session found for: ${toURLString(locationURL)}`);
}
break;
}
case SET_ROOM: {
const { locationURL } = getState()['features/base/connection'];
const session = findSessionForLocationURL(getState(), locationURL);
// Web has different logic for creating the local tracks and starting the connection
if (session && typeof APP === 'undefined') {
dispatch(createDesiredLocalTracks());
dispatch(connect());
}
break;
}
case SESSION_CREATED:
case SESSION_STARTED:
case SESSION_FAILED:
case SESSION_TERMINATED:
console.info(`DEBUG ${action.type} ${action.session}`);
break;
}
return result;
});

View File

@@ -0,0 +1,27 @@
import { ReducerRegistry } from '../redux';
import { SESSION_CREATED, SESSION_FAILED, SESSION_TERMINATED } from './actionTypes';
ReducerRegistry.register('features/base/session', (state = new Map(), action) => {
switch (action.type) {
case SESSION_CREATED: {
const { session } = action;
const nextMap = new Map(state);
nextMap.set(session.id, session);
return nextMap;
}
case SESSION_TERMINATED:
case SESSION_FAILED: {
const { session } = action;
const nextMap = new Map(state);
nextMap.delete(session.id);
return nextMap;
}
}
return state;
});

View File

@@ -0,0 +1,56 @@
/**
* FIXME.
*
* @param {Object} state - FIXME.
* @param {JitsiConnection} connection - FIXME.
* @returns {Session|null}
*/
export function findSessionForConnection(state, connection) {
const sessions = state['features/base/session'];
for (const session of sessions.values()) {
if (session.connection === connection) {
return session;
}
}
return null;
}
/**
* FIXME.
*
* @param {Object} state - FIXME.
* @param {JitsiConference} conference - FIXME.
* @returns {Session|null}
*/
export function findSessionForConference(state, conference) {
const sessions = state['features/base/session'];
for (const session of sessions.values()) {
if (session.conference === conference) {
return session;
}
}
return null;
}
/**
* FIXME.
*
* @param {Object} state - FIXME.
* @param {URL} locationURL - FIXME.
* @returns {Session|null}
*/
export function findSessionForLocationURL(state, locationURL) {
const sessions = state['features/base/session'];
for (const session of sessions.values()) {
if (session.locationURL === locationURL) {
return session;
}
}
return null;
}

View File

@@ -97,152 +97,3 @@ export function getServerURL(stateful: Object | Function) {
return state['features/base/settings'].serverURL || DEFAULT_SERVER_URL;
}
/**
* Searches known devices for a matching deviceId and fall back to matching on
* label. Returns the stored preferred cameraDeviceId if a match is not found.
*
* @param {Object|Function} stateful - The redux state object or
* {@code getState} function.
* @returns {string}
*/
export function getUserSelectedCameraDeviceId(stateful: Object | Function) {
const state = toState(stateful);
const {
userSelectedCameraDeviceId,
userSelectedCameraDeviceLabel
} = state['features/base/settings'];
const { videoInput } = state['features/base/devices'].availableDevices;
return _getUserSelectedDeviceId({
availableDevices: videoInput,
// Operating systems may append " #{number}" somewhere in the label so
// find and strip that bit.
matchRegex: /\s#\d*(?!.*\s#\d*)/,
userSelectedDeviceId: userSelectedCameraDeviceId,
userSelectedDeviceLabel: userSelectedCameraDeviceLabel,
replacement: ''
});
}
/**
* Searches known devices for a matching deviceId and fall back to matching on
* label. Returns the stored preferred micDeviceId if a match is not found.
*
* @param {Object|Function} stateful - The redux state object or
* {@code getState} function.
* @returns {string}
*/
export function getUserSelectedMicDeviceId(stateful: Object | Function) {
const state = toState(stateful);
const {
userSelectedMicDeviceId,
userSelectedMicDeviceLabel
} = state['features/base/settings'];
const { audioInput } = state['features/base/devices'].availableDevices;
return _getUserSelectedDeviceId({
availableDevices: audioInput,
// Operating systems may append " ({number}-" somewhere in the label so
// find and strip that bit.
matchRegex: /\s\(\d*-\s(?!.*\s\(\d*-\s)/,
userSelectedDeviceId: userSelectedMicDeviceId,
userSelectedDeviceLabel: userSelectedMicDeviceLabel,
replacement: ' ('
});
}
/**
* Searches known devices for a matching deviceId and fall back to matching on
* label. Returns the stored preferred audioOutputDeviceId if a match is not found.
*
* @param {Object|Function} stateful - The redux state object or
* {@code getState} function.
* @returns {string}
*/
export function getUserSelectedOutputDeviceId(stateful: Object | Function) {
const state = toState(stateful);
const {
userSelectedAudioOutputDeviceId,
userSelectedAudioOutputDeviceLabel
} = state['features/base/settings'];
const { audioOutput } = state['features/base/devices'].availableDevices;
return _getUserSelectedDeviceId({
availableDevices: audioOutput,
matchRegex: undefined,
userSelectedDeviceId: userSelectedAudioOutputDeviceId,
userSelectedDeviceLabel: userSelectedAudioOutputDeviceLabel,
replacement: undefined
});
}
/**
* A helper function to abstract the logic for choosing which device ID to
* use. Falls back to fuzzy matching on label if a device ID match is not found.
*
* @param {Object} options - The arguments used to match find the preferred
* device ID from available devices.
* @param {Array<string>} options.availableDevices - The array of currently
* available devices to match against.
* @param {Object} options.matchRegex - The regex to use to find strings
* appended to the label by the operating system. The matches will be replaced
* with options.replacement, with the intent of matching the same device that
* might have a modified label.
* @param {string} options.userSelectedDeviceId - The device ID the participant
* prefers to use.
* @param {string} options.userSelectedDeviceLabel - The label associated with the
* device ID the participant prefers to use.
* @param {string} options.replacement - The string to use with
* options.matchRegex to remove identifies added to the label by the operating
* system.
* @private
* @returns {string} The preferred device ID to use for media.
*/
function _getUserSelectedDeviceId(options) {
const {
availableDevices,
matchRegex,
userSelectedDeviceId,
userSelectedDeviceLabel,
replacement
} = options;
// If there is no label at all, there is no need to fall back to checking
// the label for a fuzzy match.
if (!userSelectedDeviceLabel || !userSelectedDeviceId) {
return userSelectedDeviceId;
}
const foundMatchingBasedonDeviceId = availableDevices.find(
candidate => candidate.deviceId === userSelectedDeviceId);
// Prioritize matching the deviceId
if (foundMatchingBasedonDeviceId) {
return userSelectedDeviceId;
}
const strippedDeviceLabel
= matchRegex ? userSelectedDeviceLabel.replace(matchRegex, replacement)
: userSelectedDeviceLabel;
const foundMatchBasedOnLabel = availableDevices.find(candidate => {
const { label } = candidate;
if (!label) {
return false;
} else if (strippedDeviceLabel === label) {
return true;
}
const strippedCandidateLabel
= label.replace(matchRegex, replacement);
return strippedDeviceLabel === strippedCandidateLabel;
});
return foundMatchBasedOnLabel
? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
}

View File

@@ -33,10 +33,7 @@ const DEFAULT_STATE = {
startWithVideoMuted: false,
userSelectedAudioOutputDeviceId: undefined,
userSelectedCameraDeviceId: undefined,
userSelectedMicDeviceId: undefined,
userSelectedAudioOutputDeviceLabel: undefined,
userSelectedCameraDeviceLabel: undefined,
userSelectedMicDeviceLabel: undefined
userSelectedMicDeviceId: undefined
};
const STORE_NAME = 'features/base/settings';

View File

@@ -1,4 +1,4 @@
import AsyncStorage from '@react-native-community/async-storage';
import { AsyncStorage } from 'react-native';
/**
* A Web Sorage API implementation used for polyfilling

View File

@@ -3,10 +3,6 @@
import JitsiMeetJS, { JitsiTrackErrors, JitsiTrackEvents }
from '../lib-jitsi-meet';
import { MEDIA_TYPE } from '../media';
import {
getUserSelectedCameraDeviceId,
getUserSelectedMicDeviceId
} from '../settings';
const logger = require('jitsi-meet-logger').getLogger(__filename);
@@ -41,13 +37,13 @@ export function createLocalTracksF(
// reliance on the global variable APP will go away.
store || (store = APP.store); // eslint-disable-line no-param-reassign
const state = store.getState();
const settings = store.getState()['features/base/settings'];
if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
cameraDeviceId = getUserSelectedCameraDeviceId(state);
cameraDeviceId = settings.userSelectedCameraDeviceId;
}
if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
micDeviceId = getUserSelectedMicDeviceId(state);
micDeviceId = settings.userSelectedMicDeviceId;
}
}

View File

@@ -5,7 +5,12 @@ import { KeyboardAvoidingView, SafeAreaView } from 'react-native';
import { translate } from '../../../base/i18n';
import { HeaderWithNavigation, SlidingView } from '../../../base/react';
import {
BackButton,
Header,
HeaderLabel,
SlidingView
} from '../../../base/react';
import { connect } from '../../../base/redux';
import AbstractChat, {
@@ -36,9 +41,10 @@ class Chat extends AbstractChat<Props> {
<KeyboardAvoidingView
behavior = 'padding'
style = { styles.chatContainer }>
<HeaderWithNavigation
headerLabelKey = 'chat.title'
onPressBack = { this.props._onToggleChat } />
<Header>
<BackButton onPress = { this.props._onToggleChat } />
<HeaderLabel labelKey = 'chat.title' />
</Header>
<SafeAreaView style = { styles.backdrop }>
<MessageContainer messages = { this.props._messages } />
<ChatInputBar onSend = { this.props._onSendMessage } />

View File

@@ -103,28 +103,13 @@ class Chat extends AbstractChat<Props> {
ref = { this._setMessageListEndRef } />);
return (
<>
<div
className = 'sideToolbarContainer__inner'
id = 'chat_container'>
<div id = 'chatconversation'>
{ messages }
</div>
<ChatInput />
</>
);
}
/**
* Instantiates a React Element to display at the top of {@code Chat} to
* close {@code Chat}.
*
* @private
* @returns {ReactElement}
*/
_renderChatHeader() {
return (
<div className = 'chat-header'>
<div
className = 'chat-close'
onClick = { this.props._onToggleChat }>X</div>
</div>
);
}
@@ -160,15 +145,17 @@ class Chat extends AbstractChat<Props> {
_renderPanelContent(state) {
this._isExited = state === 'exited';
const { _isOpen, _showNamePrompt } = this.props;
const { _isOpen, _onToggleChat, _showNamePrompt } = this.props;
const ComponentToRender = !_isOpen && state === 'exited'
? null
: (
<>
{ this._renderChatHeader() }
<div>
<div
className = 'chat-close'
onClick = { _onToggleChat }>X</div>
{ _showNamePrompt
? <DisplayNameForm /> : this._renderChat() }
</>
</div>
);
let className = '';
@@ -180,7 +167,7 @@ class Chat extends AbstractChat<Props> {
return (
<div
className = { `sideToolbarContainer ${className}` }
className = { className }
id = 'sideToolbarContainer'>
{ ComponentToRender }
</div>

View File

@@ -4,7 +4,6 @@ import React, { Component } from 'react';
import Emoji from 'react-emoji-render';
import type { Dispatch } from 'redux';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { sendMessage } from '../../actions';
@@ -24,12 +23,7 @@ type Props = {
/**
* Optional callback to get a reference to the chat input element.
*/
getChatInputRef?: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function
getChatInputRef?: Function
};
/**
@@ -120,10 +114,11 @@ class ChatInput extends Component<Props, State> {
</div>
<div className = 'usrmsg-form'>
<textarea
data-i18n = '[placeholder]chat.messagebox'
id = 'usermsg'
onChange = { this._onMessageChange }
onKeyDown = { this._onDetectSubmit }
placeholder = { this.props.t('chat.messagebox') }
placeholder = { 'Enter Text...' }
ref = { this._setTextAreaRef }
value = { this.state.message } />
</div>
@@ -238,4 +233,4 @@ class ChatInput extends Component<Props, State> {
}
}
export default translate(connect()(ChatInput));
export default connect()(ChatInput);

View File

@@ -69,6 +69,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
return (
<div className = { `chatmessage ${messageTypeClassname}` }>
<div className = 'chatArrow' />
<div className = 'display-name'>
{ message.displayName }
</div>

View File

@@ -5,7 +5,6 @@ import React from 'react';
import { BackHandler, SafeAreaView, StatusBar, View } from 'react-native';
import { appNavigate } from '../../../app';
import { connect, disconnect } from '../../../base/connection';
import { getParticipantCount } from '../../../base/participants';
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
import { connect as reactReduxConnect } from '../../../base/redux';
@@ -14,7 +13,6 @@ import {
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { TestConnectionInfo } from '../../../base/testing';
import { createDesiredLocalTracks } from '../../../base/tracks';
import { ConferenceNotification } from '../../../calendar-sync';
import { Chat } from '../../../chat';
import { DisplayNameLabel } from '../../../display-name';
@@ -66,21 +64,6 @@ type Props = AbstractProps & {
*/
_largeVideoParticipantId: string,
/**
* Current conference's full URL.
*
* @private
*/
_locationURL: URL,
/**
* The handler which dispatches the (redux) action connect.
*
* @private
* @returns {void}
*/
_onConnect: Function,
/**
* The handler which dispatches the (redux) action disconnect.
*
@@ -166,8 +149,6 @@ class Conference extends AbstractConference<Props, *> {
* @returns {void}
*/
componentDidMount() {
this.props._onConnect();
BackHandler.addEventListener(
'hardwareBackPress',
this.props._onHardwareBackPress);
@@ -186,24 +167,14 @@ class Conference extends AbstractConference<Props, *> {
*/
componentDidUpdate(pevProps: Props) {
const {
_locationURL: oldLocationURL,
_participantCount: oldParticipantCount,
_room: oldRoom
_participantCount: oldParticipantCount
} = pevProps;
const {
_locationURL: newLocationURL,
_participantCount: newParticipantCount,
_room: newRoom,
_setToolboxVisible,
_toolboxVisible
} = this.props;
// If the location URL changes we need to reconnect.
oldLocationURL !== newLocationURL && newRoom && this.props._onDisconnect();
// Start the connection process when there is a (valid) room.
oldRoom !== newRoom && newRoom && this.props._onConnect();
if (oldParticipantCount === 1
&& newParticipantCount > 1
&& _toolboxVisible) {
@@ -228,8 +199,6 @@ class Conference extends AbstractConference<Props, *> {
BackHandler.removeEventListener(
'hardwareBackPress',
this.props._onHardwareBackPress);
this.props._onDisconnect();
}
/**
@@ -396,36 +365,12 @@ class Conference extends AbstractConference<Props, *> {
* @param {Function} dispatch - Redux action dispatcher.
* @private
* @returns {{
* _onConnect: Function,
* _onDisconnect: Function,
* _onHardwareBackPress: Function,
* _setToolboxVisible: Function
* }}
*/
function _mapDispatchToProps(dispatch) {
return {
/**
* Dispatches actions to create the desired local tracks and for
* connecting to the conference.
*
* @private
* @returns {void}
*/
_onConnect() {
dispatch(createDesiredLocalTracks());
dispatch(connect());
},
/**
* Dispatches an action disconnecting from the conference.
*
* @private
* @returns {void}
*/
_onDisconnect() {
dispatch(disconnect());
},
/**
* Handles a hardware button press for back navigation. Leaves the
* associated {@code Conference}.
@@ -462,8 +407,7 @@ function _mapDispatchToProps(dispatch) {
* @returns {Props}
*/
function _mapStateToProps(state) {
const { connecting, connection, locationURL }
= state['features/base/connection'];
const { connecting, connection } = state['features/base/connection'];
const {
conference,
joining,
@@ -508,14 +452,6 @@ function _mapStateToProps(state) {
*/
_largeVideoParticipantId: state['features/large-video'].participantId,
/**
* Current conference's full URL.
*
* @private
* @type {URL}
*/
_locationURL: locationURL,
/**
* The number of participants in the conference.
*

View File

@@ -6,7 +6,6 @@ import {
import { createDeviceChangedEvent, sendAnalytics } from '../analytics';
import {
getDeviceLabelById,
setAudioInputDevice,
setAudioOutputDeviceId,
setVideoInputDevice
@@ -113,9 +112,7 @@ export function submitDeviceSelectionTab(newState) {
&& newState.selectedVideoInputId
!== currentState.selectedVideoInputId) {
dispatch(updateSettings({
userSelectedCameraDeviceId: newState.selectedVideoInputId,
userSelectedCameraDeviceLabel:
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
userSelectedCameraDeviceId: newState.selectedVideoInputId
}));
dispatch(
@@ -126,9 +123,7 @@ export function submitDeviceSelectionTab(newState) {
&& newState.selectedAudioInputId
!== currentState.selectedAudioInputId) {
dispatch(updateSettings({
userSelectedMicDeviceId: newState.selectedAudioInputId,
userSelectedMicDeviceLabel:
getDeviceLabelById(getState(), newState.selectedAudioInputId, 'audioInput')
userSelectedMicDeviceId: newState.selectedAudioInputId
}));
dispatch(
@@ -143,8 +138,7 @@ export function submitDeviceSelectionTab(newState) {
setAudioOutputDeviceId(
newState.selectedAudioOutputId,
dispatch,
true,
getDeviceLabelById(getState(), newState.selectedAudioOutputId, 'audioOutput'))
true)
.then(() => logger.log('changed audio output device'))
.catch(err => {
logger.warn(

View File

@@ -113,7 +113,6 @@ class AudioOutputPreview extends Component<Props> {
*/
_setAudioSink() {
this._audioElement
&& this.props.deviceId
&& this._audioElement.setSinkId(this.props.deviceId);
}
}

View File

@@ -15,11 +15,6 @@ import {
} from '../base/devices';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { toState } from '../base/redux';
import {
getUserSelectedCameraDeviceId,
getUserSelectedMicDeviceId,
getUserSelectedOutputDeviceId
} from '../base/settings';
/**
* Returns the properties for the device selection dialog from Redux state.
@@ -43,9 +38,9 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
// on welcome page we also show only what we have saved as user selected devices
if (!conference) {
disableAudioInputChange = false;
selectedAudioInputId = getUserSelectedMicDeviceId(state);
selectedAudioOutputId = getUserSelectedOutputDeviceId(state);
selectedVideoInputId = getUserSelectedCameraDeviceId(state);
selectedAudioInputId = settings.userSelectedMicDeviceId;
selectedAudioOutputId = settings.userSelectedAudioOutputDeviceId;
selectedVideoInputId = settings.userSelectedCameraDeviceId;
}
// we fill the device selection dialog with the devices that are currently

View File

@@ -41,16 +41,6 @@ export const REMOVE_PENDING_INVITE_REQUESTS
*/
export const SET_CALLEE_INFO_VISIBLE = 'SET_CALLEE_INFO_VISIBLE';
/**
* The type of Redux action to set the visibility of the dial in summary.
*
* {
* type: SET_DIAL_IN_SUMMARY_VISIBLE,
* visible: boolean
* }
*/
export const SET_DIAL_IN_SUMMARY_VISIBLE = 'SET_DIAL_IN_SUMMARY_VISIBLE';
/**
* The type of redux action which sets the invite dialog visible or invisible.
*

View File

@@ -11,7 +11,6 @@ import {
BEGIN_ADD_PEOPLE,
REMOVE_PENDING_INVITE_REQUESTS,
SET_CALLEE_INFO_VISIBLE,
SET_DIAL_IN_SUMMARY_VISIBLE,
SET_INVITE_DIALOG_VISIBLE,
UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS
@@ -257,15 +256,6 @@ export function addPendingInviteRequest(
};
}
/**
* Action to hide the dial in summary.
*
* @returns {showDialInSummary}
*/
export function hideDialInSummary() {
return showDialInSummary(undefined);
}
/**
* Removes all pending invite requests.
*
@@ -278,19 +268,3 @@ export function removePendingInviteRequests() {
type: REMOVE_PENDING_INVITE_REQUESTS
};
}
/**
* Action to set the dial in summary url (and show it).
*
* @param {?string} locationUrl - The location URL to show the dial in summary for.
* @returns {{
* type: SET_DIAL_IN_SUMMARY_VISIBLE,
* summaryUrl: ?string
* }}
*/
export function showDialInSummary(locationUrl: ?string) {
return {
type: SET_DIAL_IN_SUMMARY_VISIBLE,
summaryUrl: locationUrl
};
}

View File

@@ -16,7 +16,10 @@ import { Icon } from '../../../../base/font-icons';
import { translate } from '../../../../base/i18n';
import {
AvatarListItem,
HeaderWithNavigation,
BackButton,
ForwardButton,
Header,
HeaderLabel,
Modal,
type Item
} from '../../../../base/react';
@@ -143,12 +146,14 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
<Modal
onRequestClose = { this._onCloseAddPeopleDialog }
visible = { this.props._isVisible }>
<HeaderWithNavigation
forwardDisabled = { this._isAddDisabled() }
forwardLabelKey = 'inviteDialog.send'
headerLabelKey = 'inviteDialog.header'
onPressBack = { this._onCloseAddPeopleDialog }
onPressForward = { this._onInvite } />
<Header>
<BackButton onPress = { this._onCloseAddPeopleDialog } />
<HeaderLabel labelKey = 'inviteDialog.header' />
<ForwardButton
disabled = { this._isAddDisabled() }
labelKey = 'inviteDialog.send'
onPress = { this._onInvite } />
</Header>
<SafeAreaView style = { styles.dialogWrapper }>
<View
style = { styles.searchFieldWrapper }>

View File

@@ -1,8 +1,8 @@
// @flow
/* @flow */
import React, { Component } from 'react';
import { translate } from '../../../../base/i18n';
import { translate } from '../../../base/i18n';
/**
* The type of the React {@code Component} props of {@link ConferenceID}.

View File

@@ -1,8 +1,8 @@
// @flow
/* @flow */
import React, { Component } from 'react';
import { translate } from '../../../../base/i18n';
import { translate } from '../../../base/i18n';
import ConferenceID from './ConferenceID';
import NumbersList from './NumbersList';

View File

@@ -1,8 +1,8 @@
// @flow
/* @flow */
import React, { Component } from 'react';
import { translate } from '../../../../base/i18n';
import { translate } from '../../../base/i18n';
type Props = {

View File

@@ -1,3 +1 @@
// @flow
export { default as DialInSummary } from './DialInSummary';

View File

@@ -1,3 +0,0 @@
// @flow
export * from './native';

View File

@@ -1,3 +0,0 @@
// @flow
export * from './web';

View File

@@ -1,156 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Linking, View } from 'react-native';
import { WebView } from 'react-native-webview';
import { type Dispatch } from 'redux';
import { openDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import {
HeaderWithNavigation,
LoadingIndicator,
SlidingView
} from '../../../../base/react';
import { connect } from '../../../../base/redux';
import { hideDialInSummary } from '../../../actions';
import { getDialInfoPageURLForURIString } from '../../../functions';
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
import styles, { INDICATOR_COLOR } from './styles';
type Props = {
/**
* The URL to display the summary for.
*/
_summaryUrl: ?string,
dispatch: Dispatch<any>
};
/**
* Implements a React native component that displays the dial in info page for a specific room.
*/
class DialInSummary extends Component<Props> {
/**
* Initializes a new instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onCloseView = this._onCloseView.bind(this);
this._onError = this._onError.bind(this);
this._onNavigate = this._onNavigate.bind(this);
this._renderLoading = this._renderLoading.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
const { _summaryUrl } = this.props;
return (
<SlidingView
position = 'bottom'
show = { Boolean(_summaryUrl) } >
<View style = { styles.webViewWrapper }>
<HeaderWithNavigation
headerLabelKey = 'info.label'
onPressBack = { this._onCloseView } />
<WebView
onError = { this._onError }
onShouldStartLoadWithRequest = { this._onNavigate }
renderLoading = { this._renderLoading }
source = {{ uri: getDialInfoPageURLForURIString(_summaryUrl) }}
startInLoadingState = { true }
style = { styles.webView } />
</View>
</SlidingView>
);
}
_onCloseView: () => void;
/**
* Closes the view.
*
* @returns {void}
*/
_onCloseView() {
this.props.dispatch(hideDialInSummary());
}
_onError: () => void;
/**
* Callback to handle the error if the page fails to load.
*
* @returns {void}
*/
_onError() {
this.props.dispatch(hideDialInSummary());
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
}
_onNavigate: Object => Boolean;
/**
* Callback to intercept navigation inside the webview and make the native app handle the dial requests.
*
* NOTE: We don't navigate to anywhere else form that view.
*
* @param {any} request - The request object.
* @returns {boolean}
*/
_onNavigate(request) {
const { url } = request;
if (url.startsWith('tel:')) {
Linking.openURL(url);
this.props.dispatch(hideDialInSummary());
}
return url === getDialInfoPageURLForURIString(this.props._summaryUrl);
}
_renderLoading: () => React$Component<any>;
/**
* Renders the loading indicator.
*
* @returns {React$Component<any>}
*/
_renderLoading() {
return (
<View style = { styles.indicatorWrapper }>
<LoadingIndicator
color = { INDICATOR_COLOR }
size = 'large' />
</View>
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _summaryUrl: ?string
* }}
*/
function _mapStateToProps(state) {
return {
_summaryUrl: state['features/invite'].summaryUrl
};
}
export default translate(connect(_mapStateToProps)(DialInSummary));

View File

@@ -1,29 +0,0 @@
// @flow
import React, { Component } from 'react';
import { AlertDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux';
/**
* Dialog to inform the user that we could't fetch the dial-in info page.
*/
class DialInSummaryErrorDialog extends Component<{}> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<AlertDialog
contentKey = 'info.dialInSummaryError' />
);
}
_onSubmit: () => boolean;
}
export default translate(connect()(DialInSummaryErrorDialog));

View File

@@ -1,24 +0,0 @@
// @flow
import { ColorPalette } from '../../../../base/styles';
export const INDICATOR_COLOR = ColorPalette.lightGrey;
export default {
indicatorWrapper: {
alignItems: 'center',
backgroundColor: ColorPalette.white,
flex: 1,
justifyContent: 'center'
},
webView: {
flex: 1
},
webViewWrapper: {
flex: 1,
flexDirection: 'column'
}
};

View File

@@ -1,3 +0,0 @@
// @flow
export { default as DialInSummary } from './DialInSummary';

View File

@@ -1,6 +1,6 @@
// @flow
export * from './add-people-dialog';
export * from './dial-in-summary';
export { DialInSummary } from './dial-in-summary';
export * from './info-dialog';
export * from './callee-info';

View File

@@ -508,22 +508,6 @@ export function getDialInfoPageURL(
return `${origin}${newPath}/static/dialInInfo.html?room=${conferenceName}`;
}
/**
* Generates the URL for the static dial in info page.
*
* @param {string} uri - The conference URI string.
* @returns {string}
*/
export function getDialInfoPageURLForURIString(
uri: ?string) {
if (!uri) {
return undefined;
}
const { protocol, host, contextRoot, room } = parseURIString(uri);
return `${protocol}//${host}${contextRoot}static/dialInInfo.html?room=${room}`;
}
/**
* Sets the internal state of which dial-in number to display.
*

View File

@@ -6,7 +6,6 @@ import {
ADD_PENDING_INVITE_REQUEST,
REMOVE_PENDING_INVITE_REQUESTS,
SET_CALLEE_INFO_VISIBLE,
SET_DIAL_IN_SUMMARY_VISIBLE,
SET_INVITE_DIALOG_VISIBLE,
UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS
@@ -51,12 +50,6 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
initialCalleeInfo: action.initialCalleeInfo
};
case SET_DIAL_IN_SUMMARY_VISIBLE:
return {
...state,
summaryUrl: action.summaryUrl
};
case SET_INVITE_DIALOG_VISIBLE:
return {
...state,

View File

@@ -7,8 +7,6 @@ import { getDefaultURL } from '../../app';
import { translate } from '../../base/i18n';
import { NavigateSectionList, type Section } from '../../base/react';
import { connect } from '../../base/redux';
import { ColorPalette } from '../../base/styles';
import { showDialInSummary } from '../../invite';
import { deleteRecentListEntry } from '../actions';
import { isRecentListEnabled, toDisplayableList } from '../functions';
@@ -62,7 +60,6 @@ class RecentList extends AbstractRecentList<Props> {
super(props);
this._onDelete = this._onDelete.bind(this);
this._onShowDialInInfo = this._onShowDialInInfo.bind(this);
}
/**
@@ -82,10 +79,6 @@ class RecentList extends AbstractRecentList<Props> {
} = this.props;
const recentList = toDisplayableList(_recentList, t, _defaultServerURL);
const slideActions = [ {
backgroundColor: ColorPalette.blue,
onPress: this._onShowDialInInfo,
text: t('welcomepage.info')
}, {
backgroundColor: 'red',
onPress: this._onDelete,
text: t('welcomepage.recentListDelete')
@@ -114,18 +107,6 @@ class RecentList extends AbstractRecentList<Props> {
_onDelete(itemId) {
this.props.dispatch(deleteRecentListEntry(itemId));
}
_onShowDialInInfo: Object => void
/**
* Callback for the dial-in info action of the list.
*
* @param {Object} itemId - The ID of the entry for which we'd like to show the dial in numbers.
* @returns {void}
*/
_onShowDialInInfo(itemId) {
this.props.dispatch(showDialInSummary(itemId.url));
}
}
/**

View File

@@ -106,12 +106,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
}
case RECORDING_SESSION_UPDATED: {
// When in recorder mode no notifications are shown
// or extra sounds are also not desired
if (getState()['features/base/config'].iAmRecorder) {
break;
}
const updatedSessionData
= getSessionById(getState(), action.sessionData.id);
const { PENDING, OFF, ON } = JitsiRecordingConstants.status;

View File

@@ -5,7 +5,7 @@ import { Alert, NativeModules, SafeAreaView, ScrollView, Switch, Text, TextInput
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n';
import { HeaderWithNavigation, Modal } from '../../../base/react';
import { BackButton, Header, Modal } from '../../../base/react';
import { connect } from '../../../base/redux';
import {
@@ -18,6 +18,7 @@ import FormRow from './FormRow';
import FormSectionHeader from './FormSectionHeader';
import { normalizeUserInputURL } from '../../functions';
import styles from './styles';
import { HeaderLabel } from '../../../base/react/components/native';
/**
* Application information module.
@@ -212,9 +213,10 @@ class SettingsView extends AbstractSettingsView<Props> {
*/
_renderHeader() {
return (
<HeaderWithNavigation
headerLabelKey = 'settingsView.header'
onPressBack = { this._onRequestClose } />
<Header>
<BackButton onPress = { this._onRequestClose } />
<HeaderLabel labelKey = 'settingsView.header' />
</Header>
);
}

View File

@@ -20,7 +20,6 @@ import {
createDesiredLocalTracks,
destroyLocalTracks
} from '../../base/tracks';
import { DialInSummary } from '../../invite';
import { SettingsView } from '../../settings';
import {
@@ -136,7 +135,6 @@ class WelcomePage extends AbstractWelcomePage {
</SafeAreaView>
<WelcomePageLists disabled = { this.state._fieldFocused } />
<SettingsView />
<DialInSummary />
</View>
<WelcomePageSideBar />
</LocalVideoTrackUnderlay>