Compare commits

...

101 Commits

Author SHA1 Message Date
hristoterezov
9fa1b210f5 feat(loadScript): Use the function from js-utils 2018-03-22 13:15:14 -05:00
Paweł Domas
15e1633d86 fix(config): no legacy over non-legacy override (#2644)
Do not take legacy property into account if there's non-legacy value.
2018-03-22 10:57:08 -05:00
Daniel Ornelas
de0a7bfcd3 Some improvements for handling completion of transitions. Fixed a wrong conferenceEnded value when user left the conversation. 2018-03-22 00:04:25 -04:00
Daniel Ornelas
5858859838 Addressing feedback from PR 2018-03-22 00:04:25 -04:00
Daniel Ornelas
8428dd95c2 Implement Exit PiP mode in PiPWindow 2018-03-21 22:00:43 -04:00
Daniel Ornelas
a26ccf195e Added initialization of JitsiMeetManager components 2018-03-21 22:00:43 -04:00
Daniel Ornelas
0a5f60c637 Refactor PiP code into its own components 2018-03-21 22:00:43 -04:00
Daniel Ornelas
f8163de765 Implement Jitsi meet presentation interface that supports custom PiP solution 2018-03-21 22:00:43 -04:00
Daniel Ornelas
181580871f Initial work for testing PiP mode 2018-03-21 22:00:43 -04:00
damencho
ea431ca90c Adds new text for Firefox screen-sharing permission denied error. 2018-03-21 18:06:17 -05:00
damencho
a902540167 Removes Firefox extension handle (removed in FF newer than latest ESR). 2018-03-21 18:06:17 -05:00
virtuacoplenny
823481dc1d feat(recording): use google api to get stream key (#2481)
* feat(recording): use google api to get stream key

* squash: renaming pass

* squash: return full load promise

* sqush: use google api state enum

* squash: workaround for lib not loading

* another new design...

* increase timeout workaround for gapi load issue

* styling pass

* tweak copy

* squash: auto select first broadcast
2018-03-21 11:26:52 -07:00
paweldomas
b5b99301ca fix: synchronize global 'config' var with the reducer's state 2018-03-20 21:07:45 -04:00
paweldomas
ef52f09910 ref(config): override config from URL on mobile
Moves the things around to be able to override the config with the URL
params specified in the hash part of the location URI to which the app
is navigating to.
2018-03-20 21:07:45 -04:00
Leonard Kim
6a066c3079 squash: update package-lock again for integrity shas 2018-03-20 16:06:50 -05:00
Leonard Kim
71e368f70e squash: override atlaskit styles for dropdowns to display clearer 2018-03-20 16:06:50 -05:00
Leonard Kim
134ff71c78 feat(modals): use dark theme 2018-03-20 16:06:50 -05:00
bbaldino
fef1d8b520 add a prosody module to insert identity information (when available) … (#2627)
* add a prosody module to insert identity information (when available) into
presence

prosody will check for jitsi_meet_context_user and
jitsi_meet_context_group in the session and, if they are present, insert
them into presence (we do this in prosody so they cannot be spoofed).

* remove unused 'presence' variable

* refactor to modify presence message in place

* make object member access consistent

* make the group information optional
2018-03-20 15:27:39 -05:00
virtuacoplenny
309fcf9672 fix(filmstrip): fix local video alignment with no invite button (#2629)
Hardcoding an offset from the bottom of 32px causes issues in
horizontal filmstrip when there is no invite button, because
then the local video just displays 32px from the bottom as there
is no button to take up space above it. Instead leverage flex
alignments to align the bottom of the video to the bototm of
the filmstrip.
2018-03-20 12:28:09 -07:00
virtuacoplenny
2eafbacdd4 chore(deps): update lib-jitsi-meet to 0503ec4d for edge p2p progress (#2631) 2018-03-20 11:34:57 -07:00
virtuacoplenny
26ea667170 Merge pull request #2628 from jitsi/welcome_page_analytics
fix(analytics): Init analytics for the web welcome page.
2018-03-20 10:02:07 -07:00
jitsi-pootle
5f43e8f8c7 New files added from translate.jitsi.org based on templates 2018-03-20 15:52:21 +00:00
Shikhar Agnihotri
9ef83702cf Update CONTRIBUTING.md (#2601) 2018-03-20 09:49:35 -05:00
hristoterezov
fda2548a38 fix(analytics): Init analytics for the web welcome page. 2018-03-19 17:51:22 -05:00
Дамян Минков
eb53944a4d Adds poltergeist support for locked rooms. (#2626) 2018-03-19 16:20:44 -05:00
Saumeya Katyal
2334eb9967 doc: Add dev server steps (#2610)
* doc: Add webpack-dev-server steps
2018-03-19 10:16:01 -05:00
Emil Ivov
04bd4a9038 Merge pull request #2617 from virtuacoplenny/lenny/info-dialog-again
fix(info): update copy text to find correct var
2018-03-15 12:16:20 -05:00
virtuacoplenny
eb8f34cee8 Merge pull request #2612 from jitsi/no_protocol_in_intent_uri
feat(UnsupportedMobileBrowser): do not include protocol in the intent
2018-03-15 10:01:31 -07:00
Leonard Kim
b9379f5996 fix(info): update copy text to find correct var 2018-03-15 09:42:39 -07:00
paweldomas
40d7d0c9cb feat(UnsupportedMobileBrowser): do not include protocol in the intent
Do not include the protocol part in the intent URL.
2018-03-14 17:44:32 -05:00
zbettenbuk
357f173e85 Remove obsolate PlatformElements.native.js 2018-03-13 18:04:17 -05:00
zbettenbuk
7da26042b3 Avoid asking for calendar permission on app start 2018-03-13 18:04:17 -05:00
zbettenbuk
c86c7beb24 Refactor i18n calendar formatter 2018-03-13 18:04:17 -05:00
zbettenbuk
1020a54a33 Add Android navigation bar 2018-03-13 18:04:17 -05:00
zbettenbuk
c84abd543e Add support for app link scheme 2018-03-13 18:04:16 -05:00
zbettenbuk
4b17c6f015 Add pull-to-refresh functionality 2018-03-13 18:04:16 -05:00
zbettenbuk
cb973b61aa Implement adaptive known domain list 2018-03-13 18:04:16 -05:00
zbettenbuk
b096622995 Unify recent and meeting lists 2018-03-13 18:04:16 -05:00
zbettenbuk
ae0bf876a8 Add conference notification 2018-03-13 18:04:16 -05:00
zbettenbuk
bba480f329 Add calendar-sync feature 2018-03-13 18:04:14 -05:00
paweldomas
4dbcaf851f flow(AbstractAudio): specific function types 2018-03-13 16:57:29 -05:00
paweldomas
04dff9059b ref(AudioOutputPreview): use Audio from base/media 2018-03-13 16:57:28 -05:00
paweldomas
26cd2f17f6 ref(chat): port incoming chat msg sound to react 2018-03-13 16:57:28 -05:00
paweldomas
60e03e3dec feat: add join/leave sounds on mobile
Adds base/sounds feature which allows other features to register a sound
source under specified id. A new SoundsCollection component will then
render corresponding HTMLAudioElement for each such sound. Once "setRef"
callback is called by the HTMLAudioElement, this element will be added
to the Redux store. When that happens sound can be played through the
new 'playSound' action which will call play() method on the stored
HTMLAudioElement instance.
2018-03-13 16:57:28 -05:00
virtuacoplenny
bfb45ed0e8 fix(large-video): do not try to show background on safari with webrtc (#2606)
The animation for toggling filmstrip visibility was lagging on
Safari. Even though the background video is set to hidden, it is
still causing issues. Setting the background to display none
instead does help but might interfere with animations. So instead
do the easy thing and re-use logic used for Firefox to not show
the background video.
2018-03-13 14:37:35 -07:00
virtuacoplenny
e325199075 fix(invite): prefix a + when faking the validation response (#2597)
Pre-existing logic made it so numbers were assumed as valid
if no validation url was specified. To be consistent with
the validation server, the faked number should include a
+ at the beginning.
2018-03-12 13:25:42 -07:00
virtuacoplenny
4e4713c3e2 feat(invite): be able to call numbers from the invite dialog (#2555)
* feat(invite): be able to call numbers from the invite dialog

The major changes:
- Remove DialOutDialog, its views, redux hooks, css, and images.
  Its main functionality has been moved into AddPeopleDialog.
- Modify the AppPeopleDialog styling a bit so it is wider.
- Add phone numbers to AddPeopleDialog search results. Phone
  numbers are validated in parallel with the request for people
  and then appended to the result. The validation includes
  an ajax to validate the number is recognized as dialable by
  the server. The trigger for the validation is essentially if
  the entered input is numbers only.
- AddPeopleDialog holds onto the full object representation of
  an item selected in MultiSelectAutocomplete. This is so
  selected items can be removed on successful invite, leaving
  only unsuccessful items.
- More granular error handling on invite so individual invitees
  can be removed from the selected items list.

* squash: change load state, new regex for numbers

* squash: change strings, auto prepend 1 if no country code, add reminders
2018-03-12 12:23:40 -07:00
Saúl Ibarra Corretgé
ff8386e931 debian: fix setting the auth domain certificates
In 94813bc0fd (diff-6e9552c9bd8e61c8f277c21220160234)
two local variables got removed (AUTH_KEY_FILE and AUTH_CRT_FILE), which are used by the sed command
below to configure the virtualhost for auth.
2018-03-11 16:05:14 -05:00
Leonard Kim
8f520086e5 fix(info): do not show dial in numbers without a room specified
For the static page an error message displays stating no room
was specified. On mobile for unsupported browsers, the dial in
info will not show.
2018-03-09 17:18:10 -06:00
Shuai Li
5cde674eff fix(android): webrtc progurd rule
The new libwebrtc.jar contains an extra unused class file, when proguard is enabled result in the following warning:

org.chromium.build.BuildHooksAndroidImpl: can't find superclass or interface org.chromium.build.BuildHooksAndroid
2018-03-09 12:29:49 -08:00
Lyubo Marinov
c018252eee [Android] Fix RuntimeException in RNImmersiveModule
java.lang.RuntimeException: Tried to access a JS module before the React instance was fully set up. Calls to ReactContext#getJSModule should only happen once initialize() has been called on your native module.
	at com.facebook.react.bridge.ReactContext.getJSModule(ReactContext.java:102)
	at com.rnimmersive.RNImmersiveModule.emitImmersiveStateChangeEvent(RNImmersiveModule.java:74)
	at org.jitsi.meet.sdk.JitsiMeetView.onWindowFocusChanged(JitsiMeetView.java:504)
	at android.view.View.dispatchWindowFocusChanged(View.java:10257)
	at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1193)
	at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1197)
	at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1197)
	at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1197)
	at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1197)
	at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1197)
	at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1197)
	at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:3602)
	at android.os.Handler.dispatchMessage(Handler.java:102)
	at android.os.Looper.loop(Looper.java:154)
	at android.app.ActivityThread.main(ActivityThread.java:6119)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
2018-03-09 13:50:07 -06:00
George Politis
c8cab1560c Merge pull request #2589 from jitsi/update-lib-jitsi-stats
Updates lib-jitsi-meet to d4b78721.
2018-03-09 12:44:53 -06:00
damencho
d218abfd97 Updates lib-jitsi-meet to d4b78721.
Implements the promised based getStats. Enables them for Safari and FF.
2018-03-09 12:03:01 -06:00
paweldomas
9e0fee6c7d fix(android): do not require java 8 target
Updates react-native-webrtc to get rid of Java 8 requirement for
the Android app.
2018-03-08 15:47:05 -06:00
damencho
5dca9e08f4 Bumps uglifyjs-webpack-plugin and its dependencies.
Solves a GitHub warning in lib-jitsi-meet about a vulnerability in a
uglifyjs-webpack-plugin dependency.
2018-03-08 15:12:15 -06:00
Lyubo Marinov
d3a1f7d4f7 [iOS] Fix uncaught NSInvalidArgumentException in RTCPeerConnection's createAnswer
WebRTC used to report createAnswer (and createOffer) NSError with key
"error". But now the key's called "NSLocalizedDescription".

Additionally, NSMutableDictionary doesn't accept nil.
2018-03-07 15:23:20 -06:00
Leonard Kim
80bdf908ca fix(info): always remove last part of path for meeting name 2018-03-06 15:22:27 -06:00
Leonard Kim
0d3b4eedf8 fix(info): make some text selectable for manual copying 2018-03-06 13:47:55 -06:00
Leonard Kim
824a8a8864 fix(info): respect path when linking to dial in page 2018-03-06 13:47:55 -06:00
virtuacoplenny
45c1438fe6 Merge pull request #2567 from jitsi/bgrozev-patch-1
doc: Add clarifying text to the top of the readme.
2018-03-06 11:29:25 -08:00
bgrozev
8caeabf6b4 doc: Add clarifying text to the top of the readme. 2018-03-06 11:45:11 -06:00
Leonard Kim
466561f99f fix(polyfills): implement createHTMLDocument for jquery 2018-03-06 10:46:53 -06:00
zbettenbuk
8dc866fab3 Fix setTimeout polyfill 2018-03-06 09:42:25 +01:00
George Politis
1c8b8e031b feat: Whitelists the gatherStats option. (#2561)
* feat: Whitelists the gatherStats option.

* doc: Replaces obsolete disableStats option with gatherStats.
2018-03-05 11:12:39 -06:00
Lyubo Marinov
796489dc77 Coding style: naming, consistency 2018-03-04 19:28:44 -06:00
Lyubo Marinov
a30412ba65 [RN] Automatically dispatch CONFERENCE_LEFT 2018-03-04 19:28:44 -06:00
Saúl Ibarra Corretgé
8b35ea8ad5 deps: update jquery version (#2441)
* deps: update jquery version

* squash: resize thumbnails after appending shared thumb

This forces jquery animate to show the thumbnail somehow...
Remote thumbnails basically work this way (append to filmstrip
and then resize filmstrip thumbnails) so I just copied that
implementation. ... So I admit I lost this fight because
even after looking at jquery I couldn't understand why
it doesn't work on the first resize but does on the second.
Plus I'm being put on a strict timebox to update jquery.

* squash: getJSON no longer supports .success
2018-03-02 21:20:47 -06:00
hristoterezov
e05f2a9027 chore(package.json): Update lib-jitsi-meet. 2018-03-02 21:19:16 -06:00
George Politis
0d5cc8898d chore(lib-jitsi-meet): Update version. 2018-03-02 16:33:04 -06:00
virtuacoplenny
7672a88990 fix(dial-in): remove console.warn used for debugging (#2547)
Looks like this guy snuck into the commit.
2018-03-01 15:50:23 -08:00
virtuacoplenny
5a45b52881 fix(hangup): destroy local tracks on conference leave (#2546)
The difference from this change and 88325ae is there is no
attempt to do this in redux. This is the safer change in that
the cleanup logic is known only to trigger on hangup.
2018-03-01 15:47:46 -08:00
Leonard Kim
ce6e8472f0 Revert "fix(hangup): destroy local tracks on conference leave (#2502)"
This reverts commit 88325aeef2.
Turns out a conference with a password triggers a failed conference
join. It's going to be tricky to decipher when to do actual
cleanup, and where to shove that code, so reverting is easier for
now.
2018-03-01 14:07:30 -06:00
Leonard Kim
b00aaf1de7 fix(dial-in): specify url root for more numbers link
This gets around the issue of a cdn page being opened.
2018-02-28 23:17:20 -06:00
Lyubo Marinov
e622829c1c [RN] Promised-base RTCPeerConnection API
Recent changes in lib-jitsi-meet probably led to (1) our
RTCPeerConnection customizations on react-native not being used which is
a problem because we need them for at least NAT64 on iOS in order to
pass the review in Apple's App Store and (2) unexpected exceptions
inside react-native-webrtc.

The Promise-based WebRTC API should be merged from react-native-webrtc's
upstream but I don't want to do it right now because last time we got
multiple bugs in addition.
2018-02-28 17:17:08 -06:00
Lyubo Marinov
037e7f59b0 [RN] Fix a TypeError when invoking humanize() on undefined 2018-02-28 17:17:07 -06:00
hristoterezov
df754f4f41 fix(reload): Preserve URL params on reload/redirect. 2018-02-28 14:28:56 -06:00
George Politis
fd0749000e feat: Whitelists the iceTransportPolicy option. (#2535) 2018-02-28 11:28:47 -08:00
Lyubo Marinov
d727ee80b2 [RN] Fix base/profile and recent-list bugs 2018-02-27 20:52:34 -06:00
zbettenbuk
e4da0e988e Split storage keys to features 2018-02-27 07:33:33 -06:00
zbettenbuk
1474304cc5 Add the ability to do partial updates on the flatten profile object 2018-02-27 07:33:33 -06:00
zbettenbuk
d02ab2c641 Flatten the store of the profile feature 2018-02-27 07:33:33 -06:00
zbettenbuk
0e07020d09 Flatten the store of the recent-list feature 2018-02-27 07:33:33 -06:00
zbettenbuk
e0deb6d64b Prepare PersistenceRegistry for flat subtrees 2018-02-27 07:33:33 -06:00
virtuacoplenny
88325aeef2 fix(hangup): destroy local tracks on conference leave (#2502)
Destroy local tracks and also destroy large video so the
user does not wonder why camera (and mic) are still enabled
even though hangup has been pressed.
2018-02-26 21:10:49 -08:00
Lyubo Marinov
9f69c4d730 Grow features/settings from features/app-settings and features/settings-menu 2018-02-26 19:19:01 -06:00
zbettenbuk
e23d4317eb Add hint box with dynamic join button 2018-02-26 18:39:48 -06:00
zbettenbuk
547ddee3a5 Dismiss keyboard on menu open 2018-02-26 18:39:48 -06:00
zbettenbuk
3cf9fd439b Add iOS 10 compatibility header padding 2018-02-26 18:39:48 -06:00
zbettenbuk
7cd40353e7 Remove android view clipping fix from welcome page 2018-02-26 18:39:48 -06:00
zbettenbuk
04690dfc8f Facelift Welcome screen 2018-02-26 18:39:48 -06:00
zbettenbuk
9a9890f86c Introduce SafeArea for Settings and Header 2018-02-26 18:35:13 -06:00
Leonard Kim
9b04a7852a fix(welcome-page): send analytics on join 2018-02-26 18:41:47 -05:00
Emil Ivov
df9d17ba18 Merge pull request #2523 from bgrozev/whitelist-ss-fps
config: Whitelists the desktopSharingFrameRate config key.
2018-02-26 13:18:20 -06:00
Boris Grozev
5d0ac7653d config: Whitelists the desktopSharingFrameRate config key. 2018-02-26 11:45:42 -06:00
virtuacoplenny
1ef2e2ee7e Merge pull request #2503 from jitsi/ss_fps
feat(ss_framerate): Add config option for min/max frame rate.
2018-02-23 12:28:48 -08:00
hristoterezov
b3431ab3e7 chore(lib-jitsi-meet): Update version. 2018-02-23 13:38:41 -06:00
Leonard Kim
6fbe78eb34 fix(welcome-page): change font family and spacing 2018-02-23 11:35:02 -06:00
Lyubo Marinov
b8de5bbfc3 [RN] Add Picture-in-Picture support (Coding style: naming, consistency) 2018-02-23 11:21:26 -06:00
Saúl Ibarra Corretgé
b3683068d4 [RN] Add Picture-in-Picture support
This only works automatically on Android >= 8. On other platforms / versions, it
relies on the SDK user on implementing a "reduced UI" mode and reacting to the
"request PIP" delegate method.
2018-02-23 11:21:25 -06:00
Lyubo Marinov
94473e5660 [Android] Allow accessing react-native's in-app developer menu (in the emulator) 2018-02-23 11:21:25 -06:00
hristoterezov
2d9b906a3b feat(ss_framerate): Add config option for min/max frame rate. 2018-02-22 19:37:17 -06:00
251 changed files with 11897 additions and 4408 deletions

View File

@@ -18,6 +18,7 @@
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
; seen to cause errors and we have chosen not to fix.
.*/node_modules/@atlaskit/.*/*.js.flow
.*/node_modules/@atlassian
.*/node_modules/bower/lib/node_modules/bower-json/test/.*
.*/node_modules/immutable/dist/immutable.js.flow

View File

@@ -21,7 +21,7 @@ license agreement as either a [corporation](https://jitsi.org/ccla) or an
in the agreement, unfortunately, we cannot accept your contribution.
## Creating Pull Requests
- Make sure your code passes the linter rules beforehand. The linter is exeuted
- Make sure your code passes the linter rules beforehand. The linter is executed
automatically when committing code.
- Perform **one** logical change per pull request.
- Maintain a clean list of commits, squash them if necessary.

View File

@@ -1,13 +1,15 @@
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the session #482 of the VoIP Users Conference.
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. You can see Jitsi Meet in action [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
You can also try it out yourself at https://meet.jit.si .
The Jitsi Meet client runs in your browser, without the need for installing anything on your computer. You can also try it out yourself at https://meet.jit.si .
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad.
## Installation
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing the Jitsi Meet suite on your server and hosting your own conferencing service.
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system.
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
@@ -88,6 +90,19 @@ cd jitsi-meet
npm unlink lib-jitsi-meet
npm install
```
## Running with webpack-dev-server for development
Use it at the CLI, type
```
node_modules/.bin/webpack-dev-server
```
By default the backend deployment used is `beta.meet.jit.si`, you can point the Jitsi-Meet app at a different backend by using a proxy server. To do this set the WEBPACK_DEV_SERVER_PROXY_TARGET variable, type
```
WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com node_modules/.bin/webpack-dev-server
```
The app should be running at https://localhost:8080/
## Contributing

View File

@@ -43,16 +43,6 @@ Add the Maven repository
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
dependency `org.jitsi.react:jitsi-meet-sdk:1.9.0` into your `build.gradle`.
Add Java 1.8 compatibility support to your project by adding the following lines
into your `build.gradle` file:
```
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
```
## API
Jitsi Meet SDK is an Android library which embodies the whole Jitsi Meet
@@ -117,19 +107,19 @@ public class MainActivity extends AppCompatActivity {
JitsiMeetView.onNewIntent(intent);
}
@Override
protected void onPause() {
super.onPause();
JitsiMeetView.onHostPause(this);
}
@Override
protected void onResume() {
super.onResume();
JitsiMeetView.onHostResume(this);
}
@Override
protected void onStop() {
super.onStop();
JitsiMeetView.onHostPause(this);
}
}
```
@@ -142,6 +132,10 @@ which displays a single `JitsiMeetView`.
See JitsiMeetView.getDefaultURL.
#### getPictureInPictureEnabled()
See JitsiMeetView.getPictureInPictureEnabled.
#### getWelcomePageEnabled()
See JitsiMeetView.getWelcomePageEnabled.
@@ -154,6 +148,10 @@ See JitsiMeetView.loadURL.
See JitsiMeetView.setDefaultURL.
#### setPictureInPictureEnabled(boolean)
See JitsiMeetView.setPictureInPictureEnabled.
#### setWelcomePageEnabled(boolean)
See JitsiMeetView.setWelcomePageEnabled.
@@ -179,6 +177,12 @@ if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
Returns the `JitsiMeetViewListener` instance attached to the view.
#### getPictureInPictureEnabled()
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
#### getWelcomePageEnabled()
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
@@ -220,19 +224,30 @@ view.loadURLObject(urlObject);
Sets the default URL. See `getDefaultURL` for more information.
NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
NOTE: Must be called before (if at all) `loadURL`/`loadURLString` for it to take
effect.
#### setListener(listener)
Sets the given listener (class implementing the `JitsiMeetViewListener`
interface) on the view.
#### setPictureInPictureEnabled(boolean)
Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
automatically enables/disables Picture-in-Picture based on native platform
support.
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
effect.
#### setWelcomePageEnabled(boolean)
Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
information.
NOTE: Must be called before `loadURL`/`loadURLString` for it to take effect.
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
effect.
#### onBackPressed()
@@ -257,7 +272,8 @@ This is a static method.
#### onHostResume(activity)
Helper method which should be called from the activity's `onResume` method.
Helper method which should be called from the activity's `onResume` or `onStop`
method.
This is a static method.
@@ -269,6 +285,13 @@ activity's `onNewIntent` method.
This is a static method.
#### onUserLeaveHint()
Helper method for integrating automatic Picture-in-Picture. It should be called
from the activity's `onUserLeaveHint` method.
This is a static method.
#### JitsiMeetViewListener
`JitsiMeetViewListener` provides an interface apps can implement to listen to
@@ -379,9 +402,19 @@ rules file:
# WebRTC
-keep class org.webrtc.** { *; }
-dontwarn org.chromium.build.BuildHooksAndroid
# Jisti Meet SDK
-keep class org.jitsi.meet.sdk.** { *; }
```
## Picture-in-Picture
`JitsiMeetView` will automatically adjust its UI when presented in a
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
"full" UI.
Jitsi Meet SDK automatically enables (unless explicitly disabled by a
`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
mode iff the platform is supported i.e. Android >= Oreo.

View File

@@ -31,10 +31,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {

View File

@@ -7,10 +7,12 @@
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:name=".MainActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -23,6 +23,8 @@ import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import com.calendarevents.CalendarEventsPackage;
import java.util.Map;
/**
@@ -95,11 +97,18 @@ public class MainActivity extends JitsiMeetActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
// want the Welcome page to be enabled. It defaults to disabled in the
// SDK at the time of this writing but it is clearer to be explicit
// about what we want anyway.
// want to enable some options.
// The welcome page defaults to disabled in the SDK at the time of this
// writing but it is clearer to be explicit about what we want anyway.
setWelcomePageEnabled(true);
super.onCreate(savedInstanceState);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
CalendarEventsPackage.onRequestPermissionsResult(requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

View File

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

View File

@@ -29,8 +29,10 @@ dependencies {
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-locale-detector')
compile project(':react-native-sound')
compile project(':react-native-vector-icons')
compile project(':react-native-webrtc')
compile project(':react-native-calendar-events')
}
// Build process helpers

View File

@@ -17,31 +17,32 @@
package org.jitsi.meet.sdk;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import java.net.URL;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
/**
* Base Activity for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JKConferenceView} and
* contains all the required wiring between the {@code JitsiMeetView} and
* the Activity lifecycle methods already implemented.
*
* In this activity we use a single {@code JKConferenceView} instance. This
* In this activity we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifetime methods associated with this Activity are
* hooked to the React Native subsystem via proxy calls through the
* {@code JKConferenceView} static methods.
* {@code JitsiMeetView} static methods.
*/
public class JitsiMeetActivity
extends AppCompatActivity {
public class JitsiMeetActivity extends AppCompatActivity {
/**
* The request code identifying requests for the permission to draw on top
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
@@ -67,6 +68,12 @@ public class JitsiMeetActivity
*/
private JitsiMeetView view;
/**
* Whether Picture-in-Picture is enabled. The value is used only while
* {@link #view} equals {@code null}.
*/
private Boolean pictureInPictureEnabled;
/**
* Whether the Welcome page is enabled. The value is used only while
* {@link #view} equals {@code null}.
@@ -89,6 +96,17 @@ public class JitsiMeetActivity
return view == null ? defaultURL : view.getDefaultURL();
}
/**
*
* @see JitsiMeetView#getPictureInPictureEnabled()
*/
public boolean getPictureInPictureEnabled() {
return
view == null
? pictureInPictureEnabled
: view.getPictureInPictureEnabled();
}
/**
*
* @see JitsiMeetView#getWelcomePageEnabled()
@@ -121,6 +139,10 @@ public class JitsiMeetActivity
// XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
// is documented to need such an order in order to take effect:
view.setDefaultURL(defaultURL);
if (pictureInPictureEnabled != null) {
view.setPictureInPictureEnabled(
pictureInPictureEnabled.booleanValue());
}
view.setWelcomePageEnabled(welcomePageEnabled);
view.loadURL(null);
@@ -199,17 +221,26 @@ public class JitsiMeetActivity
JitsiMeetView.onHostDestroy(this);
}
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@Override
public void onNewIntent(Intent intent) {
JitsiMeetView.onNewIntent(intent);
public boolean onKeyUp(int keyCode, KeyEvent event) {
ReactInstanceManager reactInstanceManager;
if (!super.onKeyUp(keyCode, event)
&& BuildConfig.DEBUG
&& (reactInstanceManager
= JitsiMeetView.getReactInstanceManager())
!= null
&& keyCode == KeyEvent.KEYCODE_MENU) {
reactInstanceManager.showDevOptionsDialog();
return true;
}
return false;
}
@Override
protected void onPause() {
super.onPause();
JitsiMeetView.onHostPause(this);
defaultBackButtonImpl = null;
public void onNewIntent(Intent intent) {
JitsiMeetView.onNewIntent(intent);
}
@Override
@@ -220,6 +251,19 @@ public class JitsiMeetActivity
JitsiMeetView.onHostResume(this, defaultBackButtonImpl);
}
@Override
public void onStop() {
super.onStop();
JitsiMeetView.onHostPause(this);
defaultBackButtonImpl = null;
}
@Override
protected void onUserLeaveHint() {
JitsiMeetView.onUserLeaveHint();
}
/**
*
* @see JitsiMeetView#setDefaultURL(URL)
@@ -232,6 +276,19 @@ public class JitsiMeetActivity
}
}
/**
*
* @see JitsiMeetView#setPictureInPictureEnabled(boolean)
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
if (view == null) {
this.pictureInPictureEnabled
= Boolean.valueOf(pictureInPictureEnabled);
} else {
view.setPictureInPictureEnabled(pictureInPictureEnabled);
}
}
/**
*
* @see JitsiMeetView#setWelcomePageEnabled(boolean)

View File

@@ -21,17 +21,22 @@ import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.FrameLayout;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.rnimmersive.RNImmersiveModule;
import java.net.URL;
@@ -49,6 +54,12 @@ public class JitsiMeetView extends FrameLayout {
*/
private static final int BACKGROUND_COLOR = 0xFF111111;
/**
* The {@link Log} tag which identifies the source of the log messages of
* {@code JitsiMeetView}.
*/
private final static String TAG = JitsiMeetView.class.getSimpleName();
/**
* React Native bridge. The instance manager allows embedding applications
* to create multiple root views off the same JavaScript bundle.
@@ -65,6 +76,7 @@ public class JitsiMeetView extends FrameLayout {
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new ExternalAPIModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext)
);
@@ -83,6 +95,11 @@ public class JitsiMeetView extends FrameLayout {
return null;
}
// XXX Strictly internal use only (at the time of this writing)!
static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager;
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
@@ -97,6 +114,7 @@ public class JitsiMeetView extends FrameLayout {
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.addPackage(new com.calendarevents.CalendarEventsPackage())
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
.addPackage(new com.facebook.react.shell.MainReactPackage())
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
@@ -105,6 +123,7 @@ public class JitsiMeetView extends FrameLayout {
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
.addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
.addPackage(new com.rnimmersive.RNImmersivePackage())
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
.addPackage(new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(
@@ -238,6 +257,38 @@ public class JitsiMeetView extends FrameLayout {
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onUserLeaveHint} so we can do the required internal
* processing.
*
* This is currently not mandatory.
*/
public static void onUserLeaveHint() {
sendEvent("onUserLeaveHint", null);
}
/**
* Helper function to send an event to JavaScript.
*
* @param eventName {@code String} containing the event name.
* @param params {@code WritableMap} optional ancillary data for the event.
*/
private static void sendEvent(
String eventName,
@Nullable WritableMap params) {
if (reactInstanceManager != null) {
ReactContext reactContext
= reactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
reactContext
.getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}
/**
* The default base {@code URL} used to join a conference when a partial URL
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
@@ -259,6 +310,13 @@ public class JitsiMeetView extends FrameLayout {
*/
private JitsiMeetViewListener listener;
/**
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
* {@code true} iff the Android platform supports Picture-in-Picture
* natively.
*/
private Boolean pictureInPictureEnabled;
/**
* React Native root view.
*/
@@ -323,6 +381,21 @@ public class JitsiMeetView extends FrameLayout {
return listener;
}
/**
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
* natively supported on Android API >= 26 (Oreo), so it should not be
* enabled on older platform versions.
*
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
* otherwise.
*/
public boolean getPictureInPictureEnabled() {
return
PictureInPictureModule.isPictureInPictureSupported()
&& (pictureInPictureEnabled == null
|| pictureInPictureEnabled.booleanValue());
}
/**
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
* page is rendered when this {@code JitsiMeetView} is not at a URL
@@ -364,12 +437,20 @@ public class JitsiMeetView extends FrameLayout {
if (defaultURL != null) {
props.putString("defaultURL", defaultURL.toString());
}
// externalAPIScope
props.putString("externalAPIScope", externalAPIScope);
// pictureInPictureEnabled
props.putBoolean(
"pictureInPictureEnabled",
getPictureInPictureEnabled());
// url
if (urlObject != null) {
props.putBundle("url", urlObject);
}
// welcomePageEnabled
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
@@ -387,7 +468,9 @@ public class JitsiMeetView extends FrameLayout {
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
reactInstanceManager, "App", props);
reactInstanceManager,
"App",
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
@@ -426,10 +509,29 @@ public class JitsiMeetView extends FrameLayout {
super.onWindowFocusChanged(hasFocus);
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
// FIXME The singleton pattern employed by RNImmersiveModule is not
// advisable because a react-native mobule is consumable only after its
// BaseJavaModule#initialize() has completed and here we have no
// knowledge of whether the precondition is really met.
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
if (hasFocus && immersive != null) {
immersive.emitImmersiveStateChangeEvent();
try {
immersive.emitImmersiveStateChangeEvent();
} catch (RuntimeException re) {
// FIXME I don't know how to check myself whether
// BaseJavaModule#initialize() has been invoked and thus
// RNImmersiveModule is consumable. A safe workaround is to
// swallow the failure because the whole full-screen/immersive
// functionality is brittle anyway, akin to the icing on the
// cake, and has been working without onWindowFocusChanged for a
// very long time.
Log.e(
TAG,
"RNImmersiveModule#emitImmersiveStateChangeEvent() failed!",
re);
}
}
}
@@ -457,6 +559,18 @@ public class JitsiMeetView extends FrameLayout {
this.listener = listener;
}
/**
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
* natively supported only since certain platform versions, specifying
* {@code true} will have no effect on unsupported platform versions.
*
* @param pictureInPictureEnabled To enable Picture-in-Picture,
* {@code true}; otherwise, {@code false}.
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
this.pictureInPictureEnabled = Boolean.valueOf(pictureInPictureEnabled);
}
/**
* Sets whether the Welcome page is enabled. Must be called before
* {@link #loadURL(URL)} for it to take effect.

View File

@@ -0,0 +1,67 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Log;
import android.util.Rational;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class PictureInPictureModule extends ReactContextBaseJavaModule {
private final static String TAG = "PictureInPicture";
static boolean isPictureInPictureSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public PictureInPictureModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Enters Picture-in-Picture (mode) for the current {@link Activity}.
* Supported on Android API >= 26 (Oreo) only.
*
* @param promise a {@code Promise} which will resolve with a {@code null}
* value upon success, and an {@link Exception} otherwise.
*/
@ReactMethod
public void enterPictureInPicture(Promise promise) {
if (isPictureInPictureSupported()) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(new Exception("No current Activity!"));
return;
}
Log.d(TAG, "Entering Picture-in-Picture");
PictureInPictureParams.Builder builder
= new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 1));
boolean r
= currentActivity.enterPictureInPictureMode(builder.build());
if (r) {
promise.resolve(null);
} else {
promise.reject(
new Exception("Failed to enter Picture-in-Picture"));
}
return;
}
promise.reject(new Exception("Picture-in-Picture not supported"));
}
@Override
public String getName() {
return TAG;
}
}

View File

@@ -11,7 +11,11 @@ include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-locale-detector'
project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
include ':react-native-sound'
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
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-calendar-events'
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')

View File

@@ -7,7 +7,7 @@ import Recorder from './modules/recorder/Recorder';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
import { reload, reportError } from './modules/util/helpers';
import { reportError } from './modules/util/helpers';
import * as RemoteControlEvents
from './service/remotecontrol/RemoteControlEvents';
@@ -24,6 +24,10 @@ import {
initAnalytics,
sendAnalytics
} from './react/features/analytics';
import {
redirectWithStoredParams,
reloadWithStoredParams
} from './react/features/app';
import EventEmitter from 'events';
@@ -216,7 +220,7 @@ function maybeRedirectToWelcomePage(options) {
// save whether current user is guest or not, before navigating
// to close page
window.sessionStorage.setItem('guest', isGuest);
assignWindowLocationPathname(`static/${
redirectToStaticPage(`static/${
options.feedbackSubmitted ? 'close.html' : 'close2.html'}`);
return;
@@ -234,7 +238,7 @@ function maybeRedirectToWelcomePage(options) {
if (config.enableWelcomePage) {
setTimeout(
() => {
assignWindowLocationPathname('./');
APP.store.dispatch(redirectWithStoredParams('/'));
},
3000);
}
@@ -250,7 +254,7 @@ function maybeRedirectToWelcomePage(options) {
* assigning it to window.location.pathname.
* @return {void}
*/
function assignWindowLocationPathname(pathname) {
function redirectToStaticPage(pathname) {
const windowLocation = window.location;
let newPathname = pathname;
@@ -310,7 +314,7 @@ class ConferenceConnector {
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
// let's show some auth not allowed page
assignWindowLocationPathname('static/authError.html');
redirectToStaticPage('static/authError.html');
break;
}
@@ -378,7 +382,7 @@ class ConferenceConnector {
break;
case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
reload();
APP.store.dispatch(reloadWithStoredParams());
break;
default:
@@ -1705,12 +1709,6 @@ export default {
}
);
return;
} else if (error.name === JitsiTrackErrors.FIREFOX_EXTENSION_NEEDED) {
APP.UI.showExtensionRequiredDialog(
config.desktopSharingFirefoxExtensionURL
);
return;
}
@@ -1723,8 +1721,23 @@ export default {
let titleKey;
if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
titleKey = 'dialog.screenSharingFailedToInstallTitle';
// in FF the only option for user is to deny access temporary or
// permanently and we only receive permission_denied
// we always show some info cause in case of permanently, no info
// shown will be bad experience
//
// TODO: detect interval between requesting permissions and received
// error, this way we can detect user interaction which will have
// longer delay
if (JitsiMeetJS.util.browser.isFirefox()) {
descriptionKey
= 'dialog.screenSharingFirefoxPermissionDeniedError';
titleKey = 'dialog.screenSharingFirefoxPermissionDeniedTitle';
} else {
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
titleKey = 'dialog.screenSharingFailedToInstallTitle';
}
} else {
descriptionKey = 'dialog.screenSharingFailedToInstall';
titleKey = 'dialog.screenSharingFailedToInstallTitle';
@@ -2641,6 +2654,7 @@ export default {
*/
hangup(requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.removeLocalMedia();
let requestFeedbackPromise;

View File

@@ -150,22 +150,14 @@ var config = {
// Required version of Chrome extension
desktopSharingChromeMinExtVersion: '0.1',
// The ID of the jidesha extension for Firefox. If null, we assume that no
// extension is required.
desktopSharingFirefoxExtId: null,
// Whether desktop sharing should be disabled on Firefox.
desktopSharingFirefoxDisabled: false,
// The maximum version of Firefox which requires a jidesha extension.
// Example: if set to 41, we will require the extension for Firefox versions
// up to and including 41. On Firefox 42 and higher, we will run without the
// extension.
// If set to -1, an extension will be required for all versions of Firefox.
desktopSharingFirefoxMaxVersionExtRequired: 51,
// The URL to the Firefox extension for desktop sharing.
desktopSharingFirefoxExtensionURL: null,
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
// desktopSharingFrameRate: {
// min: 5,
// max: 5
// },
// Try to start calls with screen-sharing instead of camera video.
// startScreenSharing: false,
@@ -239,8 +231,11 @@ var config = {
// Stats
//
// Whether to enable stats collection or not.
// disableStats: false,
// Whether to enable stats collection or not in the TraceablePeerConnection.
// This can be useful for debugging purposes (post-processing/analysis of
// the webrtc stats) as it is done in the jitsi-meet-torture bandwidth
// estimation tests.
// gatherStats: false,
// To enable sending statistics to callstats.io you must provide the
// Application ID and Secret.
@@ -324,7 +319,6 @@ var config = {
// userRegion: "asia"
}
// List of undocumented settings used in jitsi-meet
/**
alwaysVisibleToolbar
@@ -344,6 +338,7 @@ var config = {
etherpad_base
externalConnectUrl
firefox_fake_device
googleApiApplicationClientID
iAmRecorder
iAmSipGateway
peopleSearchQueryTypes

View File

@@ -135,7 +135,6 @@ h3 {
margin: 30px 0 0 0;
}
h4 {
color: #333;
font-size: 16px;
font-weight: bold;
line-height: 1.25;

View File

@@ -1,81 +0,0 @@
/**
* The dialog content element.
*/
.dial-out-content {
margin-top: 5px;
/**
* Wrap the contents in flex so items can be aligned on the same line.
*/
.form-control {
display: flex;
}
/**
* The style of the flag icon.
*/
.dial-out-flag-icon {
position: absolute;
left: 5px;
top: 50%;
transform: translate(0, -50%);
}
/**
* The style of the dial code element.
*/
.dial-out-code {
margin-bottom: 0;
padding-left: 25px;
}
/**
* The dial-out dialog error element.
*/
.dial-out-error {
color: $errorColor;
}
/**
* The style of the dial input element.
*/
.dial-out-input {
display: inline-block;
flex: 1;
margin-left: 5px;
}
/**
* Re-styling the default dropdown inside the dial-out-content.
*/
.dropdown {
position: relative;
width: 65px;
}
/**
* Re-styling the default form-control inside the dial-out-content.
*/
.form-control {
margin-bottom: 8px;
}
.dropdown {
position: relative;
input {
padding-left: 16px;
&:read-only {
color: inherit;
}
}
}
.dropdown-trigger-icon {
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
}
}

View File

@@ -64,7 +64,7 @@
* The local video identifier.
*/
&#filmstripLocalVideo {
bottom: 32px;
align-self: flex-end;
display: block;
/**

View File

@@ -1,35 +0,0 @@
.flag-icon-background {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
}
.flag-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
width: 1.33333333em;
line-height: 1em;
}
.flag-icon:before {
content: "\00a0";
}
.flag-icon-au {
background-image: url(../images/countries/au.svg);
}
.flag-icon-ca {
background-image: url(../images/countries/ca.svg);
}
.flag-icon-de {
background-image: url(../images/countries/de.svg);
}
.flag-icon-gb {
background-image: url(../images/countries/gb.svg);
}
.flag-icon-fr {
background-image: url(../images/countries/fr.svg);
}
.flag-icon-us {
background-image: url(../images/countries/us.svg);
}

View File

@@ -30,12 +30,21 @@
.icon-event_note:before {
content: "\e616";
}
.icon-menu:before {
content: "\e5d2";
}
.icon-navigate_before:before {
content: "\e408";
}
.icon-navigate_next:before {
content: "\e409";
}
.icon-public:before {
content: "\e80b";
}
.icon-restore:before {
content: "\e8b3";
}
.icon-timer:before {
content: "\e425";
}

View File

@@ -1,3 +1,81 @@
.recordingSpinner {
vertical-align: top;
}
.live-stream-dialog {
/**
* Set font-size to be consistent with Atlaskit FieldText.
*/
font-size: 14px;
.broadcast-dropdown,
.broadcast-dropdown-trigger {
text-align: left;
}
.form-footer {
text-align: right;
}
.live-stream-cta {
a {
cursor: pointer;
}
}
.google-api {
margin-top: 10px;
min-height: 36px;
text-align: center;
width: 100%;
}
/**
* The Google sign in button must follow Google's design guidelines.
* See: https://developers.google.com/identity/branding-guidelines
*/
.google-sign-in {
background-color: #4285f4;
border-radius: 2px;
cursor: pointer;
display: inline-flex;
font-family: Roboto, arial, sans-serif;
font-size: 14px;
padding: 1px;
.google-cta {
color: white;
display: inline-block;
/**
* Hack the line height for vertical centering of text.
*/
line-height: 32px;
margin: 0 15px;
}
.google-logo {
background-color: white;
border-radius: 2px;
display: inline-block;
padding: 8px;
height: 18px;
width: 18px;
}
}
.google-panel {
align-items: center;
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
padding-bottom: 10px;
}
.stream-key-form {
.helper-link {
display: inline-block;
cursor: pointer;
margin-top: 5px;
}
}
}

View File

@@ -157,5 +157,6 @@ $watermarkHeight: 74px;
* Welcome page variables.
*/
$welcomePageDescriptionColor: #fff;
$welcomePageFontFamily: inherit;
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
$welcomePageTitleColor: #fff;

View File

@@ -72,6 +72,7 @@
* vertical filmstrip layout.
*/
#filmstripLocalVideo {
align-self: initial;
bottom: 5px;
display: flex;
flex-direction: column-reverse;

View File

@@ -1,5 +1,5 @@
.welcome {
font-family: Helvetica;
font-family: $welcomePageFontFamily;
height: 100%;
overflow: auto;
position: relative;
@@ -16,9 +16,10 @@
.header-text {
display: flex;
flex-direction: column;
height: 286px;
justify-content: space-around;
margin-top: 120px;
margin-bottom: 20px;
min-height: 286px;
width: 645px;
}
@@ -27,6 +28,7 @@
font-size: 48px;
letter-spacing: -1px;
line-height: 58px;
margin-bottom: 20px;
}
.header-text-description {
@@ -47,7 +49,7 @@
#new_enter_room {
align-items: center;
display: flex;
margin-top: 25px;
margin-bottom: 20px;
position: relative;
z-index: 2;
@@ -66,7 +68,7 @@
.welcome.with-content {
.header {
height: 552px;
min-height: 552px;
}
.header-image {
left: -61px;

View File

@@ -28,11 +28,8 @@
@import 'font-awesome';
/* Fonts END */
@import 'flag-icon';
/* Modules BEGIN */
@import 'dial-out';
@import 'aui_reset';
@import 'base';
@import 'utils';

View File

@@ -101,7 +101,7 @@
}
&-content {
background-color: $auiDialogContentBg;
background-color: $auiDialogBg;
box-sizing: border-box;
color: $auiDialogColor;
font-size: em(14, 12);
@@ -126,6 +126,11 @@
}
}
.input-control {
background-color: $auiDialogContentBg;
color: $auiDialogColor;
}
.form-control:not(:last-child) {
border-bottom: 1px solid $auiBorderColor;
}
@@ -138,7 +143,6 @@
}
.modal-dialog-form {
color: $modalTextColor;
margin-top: 5px !important;
.input-control {
@@ -150,6 +154,14 @@
&-error {
margin-bottom: 8px;
}
/**
* Override Atlaskit dropdown styling when in a modal because the dropdown
* backgrounds clash with the modal backgrounds.
*/
.htclLc[data-role=droplistContent] {
border: 1px solid #455166;
}
}
.modal-dialog-footer {
font-size: $modalButtonFontSize;

View File

@@ -36,13 +36,12 @@
}
.desktop-picker-source {
color: $defaultDarkFontColor;
margin-top: 10px;
text-align: center;
&.is-selected {
.desktop-source-preview-image-container {
background: rgba(0, 0, 0, 0.1);
background: rgba(255,255,255,0.3);
border-radius: $borderRadius;
}
}

View File

@@ -1,6 +1,4 @@
.device-selection {
color: $feedbackInputTextColor;
.device-selectors {
font-size: 14px;
@@ -22,19 +20,15 @@
/* device-selector-trigger stylings attempt to mimic AtlasKit button */
.device-selector-trigger {
background-color: rgba(9, 30, 66, 0.04);
border-radius: 3px;
color: #505f79;
background-color: #0E1624;
border: 1px solid #455166;
border-radius: 5px;
display: flex;
height: 2.3em;
justify-content: space-between;
line-height: 2.3em;
overflow: hidden;
padding: 0 8px;
&:hover {
background-color: rgba(9,30,66,.08);
}
}
.device-selector-trigger-disabled {
.device-selector-trigger {
@@ -108,19 +102,21 @@
.audio-output-preview {
font-size: 14px;
margin-top: 10px;
a {
color: 4C9AFF;
cursor: pointer;
text-decoration: none;
}
}
.audio-input-preview {
background: #f4f5f7;
background: #7b7b7b;
border-radius: 5px;
height: 6px;
.audio-input-preview-level {
background: #0052cc;
background: #4C9AFF;
border-radius: 5px;
height: 100%;
-webkit-transition: width .1s ease-in-out;

View File

@@ -11,17 +11,11 @@
padding-left: 5px;
}
}
}
}
/**
* Styles the loading element in the MultiSelectAutocomplete.
*/
.autocomplete-loading {
justify-content: center;
display: flex;
min-width: 260px;
padding: 20px;
.add-telephone-icon {
transform: scaleX(-1);
}
}
}
/**

View File

@@ -59,11 +59,17 @@
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
}
.info-dialog-dial-in {
white-space: nowrap;
.conference-id,
.phone-number {
user-select: text;
}
}
.info-dialog-icon {
@@ -101,6 +107,10 @@
padding-left: 0;
}
.info-password-local {
user-select: text;
}
.conference-id {
margin-left: 5px;
}

View File

@@ -42,10 +42,6 @@
width: 55%;
}
.speaker-stats-item:nth-child(even) {
background: whitesmoke;
}
.speaker-stats-item__name,
.speaker-stats-item__time {
overflow: hidden;

View File

@@ -14,11 +14,11 @@ $sliderThumbBackground: #3572b0;
/**
* Buttons
*/
$buttonBackground: #f5f5f5;
$buttonHoverBackground: #e9e9e9;
$buttonBorder: #ccc;
$buttonHoverBorder: #999;
$buttonColor: #333;
$buttonBackground: #44A5FF;
$buttonHoverBackground: #2c4062;
$buttonBorder: transparent;
$buttonHoverBorder: transparent;
$buttonColor: #eceef1;
$buttonLightBackground: #f5f5f5;
$buttonLightHoverBackground: #e9e9e9;
@@ -47,10 +47,10 @@ $reloadProgressBarBg: #0074E0;
/**
* Dialog colors
**/
$auiDialogColor: #333;
$auiDialogBg: #f5f5f5;
$auiDialogContentBg: $baseLight;
$auiBorderColor: #ccc;
$auiDialogColor: #eceef1;
$auiDialogBg: #253858;
$auiDialogContentBg: #344563;
$auiBorderColor: #253858;
$dialogTitleFontWeight: 400;
$dialogErrorText: #344563;
@@ -58,7 +58,7 @@ $dialogErrorText: #344563;
* Inlay colors
**/
$inlayColorBg: lighten($defaultBackground, 20%);
$inlayBorderColor: lighten($auiDialogContentBg, 10%);
$inlayBorderColor: lighten($baseLight, 10%);
$inlayIconBg: #000;
$inlayIconColor: #fff;
$inlayFilmstripOnlyColor: #474747;

View File

@@ -5,7 +5,7 @@
width: auto;
&__title {
border-bottom: 1px solid $auiBorderColor;
border-bottom: 1px solid $inlayBorderColor;
color: $unsupportedBrowserTitleColor;
font-weight: 400;
letter-spacing: 0.5px;

View File

@@ -2,7 +2,6 @@
background-color: #fff;
height: 100vh;
overflow: auto;
padding: 35px 0;
position: relative;
width: 100vw;
@@ -14,7 +13,7 @@
color: $unsupportedBrowserTextColor;
margin: auto;
max-width: 40em;
padding-bottom: 40px;
padding: 35px 0 40px 0;
text-align: center;
width: 75%;

View File

@@ -125,8 +125,11 @@ case "$1" in
# echo for using all default values
echo | prosodyctl cert generate $JICOFO_AUTH_DOMAIN
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.key /etc/prosody/certs/$JICOFO_AUTH_DOMAIN.key
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt /etc/prosody/certs/$JICOFO_AUTH_DOMAIN.crt
AUTH_KEY_FILE="/etc/prosody/certs/$JICOFO_AUTH_DOMAIN.key"
AUTH_CRT_FILE="/etc/prosody/certs/$JICOFO_AUTH_DOMAIN.crt"
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.key $AUTH_KEY_FILE
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt $AUTH_CRT_FILE
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt /usr/local/share/ca-certificates/$JICOFO_AUTH_DOMAIN.crt
update-ca-certificates

Binary file not shown.

View File

@@ -12,8 +12,10 @@
<glyph unicode="&#xe1aa;" glyph-name="bluetooth" d="M550 328l-80 82v-162zM470 776v-162l80 82zM670 696l-184-184 184-184-244-242h-42v324l-196-196-60 60 238 238-238 238 60 60 196-196v324h42zM834 738c40-64 62-142 62-222 0-84-24-160-66-226l-50 50c26 52 42 110 42 172s-16 120-42 172zM608 512l98 98c12-30 20-64 20-98s-8-70-20-100z" />
<glyph unicode="&#xe310;" glyph-name="headset" d="M512 982c212 0 384-172 384-384v-300c0-70-58-128-128-128h-128v342h170v86c0 166-132 298-298 298s-298-132-298-298v-86h170v-342h-128c-70 0-128 58-128 128v300c0 212 172 384 384 384z" />
<glyph unicode="&#xe408;" glyph-name="navigate_before" d="M658 708l-196-196 196-196-60-60-256 256 256 256z" />
<glyph unicode="&#xe409;" glyph-name="navigate_next" d="M426 768l256-256-256-256-60 60 196 196-196 196z" />
<glyph unicode="&#xe425;" glyph-name="timer" d="M512 170c166 0 298 134 298 300s-132 298-298 298-298-132-298-298 132-300 298-300zM812 708c52-66 84-148 84-238 0-212-172-384-384-384s-384 172-384 384 172 384 384 384c90 0 174-34 240-86l60 62c22-18 42-38 60-60zM470 426v256h84v-256h-84zM640 982v-86h-256v86h256z" />
<glyph unicode="&#xe5c4;" glyph-name="arrow_back" d="M854 554v-84h-520l238-240-60-60-342 342 342 342 60-60-238-240h520z" />
<glyph unicode="&#xe5d2;" glyph-name="menu" d="M128 768h768v-86h-768v86zM128 470v84h768v-84h-768zM128 256v86h768v-86h-768z" />
<glyph unicode="&#xe5d4;" glyph-name="thumb-menu" d="M512 342c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 598c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 682c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
<glyph unicode="&#xe603;" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
<glyph unicode="&#xe613;" glyph-name="recDisable" horiz-adv-x="1140" d="M1123.444 1003.015c-23.593 26.481-64.131 28.989-90.74 5.395l-1008.269-893.436c-26.609-23.468-28.991-64.131-5.46-90.676 12.674-14.306 30.308-21.649 48.126-21.649 15.123 0 30.372 5.401 42.544 16.195l130.045 115.22c90.743-81.844 210.569-132.165 342.473-132.101 282.816 0.061 510.913 227.969 511.287 510.972 0.126 109.934-34.682 211.367-93.499 294.72l118.088 104.625c26.483 23.526 28.997 64.129 5.404 90.735zM944.422 513.818c0.128-200.922-161.896-363.201-362.509-362.952-87.56 0.123-167.573 31.151-230.061 82.569l331.277 293.509v-73.176c1.071-60.993 32.696-92.18 94.944-93.692 61.997 1.512 93.686 32.763 95.131 93.756v41.096h-72.227v-47.499c0.251-4.642-0.564-10.607-2.511-17.949-1.25-3.261-3.448-6.020-6.525-8.093-3.197-2.572-7.845-3.828-13.868-3.828-10.543 0.31-17.132 4.268-19.827 11.921-1.068 3.512-1.947 6.905-2.508 10.163-0.254 2.887-0.377 5.532-0.377 7.786v143.511l42.477 37.634c0.215-0.432 0.452-0.851 0.63-1.303 1.947-6.467 2.762-12.799 2.511-19.076v-36.772h72.227v30.121c-0.246 31.245-9.086 54.699-26.363 70.447l40.711 36.069c35.787-56.055 56.803-122.585 56.867-194.244zM239.795 395.47c-12.613 37.023-19.827 76.557-19.827 117.913-0.19 200.236 161.584 362.009 361.945 362.135 56.853 0 110.313-13.302 158.133-36.398l117.846 104.421c-79.444 50.952-173.758 80.817-275.292 80.948-283.377 0.181-511.354-227.729-511.789-511.675-0.126-79.567 18.636-154.679 51.137-221.882l117.848 104.538zM388.576 690.020h-97.514v-249.057l72.23 64.070v0.689h0.815l117.72 104.418c0 0.564 0.123 0.94 0.123 1.509 0.753 53.898-30.369 80.069-93.374 78.37zM405.959 625.517c1.942-2.767 3.074-6.469 3.323-11.112 0.312-4.452 0.438-9.6 0.438-15.246 0.251-10.916-0.689-19.83-2.949-26.985-2.952-7.594-10.983-11.357-24.159-11.357h-19.325v74.043h15.31c7.842 0 13.865-0.683 18.072-2.19 4.397-1.573 7.468-3.953 9.29-7.153z" />
@@ -21,6 +23,7 @@
<glyph unicode="&#xe616;" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
<glyph unicode="&#xe61d;" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
<glyph unicode="&#xe80b;" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
<glyph unicode="&#xe8b3;" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
<glyph unicode="&#xe901;" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
<glyph unicode="&#xe902;" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
<glyph unicode="&#xe903;" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,87 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M512 342h64v180l150 90-32 52-182-110v-212zM554 128c212 0 384 172 384 384s-172 384-384 384c-106 0-200-42-270-112l60-62c54 54 128 88 210 88 166 0 300-132 300-298s-134-298-300-298-298 132-298 298h128l-172 172-4-6-166-166h128c0-212 172-384 384-384z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"restore"
],
"defaultCode": 59571,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "history, restore",
"id": 385,
"order": 930,
"prevSize": 24,
"code": 59571,
"name": "restore"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 385
},
{
"icon": {
"paths": [
"M426 256l256 256-256 256-60-60 196-196-196-196z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"navigate_next"
],
"defaultCode": 58377,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "chevron_right, navigate_next",
"id": 153,
"order": 927,
"prevSize": 24,
"code": 58377,
"name": "navigate_next"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 0
},
{
"icon": {
"paths": [
"M128 256h768v86h-768v-86zM128 554v-84h768v84h-768zM128 768v-86h768v86h-768z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"menu"
],
"defaultCode": 58834,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "menu",
"id": 489,
"order": 926,
"prevSize": 24,
"code": 58834,
"name": "menu"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 1
},
{
"icon": {
"paths": [
@@ -24,9 +105,9 @@
"code": 58820,
"name": "arrow_back"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 45
"setIdx": 1,
"setId": 1,
"iconIdx": 2
},
{
"icon": {
@@ -51,9 +132,9 @@
"code": 58376,
"name": "navigate_before"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 152
"setIdx": 1,
"setId": 1,
"iconIdx": 3
},
{
"icon": {
@@ -80,7 +161,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 0
"iconIdx": 4
},
{
"icon": {
@@ -107,7 +188,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 1
"iconIdx": 5
},
{
"icon": {
@@ -127,14 +208,14 @@
"properties": {
"ligatures": "timer",
"id": 760,
"order": 916,
"order": 928,
"prevSize": 24,
"code": 58405,
"name": "timer"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 2
"iconIdx": 6
},
{
"icon": {
@@ -161,7 +242,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 3
"iconIdx": 7
},
{
"icon": {
@@ -188,7 +269,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 4
"iconIdx": 8
},
{
"icon": {
@@ -215,7 +296,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 5
"iconIdx": 9
},
{
"icon": {
@@ -242,7 +323,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 6
"iconIdx": 10
},
{
"icon": {
@@ -271,7 +352,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 7
"iconIdx": 11
},
{
"icon": {
@@ -298,7 +379,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 8
"iconIdx": 12
},
{
"icon": {
@@ -325,7 +406,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 9
"iconIdx": 13
},
{
"icon": {
@@ -354,7 +435,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 10
"iconIdx": 14
},
{
"icon": {
@@ -383,7 +464,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 11
"iconIdx": 15
},
{
"icon": {
@@ -412,7 +493,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 12
"iconIdx": 16
},
{
"icon": {
@@ -441,7 +522,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 13
"iconIdx": 17
},
{
"icon": {
@@ -470,7 +551,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 14
"iconIdx": 18
},
{
"icon": {
@@ -496,7 +577,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 15
"iconIdx": 19
},
{
"icon": {
@@ -522,7 +603,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 16
"iconIdx": 20
},
{
"icon": {
@@ -548,7 +629,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 17
"iconIdx": 21
},
{
"icon": {
@@ -574,7 +655,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 18
"iconIdx": 22
},
{
"icon": {
@@ -600,7 +681,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 19
"iconIdx": 23
},
{
"icon": {
@@ -626,7 +707,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 20
"iconIdx": 24
},
{
"icon": {
@@ -652,7 +733,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 21
"iconIdx": 25
},
{
"icon": {
@@ -678,7 +759,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 22
"iconIdx": 26
},
{
"icon": {
@@ -704,7 +785,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 23
"iconIdx": 27
},
{
"icon": {
@@ -730,7 +811,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 24
"iconIdx": 28
},
{
"icon": {
@@ -756,7 +837,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 25
"iconIdx": 29
},
{
"icon": {
@@ -782,7 +863,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 26
"iconIdx": 30
},
{
"icon": {
@@ -808,7 +889,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 27
"iconIdx": 31
},
{
"icon": {
@@ -834,7 +915,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 28
"iconIdx": 32
},
{
"icon": {
@@ -860,7 +941,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 29
"iconIdx": 33
},
{
"icon": {
@@ -886,7 +967,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 30
"iconIdx": 34
},
{
"icon": {
@@ -912,7 +993,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 31
"iconIdx": 35
},
{
"icon": {
@@ -938,7 +1019,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 32
"iconIdx": 36
},
{
"icon": {
@@ -964,7 +1045,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 33
"iconIdx": 37
},
{
"icon": {
@@ -990,7 +1071,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 34
"iconIdx": 38
},
{
"icon": {
@@ -1016,7 +1097,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 35
"iconIdx": 39
},
{
"icon": {
@@ -1042,7 +1123,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 36
"iconIdx": 40
},
{
"icon": {
@@ -1068,7 +1149,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 37
"iconIdx": 41
},
{
"icon": {
@@ -1094,7 +1175,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 38
"iconIdx": 42
},
{
"icon": {
@@ -1120,7 +1201,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 39
"iconIdx": 43
},
{
"icon": {
@@ -1146,7 +1227,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 40
"iconIdx": 44
},
{
"icon": {
@@ -1172,7 +1253,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 41
"iconIdx": 45
},
{
"icon": {
@@ -1198,7 +1279,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 42
"iconIdx": 46
},
{
"icon": {
@@ -1224,7 +1305,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 43
"iconIdx": 47
},
{
"icon": {
@@ -1253,7 +1334,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 44
"iconIdx": 48
},
{
"icon": {
@@ -1283,7 +1364,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 45
"iconIdx": 49
},
{
"icon": {
@@ -1313,7 +1394,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 46
"iconIdx": 50
},
{
"icon": {
@@ -1339,7 +1420,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 47
"iconIdx": 51
},
{
"icon": {
@@ -1365,7 +1446,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 48
"iconIdx": 52
},
{
"icon": {
@@ -1391,7 +1472,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 49
"iconIdx": 53
}
],
"height": 1024,

BIN
images/calendar@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/calendar@3x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g stroke-width="1pt">
<path fill="#006" d="M0 0h640v480H0z"/>
<path d="M0 0v27.95L307.037 250h38.647v-27.95L38.647 0H0zm345.684 0v27.95L38.647 250H0v-27.95L307.037 0h38.647z" fill="#fff"/>
<path d="M144.035 0v250h57.614V0h-57.615zM0 83.333v83.333h345.684V83.333H0z" fill="#fff"/>
<path d="M0 100v50h345.684v-50H0zM155.558 0v250h34.568V0h-34.568zM0 250l115.228-83.334h25.765L25.765 250H0zM0 0l115.228 83.333H89.463L0 18.633V0zm204.69 83.333L319.92 0h25.764L230.456 83.333H204.69zM345.685 250l-115.228-83.334h25.765l89.464 64.7V250z" fill="#c00"/>
<path d="M299.762 392.523l-43.653 3.795 6.013 43.406-30.187-31.764-30.186 31.764 6.014-43.406-43.653-3.795 37.68-22.364-24.244-36.495 40.97 15.514 13.42-41.713 13.42 41.712 40.97-15.515-24.242 36.494m224.444 62.372l-10.537-15.854 17.81 6.742 5.824-18.125 5.825 18.126 17.807-6.742-10.537 15.854 16.37 9.718-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m16.368-291.815l-10.537-15.856 17.81 6.742 5.824-18.122 5.825 18.12 17.807-6.74-10.537 15.855 16.37 9.717-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m-89.418 104.883l-10.537-15.853 17.808 6.742 5.825-18.125 5.825 18.125 17.808-6.742-10.536 15.853 16.37 9.72-18.965 1.65 2.615 18.85-13.117-13.795-13.117 13.795 2.617-18.85-18.964-1.65m216.212-37.929l-10.558-15.854 17.822 6.742 5.782-18.125 5.854 18.125 17.772-6.742-10.508 15.854 16.362 9.718-18.97 1.65 2.608 18.85-13.118-13.793-13.117 13.793 2.61-18.85-18.936-1.65m-22.251 73.394l-10.367 6.425 2.914-11.84-9.316-7.863 12.165-.896 4.605-11.29 4.606 11.29 12.165.897-9.317 7.863 2.912 11.84" fill-rule="evenodd" fill="#fff"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g transform="translate(74.118) scale(.9375)">
<path fill="#fff" d="M81.137 0h362.276v512H81.137z"/>
<path fill="#bf0a30" d="M-100 0H81.138v512H-100zm543.413 0H624.55v512H443.414zM135.31 247.41l-14.067 4.808 65.456 57.446c4.95 14.764-1.72 19.116-5.97 26.86l71.06-9.02-1.85 71.512 14.718-.423-3.21-70.918 71.13 8.432c-4.402-9.297-8.32-14.233-4.247-29.098l65.414-54.426-11.447-4.144c-9.36-7.222 4.044-34.784 6.066-52.178 0 0-38.195 13.135-40.698 6.262l-9.727-18.685-34.747 38.17c-3.796.91-5.413-.6-6.304-3.808l16.053-79.766-25.42 14.297c-2.128.91-4.256.125-5.658-2.355l-24.45-49.06-25.21 50.95c-1.9 1.826-3.803 2.037-5.382.796l-24.204-13.578 14.53 79.143c-1.156 3.14-3.924 4.025-7.18 2.324l-33.216-37.737c-4.345 6.962-7.29 18.336-13.033 20.885-5.744 2.387-24.98-4.823-37.873-7.637 4.404 15.895 18.176 42.302 9.46 50.957z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 934 B

View File

@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<path fill="#ffce00" d="M0 320h640v160.002H0z"/>
<path d="M0 0h640v160H0z"/>
<path fill="#d00" d="M0 160h640v160H0z"/>
</svg>

Before

Width:  |  Height:  |  Size: 220 B

View File

@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g fill-rule="evenodd" stroke-width="1pt">
<path fill="#fff" d="M0 0h640v480H0z"/>
<path fill="#00267f" d="M0 0h213.337v480H0z"/>
<path fill="#f31830" d="M426.662 0H640v480H426.662z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 301 B

View File

@@ -1,15 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<defs>
<clipPath id="a">
<path fill-opacity=".67" d="M-85.333 0h682.67v512h-682.67z"/>
</clipPath>
</defs>
<g clip-path="url(#a)" transform="translate(80) scale(.94)">
<g stroke-width="1pt">
<path fill="#006" d="M-256 0H768.02v512.01H-256z"/>
<path d="M-256 0v57.244l909.535 454.768H768.02V454.77L-141.515 0H-256zM768.02 0v57.243L-141.515 512.01H-256v-57.243L653.535 0H768.02z" fill="#fff"/>
<path d="M170.675 0v512.01h170.67V0h-170.67zM-256 170.67v170.67H768.02V170.67H-256z" fill="#fff"/>
<path d="M-256 204.804v102.402H768.02V204.804H-256zM204.81 0v512.01h102.4V0h-102.4zM-256 512.01L85.34 341.34h76.324l-341.34 170.67H-256zM-256 0L85.34 170.67H9.016L-256 38.164V0zm606.356 170.67L691.696 0h76.324L426.68 170.67h-76.324zM768.02 512.01L426.68 341.34h76.324L768.02 473.848v38.162z" fill="#c00"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 956 B

View File

@@ -1,18 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g fill-rule="evenodd" transform="scale(.9375)">
<g stroke-width="1pt">
<path d="M0 0h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#bd3d44"/>
<path d="M0 39.385h972.81V78.77H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#fff"/>
</g>
<path fill="#192f5d" d="M0 0h389.12v275.69H0z"/>
<g fill="#fff">
<path d="M32.427 11.8l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 39.37l3.54 10.896h11.458L70.583 57l3.542 10.897-9.27-6.734-9.269 6.734L59.126 57l-9.269-6.734h11.458zm64.852 0l3.54 10.896h11.457L135.435 57l3.54 10.897-9.268-6.734-9.27 6.734L123.978 57l-9.27-6.734h11.458zm64.855 0l3.54 10.896h11.458L200.29 57l3.541 10.897-9.27-6.734-9.268 6.734L188.833 57l-9.269-6.734h11.457zm64.855 0l3.54 10.896h11.458L265.145 57l3.541 10.897-9.269-6.734-9.27 6.734L253.69 57l-9.27-6.734h11.458zm64.852 0l3.54 10.896h11.457L329.997 57l3.54 10.897-9.268-6.734-9.27 6.734L318.54 57l-9.27-6.734h11.458zM32.427 66.939l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 94.508l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zM32.427 122.078l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 149.647l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458z"/>
<g>
<path d="M32.427 177.217l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 204.786l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458z"/>
</g>
<g>
<path d="M32.427 232.356l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

11
images/googleLogo.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 48 48" class="abcRioButtonSvg">
<g>
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path>
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path>
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path>
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path>
<path fill="none" d="M0 0h48v48H0z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 909 B

View File

@@ -28,7 +28,10 @@ target 'JitsiMeet' do
pod 'react-native-locale-detector',
:path => '../node_modules/react-native-locale-detector'
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'RNSound', :path => '../node_modules/react-native-sound'
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
pod 'react-native-calendar-events',
:path => '../node_modules/react-native-calendar-events'
end
post_install do |installer|

View File

@@ -3,6 +3,8 @@ PODS:
- React/Core (= 0.51.0)
- react-native-background-timer (2.0.0):
- React
- react-native-calendar-events (1.4.3):
- React
- react-native-fetch-blob (0.10.6):
- React/Core
- react-native-keep-awake (2.0.6):
@@ -41,12 +43,18 @@ PODS:
- React/Core
- React/fishhook
- React/RCTBlob
- RNSound (0.10.4):
- React/Core
- RNSound/Core (= 0.10.4)
- RNSound/Core (0.10.4):
- React/Core
- RNVectorIcons (4.4.2):
- React
- yoga (0.51.0.React)
DEPENDENCIES:
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
- react-native-calendar-events (from `../node_modules/react-native-calendar-events`)
- react-native-fetch-blob (from `../node_modules/react-native-fetch-blob`)
- react-native-keep-awake (from `../node_modules/react-native-keep-awake`)
- react-native-locale-detector (from `../node_modules/react-native-locale-detector`)
@@ -61,6 +69,7 @@ DEPENDENCIES:
- React/RCTNetwork (from `../node_modules/react-native`)
- React/RCTText (from `../node_modules/react-native`)
- React/RCTWebSocket (from `../node_modules/react-native`)
- RNSound (from `../node_modules/react-native-sound`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -69,6 +78,8 @@ EXTERNAL SOURCES:
:path: ../node_modules/react-native
react-native-background-timer:
:path: ../node_modules/react-native-background-timer
react-native-calendar-events:
:path: ../node_modules/react-native-calendar-events
react-native-fetch-blob:
:path: ../node_modules/react-native-fetch-blob
react-native-keep-awake:
@@ -77,6 +88,8 @@ EXTERNAL SOURCES:
:path: ../node_modules/react-native-locale-detector
react-native-webrtc:
:path: ../node_modules/react-native-webrtc
RNSound:
:path: ../node_modules/react-native-sound
RNVectorIcons:
:path: ../node_modules/react-native-vector-icons
yoga:
@@ -85,13 +98,15 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
React: 541ba768b9855e10cdc76f55427a5cd0653ca806
react-native-background-timer: 63dcbf37dbcf294b5c6c071afcdc661fa06a7594
react-native-calendar-events: fe6fbc8ed337a7423c98f2c9012b25f20444de09
react-native-fetch-blob: 63394b1d7b0781547b3e4463b3195790177b1222
react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
react-native-webrtc: bc044ca9530fc802e7533f247aa08fe1b6bf8dc5
RNSound: d0818fe2435254fe30540fae48a429c5ffb72e09
RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0
PODFILE CHECKSUM: fabd6b6c27f8e1849f0668db3f403bf536ac8903
PODFILE CHECKSUM: 4a5a310403b99b9c2d619e0b18da89bf0fe5858c
COCOAPODS: 1.4.0

View File

@@ -53,14 +53,24 @@ partial URL (e.g. a room name only) is specified to
`loadURLString:`/`loadURLObject:`. If not set or if set to `nil`, the default
built in JavaScript is used: https://meet.jit.si.
NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
effect.
#### pictureInPictureEnabled
Property to get / set whether Picture-in-Picture is enabled. Defaults to `YES`
if `delegate` implements `enterPictureInPicture:`; otherwise, `NO`.
NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
effect.
#### welcomePageEnabled
Property to get/set whether the Welcome page is enabled. If `NO`, a black empty
view will be rendered when not in a conference. Defaults to `NO`.
NOTE: Must be set before `loadURL:`/`loadURLString:` for it to take effect.
NOTE: Must be set (if at all) before `loadURL:`/`loadURLString:` for it to take
effect.
#### loadURL:NSURL
@@ -170,6 +180,16 @@ Called before a conference is left.
The `data` dictionary contains a "url" key with the conference URL.
#### enterPictureInPicture
Called when entering Picture-in-Picture is requested by the user. The app should
now activate its Picture-in-Picture implementation (and resize the associated
`JitsiMeetView`. The latter will automatically detect its new size and adjust
its user interface to a variant appropriate for the small size ordinarily
associated with Picture-in-Picture.)
The `data` dictionary is empty.
#### loadConfigError
Called when loading the main configuration file from the Jitsi Meet deployment
@@ -178,3 +198,17 @@ fails.
The `data` dictionary contains an "error" key with the error and a "url" key
with the conference URL which necessitated the loading of the configuration
file.
### Picture-in-Picture
`JitsiMeetView` will automatically adjust its UI when presented in a
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
"full" UI.
Jitsi Meet SDK does not currently implement native Picture-in-Picture on iOS. If
desired, apps need to implement non-native Picture-in-Picture themselves and
resize `JitsiMeetView`.
If `pictureInPictureEnabled` is set to `YES` or `delegate` implements
`enterPictureInPicture:`, the in-call toolbar will render a button to afford the
user to request entering Picture-in-Picture.

View File

@@ -55,12 +55,14 @@
</dict>
</dict>
</dict>
<key>NSCalendarsUsageDescription</key>
<string>See your scheduled conferences in the app.</string>
<key>NSCameraUsageDescription</key>
<string>Participate in conferences with video.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSMicrophoneUsageDescription</key>
<string>Participate in conferences with audio.</string>
<string>Participate in conferences with voice.</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>

View File

@@ -0,0 +1,416 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objects = {
/* Begin PBXBuildFile section */
C6245F57205044120040BE68 /* JitsiMeet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6F99C4F204DE79F0001F710 /* JitsiMeet.framework */; };
C6245F58205044150040BE68 /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6A34247204DF18000E062DD /* WebRTC.framework */; };
C6A34249204DF18000E062DD /* WebRTC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C6A34247204DF18000E062DD /* WebRTC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C6F99C3B204DE6BE0001F710 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F99C3A204DE6BE0001F710 /* AppDelegate.swift */; };
C6F99C3D204DE6BE0001F710 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F99C3C204DE6BE0001F710 /* ViewController.swift */; };
C6F99C40204DE6BE0001F710 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C6F99C3E204DE6BE0001F710 /* Main.storyboard */; };
C6F99C42204DE6BE0001F710 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C6F99C41204DE6BE0001F710 /* Assets.xcassets */; };
C6F99C45204DE6BE0001F710 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C6F99C43204DE6BE0001F710 /* LaunchScreen.storyboard */; };
C6F99C60204DEDC10001F710 /* JitsiMeet.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C6F99C4F204DE79F0001F710 /* JitsiMeet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
C6F99C61204DEDC20001F710 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
C6F99C60204DEDC10001F710 /* JitsiMeet.framework in Embed Frameworks */,
C6A34249204DF18000E062DD /* WebRTC.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
C6A34247204DF18000E062DD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
C6F99C37204DE6BE0001F710 /* example-pip-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example-pip-app.app; sourceTree = BUILT_PRODUCTS_DIR; };
C6F99C3A204DE6BE0001F710 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C6F99C3C204DE6BE0001F710 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
C6F99C3F204DE6BE0001F710 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
C6F99C41204DE6BE0001F710 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C6F99C44204DE6BE0001F710 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
C6F99C46204DE6BE0001F710 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C6F99C4F204DE79F0001F710 /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
C6F99C34204DE6BE0001F710 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C6245F57205044120040BE68 /* JitsiMeet.framework in Frameworks */,
C6245F58205044150040BE68 /* WebRTC.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
C6F99C2E204DE6BE0001F710 = {
isa = PBXGroup;
children = (
C6F99C4D204DE79F0001F710 /* Frameworks */,
C6F99C38204DE6BE0001F710 /* Products */,
C6F99C4C204DE7230001F710 /* src */,
);
sourceTree = "<group>";
};
C6F99C38204DE6BE0001F710 /* Products */ = {
isa = PBXGroup;
children = (
C6F99C37204DE6BE0001F710 /* example-pip-app.app */,
);
name = Products;
sourceTree = "<group>";
};
C6F99C4C204DE7230001F710 /* src */ = {
isa = PBXGroup;
children = (
C6F99C3A204DE6BE0001F710 /* AppDelegate.swift */,
C6F99C3C204DE6BE0001F710 /* ViewController.swift */,
C6F99C3E204DE6BE0001F710 /* Main.storyboard */,
C6F99C41204DE6BE0001F710 /* Assets.xcassets */,
C6F99C43204DE6BE0001F710 /* LaunchScreen.storyboard */,
C6F99C46204DE6BE0001F710 /* Info.plist */,
);
path = src;
sourceTree = "<group>";
};
C6F99C4D204DE79F0001F710 /* Frameworks */ = {
isa = PBXGroup;
children = (
C6F99C4F204DE79F0001F710 /* JitsiMeet.framework */,
C6A34247204DF18000E062DD /* WebRTC.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
C6F99C36204DE6BE0001F710 /* example-pip-app */ = {
isa = PBXNativeTarget;
buildConfigurationList = C6F99C49204DE6BE0001F710 /* Build configuration list for PBXNativeTarget "example-pip-app" */;
buildPhases = (
C6A3424A204DF91D00E062DD /* Run Adjust ATS for loading JS bundle */,
C6F99C62204DEFFE0001F710 /* Run React Packager */,
C6F99C33204DE6BE0001F710 /* Sources */,
C6F99C34204DE6BE0001F710 /* Frameworks */,
C6F99C35204DE6BE0001F710 /* Resources */,
C6F99C61204DEDC20001F710 /* Embed Frameworks */,
C6A3426E20503ECC00E062DD /* Adjust embedded framework architectures */,
);
buildRules = (
);
dependencies = (
);
name = example-pip-app;
productName = example-pip-app;
productReference = C6F99C37204DE6BE0001F710 /* example-pip-app.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
C6F99C2F204DE6BE0001F710 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0920;
ORGANIZATIONNAME = "Atlassian Inc";
TargetAttributes = {
C6F99C36204DE6BE0001F710 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
};
};
};
buildConfigurationList = C6F99C32204DE6BE0001F710 /* Build configuration list for PBXProject "example-pip-app" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = C6F99C2E204DE6BE0001F710;
productRefGroup = C6F99C38204DE6BE0001F710 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
C6F99C36204DE6BE0001F710 /* example-pip-app */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
C6F99C35204DE6BE0001F710 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C6F99C45204DE6BE0001F710 /* LaunchScreen.storyboard in Resources */,
C6F99C42204DE6BE0001F710 /* Assets.xcassets in Resources */,
C6F99C40204DE6BE0001F710 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
C6A3424A204DF91D00E062DD /* Run Adjust ATS for loading JS bundle */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Adjust ATS for loading JS bundle";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/fixup-ats.sh";
};
C6A3426E20503ECC00E062DD /* Adjust embedded framework architectures */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Adjust embedded framework architectures";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/fixup-frameworks.sh";
};
C6F99C62204DEFFE0001F710 /* Run React Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run React Packager";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/run-packager.sh";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
C6F99C33204DE6BE0001F710 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C6F99C3D204DE6BE0001F710 /* ViewController.swift in Sources */,
C6F99C3B204DE6BE0001F710 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
C6F99C3E204DE6BE0001F710 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
C6F99C3F204DE6BE0001F710 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
C6F99C43204DE6BE0001F710 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
C6F99C44204DE6BE0001F710 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
C6F99C47204DE6BE0001F710 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
C6F99C48204DE6BE0001F710 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
C6F99C4A204DE6BE0001F710 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = "../../node_modules/react-native-webrtc/ios";
INFOPLIST_FILE = src/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.jitsi.example-pip-app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
C6F99C4B204DE6BE0001F710 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = "../../node_modules/react-native-webrtc/ios";
INFOPLIST_FILE = src/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.jitsi.example-pip-app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C6F99C32204DE6BE0001F710 /* Build configuration list for PBXProject "example-pip-app" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C6F99C47204DE6BE0001F710 /* Debug */,
C6F99C48204DE6BE0001F710 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C6F99C49204DE6BE0001F710 /* Build configuration list for PBXNativeTarget "example-pip-app" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C6F99C4A204DE6BE0001F710 /* Debug */,
C6F99C4B204DE6BE0001F710 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = C6F99C2F204DE6BE0001F710 /* Project object */;
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import JitsiMeet
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
guard let launchOptions = launchOptions else { return false }
return JitsiMeetView.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// MARK: - Linking delegate methods
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
return JitsiMeetView.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return JitsiMeetView.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
}
}

View File

@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="example-pip-app" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QxY-C8-fwD">
<rect key="frame" x="116" y="324.5" width="143" height="38"/>
<fontDescription key="fontDescription" type="system" pointSize="21"/>
<state key="normal" title="Open Jitsi Meet"/>
<connections>
<action selector="openJitsiMeetWithSender:" destination="BYZ-38-t0r" eventType="touchUpInside" id="79C-XR-05w"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="QxY-C8-fwD" firstAttribute="centerX" secondItem="6Tk-OE-BBY" secondAttribute="centerX" id="6a6-l1-7Ct"/>
<constraint firstItem="QxY-C8-fwD" firstAttribute="centerY" secondItem="6Tk-OE-BBY" secondAttribute="centerY" id="Hfg-TH-0g2"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<connections>
<outlet property="videoButton" destination="QxY-C8-fwD" id="Zwa-Hx-Zub"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="32.799999999999997" y="658.92053973013503"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,39 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import UIKit
import JitsiMeet
class ViewController: UIViewController {
@IBOutlet weak var videoButton: UIButton?
private var jitsiMeetCoordinator: JitsiMeetPresentationCoordinator?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Actions
@IBAction func openJitsiMeet(sender: Any?) {
let jitsiMeetCoordinator = JitsiMeetPresentationCoordinator()
self.jitsiMeetCoordinator = jitsiMeetCoordinator
jitsiMeetCoordinator.jitsiMeetView().welcomePageEnabled = true
jitsiMeetCoordinator.jitsiMeetView().load(nil)
jitsiMeetCoordinator.show()
}
}

View File

@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:example-pip-app/example-pip-app.xcodeproj">
</FileRef>
<FileRef
location = "group:app/app.xcodeproj">
</FileRef>

View File

@@ -27,6 +27,13 @@
0BCA496C1EC4BBF900B793EE /* jitsi.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0BCA496B1EC4BBF900B793EE /* jitsi.ttf */; };
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425C204EF76800E062DD /* PiPWindow.swift */; };
C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */; };
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */; };
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -55,6 +62,13 @@
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
C6A3425C204EF76800E062DD /* PiPWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PiPWindow.swift; sourceTree = "<group>"; };
C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JitsiMeetPresentationCoordinator.swift; sourceTree = "<group>"; };
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetViewController.swift; sourceTree = "<group>"; };
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -76,6 +90,8 @@
isa = PBXGroup;
children = (
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
0BCA496B1EC4BBF900B793EE /* jitsi.ttf */,
);
name = Resources;
@@ -103,6 +119,7 @@
0BD906E71EC0C00300C8C18E /* src */ = {
isa = PBXGroup;
children = (
C6A3426B204F127900E062DD /* picture-in-picture */,
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
@@ -112,6 +129,7 @@
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */,
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */,
@@ -143,6 +161,17 @@
name = Pods;
sourceTree = "<group>";
};
C6A3426B204F127900E062DD /* picture-in-picture */ = {
isa = PBXGroup;
children = (
C6A3425E204EF76800E062DD /* DragGestureController.swift */,
C6A3425D204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift */,
C6A3426C204F1C3300E062DD /* JitsiMeetViewController.swift */,
C6A3425C204EF76800E062DD /* PiPWindow.swift */,
);
path = "picture-in-picture";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -150,6 +179,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
@@ -192,6 +222,7 @@
TargetAttributes = {
0BD906E41EC0C00300C8C18E = {
CreatedOnToolsVersion = 8.3.2;
LastSwiftMigration = 0920;
ProvisioningStyle = Automatic;
};
};
@@ -219,7 +250,9 @@
buildActionMask = 2147483647;
files = (
0BCA496C1EC4BBF900B793EE /* jitsi.ttf in Resources */,
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */,
0BC4B8691F8C03A700CE8B21 /* CallKitIcon.png in Resources */,
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -304,14 +337,18 @@
buildActionMask = 2147483647;
files = (
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
C6A34260204EF76800E062DD /* JitsiMeetPresentationCoordinator.swift in Sources */,
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
C6A3426D204F1C3300E062DD /* JitsiMeetViewController.swift in Sources */,
C6A3425F204EF76800E062DD /* PiPWindow.swift in Sources */,
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -435,6 +472,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
@@ -450,6 +489,9 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@@ -457,6 +499,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
@@ -472,6 +516,8 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 3.0;
};
name = Release;
};

View File

@@ -25,6 +25,8 @@
@property (copy, nonatomic, nullable) NSURL *defaultURL;
@property (nonatomic) BOOL pictureInPictureEnabled;
@property (nonatomic) BOOL welcomePageEnabled;
+ (BOOL)application:(UIApplication *_Nonnull)application

View File

@@ -109,7 +109,11 @@ void registerFatalErrorHandler() {
@end
@implementation JitsiMeetView
@implementation JitsiMeetView {
NSNumber *_pictureInPictureEnabled;
}
@dynamic pictureInPictureEnabled;
static RCTBridgeWrapper *bridgeWrapper;
@@ -265,6 +269,7 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
}
props[@"externalAPIScope"] = externalAPIScope;
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
// XXX If urlObject is nil, then it must appear as undefined in the
@@ -315,6 +320,28 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
[self loadURLObject:urlString ? @{ @"url": urlString } : nil];
}
#pragma pictureInPictureEnabled getter / setter
- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
_pictureInPictureEnabled
= [NSNumber numberWithBool:pictureInPictureEnabled];
}
- (BOOL) pictureInPictureEnabled {
if (_pictureInPictureEnabled) {
return [_pictureInPictureEnabled boolValue];
}
// The SDK/JitsiMeetView client/consumer did not explicitly enable/disable
// Picture-in-Picture. However, we may automatically deduce their
// intentions: we need the support of the client in order to implement
// Picture-in-Picture on iOS (in contrast to Android) so if the client
// appears to have provided the support then we can assume that they did it
// with the intention to have Picture-in-Picture enabled.
return self.delegate
&& [self.delegate respondsToSelector:@selector(enterPictureInPicture:)];
}
#pragma mark Private methods
/**

View File

@@ -55,6 +55,17 @@
*/
- (void)conferenceWillLeave:(NSDictionary *)data;
/**
* Called when entering Picture-in-Picture is requested by the user. The app
* should now activate its Picture-in-Picture implementation (and resize the
* associated `JitsiMeetView`. The latter will automatically detect its new size
* and adjust its user interface to a variant appropriate for the small size
* ordinarily associated with Picture-in-Picture.)
*
* The `data` dictionary is empty.
*/
- (void)enterPictureInPicture:(NSDictionary *)data;
/**
* Called when loading the main configuration file from the Jitsi Meet
* deployment file.

View File

@@ -0,0 +1,125 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DragGestureController {
var insets: UIEdgeInsets = UIEdgeInsets.zero
private var frameBeforeDragging: CGRect = CGRect.zero
private weak var view: UIView?
private lazy var panGesture: UIPanGestureRecognizer = {
return UIPanGestureRecognizer(target: self,
action: #selector(handlePan(gesture:)))
}()
func startDragListener(inView view: UIView) {
self.view = view
view.addGestureRecognizer(panGesture)
panGesture.isEnabled = true
}
func stopDragListener() {
panGesture.isEnabled = false
view?.removeGestureRecognizer(panGesture)
view = nil
}
@objc private func handlePan(gesture: UIPanGestureRecognizer) {
guard let view = self.view else { return }
let translation = gesture.translation(in: view.superview)
let velocity = gesture.velocity(in: view.superview)
var frame = frameBeforeDragging
switch gesture.state {
case .began:
frameBeforeDragging = view.frame
case .changed:
frame.origin.x = floor(frame.origin.x + translation.x)
frame.origin.y = floor(frame.origin.y + translation.y)
view.frame = frame
case .ended:
let currentPos = view.frame.origin
let finalPos = calculateFinalPosition()
let distance = CGPoint(x: currentPos.x - finalPos.x,
y: currentPos.y - finalPos.y)
let distanceMagnitude = magnitude(vector: distance)
let velocityMagnitude = magnitude(vector: velocity)
let animationDuration = 0.5
let initialSpringVelocity =
velocityMagnitude / distanceMagnitude / CGFloat(animationDuration)
frame.origin = CGPoint(x: finalPos.x, y: finalPos.y)
UIView.animate(withDuration: animationDuration,
delay: 0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: initialSpringVelocity,
options: .curveLinear,
animations: {
view.frame = frame
}, completion: nil)
default:
break
}
}
private func calculateFinalPosition() -> CGPoint {
guard
let view = self.view,
let bounds = view.superview?.frame
else { return CGPoint.zero }
let currentSize = view.frame.size
let adjustedBounds = UIEdgeInsetsInsetRect(bounds, insets)
let threshold: CGFloat = 20.0
let velocity = panGesture.velocity(in: view.superview)
let location = panGesture.location(in: view.superview)
let goLeft: Bool
if fabs(velocity.x) > threshold {
goLeft = velocity.x < -threshold
} else {
goLeft = location.x < bounds.midX
}
let goUp: Bool
if fabs(velocity.y) > threshold {
goUp = velocity.y < -threshold
} else {
goUp = location.y < bounds.midY
}
let finalPosX: CGFloat =
goLeft
? adjustedBounds.origin.x
: bounds.size.width - insets.right - currentSize.width
let finalPosY: CGFloat =
goUp
? adjustedBounds.origin.y
: bounds.size.height - insets.bottom - currentSize.height
return CGPoint(x: finalPosX, y: finalPosY)
}
private func magnitude(vector: CGPoint) -> CGFloat {
return sqrt(pow(vector.x, 2) + pow(vector.y, 2))
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Coordinates the presentation of JitsiMeetViewController inside of
/// an external window that can be resized and dragged with custom PiP mode
open class JitsiMeetPresentationCoordinator: NSObject {
fileprivate let meetViewController: JitsiMeetViewController
fileprivate let meetWindow: PiPWindow
public init(meetViewController: JitsiMeetViewController? = nil,
meetWindow: PiPWindow? = nil) {
self.meetViewController = meetViewController ?? JitsiMeetViewController()
self.meetWindow = meetWindow ?? PiPWindow(frame: UIScreen.main.bounds)
super.init()
configureMeetWindow()
configureMeetViewController()
}
public func jitsiMeetView() -> JitsiMeetView {
return meetViewController.jitsiMeetView
}
open func show(completion: CompletionAction? = nil) {
meetWindow.show(completion: completion)
}
open func hide(completion: CompletionAction? = nil) {
meetWindow.hide(completion: completion)
}
open func cleanUp() {
// TODO: more clean up work on this
meetWindow.isHidden = true
meetWindow.stopDragGesture()
}
deinit {
cleanUp()
}
// MARK: - helpers
private func configureMeetViewController() {
meetViewController.jitsiMeetView.pictureInPictureEnabled = true
meetViewController.delegate = self
}
private func configureMeetWindow() {
meetWindow.backgroundColor = .clear
meetWindow.windowLevel = UIWindowLevelStatusBar + 100
meetWindow.rootViewController = self.meetViewController
}
}
extension JitsiMeetPresentationCoordinator: JitsiMeetViewControllerDelegate {
open func performPresentationUpdate(to: JitsiMeetPresentationUpdate) {
switch to {
case .enterPictureInPicture:
meetWindow.enterPictureInPicture()
case .traitChange:
// resize to full screen if rotation happens
if meetWindow.isInPiP {
meetWindow.exitPictureInPicture()
}
}
}
open func conferenceStarted() {
if meetWindow.isHidden {
meetWindow.show()
}
}
open func conferenceEnded(didFail: Bool) {
cleanUp()
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public enum JitsiMeetPresentationUpdate {
/// The conference wants to enter Picture-in-Picture
case enterPictureInPicture
/// A system traitCollectionChange (usually screen rotation)
case traitChange
}
public protocol JitsiMeetViewControllerDelegate: class {
/// Notifies a change of the conference presentation style.
///
/// - Parameter to: The presentation state that will be changed to
func performPresentationUpdate(to: JitsiMeetPresentationUpdate)
/// The conference started
func conferenceStarted()
/// The conference ended
///
/// - Parameter didFail: The reason of ending the conference
func conferenceEnded(didFail: Bool)
}
/// Wrapper ViewController of a JitsiMeetView
///
/// Is suggested to override this class and implement some customization
/// on how to handle the JitsiMeetView delegate events
open class JitsiMeetViewController: UIViewController {
open weak var delegate: JitsiMeetViewControllerDelegate?
private(set) var jitsiMeetView: JitsiMeetView = JitsiMeetView()
override open func loadView() {
super.loadView()
self.view = jitsiMeetView
}
open override func viewDidLoad() {
super.viewDidLoad()
jitsiMeetView.delegate = self
}
open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
delegate?.performPresentationUpdate(to: .traitChange)
}
}
extension JitsiMeetViewController: JitsiMeetViewDelegate {
open func conferenceWillJoin(_ data: [AnyHashable : Any]!) {
// do something
}
open func conferenceJoined(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.conferenceStarted()
}
}
open func conferenceWillLeave(_ data: [AnyHashable : Any]!) {
// do something
}
open func conferenceLeft(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.conferenceEnded(didFail: false)
}
}
open func conferenceFailed(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.conferenceEnded(didFail: true)
}
}
open func loadConfigError(_ data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.conferenceEnded(didFail: true)
}
}
open func enterPicture(inPicture data: [AnyHashable : Any]!) {
DispatchQueue.main.async {
self.delegate?.performPresentationUpdate(to: .enterPictureInPicture)
}
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// Alias defining a completion closure that returns a Bool
public typealias CompletionAction = (Bool) -> Void
/// A window that allows its root view controller to be presented
/// in full screen or in a custom Picture in Picture mode
open class PiPWindow: UIWindow {
/// Limits the boundries of root view position on screen when minimized
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
left: 5,
bottom: 5,
right: 5) {
didSet {
dragController.insets = dragBoundInsets
}
}
/// The size ratio for root view controller view when in PiP mode
public var pipSizeRatio: CGFloat = 0.333
/// The PiP state of this contents of the window
private(set) var isInPiP: Bool = false
private let dragController: DragGestureController = DragGestureController()
/// Used when in PiP mode to enable/disable exit PiP UI
private var tapGestureRecognizer: UITapGestureRecognizer?
private var exitPiPButton: UIButton?
/// Help out to bubble up the gesture detection outside of the rootVC frame
open override func point(inside point: CGPoint,
with event: UIEvent?) -> Bool {
guard let vc = rootViewController else {
return super.point(inside: point, with: event)
}
return vc.view.frame.contains(point)
}
/// animate in the window
open func show(completion: CompletionAction? = nil) {
if self.isHidden || self.alpha < 1 {
self.isHidden = false
self.alpha = 0
animateTransition(animations: {
self.alpha = 1
}, completion: completion)
}
}
/// animate out the window
open func hide(completion: CompletionAction? = nil) {
if !self.isHidden || self.alpha > 0 {
animateTransition(animations: {
self.alpha = 1
}, completion: completion)
}
}
/// Resize the root view to PiP mode
open func enterPictureInPicture() {
guard let view = rootViewController?.view else { return }
isInPiP = true
animateRootViewChange()
dragController.startDragListener(inView: view)
dragController.insets = dragBoundInsets
// add single tap gesture recognition for displaying exit PiP UI
let exitSelector = #selector(toggleExitPiP)
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: exitSelector)
self.tapGestureRecognizer = tapGestureRecognizer
view.addGestureRecognizer(tapGestureRecognizer)
}
/// Resize the root view to full screen
open func exitPictureInPicture() {
isInPiP = false
animateRootViewChange()
dragController.stopDragListener()
// hide PiP UI
exitPiPButton?.removeFromSuperview()
exitPiPButton = nil
// remove gesture
let exitSelector = #selector(toggleExitPiP)
tapGestureRecognizer?.removeTarget(self, action: exitSelector)
tapGestureRecognizer = nil
}
/// Stop the dragging gesture of the root view
public func stopDragGesture() {
dragController.stopDragListener()
}
/// Customize the presentation of exit pip button
open func configureExitPiPButton(target: Any,
action: Selector) -> UIButton {
let buttonImage = UIImage.init(named: "image-resize",
in: Bundle(for: type(of: self)),
compatibleWith: nil)
let button = UIButton(type: .custom)
let size: CGSize = CGSize(width: 44, height: 44)
button.setImage(buttonImage, for: .normal)
button.backgroundColor = .gray
button.layer.cornerRadius = size.width / 2
button.frame = CGRect(origin: CGPoint.zero, size: size)
if let view = rootViewController?.view {
button.center = view.convert(view.center, from:view.superview)
}
button.addTarget(target, action: action, for: .touchUpInside)
return button
}
// MARK: - Manage presentation switching
private func animateRootViewChange() {
UIView.animate(withDuration: 0.25) {
self.rootViewController?.view.frame = self.changeRootViewRect()
self.rootViewController?.view.setNeedsLayout()
}
}
private func changeRootViewRect() -> CGRect {
guard isInPiP else {
return self.bounds
}
// resize to suggested ratio and position to the bottom right
let adjustedBounds = UIEdgeInsetsInsetRect(self.bounds, dragBoundInsets)
let size = CGSize(width: bounds.size.width * pipSizeRatio,
height: bounds.size.height * pipSizeRatio)
let x: CGFloat = adjustedBounds.maxX - size.width
let y: CGFloat = adjustedBounds.maxY - size.height
return CGRect(x: x, y: y, width: size.width, height: size.height)
}
// MARK: - Exit PiP
@objc private func toggleExitPiP() {
guard let view = rootViewController?.view else { return }
if exitPiPButton == nil {
// show button
let exitSelector = #selector(exitPictureInPicture)
let button = configureExitPiPButton(target: self,
action: exitSelector)
view.addSubview(button)
exitPiPButton = button
} else {
// hide button
exitPiPButton?.removeFromSuperview()
exitPiPButton = nil
}
}
@objc private func exitPiP() {
exitPictureInPicture()
}
// MARK: - Animation transition
private func animateTransition(animations: @escaping () -> Void,
completion: CompletionAction?) {
UIView.animate(withDuration: 0.1,
delay: 0,
options: .beginFromCurrentState,
animations: animations,
completion: completion)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

20
lang/languages-hu.json Normal file
View File

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

502
lang/main-hu.json Normal file
View File

@@ -0,0 +1,502 @@
{
"contactlist": "",
"contactlist_plural": "",
"passwordSetRemotely": "",
"poweredby": "",
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"audioDevices": {
"bluetooth": "",
"headphones": "",
"phone": "",
"speaker": ""
},
"audioOnly": {
"audioOnly": "",
"featureToggleDisabled": ""
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": "",
"edgeGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": "",
"showSpeakerStats": ""
},
"welcomepage": {
"disable": "",
"feature1": {
"content": "",
"title": ""
},
"feature2": {
"content": "",
"title": ""
},
"feature3": {
"content": "",
"title": ""
},
"feature4": {
"content": "",
"title": ""
},
"feature5": {
"content": "",
"title": ""
},
"feature6": {
"content": "",
"title": ""
},
"feature7": {
"content": "",
"title": ""
},
"feature8": {
"content": "",
"title": ""
},
"go": "",
"join": "",
"privacy": "",
"roomname": "",
"roomnamePlaceHolder": "",
"sendFeedback": "",
"terms": ""
},
"startupoverlay": {
"policyText": "",
"title": ""
},
"suspendedoverlay": {
"title": "",
"text": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"addPeople": "",
"audioonly": "",
"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": ""
},
"unsupportedBrowser": {
"appNotInstalled": "",
"downloadApp": "",
"openApp": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
},
"chat": {
"nickname": {
"title": "",
"popover": ""
},
"messagebox": ""
},
"settings": {
"title": "",
"update": "",
"name": "",
"startAudioMuted": "",
"startVideoMuted": "",
"selectCamera": "",
"selectMic": "",
"selectAudioOutput": "",
"followMe": "",
"noDevice": "",
"cameraAndMic": "",
"moderator": "",
"password": "",
"audioVideo": ""
},
"profile": {
"title": "",
"setDisplayNameLabel": "",
"setEmailLabel": "",
"setEmailInput": ""
},
"videothumbnail": {
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": "",
"remoteControl": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"framerate": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural": "",
"localport": "",
"localport_plural": "",
"localaddress": "",
"localaddress_plural": "",
"remoteaddress": "",
"remoteaddress_plural": "",
"transport": "",
"transport_plural": "",
"bandwidth": "",
"na": "",
"turn": "",
"quality": {
"good": "",
"inactive": "",
"lost": "",
"nonoptimal": "",
"poor": ""
},
"status": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connectedOneMember": "",
"connectedTwoMembers": "",
"connectedThreePlusMembers": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"allow": "",
"kickMessage": "",
"popupErrorTitle": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"contactSupport": "",
"error": "",
"createPassword": "",
"detectext": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"dismiss": "",
"rejoinNow": "",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupportedTitle": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"alreadySharedVideoTitle": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingFailedToInstall": "",
"screenSharingFailedToInstallTitle": "",
"screenSharingPermissionDeniedError": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingDataTitle": "",
"micNotSendingData": "",
"cameraNotSendingDataTitle": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"inlineInstallationMsg": "",
"inlineInstallExtension": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": "",
"remoteControlTitle": "",
"remoteControlRequestMessage": "",
"remoteControlShareScreenWarning": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"startRemoteControlErrorMessage": "",
"remoteControlStopMessage": "",
"close": "",
"shareYourScreen": "",
"yourEntireScreen": "",
"applicationWindow": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"on": "",
"pending": "",
"serviceName": "",
"unavailable": "",
"unavailableTitle": ""
},
"liveStreaming": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "",
"error": "",
"failedToStart": "",
"off": "",
"on": "",
"pending": "",
"serviceName": "",
"streamIdRequired": "",
"streamIdHelp": "",
"unavailableTitle": ""
},
"videoSIPGW": {
"busy": "",
"busyTitle": "",
"errorInvite": "",
"errorInviteTitle": "",
"errorAlreadyInvited": "",
"errorInviteFailedTitle": "",
"errorInviteFailed": "",
"pending": "",
"serviceName": "",
"unavailableTitle": ""
},
"speakerStats": {
"hours": "",
"minutes": "",
"name": "",
"seconds": "",
"speakerStats": "",
"speakerTime": ""
},
"deviceSelection": {
"deviceSettings": "",
"noPermission": "",
"previewUnavailable": "",
"selectADevice": "",
"testAudio": ""
},
"invite": {
"addPassword": "",
"callNumber": "",
"enterID": "",
"howToDialIn": "",
"hidePassword": "",
"inviteTo": "",
"invitedYouTo": "",
"invitePeople": "",
"locked": "",
"showPassword": "",
"unlocked": ""
},
"videoStatus": {
"callQuality": "",
"hd": "",
"highDefinition": "",
"labelTooltipVideo": "",
"labelTooltipAudioOnly": "",
"ld": "",
"lowDefinition": "",
"onlyAudioAvailable": "",
"onlyAudioSupported": "",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "",
"standardDefinition": "",
"qualityButtonTip": ""
},
"dialOut": {
"dial": "",
"dialOut": "",
"statusMessage": "",
"enterPhone": "",
"phoneNotAllowed": ""
},
"addPeople": {
"add": "",
"noResults": "",
"searchPlaceholder": "",
"title": "",
"failedToAdd": ""
},
"inlineDialogFailure": {
"msg": "",
"retry": "",
"support": "",
"supportMsg": ""
},
"deviceError": {
"cameraError": "",
"microphoneError": "",
"cameraPermission": "",
"microphonePermission": ""
},
"feedback": {
"average": "",
"bad": "",
"good": "",
"rateExperience": "",
"veryBad": "",
"veryGood": ""
},
"info": {
"copy": "",
"invite": "",
"title": "",
"tooltip": ""
}
}

View File

@@ -47,10 +47,16 @@
},
"welcomepage":{
"appDescription": "Go ahead, video chat with the whole team. In fact, invite everyone you know. __app__ is a fully encrypted, 100% open source video conferencing solution that you can use all day, every day, for free — with no account needed.",
"audioVideoSwitch": {
"audio": "Voice",
"video": "Video"
},
"calendar": "Calendar",
"go": "GO",
"join": "JOIN",
"privacy": "Privacy",
"roomname": "Enter room name",
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
"sendFeedback": "Send feedback",
"terms": "Terms",
"title": "More secure, more flexible, and completely free video conferencing"
@@ -272,15 +278,13 @@
"tokenAuthFailed": "Sorry, you're not allowed to join this call.",
"displayNameRequired": "Display name is required",
"enterDisplayName": "Please enter your display name",
"extensionRequired": "Extension required:",
"firefoxExtensionPrompt": "You need to install a Firefox extension in order to use screen sharing. Please try again after you <a href='__url__'>get it from here</a>!",
"feedbackHelp": "Your feedback will help us to improve our video experience.",
"feedbackQuestion": "Tell us about your call!",
"thankYou": "Thank you for using __appName__!",
"sorryFeedback": "We're sorry to hear that. Would you like to tell us more?",
"liveStreaming": "Live Streaming",
"streamKey": "Stream name/key",
"startLiveStreaming": "Start live streaming",
"streamKey": "Live stream key",
"startLiveStreaming": "Go live now",
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
"stopLiveStreaming": "Stop live streaming",
@@ -289,6 +293,8 @@
"permissionDenied": "Permission Denied",
"screenSharingFailedToInstall": "Oops! Your screen sharing extension failed to install.",
"screenSharingFailedToInstallTitle": "Screen sharing extension failed to install",
"screenSharingFirefoxPermissionDeniedError": "Something went wrong while we were trying to share your screen. Please make sure that you have given us permission to do so. ",
"screenSharingFirefoxPermissionDeniedTitle": "Oops! We werent able to start screen sharing!",
"screenSharingPermissionDeniedError": "Oops! Something went wrong with your screen sharing extension permissions. Please reload and try again.",
"cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
"cameraUnknownError": "Cannot use camera for an unknown reason.",
@@ -390,14 +396,21 @@
"busy": "We're working on freeing streaming resources. Please try again in a few minutes.",
"busyTitle": "All streamers are currently busy",
"buttonTooltip": "Start / Stop Live Stream",
"changeSignIn": "Switch accounts.",
"choose": "Choose a live stream",
"chooseCTA": "Choose a streaming option. You're currently logged in as __email__.",
"enterStreamKey": "Enter your YouTube live stream key here.",
"error": "Live Streaming failed. Please try again.",
"errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
"failedToStart": "Live Streaming failed to start",
"off": "Live Streaming stopped",
"on": "Live Streaming",
"pending": "Starting Live Stream...",
"serviceName": "Live Streaming service",
"streamIdRequired": "Please fill in the stream id in order to launch the Live Streaming.",
"streamIdHelp": "Where do I find this?",
"signIn": "Sign in with Google",
"signInCTA": "Sign in or enter your live stream key from YouTube.",
"start": "Start a livestream",
"streamIdHelp": "What's this?",
"unavailableTitle": "Live Streaming unavailable"
},
"videoSIPGW":
@@ -447,17 +460,24 @@
"qualityButtonTip": "Change received video quality"
},
"dialOut": {
"dial": "Dial",
"dialOut": "Call a number",
"statusMessage": "is now __status__",
"enterPhone": "Enter phone number",
"phoneNotAllowed": "Oh, we don't support that destination yet! Sorry!"
"statusMessage": "is now __status__"
},
"addPeople": {
"add": "Add",
"countryNotSupported": "We do not support this destination yet.",
"countryReminder": "Calling outside the US? Please make sure you start with the country code!",
"disabled": "You can't invite people.",
"invite": "Invite",
"loading": "Searching for people and phone numbers",
"loadingNumber": "Validating phone number",
"loadingPeople": "Searching for people to invite",
"noResults": "No matching search results",
"searchPlaceholder": "Search for people and rooms to add",
"title": "Add people to your call",
"noValidNumbers": "Please enter a phone number",
"searchNumbers": "Enter a phone number to invite",
"searchPeople": "Enter a name to invite",
"searchPeopleAndNumbers": "Enter a name or phone number to invite",
"telephone": "Telephone: __number__",
"title": "Invite people to your meeting",
"failedToAdd": "Failed to add members"
},
"inlineDialogFailure": {
@@ -490,17 +510,19 @@
"dialInConferenceID": "PIN: __conferenceID__#",
"dialInNotSupported": "Sorry, dialing in is currently not suppported.",
"genericError": "Whoops, something went wrong.",
"invitePhone": "To join by phone, dial __number__ and enter this PIN: __pin__#",
"invitePhone": "To join by phone, dial __number__ and enter this PIN: __conferenceID__#",
"invitePhoneAlternatives": "To view more phone numbers, click this link: __url__",
"inviteURL": "To join the video meeting, click this link: __url__",
"moreNumbers": "More numbers",
"noNumbers": "No dial-in numbers.",
"noPassword": "None",
"noRoom": "No room was specified to dial-in into.",
"numbers": "Dial-in Numbers",
"password": "Password:",
"title": "Call info",
"tooltip": "Get access info about the meeting"
},
"profileModal": {
"settingsView": {
"alertOk": "OK",
"alertTitle": "Warning",
"alertURLText": "The entered server URL is invalid",
@@ -512,5 +534,16 @@
"serverURL": "Server URL",
"startWithAudioMuted": "Start with audio muted",
"startWithVideoMuted": "Start with video muted"
},
"calendarSync": {
"later": "Later",
"next": "Upcoming",
"nextMeeting": "next meeting",
"now": "Now"
},
"recentList": {
"today": "Today",
"yesterday": "Yesterday",
"earlier": "Earlier"
}
}

View File

@@ -28,6 +28,7 @@ import {
participantPresenceChanged,
showParticipantJoinedNotification
} from '../../react/features/base/participants';
import { destroyLocalTracks } from '../../react/features/base/tracks';
import { openDisplayNamePrompt } from '../../react/features/display-name';
import {
setNotificationsEnabled,
@@ -376,15 +377,6 @@ UI.start = function() {
document.title = interfaceConfig.APP_NAME;
};
/**
* Invokes cleanup of any deferred execution within relevant UI modules.
*
* @returns {void}
*/
UI.stopDaemons = () => {
VideoLayout.resetLargeVideo();
};
/**
* Setup some UI event listeners.
*/
@@ -511,11 +503,6 @@ UI.addUser = function(user) {
APP.store.dispatch(showParticipantJoinedNotification(displayName));
}
if (!config.startAudioMuted
|| config.startAudioMuted > APP.conference.membersCount) {
UIUtil.playSoundNotification('userJoined');
}
// Add Peer's container
VideoLayout.addParticipantContainer(user);
@@ -537,11 +524,6 @@ UI.removeUser = function(id, displayName) {
messageHandler.participantNotification(
displayName, 'notify.somebody', 'disconnected', 'notify.disconnected');
if (!config.startAudioMuted
|| config.startAudioMuted > APP.conference.membersCount) {
UIUtil.playSoundNotification('userLeft');
}
VideoLayout.removeParticipantContainer(id);
};
@@ -1057,16 +1039,6 @@ UI.getLargeVideo = function() {
*/
UI.isPinned = userId => VideoLayout.getPinnedId() === userId;
/**
* Shows dialog with a link to FF extension.
*/
UI.showExtensionRequiredDialog = function(url) {
messageHandler.openMessageDialog(
'dialog.extensionRequired',
'[html]dialog.firefoxExtensionPrompt',
{ url });
};
/**
* Shows "Please go to chrome webstore to install the desktop sharing extension"
* 2 button dialog with buttons - cancel and go to web store.
@@ -1291,6 +1263,18 @@ UI.setLocalRemoteControlActiveChanged = function() {
VideoLayout.setLocalRemoteControlActiveChanged();
};
/**
* Remove media tracks and UI elements so the user no longer sees media in the
* UI. The intent is to provide a feeling that the meeting has ended.
*
* @returns {void}
*/
UI.removeLocalMedia = function() {
APP.store.dispatch(destroyLocalTracks());
VideoLayout.resetLargeVideo();
$('#videospace').hide();
};
// TODO: Export every function separately. For now there is no point of doing
// this because we are importing everything.
export default UI;

View File

@@ -66,6 +66,8 @@ function doExternalAuth(room, lockPassword) {
* @param {string} [roomName] the name of the conference room.
*/
function redirectToTokenAuthService(roomName) {
// FIXME: This method will not preserve the other URL params that were
// originally passed.
UIUtil.redirect(getTokenAuthUrl(roomName, false));
}

View File

@@ -20,6 +20,7 @@ import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from '../util/UIUtil';
import VideoLayout from '../videolayout/VideoLayout';
import { openDialog } from '../../../react/features/base/dialog';
import {
JitsiRecordingStatus
} from '../../../react/features/base/lib-jitsi-meet';
@@ -31,6 +32,8 @@ import {
import { setToolboxEnabled } from '../../../react/features/toolbox';
import { setNotificationsEnabled } from '../../../react/features/notifications';
import {
StartLiveStreamDialog,
StopLiveStreamDialog,
hideRecordingLabel,
updateRecordingState
} from '../../../react/features/recording';
@@ -102,91 +105,11 @@ function _isRecordingButtonEnabled() {
* @returns {Promise}
*/
function _requestLiveStreamId() {
const cancelButton
= APP.translation.generateTranslationHTML('dialog.Cancel');
const backButton = APP.translation.generateTranslationHTML('dialog.Back');
const startStreamingButton
= APP.translation.generateTranslationHTML('dialog.startLiveStreaming');
const streamIdRequired
= APP.translation.generateTranslationHTML(
'liveStreaming.streamIdRequired');
const streamIdHelp
= APP.translation.generateTranslationHTML(
'liveStreaming.streamIdHelp');
return new Promise((resolve, reject) => {
dialog = APP.UI.messageHandler.openDialogWithStates({
state0: {
titleKey: 'dialog.liveStreaming',
html:
`<input class="input-control"
name="streamId" type="text"
data-i18n="[placeholder]dialog.streamKey"
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 },
{ title: startStreamingButton,
value: true }
],
focus: ':input:first',
defaultButton: 1,
submit(e, v, m, f) { // eslint-disable-line max-params
e.preventDefault();
if (v) {
if (f.streamId && f.streamId.length > 0) {
resolve(UIUtil.escapeHtml(f.streamId));
dialog.close();
return;
}
dialog.goToState('state1');
return false;
}
reject(APP.UI.messageHandler.CANCEL);
dialog.close();
return false;
}
},
state1: {
titleKey: 'dialog.liveStreaming',
html: streamIdRequired,
persistent: false,
buttons: [
{ title: cancelButton,
value: false },
{ title: backButton,
value: true }
],
focus: ':input:first',
defaultButton: 1,
submit(e, v) {
e.preventDefault();
if (v === 0) {
reject(APP.UI.messageHandler.CANCEL);
dialog.close();
} else {
dialog.goToState('state0');
}
}
}
}, {
close() {
dialog = null;
}
});
});
return new Promise((resolve, reject) =>
APP.store.dispatch(openDialog(StartLiveStreamDialog, {
onCancel: reject,
onSubmit: resolve
})));
}
/**
@@ -232,25 +155,20 @@ function _requestRecordingToken() {
* @private
*/
function _showStopRecordingPrompt(recordingType) {
let title;
let message;
let buttonKey;
if (recordingType === 'jibri') {
title = 'dialog.liveStreaming';
message = 'dialog.stopStreamingWarning';
buttonKey = 'dialog.stopLiveStreaming';
} else {
title = 'dialog.recording';
message = 'dialog.stopRecordingWarning';
buttonKey = 'dialog.stopRecording';
return new Promise((resolve, reject) => {
APP.store.dispatch(openDialog(StopLiveStreamDialog, {
onCancel: reject,
onSubmit: resolve
}));
});
}
return new Promise((resolve, reject) => {
dialog = APP.UI.messageHandler.openTwoButtonDialog({
titleKey: title,
msgKey: message,
leftButtonKey: buttonKey,
titleKey: 'dialog.recording',
msgKey: 'dialog.stopRecordingWarning',
leftButtonKey: 'dialog.stopRecording',
submitFunction: (e, v) => (v ? resolve : reject)(),
closeFunction: () => {
dialog = null;

View File

@@ -282,6 +282,7 @@ export default class SharedVideoManager {
thumb.setDisplayName('YouTube');
VideoLayout.addRemoteVideoContainer(self.url, thumb);
VideoLayout.resizeThumbnails(false, true);
const iframe = player.getIframe();

View File

@@ -25,8 +25,6 @@ const htmlStr = `
</div>
<div id="chatconversation"></div>
<audio id="chatNotification" src="sounds/incomingMessage.wav"
preload="auto"></audio>
<textarea id="usermsg" autofocus
data-i18n="[placeholder]chat.messagebox"></textarea>
<div id="smileysarea">
@@ -285,7 +283,6 @@ const Chat = {
if (!Chat.isVisible()) {
unreadMessages++;
UIUtil.playSoundNotification('chatNotification');
updateVisualNotification();
}
}

View File

@@ -1,17 +1,18 @@
/* global $, APP, interfaceConfig */
/* eslint-disable no-unused-vars */
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { i18next } from '../../../../react/features/base/i18n';
import { SettingsMenu } from '../../../../react/features/settings-menu';
/* eslint-enable no-unused-vars */
import { SettingsMenu } from '../../../../react/features/settings';
import UIUtil from '../../util/UIUtil';
/* eslint-enable no-unused-vars */
export default {
init() {
const settingsMenuContainer = document.createElement('div');
@@ -31,8 +32,7 @@ export default {
ReactDOM.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<SettingsMenu
{ ...props } />
<SettingsMenu { ...props } />
</I18nextProvider>
</Provider>,
settingsMenuContainer

View File

@@ -66,15 +66,6 @@ const UIUtil = {
return el.clientHeight + 1;
},
/**
* Plays the sound given by id.
*
* @param id the identifier of the audio element.
*/
playSoundNotification(id) {
document.getElementById(id).play();
},
/**
* Escapes the given text.
*/
@@ -217,6 +208,14 @@ const UIUtil = {
}
},
/**
* Redirects to a given URL.
*
* @param {string} url - The redirect URL.
* NOTE: Currently used to redirect to 3rd party location for
* authentication. In most cases redirectWithStoredParams action must be
* used instead of this method in order to preserve curent URL params.
*/
redirect(url) {
window.location.href = url;
},

View File

@@ -687,7 +687,11 @@ export class VideoContainer extends LargeContainer {
* @returns {void}
*/
_updateBackground() {
if (browser.isFirefox() || browser.isTemasysPluginUsed()) {
// Do not the background display on browsers that might experience
// performance issues from the presence of the background.
if (browser.isFirefox()
|| browser.isSafariWithWebrtc()
|| browser.isTemasysPluginUsed()) {
return;
}

View File

@@ -16,24 +16,6 @@ export function createDeferred() {
return deferred;
}
/**
* Reload page.
*/
export function reload() {
window.location.reload();
}
/**
* Redirects to a specific new URL by replacing the current location (in the
* history).
*
* @param {string} url the URL pointing to the location where the user should
* be redirected to.
*/
export function replace(url) {
window.location.replace(url);
}
/**
* Prints the error and reports it to the global error handler.
*

2058
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,16 +20,18 @@
"@atlaskit/dropdown-menu": "3.10.2",
"@atlaskit/droplist": "4.11.1",
"@atlaskit/field-text": "4.0.1",
"@atlaskit/field-text-area": "1.2.0",
"@atlaskit/flag": "6.1.0",
"@atlaskit/icon": "10.0.0",
"@atlaskit/inline-dialog": "5.0.2",
"@atlaskit/inline-message": "3.0.1",
"@atlaskit/layer-manager": "2.8.0",
"@atlaskit/lozenge": "3.4.2",
"@atlaskit/modal-dialog": "2.6.0",
"@atlaskit/modal-dialog": "3.4.0",
"@atlaskit/multi-select": "7.1.3",
"@atlaskit/spinner": "4.0.0",
"@atlaskit/tabs": "4.0.1",
"@atlaskit/theme": "2.2.0",
"@atlaskit/theme": "2.4.0",
"@atlaskit/tooltip": "6.0.0",
"autosize": "1.18.13",
"es6-iterator": "2.0.3",
@@ -39,12 +41,12 @@
"i18next-xhr-backend": "1.4.2",
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#6fff754a77a56ab52499f3559105a15886942a1e",
"jquery": "2.1.4",
"jquery": "3.3.1",
"jquery-contextmenu": "2.4.5",
"jquery-i18next": "1.2.0",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f9644b5c7093d61690e36a008aa7fc96a793234d ",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0503ec4d3f175b154b1c6fd7037520ce0768fa58",
"lodash": "4.17.4",
"moment": "2.19.4",
"nuclear-js": "1.4.0",
@@ -55,6 +57,7 @@
"react-i18next": "4.8.0",
"react-native": "0.51.0",
"react-native-background-timer": "2.0.0",
"react-native-calendar-events": "1.4.3",
"react-native-callstats": "3.27.0",
"react-native-fetch-blob": "0.10.8",
"react-native-img-cache": "1.5.2",
@@ -62,14 +65,15 @@
"react-native-keep-awake": "2.0.6",
"react-native-locale-detector": "github:jitsi/react-native-locale-detector#cc76092fc4335488a28a9529c8b50afae2c3ecdc",
"react-native-prompt": "1.0.0",
"react-native-sound": "0.10.4",
"react-native-vector-icons": "4.4.2",
"react-native-webrtc": "github:jitsi/react-native-webrtc#806435b41fa152a8239ebeb7d002d1c6e979be86",
"react-native-webrtc": "github:jitsi/react-native-webrtc#626818af40384356617f70366133317b6a475171",
"react-redux": "5.0.6",
"redux": "3.7.2",
"redux-thunk": "2.2.0",
"strophe.js": "github:jitsi/strophejs#1.2.14-1",
"strophejs-plugin-disco": "0.0.2",
"styled-components": "1.3.0",
"styled-components": "1.4.6",
"url-polyfill": "github:github/url-polyfill#39734186de44612bc5a16eb25f5407adcc5b2e7c",
"uuid": "3.1.0",
"xmldom": "0.1.27"
@@ -99,7 +103,7 @@
"precommit-hook": "3.0.0",
"string-replace-loader": "1.3.0",
"style-loader": "0.19.0",
"uglifyjs-webpack-plugin": "1.1.2",
"uglifyjs-webpack-plugin": "1.2.2",
"whatwg-fetch": "2.0.3",
"webpack": "3.9.1",
"webpack-dev-server": "2.9.5"

View File

@@ -76,26 +76,26 @@ export const VIDEO_MUTE = 'video.mute';
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createApiEvent = function(action, attributes = {}) {
export function createApiEvent(action, attributes = {}) {
return {
action,
attributes,
source: 'jitsi-meet-api'
};
};
}
/**
* Creates an event which indicates that the audio-only mode has been turned
* off.
* Creates an event which indicates that the audio-only mode has been changed.
*
* @param {boolean} enabled - True if audio-only is enabled, false otherwise.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createAudioOnlyDisableEvent = function() {
export function createAudioOnlyChangedEvent(enabled) {
return {
action: 'audio.only.disabled'
action: `audio.only.${enabled ? 'enabled' : 'disabled'}`
};
};
}
/**
* Creates an event which indicates that a device was changed.
@@ -106,7 +106,7 @@ export const createAudioOnlyDisableEvent = function() {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createDeviceChangedEvent = function(mediaType, deviceType) {
export function createDeviceChangedEvent(mediaType, deviceType) {
return {
action: 'device.changed',
attributes: {
@@ -114,7 +114,7 @@ export const createDeviceChangedEvent = function(mediaType, deviceType) {
'media_type': mediaType
}
};
};
}
/**
* Creates an event which specifies that the feedback dialog has been opened.
@@ -122,11 +122,11 @@ export const createDeviceChangedEvent = function(mediaType, deviceType) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createFeedbackOpenEvent = function() {
export function createFeedbackOpenEvent() {
return {
action: 'feedback.opened'
};
};
}
/**
* Creates an event which indicates that the invite dialog was closed. This is
@@ -136,11 +136,11 @@ export const createFeedbackOpenEvent = function() {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createInviteDialogClosedEvent = function() {
export function createInviteDialogClosedEvent() {
return {
action: 'invite.dialog.closed'
};
};
}
/**
* Creates a "page reload" event.
@@ -152,17 +152,16 @@ export const createInviteDialogClosedEvent = function() {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createPageReloadScheduledEvent
= function(reason, timeout, details) {
return {
action: 'page.reload.scheduled',
attributes: {
reason,
timeout,
...details
}
};
export function createPageReloadScheduledEvent(reason, timeout, details) {
return {
action: 'page.reload.scheduled',
attributes: {
reason,
timeout,
...details
}
};
}
/**
* Creates a "pinned" or "unpinned" event.
@@ -173,17 +172,16 @@ export const createPageReloadScheduledEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createPinnedEvent
= function(action, participantId, attributes) {
return {
type: TYPE_TRACK,
action,
actionSubject: 'participant',
objectType: 'participant',
objectId: participantId,
attributes
};
};
export function createPinnedEvent(action, participantId, attributes) {
return {
type: TYPE_TRACK,
action,
actionSubject: 'participant',
objectType: 'participant',
objectId: participantId,
attributes
};
}
/**
* Creates an event which indicates that a button in the profile panel was
@@ -194,16 +192,15 @@ export const createPinnedEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createProfilePanelButtonEvent
= function(buttonName, attributes = {}) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'profile.panel',
type: TYPE_UI
};
export function createProfilePanelButtonEvent(buttonName, attributes = {}) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'profile.panel',
type: TYPE_UI
};
}
/**
* Creates an event which indicates that a specific button on one of the
@@ -215,14 +212,14 @@ export const createProfilePanelButtonEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createRecordingDialogEvent = function(dialogName, buttonName) {
export function createRecordingDialogEvent(dialogName, buttonName) {
return {
action: 'clicked',
actionSubject: buttonName,
source: `${dialogName}.recording.dialog`,
type: TYPE_UI
};
};
}
/**
* Creates an event which specifies that the "confirm" button on the remote
@@ -233,7 +230,7 @@ export const createRecordingDialogEvent = function(dialogName, buttonName) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createRemoteMuteConfirmedEvent = function(participantId) {
export function createRemoteMuteConfirmedEvent(participantId) {
return {
action: 'clicked',
actionSubject: 'remote.mute.dialog.confirm.button',
@@ -243,7 +240,7 @@ export const createRemoteMuteConfirmedEvent = function(participantId) {
source: 'remote.mute.dialog',
type: TYPE_UI
};
};
}
/**
* Creates an event which indicates that one of the buttons in the "remote
@@ -254,16 +251,15 @@ export const createRemoteMuteConfirmedEvent = function(participantId) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createRemoteVideoMenuButtonEvent
= function(buttonName, attributes) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'remote.video.menu',
type: TYPE_UI
};
export function createRemoteVideoMenuButtonEvent(buttonName, attributes) {
return {
action: 'clicked',
actionSubject: buttonName,
attributes,
source: 'remote.video.menu',
type: TYPE_UI
};
}
/**
* Creates an event indicating that an action related to screen sharing
@@ -273,12 +269,12 @@ export const createRemoteVideoMenuButtonEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createScreenSharingEvent = function(action) {
export function createScreenSharingEvent(action) {
return {
action,
actionSubject: 'screen.sharing'
};
};
}
/**
* The local participant failed to send a "selected endpoint" message to the
@@ -288,7 +284,7 @@ export const createScreenSharingEvent = function(action) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createSelectParticipantFailedEvent = function(error) {
export function createSelectParticipantFailedEvent(error) {
const event = {
action: 'select.participant.failed'
};
@@ -298,7 +294,7 @@ export const createSelectParticipantFailedEvent = function(error) {
}
return event;
};
}
/**
* Creates an event associated with the "shared video" feature.
@@ -308,13 +304,13 @@ export const createSelectParticipantFailedEvent = function(error) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createSharedVideoEvent = function(action, attributes = {}) {
export function createSharedVideoEvent(action, attributes = {}) {
return {
action,
attributes,
actionSubject: 'shared.video'
};
};
}
/**
* Creates an event associated with a shortcut being pressed, released or
@@ -331,17 +327,19 @@ export const createSharedVideoEvent = function(action, attributes = {}) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createShortcutEvent
= function(shortcut, action = ACTION_SHORTCUT_TRIGGERED, attributes = {}) {
return {
action,
actionSubject: 'keyboard.shortcut',
actionSubjectId: shortcut,
attributes,
source: 'keyboard.shortcut',
type: TYPE_UI
};
export function createShortcutEvent(
shortcut,
action = ACTION_SHORTCUT_TRIGGERED,
attributes = {}) {
return {
action,
actionSubject: 'keyboard.shortcut',
actionSubjectId: shortcut,
attributes,
source: 'keyboard.shortcut',
type: TYPE_UI
};
}
/**
* Creates an event which indicates the "start audio only" configuration.
@@ -350,14 +348,14 @@ export const createShortcutEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createStartAudioOnlyEvent = function(audioOnly) {
export function createStartAudioOnlyEvent(audioOnly) {
return {
action: 'start.audio.only',
attributes: {
enabled: audioOnly
}
};
};
}
/**
* Creates an event which indicates the "start muted" configuration.
@@ -372,17 +370,19 @@ export const createStartAudioOnlyEvent = function(audioOnly) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createStartMutedConfigurationEvent
= function(source, audioMute, videoMute) {
return {
action: 'start.muted.configuration',
attributes: {
source,
'audio_mute': audioMute,
'video_mute': videoMute
}
};
export function createStartMutedConfigurationEvent(
source,
audioMute,
videoMute) {
return {
action: 'start.muted.configuration',
attributes: {
source,
'audio_mute': audioMute,
'video_mute': videoMute
}
};
}
/**
* Creates an event which indicates the delay for switching between simulcast
@@ -392,12 +392,12 @@ export const createStartMutedConfigurationEvent
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createStreamSwitchDelayEvent = function(attributes) {
export function createStreamSwitchDelayEvent(attributes) {
return {
action: 'stream.switch.delay',
attributes
};
};
}
/**
* Automatically changing the mute state of a media track in order to match
@@ -409,7 +409,7 @@ export const createStreamSwitchDelayEvent = function(attributes) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createSyncTrackStateEvent = function(mediaType, muted) {
export function createSyncTrackStateEvent(mediaType, muted) {
return {
action: 'sync.track.state',
attributes: {
@@ -417,7 +417,7 @@ export const createSyncTrackStateEvent = function(mediaType, muted) {
muted
}
};
};
}
/**
* Creates an event associated with a toolbar button being clicked/pressed. By
@@ -431,7 +431,7 @@ export const createSyncTrackStateEvent = function(mediaType, muted) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createToolbarEvent = function(buttonName, attributes = {}) {
export function createToolbarEvent(buttonName, attributes = {}) {
return {
action: 'clicked',
actionSubject: buttonName,
@@ -439,7 +439,7 @@ export const createToolbarEvent = function(buttonName, attributes = {}) {
source: 'toolbar.button',
type: TYPE_UI
};
};
}
/**
* Creates an event which indicates that a local track was muted.
@@ -452,7 +452,7 @@ export const createToolbarEvent = function(buttonName, attributes = {}) {
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createTrackMutedEvent = function(mediaType, reason, muted = true) {
export function createTrackMutedEvent(mediaType, reason, muted = true) {
return {
action: 'track.muted',
attributes: {
@@ -461,4 +461,22 @@ export const createTrackMutedEvent = function(mediaType, reason, muted = true) {
reason
}
};
};
}
/**
* Creates an event for an action on the welcome page.
*
* @param {string} action - The action that the event represents.
* @param {string} actionSubject - The subject that was acted upon.
* @param {boolean} attributes - Additional attributes to attach to the event.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createWelcomePageEvent(action, actionSubject, attributes = {}) {
return {
action,
actionSubject,
attributes,
source: 'welcomePage'
};
}

View File

@@ -1,19 +0,0 @@
/**
* The type of (redux) action which signals the request
* to hide the app settings modal.
*
* {
* type: HIDE_APP_SETTINGS
* }
*/
export const HIDE_APP_SETTINGS = Symbol('HIDE_APP_SETTINGS');
/**
* The type of (redux) action which signals the request
* to show the app settings modal where available.
*
* {
* type: SHOW_APP_SETTINGS
* }
*/
export const SHOW_APP_SETTINGS = Symbol('SHOW_APP_SETTINGS');

View File

@@ -1,29 +0,0 @@
// @flow
import { HIDE_APP_SETTINGS, SHOW_APP_SETTINGS } from './actionTypes';
/**
* Redux-signals the request to hide the app settings modal.
*
* @returns {{
* type: HIDE_APP_SETTINGS
* }}
*/
export function hideAppSettings() {
return {
type: HIDE_APP_SETTINGS
};
}
/**
* Redux-signals the request to open the app settings modal.
*
* @returns {{
* type: SHOW_APP_SETTINGS
* }}
*/
export function showAppSettings() {
return {
type: SHOW_APP_SETTINGS
};
}

View File

@@ -1,117 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n';
import { getSafetyOffset } from '../../base/react';
import { ASPECT_RATIO_WIDE } from '../../base/responsive-ui';
import styles, { CONTAINER_PADDING } from './styles';
/**
* The type of the React {@code Component} props of {@link FormSectionHeader}
*/
type Props = {
/**
* The current aspect ratio of the screen.
*/
_aspectRatio: Symbol,
/**
* The i18n key of the text label of the section.
*/
i18nLabel: string,
/**
* An external style object passed to the component.
*/
style: Object,
/**
* Invoked to obtain translated strings.
*/
t: Function
}
/**
* Implements a React {@code Component} which renders a section header on a
* form. This calculates the available safe view as well.
*/
class FormSectionHeader extends Component<Props> {
/**
* Initializes a new {@code FormSectionHeader} instance.
*
* @param {Object} props - Component properties.
*/
constructor(props) {
super(props);
this._getSafetyMargin = this._getSafetyMargin.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @override
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<View
style = { [
styles.formSectionTitle,
this.props.style,
this._getSafetyMargin()
] } >
<Text>
{ t(this.props.i18nLabel) }
</Text>
</View>
);
}
_getSafetyMargin: () => Object;
/**
* Calculates the safety margin for this header. See comment in
* functions.js.
*
* @private
* @returns {Object}
*/
_getSafetyMargin() {
if (this.props._aspectRatio === ASPECT_RATIO_WIDE) {
const safeOffset
= Math.max(getSafetyOffset() - CONTAINER_PADDING, 0);
return {
marginLeft: safeOffset,
marginRight: safeOffset
};
}
return undefined;
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code FormSectionHeader}.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Object}
*/
export function _mapStateToProps(state: Object) {
return {
_aspectRatio: state['features/base/responsive-ui'].aspectRatio
};
}
export default translate(connect(_mapStateToProps)(FormSectionHeader));

View File

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

View File

@@ -1,133 +0,0 @@
import {
BoxModel,
ColorPalette,
createStyleSheet
} from '../../base/styles';
export const ANDROID_UNDERLINE_COLOR = 'transparent';
export const CONTAINER_PADDING = 2 * BoxModel.padding;
export const HEADER_COLOR = ColorPalette.blue;
export const HEADER_PADDING = BoxModel.padding;
const TEXT_SIZE = 17;
/**
* The styles of the React {@code Components} of the feature
* {@code app-settings}.
*/
export default createStyleSheet({
/**
* The back button style.
*/
backIcon: {
alignSelf: 'center',
fontSize: 26,
padding: 8,
paddingRight: 22
},
/**
* Standardized style for a field container {@code View}.
*/
fieldContainer: {
alignItems: 'center',
flexDirection: 'row',
minHeight: 65
},
/**
* Standard container for a {@code View} containing a field label.
*/
fieldLabelContainer: {
alignItems: 'center',
flexDirection: 'row',
marginRight: 5
},
/**
* Field container style for all but last row {@code View}.
*/
fieldSeparator: {
borderBottomWidth: 1,
borderColor: 'rgba(0, 0, 0, 0.1)'
},
/**
* Style for the {@code View} containing each
* field values (the actual field).
*/
fieldValueContainer: {
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end'
},
formSectionTitle: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
marginTop: 5,
padding: 5
},
/**
* Page header {@code View}.
*/
headerContainer: {
alignItems: 'center',
backgroundColor: HEADER_COLOR,
flexDirection: 'row',
justifyContent: 'flex-start',
padding: HEADER_PADDING
},
/**
* The title {@code Text} of the header.
*/
headerTitle: {
color: ColorPalette.white,
fontSize: 22
},
/**
* Style of the ScrollView to be able to scroll the content.
*/
scrollView: {
flex: 1
},
/**
* The back button style on the settings screen.
*/
settingsBackButton: {
color: ColorPalette.white
},
/**
* The top level container {@code View}.
*/
settingsContainer: {
backgroundColor: ColorPalette.white,
flex: 1,
flexDirection: 'column',
margin: 0,
padding: CONTAINER_PADDING,
paddingTop: 0
},
/**
* Global {@code Text} color for the page.
*/
text: {
color: ColorPalette.black,
fontSize: TEXT_SIZE
},
/**
* Standard text input field style.
*/
textInputField: {
flex: 1,
fontSize: TEXT_SIZE,
textAlign: 'right'
}
});

View File

@@ -1,31 +0,0 @@
// @flow
import {
HIDE_APP_SETTINGS,
SHOW_APP_SETTINGS
} from './actionTypes';
import { ReducerRegistry } from '../base/redux';
const DEFAULT_STATE = {
visible: false
};
ReducerRegistry.register(
'features/app-settings', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case HIDE_APP_SETTINGS:
return {
...state,
visible: false
};
case SHOW_APP_SETTINGS:
return {
...state,
visible: true
};
}
return state;
});

View File

@@ -4,7 +4,6 @@ import { setRoom } from '../base/conference';
import { configWillLoad, loadConfigError, setConfig } from '../base/config';
import { setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet';
import { getProfile } from '../base/profile';
import { parseURIString } from '../base/util';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
@@ -15,7 +14,7 @@ declare var APP: Object;
* Triggers an in-app navigation to a specific route. Allows navigation to be
* abstracted between the mobile/React Native and Web/React applications.
*
* @param {(string|undefined)} uri - The URI to which to navigate. It may be a
* @param {string|undefined} uri - The URI to which to navigate. It may be a
* full URL with an HTTP(S) scheme, a full or partial URI with the app-specific
* scheme, or a mere room name.
* @returns {Function}
@@ -25,6 +24,48 @@ export function appNavigate(uri: ?string) {
_appNavigateToOptionalLocation(dispatch, getState, parseURIString(uri));
}
/**
* Redirects to another page generated by replacing the path in the original URL
* with the given path.
*
* @param {(string)} pathname - The path to navigate to.
* @returns {Function}
*/
export function redirectWithStoredParams(pathname: string) {
return (dispatch: Dispatch<*>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const newLocationURL = new URL(locationURL.href);
newLocationURL.pathname = pathname;
window.location.assign(newLocationURL.toString());
};
}
/**
* Reloads the page by restoring the original URL.
*
* @returns {Function}
*/
export function reloadWithStoredParams() {
return (dispatch: Dispatch<*>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const windowLocation = window.location;
const oldSearchString = windowLocation.search;
windowLocation.replace(locationURL.toString());
if (window.self !== window.top
&& locationURL.search === oldSearchString) {
// NOTE: Assuming that only the hash or search part of the URL will
// be changed!
// location.reload will not trigger redirect/reload for iframe when
// only the hash params are changed. That's why we need to call
// reload in addition to replace.
windowLocation.reload();
}
};
}
/**
* Triggers an in-app navigation to a specific location URI.
*
@@ -83,11 +124,10 @@ function _appNavigateToMandatoryLocation(
});
}
const profile = getProfile(getState());
const profile = getState()['features/base/profile'];
return promise.then(() => dispatch(setConfig(
_mergeConfigWithProfile(config, profile)
)));
return promise.then(() =>
dispatch(setConfig(_mergeConfigWithProfile(config, profile))));
}
}

View File

@@ -12,9 +12,10 @@ import {
localParticipantJoined,
localParticipantLeft
} from '../../base/participants';
import { getProfile } from '../../base/profile';
import '../../base/profile';
import { Fragment, RouteRegistry } from '../../base/react';
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
import { SoundCollection } from '../../base/sounds';
import { PersistenceRegistry } from '../../base/storage';
import { toURLString } from '../../base/util';
import { OverlayContainer } from '../../overlay';
@@ -123,7 +124,7 @@ export class AbstractApp extends Component {
*/
componentWillMount() {
this._init.then(() => {
const { dispatch } = this._getStore();
const { dispatch, getState } = this._getStore();
dispatch(appWillMount(this));
@@ -144,7 +145,7 @@ export class AbstractApp extends Component {
}
// Profile is the new React compatible settings.
const profile = getProfile(this._getStore().getState());
const profile = getState()['features/base/profile'];
if (profile) {
localParticipant.email
@@ -274,6 +275,7 @@ export class AbstractApp extends Component {
<Provider store = { this._getStore() }>
<Fragment>
{ this._createElement(component) }
<SoundCollection />
<OverlayContainer />
</Fragment>
</Provider>
@@ -381,7 +383,8 @@ export class AbstractApp extends Component {
return (
this.props.defaultURL
|| getProfile(this._getStore().getState()).serverURL
|| this._getStore().getState()['features/base/profile']
.serverURL
|| DEFAULT_URL);
}
@@ -500,7 +503,7 @@ export class AbstractApp extends Component {
/**
* Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
*
* @param {string|Object} url - The URL to navigate this {@code AbstractApp}
* @param {Object|string} url - The URL to navigate this {@code AbstractApp}
* to (i.e. the URL to open).
* @protected
* @returns {void}

View File

@@ -17,6 +17,7 @@ import '../../mobile/callkit';
import '../../mobile/external-api';
import '../../mobile/full-screen';
import '../../mobile/permissions';
import '../../mobile/picture-in-picture';
import '../../mobile/proximity';
import '../../mobile/wake-lock';
@@ -36,6 +37,13 @@ export class App extends AbstractApp {
static propTypes = {
...AbstractApp.propTypes,
/**
* Whether Picture-in-Picture is enabled. If {@code true}, a toolbar
* button is rendered in the {@link Conference} view to afford entering
* Picture-in-Picture.
*/
pictureInPictureEnabled: PropTypes.bool,
/**
* Whether the Welcome page is enabled. If {@code true}, the Welcome
* page is rendered when the {@link App} is not at a location (URL)

View File

@@ -3,6 +3,7 @@ import React from 'react';
import '../../base/responsive-ui';
import { getLocationContextRoot } from '../../base/util';
import '../../chat';
import '../../room-lock';
import { AbstractApp } from './AbstractApp';

View File

@@ -3,7 +3,7 @@
import {
ACTION_PINNED,
ACTION_UNPINNED,
createAudioOnlyDisableEvent,
createAudioOnlyChangedEvent,
createPinnedEvent,
sendAnalytics
} from '../../analytics';
@@ -20,6 +20,7 @@ import UIEvents from '../../../../service/UI/UIEvents';
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
import {
conferenceLeft,
createConference,
setAudioOnly,
setLastN,
@@ -32,8 +33,10 @@ import {
DATA_CHANNEL_OPENED,
SET_AUDIO_ONLY,
SET_LASTN,
SET_RECEIVE_VIDEO_QUALITY
SET_RECEIVE_VIDEO_QUALITY,
SET_ROOM
} from './actionTypes';
import { JITSI_CONFERENCE_URL_KEY } from './constants';
import {
_addLocalTracksToConference,
_handleParticipantError,
@@ -77,6 +80,9 @@ MiddlewareRegistry.register(store => next => action => {
case SET_RECEIVE_VIDEO_QUALITY:
return _setReceiveVideoQuality(store, next, action);
case SET_ROOM:
return _setRoom(store, next, action);
case TRACK_ADDED:
case TRACK_REMOVED:
return _trackAddedOrRemoved(store, next, action);
@@ -126,10 +132,22 @@ function _connectionEstablished({ dispatch }, next, action) {
function _conferenceFailedOrLeft({ dispatch, getState }, next, action) {
const result = next(action);
if (getState()['features/base/conference'].audioOnly) {
sendAnalytics(createAudioOnlyDisableEvent());
logger.log('Audio only disabled');
dispatch(setAudioOnly(false));
const state = getState();
const { audioOnly } = state['features/base/conference'];
const { startAudioOnly } = state['features/base/profile'];
// FIXME: Consider implementing a standalone audio-only feature that handles
// all these state changes.
if (audioOnly) {
if (!startAudioOnly) {
sendAnalytics(createAudioOnlyChangedEvent(false));
logger.log('Audio only disabled');
dispatch(setAudioOnly(false));
}
} else if (startAudioOnly) {
sendAnalytics(createAudioOnlyChangedEvent(true));
logger.log('Audio only enabled');
dispatch(setAudioOnly(true));
}
return result;
@@ -318,6 +336,62 @@ function _setReceiveVideoQuality({ dispatch, getState }, next, action) {
return next(action);
}
/**
* Notifies the feature {@code base/conference} that the redix action
* {@link SET_ROOM} 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 {@code SET_ROOM} which is being
* dispatched in the specified store.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _setRoom({ dispatch, getState }, next, action) {
const result = next(action);
// By the time SET_ROOM is dispatched, base/connection's locationURL and
// base/conference's leaving should be the only conference-related sources
// of truth.
const state = getState();
const {
leaving,
...stateFeaturesBaseConference
} = state['features/base/conference'];
const { locationURL } = state['features/base/connection'];
const dispatchConferenceLeft = new Set();
// Figure out which of the JitsiConferences referenced by base/conference
// have not dispatched or are not likely to dispatch CONFERENCE_FAILED and
// CONFERENCE_LEFT.
// eslint-disable-next-line guard-for-in
for (const p in stateFeaturesBaseConference) {
const v = stateFeaturesBaseConference[p];
// Does the value of the base/conference's property look like a
// JitsiConference?
if (v && typeof v === 'object') {
const url = v[JITSI_CONFERENCE_URL_KEY];
if (url && v !== leaving && url !== locationURL) {
dispatchConferenceLeft.add(v);
}
}
}
// Dispatch CONFERENCE_LEFT for the JitsiConferences referenced by
// base/conference which have not dispatched or are not likely to dispatch
// CONFERENCE_FAILED or CONFERENCE_LEFT.
for (const conference of dispatchConferenceLeft) {
dispatch(conferenceLeft(conference));
}
return result;
}
/**
* Synchronizes local tracks from state with local tracks in JitsiConference
* instance.

View File

@@ -1,10 +1,13 @@
/* @flow */
// @flow
import type { Dispatch } from 'redux';
import {
CONFIG_WILL_LOAD,
LOAD_CONFIG_ERROR,
SET_CONFIG
} from './actionTypes';
import { setConfigFromURLParams } from './functions';
/**
* Signals that the configuration for a specific locationURL will be loaded now.
@@ -51,14 +54,36 @@ export function loadConfigError(error: Error, locationURL: string | URL) {
*
* @param {Object} config - The configuration to be represented by the feature
* base/config.
* @returns {{
* type: SET_CONFIG,
* config: Object
* }}
* @returns {Function}
*/
export function setConfig(config: Object = {}) {
return {
type: SET_CONFIG,
config
return (dispatch: Dispatch<*>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
// Now that the loading of the config was successful override the values
// with the parameters passed in the hash part of the location URI.
// TODO We're still in the middle ground between old Web with config,
// interfaceConfig, and loggingConfig used via global variables and new
// Web and mobile reading the respective values from the redux store.
// On React Native there's no interfaceConfig at all yet and
// loggingConfig is not loaded but there's a default value in the redux
// store.
// Only the config will be overridden on React Native, as the other
// globals will be undefined here. It's intentional - we do not care to
// override those configs yet.
locationURL
&& setConfigFromURLParams(
// On Web the config also comes from the window.config global,
// but it is resolved in the loadConfig procedure.
config,
window.interfaceConfig,
window.loggingConfig,
locationURL);
dispatch({
type: SET_CONFIG,
config
});
};
}

View File

@@ -36,6 +36,7 @@ const WHITELISTED_KEYS = [
'desktopSharingChromeExtId',
'desktopSharingChromeMinExtVersion',
'desktopSharingChromeSources',
'desktopSharingFrameRate',
'desktopSharingFirefoxDisabled',
'desktopSharingSources',
'disable1On1Mode',
@@ -63,10 +64,12 @@ const WHITELISTED_KEYS = [
'failICE',
'firefox_fake_device',
'forceJVB121Ratio',
'gatherStats',
'hiddenDomain',
'hosts',
'iAmRecorder',
'iAmSipGateway',
'iceTransportPolicy',
'ignoreStartMuted',
'nick',
'openBridgeChannel',
@@ -176,7 +179,7 @@ export function obtainConfig(
* @returns {void}
*/
export function overrideConfigJSON(
config: Object, interfaceConfig: Object, loggingConfig: Object,
config: ?Object, interfaceConfig: ?Object, loggingConfig: ?Object,
json: Object) {
for (const configName of Object.keys(json)) {
let configObj;
@@ -209,6 +212,8 @@ export function overrideConfigJSON(
}
}
/* eslint-enable max-params, no-shadow */
/**
* Whitelist only config.js, skips this for others configs
* (interfaceConfig, loggingConfig).
@@ -217,9 +222,9 @@ export function overrideConfigJSON(
* @param {string} configName - The config name, one of config,
* interfaceConfig, loggingConfig.
* @param {Object} configJSON - The object with keys and values to override.
* @private
* @returns {Object} - The result object only with the keys
* that are whitelisted.
* @private
*/
function _getWhitelistedJSON(configName, configJSON) {
if (configName !== 'config') {
@@ -229,40 +234,48 @@ function _getWhitelistedJSON(configName, configJSON) {
return _.pick(configJSON, WHITELISTED_KEYS);
}
/* eslint-enable max-params, no-shadow */
/* eslint-disable max-params */
/**
* Converts 'URL_PARAMS' to JSON object.
* We have:
* {
* "config.disableAudioLevels": false,
* "config.channelLastN": -1,
* "interfaceConfig.APP_NAME": "Jitsi Meet"
* }.
* We want to have:
* {
* "config": {
* "disableAudioLevels": false,
* "channelLastN": -1
* },
* interfaceConfig: {
* "APP_NAME": "Jitsi Meet"
* }
* }.
* Inspects the hash part of the location URI and overrides values specified
* there in the corresponding config objects given as the arguments. The syntax
* is: {@code https://server.com/room#config.debug=true
* &interfaceConfig.showButton=false&loggingConfig.something=1}.
*
* In the hash part each parameter will be parsed to JSON and then the root
* object will be matched with the corresponding config object given as the
* argument to this function.
*
* @param {Object} config - This is the general config.
* @param {Object} interfaceConfig - This is the interface config.
* @param {Object} loggingConfig - The logging config.
* @param {URI} location - The new location to which the app is navigating to.
* @returns {void}
*/
export function setConfigFromURLParams() {
const params = parseURLParams(window.location);
const { config, interfaceConfig, loggingConfig } = window;
export function setConfigFromURLParams(
config: ?Object,
interfaceConfig: ?Object,
loggingConfig: ?Object,
location: Object) {
const params = parseURLParams(location);
const json = {};
// TODO We're still in the middle ground between old Web with config,
// interfaceConfig, and loggingConfig used via global variables and new Web
// and mobile reading the respective values from the redux store. On React
// Native there's no interfaceConfig at all yet and loggingConfig is not
// loaded but there's a default value in the redux store.
// At this point we have:
// params = {
// "config.disableAudioLevels": false,
// "config.channelLastN": -1,
// "interfaceConfig.APP_NAME": "Jitsi Meet"
// }
// We want to have:
// json = {
// config: {
// "disableAudioLevels": false,
// "channelLastN": -1
// },
// interfaceConfig: {
// "APP_NAME": "Jitsi Meet"
// }
// }
config && (json.config = {});
interfaceConfig && (json.interfaceConfig = {});
loggingConfig && (json.loggingConfig = {});
@@ -281,3 +294,5 @@ export function setConfigFromURLParams() {
overrideConfigJSON(config, interfaceConfig, loggingConfig, json);
}
/* eslint-enable max-params */

View File

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

Some files were not shown because too many files have changed in this diff Show More