Compare commits

..

36 Commits
1663 ... 1679

Author SHA1 Message Date
George Politis
08efd5ecab Merge pull request #1327 from saghul/doc-resolution
doc: document resolution config option and set it to 720 by default
2017-02-17 11:33:04 -06:00
Lyubomir Marinov
dba1bcb0e3 [RN] Increment short app version from 1.2 to 1.3
Now that Apple have approved build 1.2.199 for release in the App Store,
the short app version needs to be incremented; otherwise, no new builds
can be uploaded to TestFlight and, respectively, for release in the App
Store.
2017-02-17 09:32:24 -06:00
yanas
348403abff Merge pull request #1326 from jitsi/fix-manual-tooltips
Fixes manual triggered tooltips text and no hover.
2017-02-17 09:26:58 -06:00
Paweł Domas
c290cf45b7 Merge pull request #1328 from saghul/cleanup-adaptive-lastn
cleanup: remove adaptive las N config option
2017-02-17 09:14:47 -06:00
Saúl Ibarra Corretgé
175c8e6e50 cleanup: remove adaptive las N config option
The feature has been replaced so the option no longer applies.
2017-02-17 15:33:20 +01:00
Saúl Ibarra Corretgé
f90667b23c doc: document resolution config option and set it to 720 by default 2017-02-17 13:53:41 +01:00
damencho
cf69d591e4 Fixes manual triggered tooltips text and no hover. 2017-02-17 00:14:58 +02:00
Lyubomir Marinov
e599491583 Remove duplication 2017-02-16 15:17:05 -06:00
Lyubomir Marinov
d1520773cf Improve consistency 2017-02-16 15:16:17 -06:00
Saúl Ibarra Corretgé
573ca97b6c [RN] Add workaround for Android view clipping
Looks like Android gets confused as to what surface to blit when we hide or
show toolbars. Setting a border on the container, seems to force the entire
area to blit properly.

Other attempted approaches, with no success:
- zIndex of -100
- width and height of 0
- opacity of 0 and setting 'disabled' on touch containers

This patch applies the workaround in the welcome page and conference containers.
2017-02-16 14:37:42 -06:00
Lyubomir Marinov
0d97f14a1a flow 2017-02-16 13:59:28 -06:00
Lyubomir Marinov
b8f28abfdf [RN] Fix incorrect JitsiMeetJS.init error handling 2017-02-16 13:59:12 -06:00
Lyubomir Marinov
9ac7c97e67 [RN] Enforce mandatory mobile app-specific config 2017-02-16 13:58:39 -06:00
Lyubomir Marinov
52b3eaacb5 [RN] Fix passing config.js to JitsiMeetJS.init 2017-02-16 13:51:01 -06:00
ibauersachs
9b01ae6db9 Commit from translate.jitsi.org by user ibauersachs.: 306 of 306 strings translated (0 fuzzy). 2017-02-16 06:55:14 +00:00
Ingo Bauersachs
469487ad36 Add Chinese (China) 2017-02-16 07:39:57 +01:00
jitsi-pootle
176c3c1601 New files added from translate.jitsi.org based on templates 2017-02-16 06:46:38 +00:00
yanas
94391234cc Merge pull request #1322 from jitsi/livestream_help_link
livestream link help link
2017-02-15 22:35:57 -06:00
yanas
d84901f196 Font size adjustment and moving link to config param 2017-02-15 17:57:57 -06:00
yanas
c6b117565d Merge branch 'livestream_help_link' of https://github.com/jitsi/jitsi-meet into livestream_help_link 2017-02-15 16:41:49 -06:00
yanas
2a9124acb5 Merge pull request #1316 from jitsi/custom-button-tooltips
Uses tooltip to show custom popups for mute mic button.
2017-02-15 16:20:36 -06:00
Lyubomir Marinov
401a783d6a Coding style consistency
Includes automatic recommended Xcode project file fixes.
2017-02-15 13:48:56 -06:00
Lyubomir Marinov
39483a30b6 Polyfill Element.innerHTML
Lib-jitsi-meet uses jQuery's .append method to manipulate Jingle. The
method in question invokes the getter and setter of Element.innerHTML.
Unfortunately, xmldom which we use in React Native to polyfill DOM does
not polyfill Element.innerHTML. So polyfill it ourselves.
2017-02-15 13:18:21 -06:00
Lyubomir Marinov
0e2a07f8d7 Stick to react-native-background-timer 1.0.0
Recently expose-loader broke us when it updated from 0.7.1 to 0.7.2 and
we decided to stick to exact versions.
2017-02-15 13:18:20 -06:00
Saúl Ibarra Corretgé
36f5b0218d [RN] Fix running timers in the background
Turns out React Native's timers (setTimeout / setInterval) don't run while the
app is in the background: https://github.com/facebook/react-native/issues/167

This patch replaces the global timer functions with those from the
react-native-background-timer package, which work in the background.

These timers won't magically make an application work in the background, but
they will run if an application already happens to run in the background. That's
our case while in a conference, so these timers will run, allowing XMPP pings to
be sent and the conference to stay up as long as the user desires.
2017-02-15 11:50:54 -06:00
damencho
a1b3c56de7 Uses tooltip to show custom popups for mute mic button/shared video button. 2017-02-15 11:29:26 +02:00
Aaron van Meerten
6d664f133e Cosmetic fixes for column length lint errors 2017-02-14 17:11:31 -06:00
Aaron van Meerten
732a433ec1 livestream link to provide more context to users on where to retrieve their stream key 2017-02-14 16:07:12 -06:00
Lyubomir Marinov
f7dcd1ba2c Stick to expose-loader 0.7.1
0.7.2 causes a ReferenceError: jQuery is not defined in autosize.
2017-02-14 08:07:49 -06:00
Lyubomir Marinov
55a8b44224 Consistent middleware and reducer imports 2017-02-10 11:04:40 -06:00
Lyubomir Marinov
e29db31d91 Comply w/ coding style 2017-02-10 10:13:39 -06:00
Lyubomir Marinov
183d3c3ca4 Fix a possible undefined state usage 2017-02-10 00:47:55 -06:00
Saúl Ibarra Corretgé
c57e713696 [RN] Fix full-screen mode when coming back from the background
On Android the status and navigation bars are shown again after coming back from
the background, so enter full-screen mode again if needed.
2017-02-10 00:44:37 -06:00
Saúl Ibarra Corretgé
4519f26adf [RN] Mute local video when app is in the background 2017-02-10 00:44:37 -06:00
bgrozev
c26f9cc01f Merge pull request #1301 from jitsi/video-thumbnail-margin
Lower the margin between video thumbnails
2017-02-09 11:43:55 -06:00
yanas
f6f730b994 Lower the margin between video thumbnails 2017-02-06 15:34:05 -06:00
66 changed files with 1108 additions and 227 deletions

View File

