Compare commits

...

178 Commits

Author SHA1 Message Date
bgrozev
6cc272fbd1 log: Fixes a log message 2018-03-19 14:41:18 -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
virtuacoplenny
7f78050513 Merge pull request #2434 from slavakisel/external-notification-screen-sharing
Implement external API notification about screen sharing status
2018-02-22 17:39:03 -08:00
hristoterezov
2d9b906a3b feat(ss_framerate): Add config option for min/max frame rate. 2018-02-22 19:37:17 -06:00
Leonard Kim
1f82ce3d19 feat(unsupported-browser): show dial-in for mobile
- Move the existing components for the static dial in page into
  a separate folder for easier reuse.
- Reuse those components for displaying dial-on numbers on the
  mobile page for unsupported browsers.
- Modify those components to support having tel protocol
  links on the dial-in numbers.
- Have DialInSummary, formerly DialInInfoPage, respect a
  passed in className prop for easier styling differences.
2018-02-22 17:29:03 -06:00
Leonard Kim
68b710a222 fix(dial-in): allow text select in numbers page 2018-02-22 17:29:03 -06:00
brian baldino
9fea5e89b3 don't show 'user has left' notification for hidden users 2018-02-22 13:41:24 -06:00
Slava Kisel
e1d849e3a0 Implement external API notification about screen sharing status 2018-02-22 10:25:03 +03:00
virtuacoplenny
74a92f83c7 feat(welcome): new design (#2492)
* feat(welcome): new design

* squash: update strings

* squash: copy/paste error?

* squash: remove welcome page disabling checks

* squash: change strings again

* squash: background var

* squash: title and desc css as variables
2018-02-21 22:58:55 -06:00
virtuacoplenny
e47802538e ref(invite): remove InviteDialog (#2483)
* ref(invite): remove InviteDialog

InviteDialog functionality has been moved into InfoDialog.
The InviteButton has been temporarily hacked to show one
of its dropdown options instead as the button. Future
work will bring in a redesigned InviteModal that the button
will open.

* squash: filter invalid options and map valid options

* squash: update strings
2018-02-17 13:53:39 -06:00
Lyubo Marinov
e2cf7a788d [RN] Make full-screen more resilient on Android (Coding style: consistency) 2018-02-14 12:28:22 -06:00
Saúl Ibarra Corretgé
4757c1ebca [RN] Make full-screen more resilient on Android
On Android we go into "immersive mode" when in a conference, this is our way of
being full-creen. There are occasions, however, in which Android takes us out of
immerfive mode without us (the application / SDK) knowing: when a child activity
is started, a modal window shown, etc.

In order to be resilient to any possible change in the immersive mode, register
a listener which will be called when Android changes it, so we can re-eavluate
if we need it and thus re-enable it.
2018-02-13 15:00:36 -06:00
virtuacoplenny
59d046dca9 feat(info): new dialog design (#2452)
* feat(info): new dialog design

- Add display of a dial in number.
- Add a static page to show a full list of dial in numbers.
- Add password management.
- Invite modal will be changed soon to remove password and
  dial-in.

* squash: add classes for torture tests

* squash: class for local lock for torture tests

* squash: more classes for torture tests

* squash: more classes, work around linter

* squash: remove unused string?

* squash: work around linter and avoid react warnings

* squash: pixel push, add bold

* squash: font size bump

* squash: NumbersTable -> NumbersList

* squash: document response from fetching numbers

* squash: showEdit -> editEnabled, pixel push padding for alignment

* squash: pin -> conferenceID

* squash: prepare to receive defaultCountry from api
2018-02-13 13:46:47 -06:00
Lyubo Marinov
0bbcd3181c [RN] Adjust Conference for the reduced UI mode (Coding style) 2018-02-13 13:24:10 -06:00
Saúl Ibarra Corretgé
7a9ff9975a [RN] Adjust Conference for the reduced UI mode 2018-02-13 11:59:12 -06:00
Lyubo Marinov
10f72f8e40 [RN] Unpin participant and set last N to 1 if the filmstrip is disabled (Coding style: consistency) 2018-02-13 11:58:26 -06:00
Saúl Ibarra Corretgé
417e1e83e7 [RN] Unpin participant and set last N to 1 if the filmstrip is disabled 2018-02-13 09:58:44 -06:00
Lyubo Marinov
cacc4bd769 [RN] Dynamically adjust LargeView's Avatar to available size (Coding style: comments, flow)
Flow caught an incorrect function call.
2018-02-13 09:58:43 -06:00
Saúl Ibarra Corretgé
1419247801 [RN] Dynamically adjust LargeView's Avatar to available size
When in PiP mode the LargeView will not be large enough to hold the avatar (for
those interested in the details, our avatar's size is 200, and in PiP mode the
app is resized to about 150).

In order to solve it, this PR refactors how the avatar style is passed along,
reducing it to a single "size" prop. With this only prop, the Avatar compononent
will compute the width, height and borderRadius, plus deal with some Android
shenanigans.

In addition, the LargeView component now uses DimensionsDetector to check its
own size and adjust the size prop passed to the Avatar component as needed.
2018-02-13 09:58:43 -06:00
virtuacoplenny
4fb37c38eb fix(large-video): do not show background for Firefox and temasys (#2316)
* ref(large-video): reactify background

This is pre-requisite work for disabling the background on
certain browsers, namely Firefox. By moving the component
to react, and in general encapsulating background logic,
selectively disabling the background will be easier.

The component was left for LargeVideo to update so it can
continue to coordinate update timing with the actual large
video display. If the background were moved completely into
react and redux with LargeVideo, then background updates would
occur before large video updates causing visual jank.

* fix(large-video): do not show background for Firefox and temasys

Firefox has performance issues with adding filter effects on
animated elements. On temasys, the background videos weren't
really displaying anyway.

* some props refactoring

Instead of passing in classes to LargeVideoBackground, rely on
explicit props. At some point LargeVideo will have to be reactified
and the relationsihp between it and LargeVideoBackground might
change, so for now make use of props to be explicit about
how LargeVideoBackground can be modified.

Also, set the jitsiTrack to display on LargeVideoBackground to
null if the background is not displayed. This was an existing
optimization, although previously done with pausing and playing.

* squash: use newly exposed RTCBrowserType

* squash: rebase and use new lib browser util

* squash: move hiding logic all into LargeVideo

* squash: remove hiding of background on stream change. hopefully doesnt break anything
2018-02-12 16:29:29 -08:00
Leonard Kim
f3b5ed2ef4 ref(notifications): convert Thank You message to a notification 2018-02-12 17:53:29 -06:00
Leonard Kim
7341c7bf84 ref(notifications): stop passing around Notifications component
Passing around of the component was used when there were two
independent Notification components. Now that there is only
one Notification component, it is not necessary to pass
around the component.
2018-02-12 17:53:29 -06:00
Leonard Kim
5d31532cbb fix(chat): return formatted body to show smileys 2018-02-12 15:34:21 -07:00
virtuacoplenny
423c8d3f53 Merge pull request #2479 from bgrozev/ga-tweaks
Ga tweaks
2018-02-12 12:35:23 -08:00
Boris Grozev
a1ba7beff9 feat: Do not include the callstats name in google analytics. 2018-02-12 14:00:15 -06:00
Boris Grozev
03fc711e81 feat: Makes the google analytics tracking id configurable. 2018-02-12 14:00:04 -06:00
Lyubo Marinov
a370a88d19 [RN] Add ability to enable /disable the toolbox (Coding style: comments, consistency) 2018-02-12 11:53:42 -06:00
Saúl Ibarra Corretgé
7153d94dad [RN] Add ability to enable /disable the toolbox 2018-02-12 11:52:54 -06:00
Lyubo Marinov
240fff74c7 [RN] Add ability to enable / disable the filmstrip (Coding style: comments, naming) 2018-02-12 11:52:46 -06:00
Saúl Ibarra Corretgé
7bd8b7948f [RN] Add ability to enable / disable the filmstrip
This is only implemented for mobile at the moment, since web doesn't handle
visibility within the Filmstrip component yet, so this should be added right
then, too.
2018-02-12 10:02:34 -06:00
Дамян Минков
a505c01e9e Update uninstall documentation.
The package jitsi-meet-prosody was missing from the list of packages.
2018-02-09 10:28:01 +01:00
damencho
990b1eddf2 Adds strophe.js and plugin-disco as a dependency to fix build problem.
Bumps lib-jitsi-meet to latest. There was a problem that jitsi-meet build fail if anybody touches package.json (including PR testing), this happen after start using custom strophe.js from github:jitsi/strophejs.
The error:
    ERROR in ../strophejs-plugin-disco/lib/strophe.disco.js
    Module not found: Error: Can't resolve 'strophe.js' in '/Users/dminkov/dev/jitsi-meet/node_modules/strophejs-plugin-disco/lib'
     @ ../strophejs-plugin-disco/lib/strophe.disco.js 4:126-147 4:196-227
     @ ./modules/xmpp/xmpp.js
     @ ./JitsiConnection.js
     @ ./JitsiMeetJS.js
     @ ./index.js

Without strophejs-plugin-disco jitsi-meet builds but on runtime loading fail with:
Error: Missing strophe-plugins (disco plugin is required)!

FIXME: We should remove this once strophe.js releases new version and we are back to the official one inside lib-jitsi-meet.
2018-02-08 23:50:45 -06:00
damencho
abbfd3de9a Adds strophe.js as a dependency to fix build problem.
Bumps lib-jitsi-meet to latest. There was a problem that jitsi-meet build fail if anybody touches package.json (including PR testing), this happen after start using custom strophe.js from github:jitsi/strophejs.
The error:
    ERROR in ../strophejs-plugin-disco/lib/strophe.disco.js
    Module not found: Error: Can't resolve 'strophe.js' in '/Users/dminkov/dev/jitsi-meet/node_modules/strophejs-plugin-disco/lib'
     @ ../strophejs-plugin-disco/lib/strophe.disco.js 4:126-147 4:196-227
     @ ./modules/xmpp/xmpp.js
     @ ./JitsiConnection.js
     @ ./JitsiMeetJS.js
     @ ./index.js

FIXME: We should remove this once strophe.js releases new version and we are back to the official one inside lib-jitsi-meet.
2018-02-08 22:48:25 -06:00
Saúl Ibarra Corretgé
bd301403c4 [RN] Fix app startup from a CallKit intent
Story time.  Currently the app can be started in 4 ways:

- just tapping on the icon
- via a deep link
- via a universal link
- via the phone's recent calls list

The last 3 options will make the app join the specified room upon launch. React
Native's Linking module implements the necessary bits to handle deep or
universal linking, but CallKit is out of its scope.

In order to blend any type of app startup mode, a new LaunchOptions module (iOS
only) exports a getInitialURL function, akin to the one in the Linking module,
but taking CallKit instents into consideration. This function is then used to
make app startup with a URL consistent across all different modes.
2018-02-07 10:12:10 -06:00
bgrozev
d481c6f736 chore: Updates lib-jitsi-meet to 5f8c0a662af086e7bcc19c010f1129afc9b6… (#2460)
* chore: Updates lib-jitsi-meet to 5f8c0a662af086e7bcc19c010f1129afc9b6d650

* squash: revert changes to package-lock.json except for the lib-jitsi-meet version change.
2018-02-06 21:21:39 -07:00
Дамян Минков
ba94ba30c5 Handles connection failed event details (passing them to analytics). (#2432)
* Handles connection failed event details (passing them to analytics).

* Fixing comments.

* Updates depending versions to be able to test.

* Fixing comments.

* Fixes wrong jsdoc.
2018-02-06 14:54:21 -08:00
Saúl Ibarra Corretgé
5305557ce5 [RN] Add a "reduced UI" mode
It's detected based on a size threshold.
2018-02-06 15:53:52 -06:00
Lyubo Marinov
c9d8b5c827 Finally! Let there be... responsive-ui!
We started on the way to responsive UI and its design with aspect ratio
and keeping the filmstrip on the short side of the app's visible
rectangle.

Shortly, we're going to introduce reduced UI for Picture-in-Picture. And
that's where we'll need another dimensions-based detector akin to the
aspect ratio detector.

While the AspectRatioDetector, the up-and-coming ReducedUIDetector, and
their base DimensionsDetector are definitely separate abstractions and
implementations not mixed for the purposes of easy extensibility and
maintenance, the three of them are our building blocks on top of which
we'll build our responsive UI.
2018-02-06 15:53:27 -06:00
Saúl Ibarra Corretgé
0ad1c88cd2 [RN] Refactor AspectRatioDetector
Factor out the dimensions detection login into a DimensionsDetector component.
2018-02-06 11:21:12 -06:00
Saúl Ibarra Corretgé
78fbfba573 [iOS] Fix initial CallKit muted state
Turns out this was a bit more involved than I originally thought due to an
interesting (corner) case: IFF the user was never asked about microphone
permissions and the call starts with audio muted, unmuting from the CallKit
interface won't work (iOS won't show the prompt, it fails immediately) and we
need to sync the mute state back.
2018-02-06 10:24:06 -06:00
Saúl Ibarra Corretgé
9e53d40b9c [RN] Honor filmstrip visibility state 2018-02-05 15:55:05 -06:00
Lyubo Marinov
aa314c10ac Coding style: consistent naming, one name per abstraction
Instead of having visible and visibility and setToolboxVisible and
setFilmstripVisibility, have only visible as a name.
2018-02-05 15:55:04 -06:00
Lyubo Marinov
62c9762793 [RN] Protect AbstractApp and localStorage initialization 2018-02-05 15:26:01 -06:00
Lyubo Marinov
d7dddb2509 Introduce base/storage to represent the Web Storage API and persistence-related customizations 2018-02-02 15:13:26 -06:00
Lyubo Marinov
83243d5980 [RN] Fix legacy recent-list storage 2018-02-02 15:13:26 -06:00
Zoltan Bettenbuk
6e05cab46e [RN] Fix legacy recent-list storage 2018-02-02 15:13:26 -06:00
Lyubo Marinov
7954d5fd39 Coding style 2018-02-02 15:13:26 -06:00
zbettenbuk
158cadf4f9 Improve persistency layer 2018-02-02 15:13:26 -06:00
Lyubo Marinov
f35578c803 [RN] Polyfill __filename ASAP 2018-02-02 15:13:26 -06:00
Saúl Ibarra Corretgé
c087e90099 [RN] Fix setReceivedVideoQuality if we are not yet in a conference
It may happen that such action is fired while joining.
2018-02-02 14:33:49 -06:00
Saúl Ibarra Corretgé
da0ae73d10 [RN] Fix pinParticipant if we are not yet in a conference
It may happen that such action is fired while joining.
2018-02-02 14:32:15 -06:00
Saúl Ibarra Corretgé
b4d44f367d [RN] aspect-ratio: preserve mode when width === height
If the view gets resized to a 1:1 aspect ratio, remember the previous mode to
avoid flickering when going back to a larger size or different aspect ratio.
2018-02-02 14:19:08 -06:00
hristoterezov
083f6b400b chore(capabilities.json): deploy 2018-02-01 17:21:52 -06:00
Saúl Ibarra Corretgé
dd5ae49217 Merge pull request #2407 from zbettenbuk/new-welcome-screen
Add URL validation and larger distance behind the back button
2018-01-31 16:26:09 +01:00
zbettenbuk
6a9e6db3be [RN] Validate the URL in app-settings 2018-01-31 16:06:24 +01:00
virtuacoplenny
c4468cb7b8 chore(deps): update lib-jitsi-meet for connection quality logging (#2436) 2018-01-30 14:52:25 -08:00
Leonard Kim
80c4205fb8 chore(deps): update lib-jitsi-meet for ie11 browser caps fix 2018-01-29 16:16:21 -06:00
zbettenbuk
aa9efd6f69 [RN] Improve app-settings back button style 2018-01-29 10:58:46 +01:00
Piérre Reimertz
6f8f64ba48 [iOS] Fix crash if the app display name is not set
Fixes: #2377 #2267 #2158
2018-01-29 10:34:01 +01:00
Leonard Kim
1c3cef1eed fix(notifications): reduce duration of initially muted notification
The current notification for starting muted is 2 minutes, which
may seem like "forever" so reduce it to dismiss faster.
2018-01-28 18:48:56 -06:00
Leonard Kim
2720c76e4d fix(password): do not let guests edit password when roles are enabled
If config.enableUserRolesBasedOnToken is true, only let moderators
and non-guests modify the password. Otherwise, only let moderators
edit the password.
2018-01-28 18:48:24 -06:00
virtuacoplenny
4ab34589c8 Merge pull request #2431 from jitsi/recommended_browsers_link
fix(recommended-browsers): Fix link
2018-01-28 13:41:54 -08:00
hristoterezov
ed36132e94 fix(recommended-browsers): Fix link 2018-01-28 11:51:08 -06:00
virtuacoplenny
f43687944c Merge pull request #2429 from jitsi/fix_chromium
chore(lib-jitsi-meet): Update the version.
2018-01-27 11:45:46 -08:00
hristoterezov
dda0ea0ba9 chore(lib-jitsi-meet): Update the version. 2018-01-27 13:02:46 -06:00
Lyubo Marinov
e1f967869a [RN] Add builtin translations 2018-01-26 12:18:43 -06:00
Saúl Ibarra Corretgé
8673083829 [RN] Add builtin translations
Load all of them as imports, so the packager includes them in the bundle. Then
register them with the i18next library.
2018-01-26 16:27:33 +01:00
Lyubo Marinov
b52e584327 "feat(TPC): append TPC ID to stream IDs" & "fix(RTC): protect from counter overflow" 2018-01-25 11:21:40 -06:00
hristoterezov
4c65262a87 fix(browser-caps): Deploy. 2018-01-25 10:44:24 -06:00
bgrozev
7ce670df0c doc: Add -f to update-ca-certificates 2018-01-25 10:42:43 -06:00
Boris Grozev
f5f341ca9e npm: Updates lib-jitsi-meet to e895c9b86f57a288f644dcc61f81771034b932da. 2018-01-24 14:15:52 -06:00
Lyubo Marinov
f29fbb6757 [iOS] WebRTC 63 with 2 backports to fix a crash 2018-01-24 13:37:54 -06:00
virtuacoplenny
e5e3c6c6c4 Merge pull request #2410 from bgrozev/set-product
feat: Sets the "product" field in the analytics handler constructor.
2018-01-23 15:25:59 -08:00
Boris Grozev
dc92fb5073 feat: Sets the "product" field in the analytics handler constructor. 2018-01-23 16:14:31 -06:00
damencho
2478176f23 Adds uiLoaded event in iframe API, fired when all resources are loaded. 2018-01-23 15:43:49 -06:00
virtuacoplenny
12ec982067 Merge pull request #2400 from jitsi/browser_caps2
fix(lib-jitsi-meet): RTCBrowserType -> browser.
2018-01-22 13:57:50 -08:00
hristoterezov
c9e3e5052d fix(lib-jitsi-meet): RTCBrowserType -> browser. 2018-01-22 15:56:06 -06:00
Emil Ivov
22401614a7 Merge pull request #2401 from virtuacoplenny/lenny/spot-feedback-api
feat(api): expose a way to submit feedback
2018-01-19 16:57:18 -06:00
Leonard Kim
762f529f1d feat(api): expose a way to submit feedback
Spot will need a way to submit call feedback using the iframe
api. For now expose a method on conference.js to submit that
feedback. Exposing on conference.js looks to be the existing
pattern... Also add an event to notify consumers of the iframe
api that feedback was submitted, as postMessage is async
and the notification can at least give some guarantee maybe.

I haven't updated documentation yet as I'm not confident
about this api.
2018-01-19 14:27:44 -08:00
Saúl Ibarra Corretgé
1f6b743bec [RN] Join room when pressing "go" on the keyboard
Improve the experience when joining a room by removing the need to tap the join
button. The keyboard type has also been set to "go", which translated on the
builtin keyboard button label to be "go" (it's builtin, the operating system
translates it). This works on both Android and iOS.
2018-01-19 16:10:51 -06:00
Lyubo Marinov
48f4317adb [RN] Make video track fade-in effect cross-platform 2018-01-19 16:05:59 -06:00
Saúl Ibarra Corretgé
75f6786588 [RN] Make video track fade-in effect cross-platform
Android uses a SurfaceView to render video, which is not quite a View, so the
fade-in animation (which varies the opacity) doesn't work.

Instead, add an opaque black view covering the video, which transitions to
transparent. This creates much smoother transitions on Android, while behaving
the same.

In addition, I removed the flip animation for local tracks, which is no longer
used, since the camera is switched without changing tracks.
2018-01-19 15:32:17 -06:00
Lyubo Marinov
3ec4d67a99 [RN] Strip spaces when parsing URLs 2018-01-19 15:26:13 -06:00
Saúl Ibarra Corretgé
a23eec55e8 [RN] Strip spaces when parsing URLs 2018-01-19 15:14:51 -06:00
Lyubo Marinov
bdf2ecfe4b [iOS] CocoaPods 1.4.0 2018-01-19 15:00:18 -06:00
Lyubo Marinov
fc36759114 [RN] WebRTC 63 2018-01-19 14:56:58 -06:00
Saúl Ibarra Corretgé
9c2849a663 [Android] Enable Java 1.8 compatibility support
It was recently introduced in WebRTC, so we we need to enable it project wide.
As for what features are supportd, see:
https://developer.android.com/studio/write/java8-support.html
2018-01-19 14:05:25 -06:00
Saúl Ibarra Corretgé
98ff20a026 [RN] Simplify initialization of AsyncStorage 2018-01-19 09:41:34 -06:00
Lyubo Marinov
b04661b40b [RN] Revert unintentional disabling of the yellow box 2018-01-18 21:11:24 -06:00
Lyubo Marinov
112c856850 Fix settings screen layout on iOS and add soft back button 2018-01-18 15:45:25 -06:00
zbettenbuk
410dc132e1 Fix settings screen layout on iOS and add soft back button 2018-01-18 10:06:26 -06:00
Saúl Ibarra Corretgé
b7f950f5f7 feat(analytics): shield sending analytics events
Any failure in analytics should not prevent the natural flow of the code. Shield
the function by catching and logging any exception.
2018-01-17 16:46:54 -06:00
Paweł Domas
7ad875e735 fix(RN logging): sync with logging_config.js (#2382) 2018-01-17 10:59:48 -08:00
Saúl Ibarra Corretgé
41aa704e1f [RN] Update React Native to version 0.51.0 2018-01-11 11:04:28 -06:00
Saúl Ibarra Corretgé
e00ea353e8 Merge pull request #2380 from saghul/app-settings-kbd
[RN] Use the "email" keyboard for the email field
2018-01-10 15:52:27 +01:00
Saúl Ibarra Corretgé
6f93424d7c [RN] Use the "email" keyboard for the email field 2018-01-10 14:30:34 +01:00
Saúl Ibarra Corretgé
292f3ab1bd [Android] Fix crash if settings activity cannot be opened
The documentation states this is possible, so make sure we handle such errors.

Ref:
https://developer.android.com/reference/android/provider/Settings.html#ACTION_APPLICATION_DETAILS_SETTINGS
2018-01-09 13:12:53 -06:00
Boris Grozev
a3cb081609 fix: Fixes an exception when the chat button and shortcut are clicked. 2018-01-09 10:36:55 -08:00
Leonard Kim
64c5ae1c48 fix(close): use string concatenation for ie11 2018-01-08 16:26:54 -06:00
bgrozev
259004b8bf fix: Fixes the user ID read from JWT. (#2366) 2018-01-08 11:27:08 -06:00
ibauersachs
4fea22676b Commit from translate.jitsi.org by user ibauersachs.: 410 of 410 strings translated (0 fuzzy). 2018-01-08 16:03:42 +00:00
zbettenbuk
05a492241f Fix local participant details on web 2018-01-05 12:58:58 -06:00
virtuacoplenny
8ec4697a27 Merge pull request #2353 from jitsi/suboptimal_experience
feat(notification): Add suboptimal browser exp notification.
2018-01-04 14:36:58 -08:00
hristoterezov
a357b0cf14 feat(notification): Add suboptimal browser exp notification. 2018-01-04 15:57:17 -06:00
348 changed files with 15510 additions and 9294 deletions

View File

@@ -56,8 +56,8 @@ suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
@@ -76,4 +76,4 @@ module.file_ext=.jsx
module.file_ext=.json
[version]
^0.56.0
^0.57.0

View File

@@ -33,6 +33,8 @@ deploy-appbundle:
$(BUILD_DIR)/external_api.min.map \
$(BUILD_DIR)/device_selection_popup_bundle.min.js \
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
$(BUILD_DIR)/dial_in_info_bundle.min.js \
$(BUILD_DIR)/dial_in_info_bundle.min.map \
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.map \
$(OUTPUT_DIR)/analytics-ga.js \
@@ -43,6 +45,7 @@ deploy-lib-jitsi-meet:
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
$(DEPLOY_DIR)
deploy-css:

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

@@ -4,16 +4,23 @@
/**
*
*/
function Analytics() {
function Analytics(options) {
/* eslint-disable */
if (!options.googleAnalyticsTrackingId) {
console.log(
'Failed to initialize Google Analytics handler, no tracking ID');
return;
}
/**
* Google Analytics
* TODO: Keep this local, there's no need to add it to window.
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-319188-14', 'jit.si');
ga('create', options.googleAnalyticsTrackingId, 'jit.si');
ga('send', 'pageview');
/* eslint-enable */
@@ -93,6 +100,7 @@
// lengthy and is probably included from elsewhere.
for (const property in event.attributes) {
if (property !== 'permanent_user_agent'
&& property !== 'permanent_callstats_name'
&& event.attributes.hasOwnProperty(property)) {
// eslint-disable-next-line prefer-template
label += property + '=' + event.attributes[property] + '&';
@@ -114,7 +122,7 @@
* lib-jitsi-meet.
*/
Analytics.prototype.sendEvent = function(event) {
if (!event) {
if (!event || !ga) {
return;
}

View File

@@ -107,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);
}
}
```
@@ -132,6 +132,10 @@ which displays a single `JitsiMeetView`.
See JitsiMeetView.getDefaultURL.
#### getPictureInPictureEnabled()
See JitsiMeetView.getPictureInPictureEnabled.
#### getWelcomePageEnabled()
See JitsiMeetView.getWelcomePageEnabled.
@@ -144,6 +148,10 @@ See JitsiMeetView.loadURL.
See JitsiMeetView.setDefaultURL.
#### setPictureInPictureEnabled(boolean)
See JitsiMeetView.setPictureInPictureEnabled.
#### setWelcomePageEnabled(boolean)
See JitsiMeetView.setWelcomePageEnabled.
@@ -169,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
@@ -210,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()
@@ -247,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.
@@ -259,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
@@ -369,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

@@ -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

@@ -5,11 +5,13 @@
package org.jitsi.meet.sdk;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
@@ -25,7 +27,7 @@ class AndroidSettingsModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void open() {
public void open(Promise promise) {
Context context = getReactApplicationContext();
Intent intent = new Intent();
@@ -34,6 +36,15 @@ class AndroidSettingsModule extends ReactContextBaseJavaModule {
intent.setData(
Uri.fromParts("package", context.getPackageName(), null));
context.startActivity(intent);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
// Some devices may give an error here.
// https://developer.android.com/reference/android/provider/Settings.html#ACTION_APPLICATION_DETAILS_SETTINGS
promise.reject(e);
return;
}
promise.resolve(null);
}
}

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,23 @@ 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;
import java.util.Arrays;
@@ -48,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.
@@ -64,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)
);
@@ -82,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
@@ -96,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())
@@ -104,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(
@@ -237,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
@@ -258,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.
*/
@@ -322,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
@@ -363,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);
@@ -386,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 {
@@ -414,6 +498,43 @@ public class JitsiMeetView extends FrameLayout {
loadURLObject(urlObject);
}
/**
* Called when the window containing this view gains or loses focus.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
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) {
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);
}
}
}
/**
* Sets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to
@@ -438,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';
@@ -62,6 +66,7 @@ import {
setVideoAvailable,
setVideoMuted
} from './react/features/base/media';
import { showNotification } from './react/features/notifications';
import {
dominantSpeakerChanged,
getAvatarURLByParticipantId,
@@ -91,7 +96,10 @@ import {
import { statsEmitter } from './react/features/connection-indicator';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import { maybeOpenFeedbackDialog } from './react/features/feedback';
import {
maybeOpenFeedbackDialog,
submitFeedback
} from './react/features/feedback';
import {
mediaPermissionPromptVisibilityChanged,
suspendDetected
@@ -200,7 +208,7 @@ function muteLocalVideo(muted) {
*
* @param {object} options used to decide which particular close page to show
* or if close page is disabled, whether we should show the thankyou dialog
* @param {boolean} options.thankYouDialogVisible - whether we should
* @param {boolean} options.showThankYou - whether we should
* show thank you dialog
* @param {boolean} options.feedbackSubmitted - whether feedback was submitted
*/
@@ -212,24 +220,25 @@ 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;
}
// else: show thankYou dialog only if there is no feedback
if (options.thankYouDialogVisible) {
APP.UI.messageHandler.openMessageDialog(
null, 'dialog.thankYou', { appName: interfaceConfig.APP_NAME });
if (options.showThankYou) {
APP.store.dispatch(showNotification({
titleArguments: { appName: interfaceConfig.APP_NAME },
titleKey: 'dialog.thankYou'
}));
}
// if Welcome page is enabled redirect to welcome page after 3 sec.
if (config.enableWelcomePage) {
setTimeout(
() => {
APP.settings.setWelcomePageEnabled(true);
assignWindowLocationPathname('./');
APP.store.dispatch(redirectWithStoredParams('/'));
},
3000);
}
@@ -245,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;
@@ -305,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;
}
@@ -373,7 +382,7 @@ class ConferenceConnector {
break;
case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
reload();
APP.store.dispatch(reloadWithStoredParams());
break;
default:
@@ -745,7 +754,7 @@ export default {
track.mute();
}
});
logger.log('initialized with %s local tracks', tracks.length);
logger.log(`initialized with ${tracks.length} local tracks`);
this._localTracksInitialized = true;
con.addEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
@@ -1331,19 +1340,35 @@ export default {
replaceLocalTrack(this.localVideo, newStream, room))
.then(() => {
this.localVideo = newStream;
this._setSharingScreen(newStream);
if (newStream) {
this.isSharingScreen = newStream.videoType === 'desktop';
APP.UI.addLocalStream(newStream);
} else {
this.isSharingScreen = false;
}
this.setVideoMuteStatus(this.isLocalVideoMuted());
APP.UI.updateDesktopSharingButtons();
});
},
/**
* Sets `this.isSharingScreen` depending on provided video stream.
* In case new screen sharing status is not equal previous one
* it updates desktop sharing buttons in UI
* and notifies external application.
*
* @param {JitsiLocalTrack} [newStream] new stream to use or null
* @private
* @returns {void}
*/
_setSharingScreen(newStream) {
const wasSharingScreen = this.isSharingScreen;
this.isSharingScreen = newStream && newStream.videoType === 'desktop';
if (wasSharingScreen !== this.isSharingScreen) {
APP.UI.updateDesktopSharingButtons();
APP.API.notifyScreenSharingStatusChanged(this.isSharingScreen);
}
},
/**
* Start using provided audio stream.
* Stops previous audio stream.
@@ -1760,6 +1785,9 @@ export default {
});
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
if (user.isHidden()) {
return;
}
APP.store.dispatch(participantLeft(id, user));
logger.log('USER %s LEFT', id, user);
APP.API.notifyUserLeft(id);
@@ -2617,6 +2645,7 @@ export default {
*/
hangup(requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.removeLocalMedia();
let requestFeedbackPromise;
@@ -2798,5 +2827,21 @@ export default {
setAudioMuteStatus(muted) {
APP.UI.setAudioMuted(this.getMyUserId(), muted);
APP.API.notifyAudioMutedStatusChanged(muted);
},
/**
* Dispatches the passed in feedback for submission. The submitted score
* should be a number inclusively between 1 through 5, or -1 for no score.
*
* @param {number} score - a number between 1 and 5 (inclusive) or -1 for no
* score.
* @param {string} message - An optional message to attach to the feedback
* in addition to the score.
* @returns {void}
*/
submitFeedback(score = -1, message = '') {
if (score === -1 || (score >= 1 && score <= 5)) {
APP.store.dispatch(submitFeedback(score, message, room));
}
}
};

View File

@@ -94,7 +94,7 @@ var config = {
// w3c spec-compliant video constraints to use for video capture. Currently
// used by browsers that return true from lib-jitsi-meet's
// RTCBrowserType#usesNewGumFlow. The constraints are independency from
// util#browser#usesNewGumFlow. The constraints are independency from
// this config's resolution value. Defaults to requesting an ideal aspect
// ratio of 16:9 with an ideal resolution of 1080p.
// constraints: {
@@ -167,6 +167,12 @@ var config = {
// 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 +245,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.
@@ -313,6 +322,9 @@ var config = {
// "https://example.com/my-custom-analytics.js"
// ],
// The Google Analytics Tracking ID
// googleAnalyticsTrackingId = 'your-tracking-id-here-UA-123456-1',
// Information about the jitsi-meet instance we are connecting to, including
// the user region as seen by the server.
deploymentInfo: {

View File

@@ -94,12 +94,15 @@ function connect(id, password, roomName) {
JitsiConnectionEvents.CONNECTION_FAILED,
connectionFailedHandler);
/* eslint-disable max-params */
/**
*
*/
function connectionFailedHandler(error, message, credentials) {
function connectionFailedHandler(error, message, credentials, details) {
/* eslint-enable max-params */
APP.store.dispatch(
connectionFailed(connection, error, message, credentials));
connectionFailed(
connection, error, message, credentials, details));
if (isFatalJitsiConnectionError(error)) {
connection.removeEventListener(

View File

@@ -13,11 +13,6 @@
.clickable {
cursor: pointer;
}
.icon-security,
.icon-security-locked {
font-size: 16px;
}
}
#contacts {

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

@@ -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

@@ -24,13 +24,27 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-arrow_back:before {
content: "\e5c4";
}
.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,11 @@
.flip-x {
transform: scaleX(-1);
}
.hidden {
display: none;
}
/**
* Hides an element.
*/
@@ -5,6 +13,10 @@
display: none !important;
}
.invisible {
visibility: hidden;
}
/**
* Shows an element.
*/
@@ -36,7 +48,3 @@
display: -webkit-flex !important;
display: flex !important;
}
.hidden {
display: none;
}

View File

@@ -151,4 +151,12 @@ $unsupportedDesktopBrowserTextFontSize: 21px;
* The size of the default watermark.
*/
$watermarkWidth: 186px;
$watermarkHeight: 74px;
$watermarkHeight: 74px;
/**
* Welcome page variables.
*/
$welcomePageDescriptionColor: #fff;
$welcomePageFontFamily: inherit;
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
$welcomePageTitleColor: #fff;

View File

@@ -12,7 +12,8 @@
overflow: hidden;
}
.video_blurred_container {
#largeVideoBackgroundContainer,
.large-video-background {
height: 100%;
filter: blur(40px);
left: 0;
@@ -20,6 +21,16 @@
position: absolute;
top: 0;
width: 100%;
&.fit-full-height #largeVideoBackground {
height: 100%;
width: auto;
}
.fit-full-width #largeVideoBackground {
height: auto;
width: 100%;
}
}
.videocontainer {

View File

@@ -1,208 +1,87 @@
#welcome_page {
.welcome {
font-family: $welcomePageFontFamily;
height: 100%;
overflow: auto;
position: relative;
}
#disable_welcome {
display:none;
}
.disable_welcome_position
{
margin: -139px auto 0px auto;
padding-left: 39px;
padding-top: 7px;
width: 269px;
height: 31px;
display:block;
}
#disable_welcome + label
{
background-image: url(../images/welcome_page/disable-welcome.png);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
background-repeat: no-repeat;
font-weight: 500;
font-size: 16px;
color: #acacac;
z-index: $zindex2;
}
#disable_welcome:checked + label
{
background-image: url(../images/welcome_page/disable-welcome-selected.png);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
background-repeat: no-repeat;
font-weight: 500;
font-size: 16px;
color: #acacac;
z-index: $zindex2;
}
#enter_room_form {
border-radius: 1px;
background-color: #FFFFFF;
border: none;
-moz-border-radius: 1px;
-webkit-border-radius: 1px;
-webkit-appearance: none;
height: 55px;
box-shadow: none;
float: left;
}
.domain-name
{
float: left;
height: 55px;
line-height: 55px;
font-size: 18px;
font-weight: 500;
padding-left: 20px;
color: $defaultDarkColor;
}
.enter-room {
&__field {
font-size: 15px;
border: none;
-webkit-appearance: none;
width: 228px;
height: 55px;
line-height: 55px;
font-weight: 500;
box-shadow: none;
float: left;
background-color: #FFFFFF;
.header {
align-items: center;
background: $welcomePageHeaderBackground;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
z-index: $zindex2;
}
&__reload {
display: block;
width: 30px;
color: #acacac;
font-size: 1.9em;
line-height: 55px;
z-index: $zindex3;
float: left;
cursor: pointer;
text-align: center;
.header-text {
display: flex;
flex-direction: column;
justify-content: space-around;
margin-top: 120px;
margin-bottom: 20px;
min-height: 286px;
width: 645px;
}
.header-text-title {
color: $welcomePageTitleColor;
font-size: 48px;
letter-spacing: -1px;
line-height: 58px;
margin-bottom: 20px;
}
.header-text-description {
color: $welcomePageDescriptionColor;
font-size: 20px;
line-height: 28px;
opacity: 0.8;
}
.header-image {
background-image: url(../images/welcome_page/curves.png);
background-size: contain;
height: 209px;
position: absolute;
width: 1070px;
}
#new_enter_room {
align-items: center;
display: flex;
margin-bottom: 20px;
position: relative;
z-index: 2;
.enter-room-input {
display: inline-block;
margin-right: 15px;
width: 350px;
}
}
}
&__button {
width: 73px;
height: 45px;
background-color: #21B9FC;
moz-border-radius: 1px;
-webkit-border-radius: 1px;
color: #ffffff;
font-weight: 600;
border: none;
margin-top: 5px;
font-size: 19px;
padding-top: 6px;
outline: none;
float:left;
position: relative;
z-index: $zindex2;
.welcome-page-button {
font-size: 16px;
}
}
#enter_room_container {
margin: 70px auto 0px auto;
display: table;
.welcome.with-content {
.header {
min-height: 552px;
}
.header-image {
left: -61px;
top: 401px;
}
}
#enter_room{
float:left;
padding-right: 5px;
}
#welcome_page_header
{
background-image: url(../images/welcome_page/pattern-header.png);
height: 290px;
width: 100%;
position: absolute;
}
#welcome_page_main
{
background-image:url(../images/welcome_page/pattern-body.png);
width: 100%;
position: absolute;
margin-top: 290px;
}
#brand_header
{
background-image:url(../images/welcome_page/header-big.png);
width: 583px;
height: 274px;
margin: -110px auto 0px auto;
}
#header_text
{
width: 885px;
height: 100px;
color: #ffffff;
font-size: 24px;
text-align: center;
margin: 0px auto 0px auto;
}
#features
{
margin-top: 30px;
position: relative;
}
.feature_row
{
position: relative;
width: 976px;
margin: 0px auto 30px auto;
padding-right: 75px;
}
.feature_holder
{
display: inline-block;
width: 169px;
padding-left: 75px;
padding-bottom: 30px;
vertical-align: top;
}
.feature_icon
{
background-image:url(../images/welcome_page/bubble.png);
background-repeat: no-repeat;
width: 169px;
height: 169px;
color: #ffffff;
font-size: 22px;
/*font-weight: bold;*/
text-align: center;
display: table-cell;
padding: 0px 26px 0px 20px;
vertical-align: middle;
}
.feature_description
{
width: 190px;
color: #ffffff;
font-size: 16px;
padding-top: 30px;
line-height: 22px;
font-weight: 200;
.welcome.without-content {
.header {
height: 100%;
}
.header-image {
bottom: -20px;
left: 0;
}
}

View File

@@ -0,0 +1 @@
/** Insert custom CSS for any additional content in the welcome page **/

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';
@@ -55,6 +52,7 @@
@import 'chat';
@import 'ringing/ringing';
@import 'welcome_page';
@import 'welcome_page_content';
@import 'toolbars';
@import 'side_toolbar_container';
@import 'jquery.contextMenu';
@@ -65,7 +63,6 @@
@import 'components/button-control';
@import 'components/input-control';
@import 'components/input-slider';
@import "modals/invite/invite";
@import "connection-info";
@import 'aui-components/dropdown';
@import '404';

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

@@ -4,16 +4,20 @@
.info-dialog-action-link {
display: inline-block;
line-height: 1.5em;
a {
cursor: pointer;
vertical-align: middle;
}
}
.info-dialog-action-link:before {
color: $linkFontColor;
content: '\2022';
font-size: 1.5em;
padding: 0 10px;
vertical-align: middle;
}
.info-dialog-action-link:first-child:before {
@@ -22,6 +26,8 @@
}
.info-dialog-action-links {
font-weight: bold;
margin-top: 10px;
white-space: nowrap;
}
@@ -39,16 +45,33 @@
.info-dialog-column {
margin-right: 10px;
overflow: hidden;
a,
a:active,
a:focus,
a:hover {
text-decoration: none;
}
}
.info-dialog-conference-url {
margin: 10px 0;
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 {
color: #6453C0;
font-size: 16px;
@@ -56,5 +79,63 @@
.info-dialog-title {
font-weight: bold;
margin-bottom: 10px;
}
.info-password,
.info-dialog-password,
.info-password-form {
display: flex;
}
.info-password-field {
margin-left: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.info-password-none,
.info-password-remote {
opacity: 0.5;
}
.info-password-input {
background-color: transparent;
border: none;
color: inherit;
padding-left: 0;
}
.info-password-local {
user-select: text;
}
.conference-id {
margin-left: 5px;
}
}
.dial-in-page {
align-items: center;
display: flex;
flex-direction: column;
font-size: 24px;
height: 100%;
justify-content: center;
width: 100%;
* {
user-select: text;
}
.dial-in-numbers-list {
font-size: 24px;
margin-top: 20px;
}
.dial-in-conference-id {
text-align: center;
width: 30%;
}
}

View File

@@ -1,96 +0,0 @@
/*
* Sets the default cursor the remove password link. The link doesn't use
* the href attribute, so we need to set the cursor manually.
*/
#inviteDialogRemovePassword {
cursor: hand;
}
.invite-dialog {
.dial-in-numbers {
.dial-in-numbers-conference-id {
color: orange;
margin-left: 3px;
}
/*
* dial-in-numbers-copy styling is needed for the feature of copying
* text to the clipboard. The styling keeps the element invisible
* to the user but still programmatically selectable for copying.
*/
.dial-in-numbers-copy {
opacity: 0;
pointer-events: none;
position: fixed;
-webkit-user-select: text;
user-select: text;
}
.is-disabled,
.is-loading {
.dial-in-numbers-trigger-icon {
display: none;
}
}
}
.form-control {
padding: 0;
&__container {
/**
* Ensure contents display in a line and vertically centered.
*/
align-items: center;
button {
font-size: $modalButtonFontSize;
}
}
&__input-container {
flex: 1;
margin-right: 10px;
.dropdown-button-trigger {
text-align: left;
}
}
}
.inviteLink {
color: $readOnlyInputColor;
}
.lock-state {
display: flex;
}
.password-overview {
margin-top: 10px;
.form-control {
margin-top: 10px;
}
.password-overview-status,
.remove-password {
display: flex;
justify-content: space-between;
}
.password-overview-toggle-edit,
.remove-password-link {
cursor: pointer;
text-decoration: none;
}
.remove-password {
margin-top: 15px;
}
}
.remove-password-current {
color: $inputControlEmColor;
}
}

View File

@@ -1,7 +1,8 @@
.unsupported-mobile-browser {
background-color: #fff;
height: 100vh;
padding: 35px 0;
overflow: auto;
position: relative;
width: 100vw;
a {
@@ -12,6 +13,7 @@
color: $unsupportedBrowserTextColor;
margin: auto;
max-width: 40em;
padding: 35px 0 40px 0;
text-align: center;
width: 75%;
@@ -20,7 +22,8 @@
}
}
&__text {
&__text,
.unsupported-dial-in {
font-size: 1.2em;
line-height: em(29px, 21px);
margin-bottom: 0.65em;
@@ -65,4 +68,22 @@
}
}
}
.unsupported-dial-in {
display: none;
&.has-numbers {
align-items: center;
display: flex;
flex-direction: column;
}
.dial-in-numbers-list {
color: $unsupportedBrowserTextColor;
}
.dial-in-numbers-body {
vertical-align: top;
}
}
}

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

View File

@@ -27,6 +27,7 @@ Its constructor gets a number of options:
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
* **noSSL**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
* **jwt**: (optional) [JWT](https://jwt.io/) token.
* **onload**: (optional) handler for the iframe onload event.
Example:
@@ -163,6 +164,13 @@ changes. The listener will receive an object with the following structure:
}
```
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
```javascript
{
"on": on //whether screen sharing is on
}
```
* **incomingMessage** - Event notifications about incoming
messages. The listener will receive an object with the following structure:
```javascript

View File

@@ -98,8 +98,9 @@ prosodyctl cert generate auth.jitsi.example.com
Add auth.jitsi.example.com to the trusted certificates on the local machine:
```sh
ln -sf /var/lib/prosody/auth.jitsi.example.com.crt /usr/local/share/ca-certificates/auth.jitsi.example.com.crt
update-ca-certificates
update-ca-certificates -f
```
Note that the `-f` flag is necessary if there are symlinks left from a previous installation.
Create conference focus user:
```sh

View File

@@ -84,7 +84,7 @@ Enjoy!
## Uninstall
```sh
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-web jicofo jitsi-videobridge
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-prosody jitsi-meet-web jicofo jitsi-videobridge
```
Sometimes the following packages will fail to uninstall properly:

Binary file not shown.

View File

@@ -11,7 +11,11 @@
<glyph unicode="&#xe145;" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
<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" />
@@ -19,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,141 @@
{
"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": [
"M854 470v84h-520l238 240-60 60-342-342 342-342 60 60-238 240h520z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"arrow_back"
],
"defaultCode": 58820,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "arrow_back",
"id": 45,
"order": 924,
"prevSize": 24,
"code": 58820,
"name": "arrow_back"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 2
},
{
"icon": {
"paths": [
"M658 316l-196 196 196 196-60 60-256-256 256-256z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"navigate_before"
],
"defaultCode": 58376,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "chevron_left, navigate_before",
"id": 152,
"order": 923,
"prevSize": 24,
"code": 58376,
"name": "navigate_before"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 3
},
{
"icon": {
"paths": [
@@ -24,9 +159,9 @@
"code": 59403,
"name": "public"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 605
"setIdx": 1,
"setId": 1,
"iconIdx": 4
},
{
"icon": {
@@ -53,7 +188,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 0
"iconIdx": 5
},
{
"icon": {
@@ -73,14 +208,14 @@
"properties": {
"ligatures": "timer",
"id": 760,
"order": 916,
"order": 928,
"prevSize": 24,
"code": 58405,
"name": "timer"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 1
"iconIdx": 6
},
{
"icon": {
@@ -107,7 +242,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 2
"iconIdx": 7
},
{
"icon": {
@@ -134,7 +269,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 3
"iconIdx": 8
},
{
"icon": {
@@ -161,7 +296,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 4
"iconIdx": 9
},
{
"icon": {
@@ -188,7 +323,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 5
"iconIdx": 10
},
{
"icon": {
@@ -217,7 +352,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 6
"iconIdx": 11
},
{
"icon": {
@@ -244,7 +379,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 7
"iconIdx": 12
},
{
"icon": {
@@ -271,7 +406,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 8
"iconIdx": 13
},
{
"icon": {
@@ -300,7 +435,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 9
"iconIdx": 14
},
{
"icon": {
@@ -329,7 +464,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 10
"iconIdx": 15
},
{
"icon": {
@@ -358,7 +493,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 11
"iconIdx": 16
},
{
"icon": {
@@ -387,7 +522,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 12
"iconIdx": 17
},
{
"icon": {
@@ -416,7 +551,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 13
"iconIdx": 18
},
{
"icon": {
@@ -442,7 +577,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 14
"iconIdx": 19
},
{
"icon": {
@@ -468,7 +603,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 15
"iconIdx": 20
},
{
"icon": {
@@ -494,7 +629,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 16
"iconIdx": 21
},
{
"icon": {
@@ -520,7 +655,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 17
"iconIdx": 22
},
{
"icon": {
@@ -546,7 +681,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 18
"iconIdx": 23
},
{
"icon": {
@@ -572,7 +707,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 19
"iconIdx": 24
},
{
"icon": {
@@ -598,7 +733,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 20
"iconIdx": 25
},
{
"icon": {
@@ -616,7 +751,7 @@
"attrs": [],
"properties": {
"id": 10,
"order": 900,
"order": 922,
"ligatures": "expand_less",
"prevSize": 32,
"code": 59679,
@@ -624,7 +759,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 21
"iconIdx": 26
},
{
"icon": {
@@ -650,7 +785,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 22
"iconIdx": 27
},
{
"icon": {
@@ -676,7 +811,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 23
"iconIdx": 28
},
{
"icon": {
@@ -702,7 +837,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 24
"iconIdx": 29
},
{
"icon": {
@@ -728,7 +863,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 25
"iconIdx": 30
},
{
"icon": {
@@ -754,7 +889,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 26
"iconIdx": 31
},
{
"icon": {
@@ -780,7 +915,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 27
"iconIdx": 32
},
{
"icon": {
@@ -806,7 +941,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 28
"iconIdx": 33
},
{
"icon": {
@@ -832,7 +967,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 29
"iconIdx": 34
},
{
"icon": {
@@ -858,7 +993,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 30
"iconIdx": 35
},
{
"icon": {
@@ -884,7 +1019,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 31
"iconIdx": 36
},
{
"icon": {
@@ -910,7 +1045,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 32
"iconIdx": 37
},
{
"icon": {
@@ -936,7 +1071,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 33
"iconIdx": 38
},
{
"icon": {
@@ -962,7 +1097,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 34
"iconIdx": 39
},
{
"icon": {
@@ -988,7 +1123,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 35
"iconIdx": 40
},
{
"icon": {
@@ -1014,7 +1149,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 36
"iconIdx": 41
},
{
"icon": {
@@ -1040,7 +1175,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 37
"iconIdx": 42
},
{
"icon": {
@@ -1066,7 +1201,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 38
"iconIdx": 43
},
{
"icon": {
@@ -1092,7 +1227,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 39
"iconIdx": 44
},
{
"icon": {
@@ -1118,7 +1253,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 40
"iconIdx": 45
},
{
"icon": {
@@ -1144,7 +1279,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 41
"iconIdx": 46
},
{
"icon": {
@@ -1170,7 +1305,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 42
"iconIdx": 47
},
{
"icon": {
@@ -1199,7 +1334,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 43
"iconIdx": 48
},
{
"icon": {
@@ -1229,7 +1364,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 44
"iconIdx": 49
},
{
"icon": {
@@ -1259,7 +1394,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 45
"iconIdx": 50
},
{
"icon": {
@@ -1285,7 +1420,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 46
"iconIdx": 51
},
{
"icon": {
@@ -1311,7 +1446,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 47
"iconIdx": 52
},
{
"icon": {
@@ -1337,7 +1472,7 @@
},
"setIdx": 1,
"setId": 1,
"iconIdx": 48
"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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -138,6 +138,7 @@
<script src="libs/app.bundle.min.js?v=139"></script>
<!--#include virtual="title.html" -->
<!--#include virtual="plugin.head.html" -->
<!--#include virtual="static/welcomePageAdditionalContent.html" -->
</head>
<body>
<div id="react"></div>

View File

@@ -24,6 +24,7 @@ var interfaceConfig = {
BRAND_WATERMARK_LINK: '',
SHOW_POWERED_BY: false,
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
DISPLAY_WELCOME_PAGE_CONTENT: true,
APP_NAME: 'Jitsi Meet',
LANG_DETECTION: false, // Allow i18n to detect the system language
INVITATION_POWERED_BY: true,
@@ -40,7 +41,7 @@ var interfaceConfig = {
TOOLBAR_BUTTONS: [
// main toolbar
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup',
'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup',
// extended toolbar
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip' ],
@@ -49,7 +50,7 @@ var interfaceConfig = {
* Main Toolbar Buttons
* All of them should be in TOOLBAR_BUTTONS
*/
MAIN_TOOLBAR_BUTTONS: [ 'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup' ],
MAIN_TOOLBAR_BUTTONS: [ 'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup' ],
SETTINGS_SECTIONS: [ 'language', 'devices', 'moderator' ],
INVITE_OPTIONS: [ 'invite', 'dialout', 'addtocall' ],

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

@@ -1,8 +1,10 @@
PODS:
- React (0.50.4):
- React/Core (= 0.50.4)
- React (0.51.0):
- 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):
@@ -10,43 +12,49 @@ PODS:
- react-native-locale-detector (1.0.0):
- React
- react-native-webrtc (1.58.2)
- React/BatchedBridge (0.50.4):
- React/BatchedBridge (0.51.0):
- React/Core
- React/cxxreact_legacy
- React/Core (0.50.4):
- yoga (= 0.50.4.React)
- React/cxxreact_legacy (0.50.4):
- React/Core (0.51.0):
- yoga (= 0.51.0.React)
- React/cxxreact_legacy (0.51.0):
- React/jschelpers_legacy
- React/DevSupport (0.50.4):
- React/DevSupport (0.51.0):
- React/Core
- React/RCTWebSocket
- React/fishhook (0.50.4)
- React/jschelpers_legacy (0.50.4)
- React/RCTActionSheet (0.50.4):
- React/fishhook (0.51.0)
- React/jschelpers_legacy (0.51.0)
- React/RCTActionSheet (0.51.0):
- React/Core
- React/RCTAnimation (0.50.4):
- React/RCTAnimation (0.51.0):
- React/Core
- React/RCTBlob (0.50.4):
- React/RCTBlob (0.51.0):
- React/Core
- React/RCTImage (0.50.4):
- React/RCTImage (0.51.0):
- React/Core
- React/RCTNetwork
- React/RCTLinkingIOS (0.50.4):
- React/RCTLinkingIOS (0.51.0):
- React/Core
- React/RCTNetwork (0.50.4):
- React/RCTNetwork (0.51.0):
- React/Core
- React/RCTText (0.50.4):
- React/RCTText (0.51.0):
- React/Core
- React/RCTWebSocket (0.50.4):
- React/RCTWebSocket (0.51.0):
- 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.50.4.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,21 +88,25 @@ 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:
:path: ../node_modules/react-native/ReactCommon/yoga
SPEC CHECKSUMS:
React: 6d7efef18e0241eb832cf4c085405169a15080ed
react-native-background-timer: 10063c04bf85d7f8811dff8c74399f0aa715245f
react-native-fetch-blob: 2bef9be702de8726f4d7bf58d2345579aaaee60d
react-native-keep-awake: bb4dbb6fd21a7879432f9538b0b7d71398fe9f81
react-native-locale-detector: bb60716189d4c69c459355d0a4804c9b13f65e84
react-native-webrtc: 6fd0b3aa890d7a9b9b4d01d30f958d17ae88a785
RNVectorIcons: c1821d56c775cc5a3bca66c77dfc8cb4a90d27e2
yoga: b9aebf996711e50fc31f5608c10aa108a5a0c29e
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.3.1
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,6 +55,8 @@
</dict>
</dict>
</dict>
<key>NSCalendarsUsageDescription</key>
<string>Displays the user's meetings in the app.</string>
<key>NSCameraUsageDescription</key>
<string>Participate in conferences with video.</string>
<key>NSLocationWhenInUseUsageDescription</key>

View File

@@ -11,6 +11,7 @@
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
@@ -34,6 +35,7 @@
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchOptions.m; sourceTree = "<group>"; };
0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
@@ -106,6 +108,7 @@
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
0BD906E91EC0C00300C8C18E /* Info.plist */,
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
@@ -276,7 +279,17 @@
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -295,6 +308,7 @@
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,

View File

@@ -32,6 +32,12 @@ RCT_EXPORT_MODULE();
NSString *name = infoDictionary[@"CFBundleDisplayName"];
NSString *version = infoDictionary[@"CFBundleShortVersionString"];
if (name == nil) {
name = infoDictionary[@"CFBundleName"];
if (name == nil) {
name = @"";
}
}
if (version == nil) {
version = infoDictionary[@"CFBundleVersion"];
if (version == nil) {

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,83 @@
/*
* Copyright @ 2018-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 <Intents/Intents.h>
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
@interface LaunchOptions : NSObject<RCTBridgeModule>
@property (nonatomic, weak) RCTBridge *bridge;
@end
@implementation LaunchOptions
RCT_EXPORT_MODULE();
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject) {
id initialURL = nil;
if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
NSURL *url = self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
initialURL = url.absoluteString;
} else {
NSDictionary *userActivityDictionary
= self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
NSUserActivity *userActivity
= [userActivityDictionary objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];
if (userActivity != nil) {
NSString *activityType = userActivity.activityType;
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
// App was started by opening a URL in the browser
initialURL = userActivity.webpageURL.absoluteString;
} else if ([activityType isEqualToString:@"INStartAudioCallIntent"]
|| [activityType isEqualToString:@"INStartVideoCallIntent"]) {
// App was started by a CallKit Intent
INIntent *intent = userActivity.interaction.intent;
NSArray<INPerson *> *contacts;
NSString *url;
BOOL startAudioOnly = NO;
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
contacts = ((INStartAudioCallIntent *) intent).contacts;
startAudioOnly = YES;
} else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
contacts = ((INStartVideoCallIntent *) intent).contacts;
}
if (contacts && (url = contacts.firstObject.personHandle.value)) {
initialURL
= @{
@"config": @{@"startAudioOnly":@(startAudioOnly)},
@"url": url
};
}
}
}
}
resolve(initialURL != nil ? initialURL : (id)kCFNull);
}
@end

View File

@@ -2,23 +2,19 @@
"contactlist": "__count__ Membres",
"contactlist_plural": "",
"passwordSetRemotely": "défini par un autre membre",
"connectionsettings": "Paramètres de connexion",
"poweredby": "Produit par",
"feedback": {
"average": "Moyen",
"bad": "Mauvais",
"good": "",
"rateExperience": "Veuillez évaluer votre réunion.",
"veryBad": "Très mauvais",
"veryGood": "Très bon"
},
"inviteUrlDefaultMsg": "Votre conférence est en cours de création...",
"me": "moi",
"speaker": "Haut-parleur",
"raisedHand": "Aimerait prendre la parole",
"defaultNickname": "ex. Jean Dupont",
"defaultLink": "ex. __url__",
"callingName": "__name__",
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Écouteurs",
"phone": "Téléphone",
"speaker": "Haut-parleur"
},
"audioOnly": {
"audioOnly": "Audio uniquement",
"featureToggleDisabled": "Le basculement de __feature__ est désactivé en mode audio uniquement"
@@ -101,7 +97,7 @@
"rejoinKeyTitle": "Rejoindre"
},
"toolbar": {
"addPeople": "",
"addPeople": "Ajouter des personnes à votre appel",
"audioonly": "Activer / Désactiver le mode audio uniquement (économiser de la bande passante)",
"mute": "Muet / Actif",
"videomute": "Démarrer / Arrêter la caméra",
@@ -130,11 +126,9 @@
"raiseHand": "Lever / Baisser la main"
},
"unsupportedBrowser": {
"appInstalled": "ou si vous l'avez déjà installé<br /><strong></strong>",
"appNotInstalled": "Vous devez installer <strong>__app__</strong> pour rejoindre une conversation sur votre mobile",
"downloadApp": "Télécharger l'Application",
"joinConversation": "Rejoindre la discussion de groupe",
"startConference": "Démarrer une conférence"
"appNotInstalled": "Rejoignez cette réunion avec __app__ sur votre téléphone.",
"downloadApp": "Télécharger l'application",
"openApp": "Continuer sur __app__"
},
"bottomtoolbar": {
"chat": "Ouvrir / fermer le chat",
@@ -214,7 +208,9 @@
"notify": {
"disconnected": "déconnecté",
"moderator": "Droits modérateur accordés !",
"connected": "connecté",
"connectedOneMember": "__name__ connecté",
"connectedTwoMembers": "__first__ et __second__ connectés",
"connectedThreePlusMembers": "__name__ and __count__ others connectés",
"somebody": "Quelqu'un",
"me": "Moi",
"focus": "Focus de conférence",
@@ -322,14 +318,11 @@
"stopRecordingWarning": "Désirez-vous vraiment arrêter l'enregistrement?",
"stopLiveStreaming": "Arrêter le direct",
"stopRecording": "Arrêter l'enregistrement",
"doNotShowWarningAgain": "Ne plus afficher cet avertissement",
"doNotShowMessageAgain": "Ne plus afficher ce message",
"permissionDenied": "Permission refusée",
"screenSharingFailedToInstall": "Oups! Votre extension de partage d'écran n'a pas pu être installée.",
"screenSharingFailedToInstallTitle": "L'extension de partage d'écran n'a pas pu être installée",
"screenSharingPermissionDeniedError": "Oups! Une erreur s'est produite avec vos autorisations d'extension de partage d'écran. Veuillez rafraîchir et réessayer.",
"micErrorPresent": "Une erreur est survenue lors de la connexion à votre microphone.",
"cameraErrorPresent": "Votre caméra ne satisfait pas certaines des contraintes nécessaires.",
"cameraUnsupportedResolutionError": "Votre appareil ne prend pas en charge la résolution vidéo requise.",
"cameraUnknownError": "Vous ne pouvez pas utiliser la caméra pour une raison inconnue.",
"cameraPermissionDeniedError": "Vous n'avez pas autorisé l'utilisation de votre caméra. Vous pouvez toujours participer à la conférence, mais les autres ne vont pas vous voir. Utilisez le bouton de la caméra dans la barre d'adresse pour résoudre ce problème.",
@@ -413,7 +406,8 @@
"off": "Enregistrement arrêté",
"on": "Enregistrement",
"pending": "Enregistrement en attente de participant...",
"unavailable": "Oups! Le service d'enregistrement est actuellement indisponible. Nous travaillons sur la résolution du problème. Veuillez réessayer plus tard.",
"serviceName": "Service d'enregistrement",
"unavailable": "Oups! Le __serviceName__ est actuellement indisponible. Nous travaillons sur la résolution du problème. Veuillez réessayer plus tard.",
"unavailableTitle": "Enregistrement indisponible"
},
"liveStreaming": {
@@ -425,11 +419,23 @@
"off": "Le Streaming a été arrêter",
"on": "Direct",
"pending": "Commencer le direct...",
"serviceName": "Service de diffusion en direct",
"streamIdRequired": "Merci de renseigner le stream id pour lancer le streaming.",
"streamIdHelp": "Où puis-je trouver ceci?",
"unavailable": "Oups! Le service de Streaming est actuellement indisponible. Nous sommes en train de résoudre le problème. Veuillez réessayer plus tard.",
"unavailableTitle": "Le Streaming est indisponible"
},
"videoSIPGW": {
"busy": "Nous travaillons sur la libération des ressources. Veuillez réessayez dans quelques minutes.",
"busyTitle": "Le service du Salon est actuellement occupé",
"errorInvite": "La conférence n'est pas encore établie. Veuillez réessayer plus tard.",
"errorInviteTitle": "Erreur lors de l'invitation",
"errorAlreadyInvited": "__displayName__ est déjà invité(e)",
"errorInviteFailedTitle": "l'invitation de __displayName__ a échoué",
"errorInviteFailed": "Nous travaillons sur la résolution du problème. Veuillez réessayer plus tard.",
"pending": "__displayName__ a été invité(e)",
"serviceName": "Service de salon",
"unavailableTitle": "Service indisponible"
},
"speakerStats": {
"hours": "__count__h",
"minutes": "__count__m",
@@ -453,7 +459,7 @@
"hidePassword": "Cacher le mot de passe",
"inviteTo": "inviter des participants à __conferenceName__",
"invitedYouTo": "__userName__ vous a invité(e) à la conférence __inviteURL__",
"invitePeople": "Inviter des participants",
"invitePeople": "Inviter",
"locked": "Cet appel est verrouillé. les nouveaux interlocuteurs devraient avoir le lien et saisir le mot de passe pour rejoindre.",
"showPassword": "Afficher le mot de passe",
"unlocked": "Cet appel est verrouillé. Tout nouveau participant avec un lien peut rejoindre l'appel."
@@ -466,6 +472,8 @@
"labelTooltipAudioOnly": "Mode audio uniquement activé",
"ld": "BD",
"lowDefinition": "Basse définition",
"onlyAudioAvailable": "Seul l'audio est disponible",
"onlyAudioSupported": "Nous ne supportons que l'audio sur ce navigateur.",
"p2pEnabled": "Peer to Peer activé",
"p2pVideoQualityDescription": "En mode peer to peer, la qualité d'appel reçue ne peut être basculée qu'entre haut et audio. Les autres paramètres ne seront pas respectés tant que l'on n'aura pas quitté peer to peer.",
"recHighDefinitionOnly": "Va préférer la haute définition",
@@ -499,6 +507,14 @@
"cameraPermission": "Erreur lors de l'obtention de la permission de la caméra ",
"microphonePermission": "Erreur lors de l'obtention de la permission du microphone"
},
"feedback": {
"average": "Moyen",
"bad": "Mauvais",
"good": "Bien",
"rateExperience": "Veuillez évaluer votre réunion.",
"veryBad": "Très mauvais",
"veryGood": "Très bon"
},
"info": {
"copy": "Copier le lien",
"invite": "Inviter à __app__",

View File

@@ -46,46 +46,20 @@
"showSpeakerStats": "Show speaker stats"
},
"welcomepage":{
"disable": "Don't show this page again",
"feature1": {
"content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started.",
"title": "Simple to use"
},
"feature2": {
"content": "Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less.",
"title": "Low bandwidth"
},
"feature3": {
"content": "__app__ is licensed under the Apache License. You are free to download, use, modify, and share it as per this license.",
"title": "Open source"
},
"feature4": {
"content": "There are no artificial restrictions on the number of users or conference members. Server power and bandwidth are the only limiting factors.",
"title": "Unlimited users"
},
"feature5": {
"content": "It's easy to share your screen with others. __app__ is ideal for on-line presentations, lectures, and tech support sessions.",
"title": "Screen sharing"
},
"feature6": {
"content": "Need some privacy? __app__ conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions.",
"title": "Secure rooms"
},
"feature7": {
"content": "__app__ features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more.",
"title": "Shared notes"
},
"feature8": {
"content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems.",
"title": "Usage statistics"
"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",
"roomnamePlaceHolder": "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"
"terms": "Terms",
"title": "More secure, more flexible, and completely free video conferencing"
},
"startupoverlay": {
"policyText": " ",
@@ -103,7 +77,6 @@
"videomute": "Start / Stop camera",
"authenticate": "Authenticate",
"lock": "Lock / Unlock room",
"invite": "Share the link",
"chat": "Open / Close chat",
"etherpad": "Open / Close shared document",
"sharedvideo": "Share a YouTube video",
@@ -223,10 +196,11 @@
"grantedToUnknown": "Moderator rights granted to $t(notify.somebody)!",
"muted": "You have started the conversation muted.",
"mutedTitle": "You're muted!",
"raisedHand": "Would like to speak."
"raisedHand": "Would like to speak.",
"suboptimalExperienceTitle": "Browser Warning",
"suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>."
},
"dialog": {
"add": "Add",
"allow": "Allow",
"kickMessage": "Ouch! You have been kicked out of the meet!",
"popupErrorTitle": "Pop-up blocked",
@@ -241,7 +215,6 @@
"copy": "Copy",
"contactSupport": "Contact support",
"error": "Error",
"createPassword": "Create password",
"detectext": "Error when trying to detect desktopsharing extension.",
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
"conferenceReloadTitle": "Unfortunately, something went wrong.",
@@ -292,10 +265,6 @@
"Save": "Save",
"recording": "Recording",
"recordingToken": "Enter recording token",
"passwordCheck": "Are you sure you would like to remove your password?",
"passwordMsg": "Set a password to lock your room",
"shareLink": "Share the link to the call",
"yourPassword": "Enter new password",
"Back": "Back",
"serviceUnavailable": "Service unavailable",
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
@@ -466,19 +435,6 @@
"selectADevice": "Select a device",
"testAudio": "Test sound"
},
"invite": {
"addPassword": "Add password",
"callNumber": "Call __number__",
"enterID": "Enter Meeting ID: __conferenceID__ following by # to dial in from a phone",
"howToDialIn": "To dial in, use one of the following numbers and meeting ID",
"hidePassword": "Hide password",
"inviteTo": "Invite people to __conferenceName__",
"invitedYouTo": "__userName__ has invited you to the __inviteURL__ conference",
"invitePeople": "Invite",
"locked": "This call is locked. New callers must have the link and enter the password to join.",
"showPassword": "Show password",
"unlocked": "This call is unlocked. Any new caller with the link may join the call."
},
"videoStatus": {
"callQuality": "Call Quality",
"hd": "HD",
@@ -497,17 +453,24 @@
"qualityButtonTip": "Change received video quality"
},
"dialOut": {
"dial": "Dial",
"dialOut": "Call a #",
"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": {
@@ -531,17 +494,49 @@
"veryGood": "Very Good"
},
"info": {
"copy": "Copy link",
"invite": "Invite in __app__",
"title": "Call access info",
"addPassword": "Add password",
"cancelPassword": "Cancel password",
"conferenceURL": "Link: __url__",
"country": "Country",
"dialANumber": "To join your meeting, dial one of these numbers and then enter this PIN: __conferenceID__#",
"dialInNumber": "Dial-in: __phoneNumber__",
"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: __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",
"conferenceSection": "Conference",
"displayName": "Display name",
"email": "Email",
"header": "Settings",
"profileSection": "Profile",
"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

@@ -1,5 +1,7 @@
/* eslint-disable no-unused-vars, no-var */
// Logging configuration
// XXX When making any changes to this file make sure to also update it's React
// version at ./react/features/base/logging/reducer.js !!!
var loggingConfig = {
// default log level for the app and lib-jitsi-meet
defaultLogLevel: 'trace',
@@ -7,9 +9,10 @@ var loggingConfig = {
// Option to disable LogCollector (which stores the logs on CallStats)
// disableLogCollector: true,
// Logging level adjustments for verbose modules:
'modules/xmpp/strophe.util.js': 'log',
// The following are too verbose in their logging with the
// {@link #defaultLogLevel}:
'modules/statistics/CallStats.js': 'info',
'modules/xmpp/strophe.util.js': 'log',
'modules/RTC/TraceablePeerConnection.js': 'info'
};

View File

@@ -59,6 +59,10 @@ function initCommands() {
sendAnalytics(createApiEvent('display.name.changed'));
APP.conference.changeLocalDisplayName(displayName);
},
'submit-feedback': feedback => {
sendAnalytics(createApiEvent('submit.feedback'));
APP.conference.submitFeedback(feedback.score, feedback.message);
},
'toggle-audio': () => {
sendAnalytics(createApiEvent('toggle-audio'));
logger.log('Audio toggle: API command received');
@@ -456,6 +460,30 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that conference feedback
* has been submitted. Intended to be used in conjunction with the
* submit-feedback command to get notified if feedback was submitted.
*
* @returns {void}
*/
notifyFeedbackSubmitted() {
this._sendEvent({ name: 'feedback-submitted' });
}
/**
* Notify external application (if API is enabled) that the screen sharing
* has been turned on/off.
*
* @param {boolean} on - True if screen sharing is enabled.
* @returns {void}
*/
notifyScreenSharingStatusChanged(on: boolean) {
this._sendEvent({
name: 'screen-sharing-status-changed',
on
});
}
/**
* Disposes the allocated resources.

View File

@@ -21,6 +21,7 @@ const commands = {
displayName: 'display-name',
email: 'email',
hangup: 'video-hangup',
submitFeedback: 'submit-feedback',
toggleAudio: 'toggle-audio',
toggleChat: 'toggle-chat',
toggleContactList: 'toggle-contact-list',
@@ -38,6 +39,7 @@ const events = {
'audio-availability-changed': 'audioAvailabilityChanged',
'audio-mute-status-changed': 'audioMuteStatusChanged',
'display-name-change': 'displayNameChange',
'feedback-submitted': 'feedbackSubmitted',
'incoming-message': 'incomingMessage',
'outgoing-message': 'outgoingMessage',
'participant-joined': 'participantJoined',
@@ -46,7 +48,8 @@ const events = {
'video-conference-joined': 'videoConferenceJoined',
'video-conference-left': 'videoConferenceLeft',
'video-availability-changed': 'videoAvailabilityChanged',
'video-mute-status-changed': 'videoMuteStatusChanged'
'video-mute-status-changed': 'videoMuteStatusChanged',
'screen-sharing-status-changed': 'screenSharingStatusChanged'
};
/**
@@ -121,7 +124,8 @@ function parseArguments(args) {
configOverwrite,
interfaceConfigOverwrite,
noSSL,
jwt
jwt,
onload
] = args;
return {
@@ -132,7 +136,8 @@ function parseArguments(args) {
configOverwrite,
interfaceConfigOverwrite,
noSSL,
jwt
jwt,
onload
};
case 'object': // new arguments format
return args[0];
@@ -194,6 +199,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* used.
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @param {string} [options.onload] - The onload function that will listen
* for iframe onload event.
*/
constructor(domain, ...args) {
super();
@@ -205,7 +212,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
configOverwrite = {},
interfaceConfigOverwrite = {},
noSSL = false,
jwt = undefined
jwt = undefined,
onload = undefined
} = parseArguments(args);
this._parentNode = parentNode;
@@ -216,7 +224,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
noSSL,
roomName
});
this._createIFrame(height, width);
this._createIFrame(height, width, onload);
this._transport = new Transport({
backend: new PostMessageTransportBackend({
postisOptions: {
@@ -241,11 +249,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* parseSizeParam for format details.
* @param {number|string} width - The with of the iframe. Check
* parseSizeParam for format details.
* @param {Function} onload - The function that will listen
* for onload event.
* @returns {void}
*
* @private
*/
_createIFrame(height, width) {
_createIFrame(height, width, onload) {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
@@ -256,6 +266,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._setSize(height, width);
this._frame.setAttribute('allowFullScreen', 'true');
this._frame.style.border = 0;
if (onload) {
// waits for iframe resources to load
// and fires event when it is done
this._frame.onload = onload;
}
this._frame = this._parentNode.appendChild(this._frame);
}
@@ -469,6 +486,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* {{
* roomName: room //the room name of the conference
* }}
* screenSharingStatusChanged - receives event notifications about
* turning on/off the local user screen sharing.
* The listener will receive object with the following structure:
* {{
* on: on //whether screen sharing is on
* }}
* readyToClose - all hangup operations are completed and Jitsi Meet is
* ready to be disposed.
* @returns {void}

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);
};
@@ -642,11 +624,16 @@ UI.toggleFilmstrip = function() {
};
/**
* Indicates if the filmstrip is currently visible or not.
* @returns {true} if the filmstrip is currently visible, otherwise
* Checks if the filmstrip is currently visible or not.
* @returns {true} if the filmstrip is currently visible, and false otherwise.
*/
UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
/**
* @returns {true} if the chat panel is currently visible, and false otherwise.
*/
UI.isChatVisible = () => Chat.isVisible();
/**
* Toggles chat panel.
*/
@@ -877,8 +864,7 @@ UI.notifyInitiallyMuted = function() {
'notify.mutedTitle',
'connected',
'notify.muted',
null,
120000);
null);
};
/**
@@ -1287,6 +1273,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

@@ -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

@@ -54,5 +54,5 @@ function smilify(body) {
}
}
return body;
return formattedBody;
}

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

@@ -4,7 +4,6 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
import jitsiLocalStorage from '../../util/JitsiLocalStorage';
import {
Notification,
showErrorNotification,
showNotification,
showWarningNotification
@@ -493,16 +492,13 @@ const messageHandler = {
messageKey,
messageArguments,
timeout = 2500) {
APP.store.dispatch(
showNotification(
Notification,
{
descriptionArguments: messageArguments,
descriptionKey: messageKey,
titleKey: displayNameKey,
title: displayName
},
timeout));
APP.store.dispatch(showNotification({
descriptionArguments: messageArguments,
descriptionKey: messageKey,
titleKey: displayNameKey,
title: displayName
},
timeout));
},
/**

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

@@ -1,6 +1,6 @@
/* global $, APP, interfaceConfig */
import { setFilmstripVisibility } from '../../../react/features/filmstrip';
import { setFilmstripVisible } from '../../../react/features/filmstrip';
import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from '../util/UIUtil';
@@ -183,7 +183,7 @@ const Filmstrip = {
UIEvents.TOGGLED_FILMSTRIP,
!wasFilmstripVisible);
}
APP.store.dispatch(setFilmstripVisibility(!wasFilmstripVisible));
APP.store.dispatch(setFilmstripVisible(!wasFilmstripVisible));
},
/**

View File

@@ -1,5 +1,16 @@
/* global $, APP, interfaceConfig */
/* eslint-disable no-unused-vars */
import React from 'react';
import ReactDOM from 'react-dom';
import { browser } from '../../../react/features/base/lib-jitsi-meet';
import {
ORIENTATION,
LargeVideoBackground
} from '../../../react/features/large-video';
/* eslint-enable no-unused-vars */
import Filmstrip from './Filmstrip';
import LargeContainer from './LargeContainer';
import UIEvents from '../../../service/UI/UIEvents';
@@ -10,7 +21,23 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
const FADE_DURATION_MS = 300;
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* The CSS class used to add a filter effect on the large video when there is
* a problem with local video.
*
* @private
* @type {string}
*/
const LOCAL_PROBLEM_FILTER_CLASS = 'videoProblemFilter';
/**
* The CSS class used to add a filter effect on the large video when there is
* a problem with remote video.
*
* @private
* @type {string}
*/
const REMOTE_PROBLEM_FILTER_CLASS = 'remoteVideoProblemFilter';
/**
* Returns an array of the video dimensions, so that it keeps it's aspect
@@ -167,13 +194,6 @@ export class VideoContainer extends LargeContainer {
return $('#largeVideo');
}
/**
*
*/
get $videoBackground() {
return $('#largeVideoBackground');
}
/**
*
*/
@@ -199,6 +219,23 @@ export class VideoContainer extends LargeContainer {
this.isVisible = false;
/**
* Whether the background should fit the height of the container
* (portrait) or fit the width of the container (landscape).
*
* @private
* @type {string|null}
*/
this._backgroundOrientation = null;
/**
* Flag indicates whether or not the background should be rendered.
* If the background will not be visible then it is hidden to save
* on performance.
* @type {boolean}
*/
this._hideBackground = true;
/**
* Flag indicates whether or not the avatar is currently displayed.
* @type {boolean}
@@ -277,8 +314,8 @@ export class VideoContainer extends LargeContainer {
* <tt>false</tt> otherwise.
*/
enableLocalConnectionProblemFilter(enable) {
this.$video.toggleClass('videoProblemFilter', enable);
this.$videoBackground.toggleClass('videoProblemFilter', enable);
this.$video.toggleClass(LOCAL_PROBLEM_FILTER_CLASS, enable);
this._updateBackground();
}
/**
@@ -402,23 +439,19 @@ export class VideoContainer extends LargeContainer {
return;
}
this._hideVideoBackground();
const [ width, height ]
= this.getVideoSize(containerWidth, containerHeight);
if ((containerWidth > width) || (containerHeight > height)) {
this._showVideoBackground();
const css
= containerWidth > width
? { width: '100%',
height: 'auto' }
: { width: 'auto',
height: '100%' };
this.$videoBackground.css(css);
this._backgroundOrientation = containerWidth > width
? ORIENTATION.LANDSCAPE : ORIENTATION.PORTRAIT;
this._hideBackground = false;
} else {
this._hideBackground = true;
}
this._updateBackground();
const { horizontalIndent, verticalIndent }
= this.getVideoPosition(width, height,
containerWidth, containerHeight);
@@ -484,7 +517,6 @@ export class VideoContainer extends LargeContainer {
// detach old stream
if (this.stream) {
this.stream.detach(this.$video[0]);
this.stream.detach(this.$videoBackground[0]);
}
this.stream = stream;
@@ -495,18 +527,14 @@ export class VideoContainer extends LargeContainer {
}
stream.attach(this.$video[0]);
stream.attach(this.$videoBackground[0]);
this._hideVideoBackground();
const flipX = stream.isLocal() && this.localFlipX;
this.$video.css({
transform: flipX ? 'scaleX(-1)' : 'none'
});
this.$videoBackground.css({
transform: flipX ? 'scaleX(-1)' : 'none'
});
this._updateBackground();
// Reset the large video background depending on the stream.
this.setLargeVideoBackground(this.avatarDisplayed);
@@ -525,9 +553,7 @@ export class VideoContainer extends LargeContainer {
transform: this.localFlipX ? 'scaleX(-1)' : 'none'
});
this.$videoBackground.css({
transform: this.localFlipX ? 'scaleX(-1)' : 'none'
});
this._updateBackground();
}
@@ -567,10 +593,9 @@ export class VideoContainer extends LargeContainer {
* the indication.
*/
showRemoteConnectionProblemIndicator(show) {
this.$video.toggleClass('remoteVideoProblemFilter', show);
this.$videoBackground.toggleClass('remoteVideoProblemFilter', show);
this.$avatar.toggleClass('remoteVideoProblemFilter', show);
this.$video.toggleClass(REMOTE_PROBLEM_FILTER_CLASS, show);
this.$avatar.toggleClass(REMOTE_PROBLEM_FILTER_CLASS, show);
this._updateBackground();
}
@@ -643,17 +668,6 @@ export class VideoContainer extends LargeContainer {
? '#000' : interfaceConfig.DEFAULT_BACKGROUND);
}
/**
* Sets the blur background to be invisible and pauses any playing video.
*
* @private
* @returns {void}
*/
_hideVideoBackground() {
this.$videoBackground.css({ visibility: 'hidden' });
this.$videoBackground[0].pause();
}
/**
* Callback invoked when the video element changes dimensions.
*
@@ -665,26 +679,37 @@ export class VideoContainer extends LargeContainer {
}
/**
* Sets the blur background to be visible and starts any loaded video.
* Attaches and/or updates a React Component to be used as a background for
* the large video, to display blurred video and fill up empty space not
* taken up by the large video.
*
* @private
* @returns {void}
*/
_showVideoBackground() {
this.$videoBackground.css({ visibility: 'visible' });
// XXX HTMLMediaElement.play's Promise may be rejected. Certain
// environments such as Google Chrome and React Native will report the
// rejection as unhandled. And that may appear scary depending on how
// the environment words the report. To reduce the risk of scaring a
// developer, make sure that the rejection is handled. We cannot really
// do anything substantial about the rejection and, more importantly, we
// do not care. Some browsers (at this time, only Edge is known) don't
// return a promise from .play(), so check before trying to catch.
const res = this.$videoBackground[0].play();
if (typeof res !== 'undefined') {
res.catch(reason => logger.error(reason));
_updateBackground() {
// 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;
}
ReactDOM.render(
<LargeVideoBackground
hidden = { this._hideBackground }
mirror = {
this.stream
&& this.stream.isLocal()
&& this.localFlipX
}
orientationFit = { this._backgroundOrientation }
showLocalProblemFilter
= { this.$video.hasClass(LOCAL_PROBLEM_FILTER_CLASS) }
showRemoteProblemFilter
= { this.$video.hasClass(REMOTE_PROBLEM_FILTER_CLASS) }
videoTrack = { this.stream } />,
document.getElementById('largeVideoBackgroundContainer')
);
}
}

View File

@@ -21,8 +21,6 @@ let displayName = UIUtil.unescapeHtml(
jitsiLocalStorage.getItem('displayname') || '');
let cameraDeviceId = jitsiLocalStorage.getItem('cameraDeviceId') || '';
let micDeviceId = jitsiLocalStorage.getItem('micDeviceId') || '';
let welcomePageDisabled = JSON.parse(
jitsiLocalStorage.getItem('welcomePageDisabled') || false);
// Currently audio output device change is supported only in Chrome and
// default output always has 'default' device ID
@@ -189,22 +187,5 @@ export default {
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
.then(() =>
jitsiLocalStorage.setItem('audioOutputDeviceId', newId));
},
/**
* Check if welcome page is enabled or not.
* @returns {boolean}
*/
isWelcomePageEnabled() {
return !welcomePageDisabled;
},
/**
* Enable or disable welcome page.
* @param {boolean} enabled if welcome page should be enabled or not
*/
setWelcomePageEnabled(enabled) {
welcomePageDisabled = !enabled;
jitsiLocalStorage.setItem('welcomePageDisabled', welcomePageDisabled);
}
};

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.
*

6257
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -39,12 +39,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#2aa85ad1719f81541ac1a3582f1369e2f4d1f5c2",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#410ad5e76d33b1422ce34f83719eda7dfd584e22",
"lodash": "4.17.4",
"moment": "2.19.4",
"nuclear-js": "1.4.0",
@@ -53,8 +53,9 @@
"react": "16.2.0",
"react-dom": "16.2.0",
"react-i18next": "4.8.0",
"react-native": "0.50.4",
"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,11 +63,14 @@
"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#7c6b167120d2224f62222037ff71deb8ff93bd38",
"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",
"url-polyfill": "github:github/url-polyfill#39734186de44612bc5a16eb25f5407adcc5b2e7c",
"uuid": "3.1.0",
@@ -91,13 +95,14 @@
"eslint-plugin-react-native": "3.2.0",
"expose-loader": "0.7.4",
"file-loader": "1.1.5",
"flow-bin": "0.56.0",
"flow-bin": "0.57.3",
"imports-loader": "0.7.1",
"node-sass": "3.13.1",
"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.
@@ -148,18 +148,20 @@ export const createInviteDialogClosedEvent = function() {
* @param {string} reason - The reason for the reload.
* @param {number} timeout - The timeout in seconds after which the page is
* scheduled to reload.
* @param {Object} details - The details for the error.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export const createPageReloadScheduledEvent = function(reason, timeout) {
export function createPageReloadScheduledEvent(reason, timeout, details) {
return {
action: 'page.reload.scheduled',
attributes: {
reason,
timeout
timeout,
...details
}
};
};
}
/**
* Creates a "pinned" or "unpinned" event.
@@ -170,17 +172,16 @@ export const createPageReloadScheduledEvent = function(reason, timeout) {
* @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
@@ -191,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
@@ -212,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
@@ -230,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',
@@ -240,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
@@ -251,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
@@ -270,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
@@ -285,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'
};
@@ -295,7 +294,7 @@ export const createSelectParticipantFailedEvent = function(error) {
}
return event;
};
}
/**
* Creates an event associated with the "shared video" feature.
@@ -305,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
@@ -328,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.
@@ -347,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.
@@ -369,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
@@ -389,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
@@ -406,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: {
@@ -414,7 +417,7 @@ export const createSyncTrackStateEvent = function(mediaType, muted) {
muted
}
};
};
}
/**
* Creates an event associated with a toolbar button being clicked/pressed. By
@@ -428,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,
@@ -436,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.
@@ -449,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: {
@@ -458,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

@@ -16,7 +16,11 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* @returns {void}
*/
export function sendAnalytics(event: Object) {
analytics.sendEvent(event);
try {
analytics.sendEvent(event);
} catch (e) {
logger.warn(`Error sending analytics event: ${e}`);
}
}
/**
@@ -39,13 +43,16 @@ export function initAnalytics({ getState }: { getState: Function }) {
const state = getState();
const config = state['features/base/config'];
const { analyticsScriptUrls, deploymentInfo } = config;
const { analyticsScriptUrls, deploymentInfo, googleAnalyticsTrackingId }
= config;
const { group, server, user } = state['features/base/jwt'];
const handlerConstructorOptions = {
envType: (deploymentInfo && deploymentInfo.envType) || 'dev',
googleAnalyticsTrackingId,
group,
product: deploymentInfo && deploymentInfo.product,
subproduct: deploymentInfo && deploymentInfo.environment,
user,
user: user && user.id,
version: JitsiMeetJS.version
};

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,32 +0,0 @@
/* @flow */
import {
HIDE_APP_SETTINGS,
SHOW_APP_SETTINGS
} from './actionTypes';
/**
* Redux-signals the request to open the app settings modal.
*
* @returns {{
* type: SHOW_APP_SETTINGS
* }}
*/
export function showAppSettings() {
return {
type: SHOW_APP_SETTINGS
};
}
/**
* Redux-signals the request to hide the app settings modal.
*
* @returns {{
* type: HIDE_APP_SETTINGS
* }}
*/
export function hideAppSettings() {
return {
type: HIDE_APP_SETTINGS
};
}

View File

@@ -1,311 +0,0 @@
// @flow
import { Component } from 'react';
import { hideAppSettings } from '../actions';
import { getProfile, updateProfile } from '../../base/profile';
/**
* The type of the React {@code Component} props of {@link AbstractAppSettings}
*/
type Props = {
/**
* The current profile object.
*/
_profile: Object,
/**
* The visibility prop of the settings modal.
*/
_visible: boolean,
/**
* Redux store dispatch function.
*/
dispatch: Dispatch<*>
};
/**
* The type of the React {@code Component} state of {@link AbstractAppSettings}.
*/
type State = {
/**
* The display name field value on the settings screen.
*/
displayName: string,
/**
* The email field value on the settings screen.
*/
email: string,
/**
* The server url field value on the settings screen.
*/
serverURL: string,
/**
* The start audio muted switch value on the settings screen.
*/
startWithAudioMuted: boolean,
/**
* The start video muted switch value on the settings screen.
*/
startWithVideoMuted: boolean
}
/**
* Base (abstract) class for container component rendering
* the app settings page.
*
* @abstract
*/
export class AbstractAppSettings extends Component<Props, State> {
/**
* Initializes a new {@code AbstractAppSettings} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* the component.
*/
constructor(props: Props) {
super(props);
this._onChangeDisplayName = this._onChangeDisplayName.bind(this);
this._onChangeEmail = this._onChangeEmail.bind(this);
this._onChangeServerName = this._onChangeServerName.bind(this);
this._onRequestClose = this._onRequestClose.bind(this);
this._onSaveDisplayName = this._onSaveDisplayName.bind(this);
this._onSaveEmail = this._onSaveEmail.bind(this);
this._onSaveServerName = this._onSaveServerName.bind(this);
this._onStartAudioMutedChange
= this._onStartAudioMutedChange.bind(this);
this._onStartVideoMutedChange
= this._onStartVideoMutedChange.bind(this);
}
/**
* Invokes React's {@link Component#componentWillReceiveProps()} to make
* sure we have the state Initialized on component mount.
*
* @inheritdoc
*/
componentWillMount() {
this._updateStateFromProps(this.props);
}
/**
* Implements React's {@link Component#componentWillReceiveProps()}. Invoked
* before this mounted component receives new props.
*
* @inheritdoc
* @param {Props} nextProps - New props component will receive.
*/
componentWillReceiveProps(nextProps: Props) {
this._updateStateFromProps(nextProps);
}
_onChangeDisplayName: (string) => void;
/**
* Handles the display name field value change.
*
* @protected
* @param {string} text - The value typed in the name field.
* @returns {void}
*/
_onChangeDisplayName(text) {
this.setState({
displayName: text
});
}
_onChangeEmail: (string) => void;
/**
* Handles the email field value change.
*
* @protected
* @param {string} text - The value typed in the email field.
* @returns {void}
*/
_onChangeEmail(text) {
this.setState({
email: text
});
}
_onChangeServerName: (string) => void;
/**
* Handles the server name field value change.
*
* @protected
* @param {string} text - The server URL typed in the server field.
* @returns {void}
*/
_onChangeServerName(text) {
this.setState({
serverURL: text
});
}
_onRequestClose: () => void;
/**
* Handles the hardware back button.
*
* @returns {void}
*/
_onRequestClose() {
this.props.dispatch(hideAppSettings());
}
_onSaveDisplayName: () => void;
/**
* Handles the display name field onEndEditing.
*
* @protected
* @returns {void}
*/
_onSaveDisplayName() {
this._updateProfile({
displayName: this.state.displayName
});
}
_onSaveEmail: () => void;
/**
* Handles the email field onEndEditing.
*
* @protected
* @returns {void}
*/
_onSaveEmail() {
this._updateProfile({
email: this.state.email
});
}
_onSaveServerName: () => void;
/**
* Handles the server name field onEndEditing.
*
* @protected
* @returns {void}
*/
_onSaveServerName() {
let serverURL;
if (this.state.serverURL.endsWith('/')) {
serverURL = this.state.serverURL.substr(
0, this.state.serverURL.length - 1
);
} else {
serverURL = this.state.serverURL;
}
this._updateProfile({
defaultURL: serverURL
});
this.setState({
serverURL
});
}
_onStartAudioMutedChange: (boolean) => void;
/**
* Handles the start audio muted change event.
*
* @protected
* @param {boolean} newValue - The new value for the
* start audio muted option.
* @returns {void}
*/
_onStartAudioMutedChange(newValue) {
this.setState({
startWithAudioMuted: newValue
});
this._updateProfile({
startWithAudioMuted: newValue
});
}
_onStartVideoMutedChange: (boolean) => void;
/**
* Handles the start video muted change event.
*
* @protected
* @param {boolean} newValue - The new value for the
* start video muted option.
* @returns {void}
*/
_onStartVideoMutedChange(newValue) {
this.setState({
startWithVideoMuted: newValue
});
this._updateProfile({
startWithVideoMuted: newValue
});
}
_updateProfile: (Object) => void;
/**
* Updates the persisted profile on any change.
*
* @private
* @param {Object} updateObject - The partial update object for the profile.
* @returns {void}
*/
_updateProfile(updateObject: Object) {
this.props.dispatch(updateProfile({
...this.props._profile,
...updateObject
}));
}
_updateStateFromProps: (Object) => void;
/**
* Updates the component state when (new) props are received.
*
* @private
* @param {Object} props - The component's props.
* @returns {void}
*/
_updateStateFromProps(props) {
this.setState({
displayName: props._profile.displayName,
email: props._profile.email,
serverURL: props._profile.defaultURL,
startWithAudioMuted: props._profile.startWithAudioMuted,
startWithVideoMuted: props._profile.startWithVideoMuted
});
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code AbstractAppSettings}.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Object}
*/
export function _mapStateToProps(state: Object) {
return {
_profile: getProfile(state),
_visible: state['features/app-settings'].visible
};
}

View File

@@ -1,99 +0,0 @@
import React from 'react';
import {
Modal,
Switch,
Text,
TextInput,
View } from 'react-native';
import { connect } from 'react-redux';
import {
_mapStateToProps,
AbstractAppSettings
} from './AbstractAppSettings';
import FormRow from './FormRow';
import styles from './styles';
import { translate } from '../../base/i18n';
/**
* The native container rendering the app settings page.
*
* @extends AbstractAppSettings
*/
class AppSettings extends AbstractAppSettings {
/**
* Implements React's {@link Component#render()}, renders the settings page.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
return (
<Modal
animationType = 'slide'
onRequestClose = { this._onRequestClose }
presentationStyle = 'fullScreen'
style = { styles.modal }
visible = { this.props._visible }>
<View style = { styles.headerContainer } >
<Text style = { [ styles.text, styles.headerTitle ] } >
{ t('profileModal.header') }
</Text>
</View>
<View style = { styles.settingsContainer } >
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.serverURL' >
<TextInput
autoCapitalize = 'none'
onChangeText = { this._onChangeServerName }
onEndEditing = { this._onSaveServerName }
placeholder = 'https://jitsi.example.com'
value = { this.state.serverURL } />
</FormRow>
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.displayName' >
<TextInput
onChangeText = { this._onChangeDisplayName }
onEndEditing = { this._onSaveDisplayName }
placeholder = 'John Doe'
value = { this.state.displayName } />
</FormRow>
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.email' >
<TextInput
onChangeText = { this._onChangeEmail }
onEndEditing = { this._onSaveEmail }
placeholder = 'email@example.com'
value = { this.state.email } />
</FormRow>
<FormRow
fieldSeparator = { true }
i18nLabel = 'profileModal.startWithAudioMuted' >
<Switch
onValueChange = {
this._onStartAudioMutedChange
}
value = { this.state.startWithAudioMuted } />
</FormRow>
<FormRow
i18nLabel = 'profileModal.startWithVideoMuted' >
<Switch
onValueChange = {
this._onStartVideoMutedChange
}
value = { this.state.startWithVideoMuted } />
</FormRow>
</View>
</Modal>
);
}
}
export default translate(connect(_mapStateToProps)(AppSettings));

View File

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

View File

@@ -1,98 +0,0 @@
import {
BoxModel,
ColorPalette,
createStyleSheet
} from '../../base/styles';
const LABEL_TAB = 300;
export const ANDROID_UNDERLINE_COLOR = 'transparent';
/**
* The styles of the React {@code Components} of the feature welcome including
* {@code WelcomePage} and {@code BlankPage}.
*/
export default createStyleSheet({
/**
* Standardized style for a field container {@code View}.
*/
fieldContainer: {
flexDirection: 'row',
alignItems: 'center',
minHeight: 65
},
/**
* Standard container for a {@code View} containing a field label.
*/
fieldLabelContainer: {
flexDirection: 'row',
alignItems: 'center',
width: LABEL_TAB
},
/**
* Field container style for all but last row {@code View}.
*/
fieldSeparator: {
borderBottomWidth: 1
},
/**
* Style for the {@code View} containing each
* field values (the actual field).
*/
fieldValueContainer: {
flex: 1,
justifyContent: 'flex-end',
flexDirection: 'row',
alignItems: 'center'
},
/**
* Page header {@code View}.
*/
headerContainer: {
backgroundColor: ColorPalette.blue,
flexDirection: 'row',
alignItems: 'center',
padding: 2 * BoxModel.margin
},
/**
* The title {@code Text} of the header.
*/
headerTitle: {
color: ColorPalette.white,
fontSize: 25
},
/**
* The top level container {@code View}.
*/
settingsContainer: {
backgroundColor: ColorPalette.white,
flex: 1,
flexDirection: 'column',
margin: 0,
padding: 2 * BoxModel.padding
},
/**
* Global {@code Text} color for the page.
*/
text: {
color: ColorPalette.black,
fontSize: 20
},
/**
* Standard text input field style.
*/
textInputField: {
fontSize: 20,
flex: 1,
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,13 +12,11 @@ import {
localParticipantJoined,
localParticipantLeft
} from '../../base/participants';
import '../../base/profile';
import { Fragment, RouteRegistry } from '../../base/react';
import {
getPersistedState,
MiddlewareRegistry,
ReducerRegistry
} from '../../base/redux';
import { getProfile } from '../../base/profile';
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';
import { BlankPage } from '../../welcome';
@@ -100,20 +98,22 @@ export class AbstractApp extends Component {
};
/**
* This way we make the mobile version wait until the
* {@code AsyncStorage} implementation of {@code Storage}
* properly initializes. On web it does actually nothing, see
* {@link #_initStorage}.
* Make the mobile {@code AbstractApp} wait until the
* {@code AsyncStorage} implementation of {@code Storage} initializes
* fully.
*
* @private
* @see {@link #_initStorage}
* @type {Promise}
*/
this.init = new Promise(resolve => {
this._initStorage().then(() => {
this.setState({
route: undefined,
store: this._maybeCreateStore(props)
});
resolve();
});
});
this._init
= this._initStorage()
.catch(() => { /* AbstractApp should always initialize! */ })
.then(() =>
this.setState({
route: undefined,
store: this._maybeCreateStore(props)
}));
}
/**
@@ -123,8 +123,8 @@ export class AbstractApp extends Component {
* @inheritdoc
*/
componentWillMount() {
this.init.then(() => {
const { dispatch } = this._getStore();
this._init.then(() => {
const { dispatch, getState } = this._getStore();
dispatch(appWillMount(this));
@@ -145,12 +145,14 @@ export class AbstractApp extends Component {
}
// Profile is the new React compatible settings.
const profile = getProfile(this._getStore().getState());
const profile = getState()['features/base/profile'];
Object.assign(localParticipant, {
email: profile.email,
name: profile.displayName
});
if (profile) {
localParticipant.email
= profile.email || localParticipant.email;
localParticipant.name
= profile.displayName || localParticipant.name;
}
// We set the initialized state here and not in the contructor to
// make sure that {@code componentWillMount} gets invoked before
@@ -177,7 +179,9 @@ export class AbstractApp extends Component {
* @returns {void}
*/
componentWillReceiveProps(nextProps) {
this.init.then(() => {
const { props } = this;
this._init.then(() => {
// The consumer of this AbstractApp did not provide a redux store.
if (typeof nextProps.store === 'undefined'
@@ -187,7 +191,7 @@ export class AbstractApp extends Component {
// its own internal redux store. If the consumer did not
// provide a redux store before, then this instance is
// using its own internal redux store already.
&& typeof this.props.store !== 'undefined') {
&& typeof props.store !== 'undefined') {
this.setState({
store: this._maybeCreateStore(nextProps)
});
@@ -197,11 +201,11 @@ export class AbstractApp extends Component {
let { url } = nextProps;
url = toURLString(url);
if (toURLString(this.props.url) !== url
if (toURLString(props.url) !== url
// XXX Refer to the implementation of loadURLObject: in
// ios/sdk/src/JitsiMeetView.m for further information.
|| this.props.timestamp !== nextProps.timestamp) {
|| props.timestamp !== nextProps.timestamp) {
this._openURL(url || this._getDefaultURL());
}
});
@@ -238,20 +242,21 @@ export class AbstractApp extends Component {
}
/**
* Delays app start until the {@code Storage} implementation initialises.
* This is instantaneous on web, but is async on mobile.
* Delays this {@code AbstractApp}'s startup until the {@code Storage}
* implementation of {@code localStorage} initializes. While the
* initialization is instantaneous on Web (with Web Storage API), it is
* asynchronous on mobile/react-native.
*
* @private
* @returns {ReactElement}
* @returns {Promise}
*/
_initStorage() {
return new Promise(resolve => {
if (window.localStorage._initializing) {
window.localStorage._inited.then(resolve);
} else {
resolve();
}
});
const localStorageInitializing = window.localStorage._initializing;
return (
typeof localStorageInitializing === 'undefined'
? Promise.resolve()
: localStorageInitializing);
}
/**
@@ -270,6 +275,7 @@ export class AbstractApp extends Component {
<Provider store = { this._getStore() }>
<Fragment>
{ this._createElement(component) }
<SoundCollection />
<OverlayContainer />
</Fragment>
</Provider>
@@ -347,7 +353,11 @@ export class AbstractApp extends Component {
middleware = compose(middleware, devToolsExtension());
}
return createStore(reducer, getPersistedState(), middleware);
return (
createStore(
reducer,
PersistenceRegistry.getPersistedState(),
middleware));
}
/**
@@ -371,11 +381,11 @@ export class AbstractApp extends Component {
}
}
const profileDefaultURL = getProfile(
this._getStore().getState()
).defaultURL;
return this.props.defaultURL || profileDefaultURL || DEFAULT_URL;
return (
this.props.defaultURL
|| this._getStore().getState()['features/base/profile']
.serverURL
|| DEFAULT_URL);
}
/**
@@ -493,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

@@ -6,14 +6,18 @@ import { Linking } from 'react-native';
import '../../analytics';
import '../../authentication';
import { AspectRatioDetector } from '../../base/aspect-ratio';
import { Platform } from '../../base/react';
import {
AspectRatioDetector,
ReducedUIDetector
} from '../../base/responsive-ui';
import '../../mobile/audio-mode';
import '../../mobile/background';
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';
@@ -33,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)
@@ -97,7 +108,9 @@ export class App extends AbstractApp {
_createElement(component, props) {
return (
<AspectRatioDetector>
{ super._createElement(component, props) }
<ReducedUIDetector>
{ super._createElement(component, props) }
</ReducedUIDetector>
</AspectRatioDetector>
);
}

View File

@@ -1,7 +1,9 @@
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React from 'react';
import '../../base/responsive-ui';
import { getLocationContextRoot } from '../../base/util';
import '../../chat';
import '../../room-lock';
import { AbstractApp } from './AbstractApp';

View File

@@ -1,10 +0,0 @@
/**
* The type of (redux) action which sets the aspect ratio of the app's user
* interface.
*
* {
* type: SET_ASPECT_RATIO,
* aspectRatio: Symbol
* }
*/
export const SET_ASPECT_RATIO = Symbol('SET_ASPECT_RATIO');

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