@@ -91,7 +91,7 @@ android {
minSdkVersion 16
targetSdkVersion 22
versionCode Integer.parseInt("${version}")
versionName "1.2.${version}"
versionName "1.3.${version}"
ndk {
abiFilters 'armeabi-v7a', 'x86'
}
@@ -139,6 +139,7 @@ if (project.hasProperty('JITSI_SIGNING')
}
dependencies {
compile project(':react-native-background-timer')
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-vector-icons')

View File

@@ -29,6 +29,7 @@ public class MainApplication extends Application implements ReactApplication {
new com.corbt.keepawake.KCKeepAwakePackage(),
new com.facebook.react.shell.MainReactPackage(),
new com.oblador.vectoricons.VectorIconsPackage(),
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.rnimmersive.RNImmersivePackage(),
new org.jitsi.meet.audiomode.AudioModePackage()

View File

@@ -1,6 +1,8 @@
rootProject.name = 'jitsi-meet-react'
include ':app'
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-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
include ':react-native-keep-awake'

View File

@@ -1229,7 +1229,8 @@ export default {
room.on(ConferenceEvents.TALK_WHILE_MUTED, () => {
APP.UI.showToolbar(6000);
UIUtil.animateShowElement($("#talkWhileMutedPopup"), true, 5000);
APP.UI.showCustomToolbarPopup('#talkWhileMutedPopup', true, 5000);
});
/*

View File

@@ -53,7 +53,6 @@ var config = { // eslint-disable-line no-unused-vars
disableStats: false,
disableAudioLevels: false,
channelLastN: -1, // The default value of the channel attribute last-n.
adaptiveLastN: false,
//disableAdaptiveSimulcast: false,
enableRecording: false,
enableWelcomePage: true,
@@ -80,5 +79,7 @@ var config = { // eslint-disable-line no-unused-vars
// Suspending video might cause problems with audio playback. Disabling until these are fixed.
disableSuspendVideo: true,
// disables or enables RTX (RFC 4588).
disableRtx: true
disableRtx: true,
// Sets the preferred resolution (height) for local video. Defaults to 360.
resolution: 720
};

View File

@@ -122,6 +122,7 @@ form {
z-index: $tooltipsZ;
&-inner {
background-color: $tooltipBg;
max-width: 350px;
}
&-arrow {

View File

@@ -1,34 +1,10 @@
/*Initialize*/
ul.loginmenu {
font-family: $baseFontFamily;
line-height: normal;
display:none;
div.loginmenu {
position: absolute;
margin: 0;
padding: 5px;
padding-bottom: 7px;
top: 45px;
left: -5px;
background-color: rgba(0,0,0,0.9);
border: 1px solid rgba(256, 256, 256, 0.2);
border-radius:8px;
}
ul.loginmenu li {
list-style-type: none;
padding: 7px;
color: #fff;
font-size: 11pt;
cursor: default;
white-space: pre;
}
ul.loginmenu:after {
content: url('../images/dropdownPointer.png');
display: block;
position: absolute;
top: -7px;
left: 18px;
top: 40px;
left: 20px;
}
a.disabled {
@@ -36,23 +12,7 @@ a.disabled {
pointer-events: none;
}
.loginmenuPadding {
width: 50px;
height: 30px;
position: absolute;
top: -30px;
left: 0px;
}
.loginmenu.extendedToolbarPopup {
left: 55px;
top: 0px;
}
ul.loginmenu.extendedToolbarPopup:after {
content: url('../images/leftDropdownPointer.png');
display: block;
position: absolute;
top: 18px;
left: -7px;
top: 20px;
left: 40px;
}

View File

@@ -16,7 +16,7 @@ $defaultToolbarSize: 50px;
$thumbnailToolbarHeight: 22px;
$thumbnailIndicatorBorder: 2px;
$thumbnailIndicatorSize: $thumbnailToolbarHeight;
$thumbnailVideoMargin: 5px;
$thumbnailVideoMargin: 2px;
$thumbnailsBorder: 2px;
$thumbnailVideoBorder: 2px;
$hideFilmstripButtonWidth: 17px;

View File

@@ -8,4 +8,13 @@
text-decoration: underline;
@include transition(color .1s ease-in);
}
}
/**
* Helper links are links that are meant to open a documentation page or more
* detailed info.
*/
.helper-link {
@extend .link;
font-size: 12px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 B

View File

@@ -70,5 +70,7 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)",
POLICY_LOGO: null,
LOCAL_THUMBNAIL_RATIO: 16/9, //16:9
REMOTE_THUMBNAIL_RATIO: 1 //1:1
REMOTE_THUMBNAIL_RATIO: 1, //1:1
// Documentation reference for the live streaming feature.
LIVE_STREAMING_HELP_LINK: "https://jitsi.org/live"
};

View File

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

View File

@@ -25,6 +25,7 @@
2602576C1D0A7703001E3363 /* jitsi.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2602576B1D0A7703001E3363 /* jitsi.ttf */; };
3847F11906B4479A9162628F /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 821D8ABD506944B4BDBB069B /* libRNVectorIcons.a */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
7FAD39BE09A84D6AB0ABACA8 /* libRNBackgroundTimer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A0018BBB2C4FD5A4F9CE71 /* libRNBackgroundTimer.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
901FE90FA5744B5B94DCDC41 /* libKCKeepAwake.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */; };
B30EF2311DC0ED7C00690F45 /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B30EF2301DC0ED7C00690F45 /* WebRTC.framework */; };
@@ -184,6 +185,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
B332D04E1E54E3170086EA16 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNBackgroundTimer;
};
B3BA19D41DC6B37B00BCD481 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
@@ -263,6 +271,7 @@
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = "<group>"; };
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = "<group>"; };
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = "<group>"; };
0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNBackgroundTimer.xcodeproj; path = "../node_modules/react-native-background-timer/ios/RNBackgroundTimer.xcodeproj"; sourceTree = "<group>"; };
0B42DFAD1E2FD90700111B12 /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AudioMode.m; path = app/AudioMode.m; sourceTree = "<group>"; };
0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libKCKeepAwake.a; sourceTree = "<group>"; };
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = "<group>"; };
@@ -277,6 +286,7 @@
146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = "<group>"; };
22418656B14845609F953A42 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
2602576B1D0A7703001E3363 /* jitsi.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = jitsi.ttf; path = ../android/app/src/main/assets/fonts/jitsi.ttf; sourceTree = "<group>"; };
27A0018BBB2C4FD5A4F9CE71 /* libRNBackgroundTimer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNBackgroundTimer.a; sourceTree = "<group>"; };
5B09C20C78C74A548AAAC1FA /* KCKeepAwake.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = KCKeepAwake.xcodeproj; path = "../node_modules/react-native-keep-awake/ios/KCKeepAwake.xcodeproj"; sourceTree = "<group>"; };
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
@@ -315,7 +325,7 @@
BF9643881C34FBC800B0BBDF /* GLKit.framework in Frameworks */,
B30EF2311DC0ED7C00690F45 /* WebRTC.framework in Frameworks */,
BF96438E1C34FBE100B0BBDF /* VideoToolbox.framework in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
901FE90FA5744B5B94DCDC41 /* libKCKeepAwake.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */,
@@ -327,8 +337,9 @@
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
BFC745141CB829B300673F38 /* libRCTWebRTC.a in Frameworks */,
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
7FAD39BE09A84D6AB0ABACA8 /* libRNBackgroundTimer.a in Frameworks */,
3847F11906B4479A9162628F /* libRNVectorIcons.a in Frameworks */,
901FE90FA5744B5B94DCDC41 /* libKCKeepAwake.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -488,6 +499,7 @@
BFC7450D1CB829A700673F38 /* RCTWebRTC.xcodeproj */,
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */,
22418656B14845609F953A42 /* RNVectorIcons.xcodeproj */,
);
name = Libraries;
@@ -524,6 +536,14 @@
name = Products;
sourceTree = "<group>";
};
B332D0301E54E3170086EA16 /* Products */ = {
isa = PBXGroup;
children = (
B332D04F1E54E3170086EA16 /* libRNBackgroundTimer.a */,
);
name = Products;
sourceTree = "<group>";
};
B3BA19B71DC6B02F00BCD481 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -653,6 +673,10 @@
ProductGroup = 146834001AC3E56700842450 /* Products */;
ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */;
},
{
ProductGroup = B332D0301E54E3170086EA16 /* Products */;
ProjectRef = 0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */;
},
{
ProductGroup = 26D589F81D0B42EE00FC396B /* Products */;
ProjectRef = 22418656B14845609F953A42 /* RNVectorIcons.xcodeproj */;
@@ -806,6 +830,13 @@
remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B332D04F1E54E3170086EA16 /* libRNBackgroundTimer.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNBackgroundTimer.a;
remoteRef = B332D04E1E54E3170086EA16 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
B3BA19D51DC6B37B00BCD481 /* libRCTImage-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -952,6 +983,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);
@@ -988,6 +1020,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);
@@ -1050,6 +1083,7 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);
@@ -1095,6 +1129,7 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-background-timer/ios",
"$(SRCROOT)/../node_modules/react-native-keep-awake/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
);

View File

@@ -13,5 +13,6 @@
"sk": "Slowakisch",
"sl": "Slowenisch",
"sv": "Schwedisch",
"tr": "Türkisch"
"tr": "Türkisch",
"zhCN": "Chinesisch (China)"
}

17
lang/languages-zhCN.json Normal file
View File

@@ -0,0 +1,17 @@
{
"en": "",
"bg": "",
"de": "",
"es": "",
"fr": "",
"hy": "",
"it": "",
"oc": "",
"pl": "",
"ptBR": "",
"ru": "",
"sk": "",
"sl": "",
"sv": "",
"tr": ""
}

View File

@@ -13,5 +13,6 @@
"sk": "Slovak",
"sl": "Slovenian",
"sv": "Swedish",
"tr": "Turkish"
"tr": "Turkish",
"zhCN": "Chinese (China)"
}

View File

@@ -154,7 +154,8 @@
"kick": "Hinauswerfen",
"muted": "Stummgeschaltet",
"domute": "Stummschalten",
"flip": "Spiegeln"
"flip": "Spiegeln",
"remoteControl": "Fernsteuerung"
},
"connectionindicator": {
"header": "Verbindungsdaten",
@@ -312,7 +313,12 @@
"externalInstallationMsg": "Die Bildschirmfreigabeerweiterung muss installiert werden.",
"muteParticipantTitle": "Teilnehmer stummschalten?",
"muteParticipantBody": "Sie können die Stummschaltung anderer Teilnehmer nicht aufheben, aber ein Teilnehmer kann seine eigene Stummschaltung jederzeit beenden.",
"muteParticipantButton": "Stummschalten"
"muteParticipantButton": "Stummschalten",
"remoteControlTitle": "Fernsteuerung",
"remoteControlDeniedMessage": "__user__ hat die Anfrage zur Fernsteuerung verweigert.",
"remoteControlAllowedMessage": "__user__ hat die Anfrage zur Fernsteuerung angenommen.",
"remoteControlErrorMessage": "Beim Anfordern der Fernsteuerungsberechtigung von __user__ ist ein Fehler aufgetreten.",
"remoteControlStopMessage": "Die Fernsteuerung wurde beendet."
},
"\u0005dialog": {},
"email": {
@@ -375,6 +381,7 @@
"failedToStart": "Live-Streaming konnte nicht gestartet werden",
"buttonTooltip": "Live-Stream starten / stoppen",
"streamIdRequired": "Bitte Stream-ID eingeben um das Live-Streaming zu starten.",
"streamIdHelp": "Wo ist die Stream-ID zu finden?",
"error": "Das Live-Streaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
"busy": "All Aufnahmegeräte sind momentan ausgelastet. Bitte versuchen Sie es später noch einmal."
}

355
lang/main-zhCN.json Normal file
View File

@@ -0,0 +1,355 @@
{
"contactlist": "",
"addParticipants": "",
"roomLocked": "",
"roomUnlocked": "",
"passwordSetRemotely": "",
"connectionsettings": "",
"poweredby": "",
"feedback": "",
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"callingName": "",
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": ""
},
"welcomepage": {
"go": "",
"roomname": "",
"disable": "",
"feature1": {
"title": "",
"content": ""
},
"feature2": {
"title": "",
"content": ""
},
"feature3": {
"title": "",
"content": ""
},
"feature4": {
"title": "",
"content": ""
},
"feature5": {
"title": "",
"content": ""
},
"feature6": {
"title": "",
"content": ""
},
"feature7": {
"title": "",
"content": ""
},
"feature8": {
"title": "",
"content": ""
}
},
"startupoverlay": {
"policyText": "",
"title": ""
},
"suspendedoverlay": {
"title": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"mute": "",
"videomute": "",
"authenticate": "",
"lock": "",
"invite": "",
"chat": "",
"etherpad": "",
"sharedvideo": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"logout": "",
"dialpad": "",
"sharedVideoMutedPopup": "",
"micMutedPopup": "",
"talkWhileMutedPopup": "",
"unableToUnmutePopup": "",
"cameraDisabled": "",
"micDisabled": "",
"filmstrip": "",
"profile": "",
"raiseHand": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
},
"chat": {
"nickname": {
"title": "",
"popover": ""
},
"messagebox": ""
},
"settings": {
"title": "",
"update": "",
"name": "",
"startAudioMuted": "",
"startVideoMuted": "",
"selectCamera": "",
"selectMic": "",
"selectAudioOutput": "",
"followMe": "",
"noDevice": "",
"noPermission": "",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": "",
"setPasswordLabel": ""
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
},
"videothumbnail": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"less": "",
"more": "",
"address": "",
"remoteport_plural": "",
"remoteport": "",
"localport_plural": "",
"localport": "",
"localaddress_plural": "",
"localaddress": "",
"remoteaddress_plural": "",
"remoteaddress": "",
"transport": "",
"bandwidth": "",
"na": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"kickMessage": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"error": "",
"roomLocked": "",
"addPassword": "",
"createPassword": "",
"detectext": "",
"failtoinstall": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"reconnectNow": "",
"conferenceReloadTimeLeft": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"Dial": "",
"sipMsg": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"settings1": "",
"settings2": "",
"settings3": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"rateExperience": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowWarningAgain": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "",
"cameraErrorPresent": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingData": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"pending": "",
"on": "",
"off": "",
"failedToStart": "",
"buttonTooltip": "",
"error": "",
"unavailable": ""
},
"liveStreaming": {
"pending": "",
"on": "",
"off": "",
"unavailable": "",
"failedToStart": "",
"buttonTooltip": "",
"streamIdRequired": "",
"error": "",
"busy": ""
}
}

View File

@@ -387,6 +387,7 @@
"failedToStart": "Live streaming failed to start",
"buttonTooltip": "Start / Stop live stream",
"streamIdRequired": "Please fill in the stream id in order to launch the live streaming.",
"streamIdHelp": "Where do I find this?",
"error": "Live streaming failed. Please try again.",
"busy": "All recorders are currently busy. Please try again later."
}

View File

@@ -712,6 +712,17 @@ UI.inputDisplayNameHandler = function (newDisplayName) {
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
};
/**
* Show custom popup/tooltip for a specified button.
* @param popupSelectorID the selector id of the popup to show
* @param show true or false/show or hide the popup
* @param timeout the time to show the popup
*/
UI.showCustomToolbarPopup = function (popupSelectorID, show, timeout) {
eventEmitter.emit(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
popupSelectorID, show, timeout);
};
/**
* Return the type of the remote video.
* @param jid the jid for the remote video

View File

@@ -51,6 +51,9 @@ function _requestLiveStreamId() {
const streamIdRequired
= APP.translation.generateTranslationHTML(
"liveStreaming.streamIdRequired");
const streamIdHelp
= APP.translation.generateTranslationHTML(
"liveStreaming.streamIdHelp");
return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openDialogWithStates({
@@ -60,7 +63,11 @@ function _requestLiveStreamId() {
`<input class="input-control"
name="streamId" type="text"
data-i18n="[placeholder]dialog.streamKey"
autofocus>`,
autofocus><div style="text-align: right">
<a class="helper-link" target="_new"
href="${interfaceConfig.LIVE_STREAMING_HELP_LINK}">`
+ streamIdHelp
+ `</a></div>`,
persistent: false,
buttons: [
{title: cancelButton, value: false},

View File

@@ -541,7 +541,7 @@ export default class SharedVideoManager {
if(show)
this.showSharedVideoMutedPopup(false);
UIUtil.animateShowElement($("#micMutedPopup"), show, 5000);
APP.UI.showCustomToolbarPopup('#micMutedPopup', show, 5000);
}
/**
@@ -554,7 +554,7 @@ export default class SharedVideoManager {
if(show)
this.showMicMutedPopup(false);
UIUtil.animateShowElement($("#sharedVideoMutedPopup"), show, 5000);
APP.UI.showCustomToolbarPopup('#sharedVideoMutedPopup', show, 5000);
}
}

View File

@@ -1,4 +1,4 @@
/* global APP, $, config, interfaceConfig, JitsiMeetJS */
/* global AJS, APP, $, config, interfaceConfig, JitsiMeetJS */
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
import SideContainerToggler from "../side_pannels/SideContainerToggler";
@@ -26,8 +26,8 @@ const buttonHandlers = {
if (sharedVideoManager
&& sharedVideoManager.isSharedVideoVolumeOn()
&& !sharedVideoManager.isSharedVideoOwner()) {
UIUtil.animateShowElement(
$("#unableToUnmutePopup"), true, 5000);
APP.UI.showCustomToolbarPopup(
'#unableToUnmutePopup', true, 5000);
}
else {
JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
@@ -120,19 +120,19 @@ const defaultToolbarButtons = {
shortcutDescription: "keyboardShortcuts.mute",
popups: [
{
id: "micMutedPopup",
className: "loginmenu",
dataAttr: "[html]toolbar.micMutedPopup"
id: 'micMutedPopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.micMutedPopup'
},
{
id: "unableToUnmutePopup",
className: "loginmenu",
dataAttr: "[html]toolbar.unableToUnmutePopup"
id: 'unableToUnmutePopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.unableToUnmutePopup'
},
{
id: "talkWhileMutedPopup",
className: "loginmenu",
dataAttr: "[html]toolbar.talkWhileMutedPopup"
id: 'talkWhileMutedPopup',
className: 'loginmenu',
dataAttr: '[title]toolbar.talkWhileMutedPopup'
}
],
content: "Mute / Unmute",
@@ -263,11 +263,14 @@ const defaultToolbarButtons = {
id: 'toolbar_button_sharedvideo',
tooltipKey: 'toolbar.sharedvideo',
className: 'button icon-shared-video',
html: `<ul id="sharedVideoMutedPopup"
class="loginmenu extendedToolbarPopup">
<li data-i18n="[html]toolbar.sharedVideoMutedPopup"></li>
</ul>
`
popups: [
{
id: 'sharedVideoMutedPopup',
className: 'loginmenu extendedToolbarPopup',
dataAttr: '[title]toolbar.sharedVideoMutedPopup',
dataAttrPosition: 'w'
}
]
},
'sip': {
id: 'toolbar_button_sip',
@@ -358,6 +361,11 @@ Toolbar = {
Toolbar._handleFullScreenToggled(isFullScreen);
});
APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
(popupID, show, timeout) => {
Toolbar._showCustomToolbarPopup(popupID, show, timeout);
});
if(!APP.tokenData.isGuest) {
$("#toolbar_button_profile").addClass("unclickable");
UIUtil.removeTooltip(
@@ -724,17 +732,52 @@ Toolbar = {
_addPopups(buttonElement, popups = []) {
popups.forEach((popup) => {
let popupElement = document.createElement("ul");
const popupElement = document.createElement('div');
popupElement.id = popup.id;
popupElement.className = popup.className;
let liElement = document.createElement("li");
liElement.setAttribute("data-i18n", popup.dataAttr);
popupElement.appendChild(liElement);
popupElement.setAttribute('data-i18n', popup.dataAttr);
let gravity = 'n';
if (popup.dataAttrPosition)
gravity = popup.dataAttrPosition;
// use custom attribute to save gravity option
// we use 'data-tooltip' in UIUtil to activate all tooltips
// but we want these to be manually triggered
popupElement.setAttribute('tooltip-gravity', gravity);
APP.translation.translateElement($(popupElement));
buttonElement.appendChild(popupElement);
});
},
/**
* Show custom popup/tooltip for a specified button.
* @param popupSelectorID the selector id of the popup to show
* @param show true or false/show or hide the popup
* @param timeout the time to show the popup
*/
_showCustomToolbarPopup(popupSelectorID, show, timeout) {
const gravity = $(popupSelectorID).attr('tooltip-gravity');
AJS.$(popupSelectorID)
.tooltip({
trigger: 'manual',
html: true,
gravity: gravity,
title: 'title'});
if (show) {
AJS.$(popupSelectorID).tooltip('show');
setTimeout(function () {
// hide the tooltip
AJS.$(popupSelectorID).tooltip('hide');
}, timeout);
} else {
AJS.$(popupSelectorID).tooltip('hide');
}
},
/**
* Sets the toggled state of the given element depending on the isToggled
* parameter.
*

View File

@@ -35,6 +35,7 @@
"react": "15.4.2",
"react-dom": "15.4.2",
"react-native": "0.41.2",
"react-native-background-timer": "1.0.0",
"react-native-immersive": "0.0.4",
"react-native-keep-awake": "^2.0.2",
"react-native-prompt": "^1.0.0",
@@ -66,7 +67,7 @@
"eslint-plugin-jsdoc": "*",
"eslint-plugin-react": "*",
"eslint-plugin-react-native": "^2.2.1",
"expose-loader": "*",
"expose-loader": "0.7.1",
"file-loader": "^0.10.0",
"flow-bin": "^0.37.0",
"haste-resolver-webpack-plugin": "^0.2.2",

View File

@@ -8,7 +8,6 @@ import {
_parseURIString,
init
} from './functions';
import './reducer';
/**
* Temporary solution. Should dispatch actions related to initial settings of

View File

@@ -2,8 +2,9 @@
import { Linking } from 'react-native';
import { Platform } from '../../base/react';
import '../../audio-mode';
import '../../background';
import { Platform } from '../../base/react';
import '../../full-screen';
import '../../wake-lock';

View File

@@ -2,3 +2,5 @@ export * from './actions';
export * from './actionTypes';
export * from './components';
export * from './functions';
import './reducer';

View File

@@ -0,0 +1,43 @@
import { Symbol } from '../base/react';
/**
* The type of redux action to set the AppState API change event listener.
*
* {
* type: _SET_APP_STATE_LISTENER,
* listener: Function
* }
*
* @protected
*/
export const _SET_APP_STATE_LISTENER
= Symbol('_SET_APP_STATE_LISTENER');
/**
* The type of redux action which signals that video will be muted because the
* app is going to the background.
*
* {
* type: _SET_BACKGROUND_VIDEO_MUTED,
* muted: boolean
* }
*
* @protected
*/
export const _SET_BACKGROUND_VIDEO_MUTED
= Symbol('_SET_BACKGROUND_VIDEO_MUTED');
/**
* The type of redux action which signals that the app state has changed (in
* terms of execution mode). The app state can be one of 'active', 'inactive',
* or 'background'.
*
* {
* type: APP_STATE_CHANGED,
* appState: string
* }
*
* @public
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
export const APP_STATE_CHANGED = Symbol('APP_STATE_CHANGED');

View File

@@ -0,0 +1,81 @@
import { setVideoMuted } from '../base/media';
import {
_SET_APP_STATE_LISTENER,
_SET_BACKGROUND_VIDEO_MUTED,
APP_STATE_CHANGED
} from './actionTypes';
/**
* Signals that the App state has changed (in terms of execution state). The
* application can be in 3 states: 'active', 'inactive' and 'background'.
*
* @param {string} appState - The new App state.
* @public
* @returns {{
* type: APP_STATE_CHANGED,
* appState: string
* }}
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
export function appStateChanged(appState: string) {
return {
type: APP_STATE_CHANGED,
appState
};
}
/**
* Sets the listener to be used with React Native's AppState API.
*
* @param {Function} listener - Function to be set as the change event listener.
* @protected
* @returns {{
* type: _SET_APP_STATE_LISTENER,
* listener: Function
* }}
*/
export function _setAppStateListener(listener: ?Function) {
return {
type: _SET_APP_STATE_LISTENER,
listener
};
}
/**
* Signals that the app should mute video because it's now running in the
* background, or unmute it because it came back from the background. If video
* was already muted nothing will happen; otherwise, it will be muted. When
* coming back from the background the previous state will be restored.
*
* @param {boolean} muted - True if video should be muted; false, otherwise.
* @protected
* @returns {Function}
*/
export function _setBackgroundVideoMuted(muted: boolean) {
return (dispatch, getState) => {
if (muted) {
const mediaState = getState()['features/base/media'];
if (mediaState.video.muted) {
// Video is already muted, do nothing.
return;
}
} else {
const bgState = getState()['features/background'];
if (!bgState.videoMuted) {
// We didn't mute video, do nothing.
return;
}
}
// Remember that video was muted due to the app going to the background
// vs user's choice.
dispatch({
type: _SET_BACKGROUND_VIDEO_MUTED,
muted
});
dispatch(setVideoMuted(muted));
};
}

View File

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

View File

@@ -0,0 +1,109 @@
/* @flow */
import { AppState } from 'react-native';
import type { Dispatch } from 'redux';
import {
APP_WILL_MOUNT,
APP_WILL_UNMOUNT
} from '../app';
import { MiddlewareRegistry } from '../base/redux';
import {
_setAppStateListener,
_setBackgroundVideoMuted,
appStateChanged
} from './actions';
import {
_SET_APP_STATE_LISTENER,
APP_STATE_CHANGED
} from './actionTypes';
/**
* Middleware that captures App lifetime actions and subscribes to application
* state changes. When the application state changes it will fire the action
* required to mute or unmute the local video in case the application goes to
* the background or comes back from it.
*
* @param {Store} store - Redux store.
* @returns {Function}
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case _SET_APP_STATE_LISTENER: {
// Remove the current/old AppState listener.
const { appStateListener } = store.getState()['features/background'];
if (appStateListener) {
AppState.removeEventListener('change', appStateListener);
}
// Add the new AppState listener.
if (action.listener) {
AppState.addEventListener('change', action.listener);
}
break;
}
case APP_STATE_CHANGED:
_appStateChanged(store.dispatch, action.appState);
break;
case APP_WILL_MOUNT:
store.dispatch(
_setAppStateListener(
_onAppStateChange.bind(undefined, store.dispatch)));
break;
case APP_WILL_UNMOUNT:
store.dispatch(_setAppStateListener(null));
break;
}
return next(action);
});
/**
* Handles app state changes. Dispatches the necessary Redux actions for the
* local video to be muted when the app goes to the background, and to be
* unmuted when the app comes back.
*
* @param {Dispatch} dispatch - Redux dispatch function.
* @param {string} appState - The current app state.
* @private
* @returns {void}
*/
function _appStateChanged(dispatch: Dispatch<*>, appState: string) {
let muted;
switch (appState) {
case 'active':
muted = false;
break;
case 'background':
muted = true;
break;
case 'inactive':
default:
// XXX: We purposely don't handle the 'inactive' app state.
return;
}
dispatch(_setBackgroundVideoMuted(muted));
}
/**
* Called by React Native's AppState API to notify that the application state
* has changed. Dispatches the change within the (associated) Redux store.
*
* @param {Dispatch} dispatch - Redux dispatch function.
* @param {string} appState - The current application execution state.
* @private
* @returns {void}
*/
function _onAppStateChange(dispatch: Dispatch<*>, appState: string) {
dispatch(appStateChanged(appState));
}

View File

@@ -0,0 +1,31 @@
import { ReducerRegistry } from '../base/redux';
import {
_SET_APP_STATE_LISTENER,
_SET_BACKGROUND_VIDEO_MUTED,
APP_STATE_CHANGED
} from './actionTypes';
ReducerRegistry.register('features/background', (state = {}, action) => {
switch (action.type) {
case _SET_APP_STATE_LISTENER:
return {
...state,
appStateListener: action.listener
};
case _SET_BACKGROUND_VIDEO_MUTED:
return {
...state,
videoMuted: action.muted
};
case APP_STATE_CHANGED:
return {
...state,
appState: action.appState
};
}
return state;
});

View File

@@ -20,8 +20,6 @@ import {
} from './actionTypes';
import { EMAIL_COMMAND } from './constants';
import { _addLocalTracksToConference } from './functions';
import './middleware';
import './reducer';
/**
* Adds conference (event) listeners.

View File

@@ -19,8 +19,8 @@ export function _addLocalTracksToConference(conference, localTracks) {
// XXX The library lib-jitsi-meet may be draconian, for example, when
// adding one and the same video track multiple times.
if (conferenceLocalTracks.indexOf(track) === -1) {
promises.push(conference.addTrack(track)
.catch(err => {
promises.push(
conference.addTrack(track).catch(err => {
_reportError(
'Failed to add local track to conference',
err);

View File

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

View File

@@ -11,7 +11,6 @@ import {
CONNECTION_FAILED,
SET_DOMAIN
} from './actionTypes';
import './reducer';
const JitsiConnectionEvents = JitsiMeetJS.events.connection;

View File

@@ -5,7 +5,6 @@ import type { Dispatch } from 'redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { SET_DOMAIN } from './actionTypes';
import './reducer';
declare var APP: Object;
declare var JitsiMeetJS: Object;

View File

@@ -1,3 +1,5 @@
export * from './actions';
export * from './actionTypes';
export * from './functions';
import './reducer';

View File

@@ -1,3 +1,5 @@
/* global JitsiMeetJS */
/* @flow */
declare var JitsiMeetJS: Object;
export default JitsiMeetJS;

View File

@@ -1,3 +1,7 @@
/* @flow */
import type { Dispatch } from 'redux';
import JitsiMeetJS from './';
import {
LIB_DISPOSED,
@@ -5,8 +9,8 @@ import {
LIB_INITIALIZED,
SET_CONFIG
} from './actionTypes';
import './middleware';
import './reducer';
declare var APP: Object;
/**
* Disposes lib-jitsi-meet.
@@ -20,7 +24,7 @@ export function disposeLib() {
// there is a big chance it will be async.
// TODO Currently, lib-jitsi-meet doesn't have any functionality to
// dispose itself.
return dispatch => {
return (dispatch: Dispatch<*>) => {
dispatch({ type: LIB_DISPOSED });
return Promise.resolve();
@@ -33,7 +37,7 @@ export function disposeLib() {
* @returns {Function}
*/
export function initLib() {
return (dispatch, getState) => {
return (dispatch: Dispatch<*>, getState: Function) => {
const config = getState()['features/base/lib-jitsi-meet'].config;
if (!config) {
@@ -51,7 +55,7 @@ export function initLib() {
.catch(error => {
dispatch({
type: LIB_INIT_ERROR,
lib: { error }
error
});
// TODO Handle LIB_INIT_ERROR error somewhere instead.
@@ -67,11 +71,11 @@ export function initLib() {
* @param {Object} config - Config object accepted by JitsiMeetJS#init()
* method.
* @returns {{
* type: SET_CONFIG,
* config: Object
* }}
* type: SET_CONFIG,
* config: Object
* }}
*/
export function setConfig(config) {
export function setConfig(config: Object) {
return {
type: SET_CONFIG,
config

View File

@@ -6,3 +6,6 @@ export { JitsiMeetJS as default };
export * from './actions';
export * from './actionTypes';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -22,24 +22,44 @@ MiddlewareRegistry.register(store => next => action => {
action.participant.local && store.dispatch(disposeLib());
break;
case SET_CONFIG: {
const { dispatch, getState } = store;
const initialized
= getState()['features/base/lib-jitsi-meet'].initialized;
// XXX If we already have config, that means new config is coming, which
// means that we should dispose instance of lib initialized with
// previous config first.
// TODO Currently 'disposeLib' actually does not dispose lib-jitsi-meet.
// This functionality should be implemented.
const promise
= initialized ? dispatch(disposeLib()) : Promise.resolve();
promise.then(dispatch(initLib()));
break;
}
case SET_CONFIG:
return _setConfig(store, next, action);
}
return next(action);
});
/**
* Notifies the feature base/lib-jitsi-meet that the action SET_CONFIG is being
* dispatched within a specific Redux store.
*
* @param {Store} store - The Redux store in which the specified action is being
* dispatched.
* @param {Dispatch} next - The Redux dispatch function to dispatch the
* specified action to the specified store.
* @param {Action} action - The Redux action SET_CONFIG which is being
* dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified action.
*/
function _setConfig(store, next, action) {
const { dispatch, getState } = store;
const { initialized } = getState()['features/base/lib-jitsi-meet'];
// XXX Since the config is changing, the library lib-jitsi-meet must be
// initialized again with the new config. Consequntly, it may need to be
// disposed of first.
// TODO Currently, disposeLib actually does not dispose of lib-jitsi-meet
// because lib-jitsi-meet does not implement such functionality.
const disposeLIbPromise
= initialized ? dispatch(disposeLib()) : Promise.resolve();
// Let the new config into the Redux store (because initLib will read it
// from there).
const nextState = next(action);
disposeLIbPromise.then(dispatch(initLib()));
return nextState;
}

View File

@@ -1,2 +1,2 @@
require('./polyfills-browser');
require('./polyfills-browserify');
import './polyfills-browser';
import './polyfills-browserify';

View File

@@ -1,3 +1,6 @@
import BackgroundTimer from 'react-native-background-timer';
import 'url-polyfill'; // Polyfill for URL constructor
/**
* Gets the first common prototype of two specified Objects (treating the
* objects themselves as prototypes as well).
@@ -84,10 +87,6 @@ function _visitNode(node, callback) {
}
(global => {
// Polyfill for URL constructor
require('url-polyfill');
const DOMParser = require('xmldom').DOMParser;
// addEventListener
@@ -108,8 +107,8 @@ function _visitNode(node, callback) {
if (typeof global.document === 'undefined') {
const document
= new DOMParser().parseFromString(
/* source */ '<html><head></head><body></body></html>',
/* mineType */ 'text/xml');
'<html><head></head><body></body></html>',
'text/xml');
// document.addEventListener
//
@@ -141,11 +140,49 @@ function _visitNode(node, callback) {
const elementPrototype
= Object.getPrototypeOf(document.documentElement);
if (elementPrototype
&& typeof elementPrototype.querySelector === 'undefined') {
elementPrototype.querySelector = function(selectors) {
return _querySelector(this, selectors);
};
if (elementPrototype) {
if (typeof elementPrototype.querySelector === 'undefined') {
elementPrototype.querySelector = function(selectors) {
return _querySelector(this, selectors);
};
}
// Element.innerHTML
//
// Required by:
// - jQuery's .append method
if (!elementPrototype.hasOwnProperty('innerHTML')) {
Object.defineProperty(elementPrototype, 'innerHTML', {
get() {
return this.childNodes.toString();
},
set(innerHTML) {
// MDN says: removes all of element's children, parses
// the content string and assigns the resulting nodes as
// children of the element.
// Remove all of element's children.
this.textContent = '';
// Parse the content string.
const d
= new DOMParser().parseFromString(
`<div>${innerHTML}</div>`,
'text/xml');
// Assign the resulting nodes as children of the
// element.
const documentElement = d.documentElement;
let child;
// eslint-disable-next-line no-cond-assign
while (child = documentElement.firstChild) {
this.appendChild(child);
}
}
});
}
}
// FIXME There is a weird infinite loop related to console.log and
@@ -294,17 +331,17 @@ function _visitNode(node, callback) {
//
// Required by:
// - Strophe
if (prototype && typeof prototype.responseXML === 'undefined') {
if (prototype && !prototype.hasOwnProperty('responseXML')) {
Object.defineProperty(prototype, 'responseXML', {
configurable: true,
enumerable: true,
get() {
const responseText = this.responseText;
let responseXML;
if (responseText) {
responseXML = new DOMParser()
.parseFromString(responseText);
responseXML
= new DOMParser().parseFromString(
responseText,
'text/xml');
}
return responseXML;
@@ -313,4 +350,18 @@ function _visitNode(node, callback) {
}
}
// Timers
//
// React Native's timers won't run while the app is in the background, this
// is a known limitation. Replace them with a background-friendly
// alternative.
//
// Required by:
// - lib-jitsi-meet
// - Strophe
global.clearTimeout = window.clearTimeout = BackgroundTimer.clearTimeout;
global.clearInterval = window.clearInterval = BackgroundTimer.clearInterval;
global.setInterval = window.setInterval = BackgroundTimer.setInterval;
global.setTimeout = window.setTimeout = BackgroundTimer.setTimeout;
})(global || window || this); // eslint-disable-line no-invalid-this

View File

@@ -8,7 +8,7 @@ import {
} from './actionTypes';
/**
* Initial state of 'features/base/lib-jitsi-meet'.
* The initial state of 'features/base/lib-jitsi-meet'.
*
* @type {{
* initializationError: null,
@@ -16,6 +16,14 @@ import {
* }}
*/
const INITIAL_STATE = {
/**
* The mandatory configuration to be passed to JitsiMeetJS#init(). The app
* will download config.js from the Jitsi Meet deployment and taks its
* values into account but the values bellow will be enforced (because they
* are essential to the correct execution of the application).
*
* @type {Object}
*/
config: {
// FIXME The support for audio levels in lib-jitsi-meet polls the
// statistics of WebRTC at a short interval multiple times a second.
@@ -47,7 +55,7 @@ ReducerRegistry.register(
case LIB_INIT_ERROR:
return {
...state,
initializationError: action.lib.error,
initializationError: action.error,
initialized: false
};
@@ -80,10 +88,12 @@ function _setConfig(state, action) {
return {
...state,
config: {
// The final config is the result of augmenting the default config
// with whatever the deployment has chosen to override/overwrite.
...INITIAL_STATE.config,
...action.config
...action.config,
// The config of INITIAL_STATE is meant to override the config
// downloaded from the Jitsi Meet deployment because the former
// contains values that are mandatory.
...INITIAL_STATE.config
}
};
}

View File

@@ -8,8 +8,6 @@ import {
SET_VIDEO_MUTED
} from './actionTypes';
import { CAMERA_FACING_MODE } from './constants';
import './middleware';
import './reducer';
/**
* Action to set the muted state of the local audio.

View File

@@ -3,3 +3,6 @@ export * from './actionTypes';
export * from './components';
export * from './constants';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -7,8 +7,6 @@ import {
PIN_PARTICIPANT
} from './actionTypes';
import { getLocalParticipant } from './functions';
import './middleware';
import './reducer';
/**
* Action to update a participant's email.

View File

@@ -2,3 +2,6 @@ export * from './actions';
export * from './actionTypes';
export * from './constants';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -1,24 +0,0 @@
import { shimStyles } from './shimStyles';
/**
* Create a style sheet using the provided style definitions.
*
* @param {Object} styles - A dictionary of named style definitions.
* @param {Object} [overrides={}] - Optional set of additional (often
* platform-dependent/specific) style definitions that will override the base
* (often platform-independent) styles.
* @returns {Object}
*/
export function createStyleSheet(styles, overrides = {}) {
const combinedStyles = {};
for (const k of Object.keys(styles)) {
combinedStyles[k]
= shimStyles({
...styles[k],
...overrides[k]
});
}
return combinedStyles;
}

View File

@@ -0,0 +1,96 @@
/* @flow */
import { Platform } from '../react';
import { ColorPalette } from './components';
declare type StyleSheet = Object;
/**
* The list of the well-known style properties which may not be numbers on Web
* but must be numbers on React Native.
*
* @private
*/
const _WELL_KNOWN_NUMBER_PROPERTIES = [ 'height', 'width' ];
/* eslint-disable flowtype/space-before-type-colon */
/**
* Create a style sheet using the provided style definitions.
*
* @param {StyleSheet} styles - A dictionary of named style definitions.
* @param {StyleSheet} [overrides={}] - Optional set of additional (often
* platform-dependent/specific) style definitions that will override the base
* (often platform-independent) styles.
* @returns {StyleSheet}
*/
export function createStyleSheet(styles: StyleSheet, overrides: StyleSheet = {})
: StyleSheet {
/* eslint-enable flowtype/space-before-type-colon */
const combinedStyles = {};
for (const k of Object.keys(styles)) {
combinedStyles[k]
= _shimStyles({
...styles[k],
...overrides[k]
});
}
return combinedStyles;
}
/**
* Works around a bug in react-native or react-native-webrtc on Android which
* causes Views overlaying RTCView to be clipped. Even though we (may) display
* multiple RTCViews, it is enough to apply the fix only to a View with a
* bounding rectangle containing all RTCviews and their overlaying Views.
*
* @param {StyleSheet} styles - An object which represents a stylesheet.
* @public
* @returns {StyleSheet}
*/
export function fixAndroidViewClipping<T: StyleSheet>(styles: T): T {
if (Platform.OS === 'android') {
styles.borderColor = ColorPalette.appBackground;
styles.borderWidth = 0.2;
}
return styles;
}
/**
* Shims style properties to work correctly on native. Allows us to minimize the
* number of style declarations that need to be set or overridden for specific
* platforms.
*
* @param {StyleSheet} styles - An object which represents a stylesheet.
* @private
* @returns {StyleSheet}
*/
function _shimStyles<T: StyleSheet>(styles: T): T {
// Certain style properties may not be numbers on Web but must be numbers on
// React Native. For example, height and width may be expressed in percent
// on Web but React Native will not understand them and we will get errors
// (at least during development). Convert such well-known properties to
// numbers if possible; otherwise, remove them to avoid runtime errors.
for (const k of _WELL_KNOWN_NUMBER_PROPERTIES) {
const v = styles[k];
const typeofV = typeof v;
if (typeofV !== 'undefined' && typeofV !== 'number') {
const numberV = Number(v);
if (Number.isNaN(numberV)) {
delete styles[k];
} else {
styles[k] = numberV;
}
}
}
return styles;
}

View File

@@ -1,2 +1,2 @@
export * from './components';
export * from './createStyleSheet';
export * from './functions';

View File

@@ -1,38 +0,0 @@
/**
* The list of the well-known style properties which may not be numbers on Web
* but must be numbers on React Native.
*/
const WELL_KNOWN_NUMBER_PROPERTIES = [ 'height', 'width' ];
/**
* Shim style properties to work correctly on native.
*
* Using this shimStyles method allows us to minimize the number of style
* declarations that need to be set or overridden for specific platforms.
*
* @param {Object} styles - A dictionary of named style definitions.
* @returns {Object}
*/
export function shimStyles(styles) {
// Certain style properties may not be numbers on Web but must be numbers on
// React Native. For example, height and width may be expressed in percent
// on Web but React Native will not understand them and we will get errors
// (at least during development). Convert such well-known properties to
// numbers if possible; otherwise, remove them to avoid runtime errors.
for (const k of WELL_KNOWN_NUMBER_PROPERTIES) {
const v = styles[k];
const typeofV = typeof v;
if (typeofV !== 'undefined' && typeofV !== 'number') {
const numberV = Number(v);
if (Number.isNaN(numberV)) {
delete styles[k];
} else {
styles[k] = numberV;
}
}
}
return styles;
}

View File

@@ -10,8 +10,6 @@ import {
TRACK_REMOVED,
TRACK_UPDATED
} from './actionTypes';
import './middleware';
import './reducer';
const JitsiTrackErrors = JitsiMeetJS.errors.track;
const JitsiTrackEvents = JitsiMeetJS.events.track;

View File

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

View File

@@ -1,4 +1,8 @@
import { ColorPalette, createStyleSheet } from '../../base/styles';
import {
ColorPalette,
createStyleSheet,
fixAndroidViewClipping
} from '../../base/styles';
/**
* The style of the conference UI (component).
@@ -15,11 +19,11 @@ export const styles = createStyleSheet({
/**
* Conference style.
*/
conference: {
conference: fixAndroidViewClipping({
alignSelf: 'stretch',
backgroundColor: ColorPalette.appBackground,
flex: 1
},
}),
/**
* ParticipantView style

View File

@@ -3,6 +3,7 @@
import { StatusBar } from 'react-native';
import { Immersive } from 'react-native-immersive';
import { APP_STATE_CHANGED } from '../background';
import {
CONFERENCE_FAILED,
CONFERENCE_LEFT,
@@ -23,9 +24,20 @@ import { MiddlewareRegistry } from '../base/redux';
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
let fullScreen;
let fullScreen = null;
switch (action.type) {
case APP_STATE_CHANGED: {
// Check if we just came back from the background and reenable full
// screen mode if necessary.
if (action.appState === 'active') {
const conference = store.getState()['features/base/conference'];
fullScreen = conference ? !conference.audioOnly : false;
}
break;
}
case CONFERENCE_WILL_JOIN: {
const conference = store.getState()['features/base/conference'];
@@ -37,10 +49,6 @@ MiddlewareRegistry.register(store => next => action => {
case CONFERENCE_LEFT:
fullScreen = false;
break;
default:
fullScreen = null;
break;
}
if (fullScreen !== null) {

View File

@@ -6,8 +6,6 @@ import {
} from '../base/tracks';
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
import './middleware';
import './reducer';
/**
* Signals conference to select a participant.

View File

@@ -1,2 +1,5 @@
export * from './actions';
export * from './components';
import './middleware';
import './reducer';

View File

@@ -1,7 +1,6 @@
import { setPassword } from '../base/conference';
import { BEGIN_ROOM_LOCK_REQUEST, END_ROOM_LOCK_REQUEST } from './actionTypes';
import './reducer';
/**
* Begins a (user) request to lock a specific conference/room.

View File

@@ -1,2 +1,4 @@
export * from './actions';
export * from './components';
import './reducer';

View File

@@ -1,5 +1,4 @@
import { DISMISS_MOBILE_APP_PROMO } from './actionTypes';
import './reducer';
/**
* Returns a Redux action which signals that the UnsupportedMobileBrowser which

View File

@@ -1,2 +1,4 @@
export * from './actions';
export * from './components';
import './reducer';

View File

@@ -1,4 +1,9 @@
import { BoxModel, ColorPalette, createStyleSheet } from '../../base/styles';
import {
BoxModel,
ColorPalette,
createStyleSheet,
fixAndroidViewClipping
} from '../../base/styles';
/**
* The default color of text on the WelcomePage.
@@ -35,11 +40,11 @@ export const styles = createStyleSheet({
/**
* The style of the top-level container of WelcomePage.
*/
container: {
container: fixAndroidViewClipping({
alignSelf: 'stretch',
backgroundColor: ColorPalette.blue,
flex: 1
},
}),
/**
* The style of the legal-related content such as (hyper)links to Privacy

View File

@@ -153,5 +153,10 @@ export default {
/**
* Indicates that a password is required for the call.
*/
PASSWORD_REQUIRED: "UI.password_required"
PASSWORD_REQUIRED: "UI.password_required",
/**
* Show custom popup/tooltip for a specified button.
*/
SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP: "UI.show_custom_toolbar_button_popup"
};