Compare commits

...

143 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
a56eb83346 rn: drop {AddPeople,Invite}Controller
We are going to implement the invite dialog *inside* the SDK, so there is no
need to have all this machinery anymore.
2019-02-07 11:48:24 +01:00
virtuacoplenny
b7133f5717 fix(large-video): do not show avatar if no url (#3871)
* fix(large-video): do not show avatar if no url

By default the large video dominant speaker avatar
has an empty src, which will result in a broken
image displaying. There is also disconnect with
non-react code trying to set an undefined src.
To prevent such until local avatar generation
work is done in the future, just don't show the
avatar.

* fix(conference): set the room instance earlier

Set the room instance on APP.conference before triggering
a redux update of the conference being set,, because
middleware can then fire and call methods on APP.conference
that depend on the room being set.

* get local participant directly from store instead of from global
2019-02-06 19:19:02 -08:00
virtuacoplenny
f77e1dc591 fix(speaker-levels): convert calculation from string to float (#3870) 2019-02-06 10:49:20 -08:00
virtuacoplenny
4d817fc6c2 fix(home): fix plus button alignment for calendar events (#3869) 2019-02-06 09:33:44 -08:00
Paweł Domas
b8a7037959 Merge pull request #3866 from jitsi/ice_failed_notification
chore(deps): update LJM to get ICE failed notifications
2019-02-06 09:00:55 -06:00
Saúl Ibarra Corretgé
6f95c50d6e Revert "misc: make URL protocol matching regexes non-greedy"
This reverts commit 7c911eca96.

I'm dumb. We need global mode because otherwise lastIndex is not updated in the
regex object, which we rely upon, so this is intentional.
2019-02-06 15:49:44 +01:00
Bettenbuk Zoltan
9f3ef43daa [RN] Add conference navigation bar 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
46713cab3b Move Labels to Conference 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
8065cc0348 [RN] Remove unused code 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
045a2d6aca Extract isToolboxVisible function 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
d7d9bc4eeb Reorganize conference feature files 2019-02-06 14:27:25 +01:00
Saúl Ibarra Corretgé
33db155eb9 ios: don't override AVAudioSession category and mode in default state
When we are in the default state (ie, not in a meeting) we shouldn't override
the AVAudioSession category and mode. It's a singleton and we might be bothering
other components of the host app which use it.
2019-02-06 10:17:39 +01:00
paweldomas
1cfd6164f5 chore(deps): update LJM to get ICE failed notifications
Updates LJM to 2e1436e20d4d8fb6020497a87b2714dff38a6c86 which includes
the ICE failed notification logic.
2019-02-05 21:45:02 -06:00
bgrozev
d9cf33b4c4 fix: Shows the "turn" indication for non-p2p connections. (#3865) 2019-02-05 23:13:59 +00:00
virtuacoplenny
2b56822a41 fix(ie11): redirect to recommended browsers page (#3853) 2019-02-05 08:26:27 -08:00
damencho
c34fee4305 Commit from translate.jitsi.org by user damencho.: 500 of 583 strings translated (0 fuzzy). 2019-02-05 13:36:52 +00:00
jitsi-pootle
ddc8a670f9 New files added from translate.jitsi.org based on templates 2019-02-05 10:09:34 +00:00
Saúl Ibarra Corretgé
7c911eca96 misc: make URL protocol matching regexes non-greedy 2019-02-02 21:21:21 +01:00
Saúl Ibarra Corretgé
f3c83f6e6d rn: finally fix Android deep-linking
The URL cannot end in a /.
2019-02-02 21:21:21 +01:00
Aaron van Meerten
b9a14acd3c adds conference to lookup of dial in numbers (#3859) 2019-02-01 19:10:34 -08:00
Aaron van Meerten
c998dbb47e Merge pull request #3858 from jmacelroy/master
Normalizing subdomain when checking JWTs; similar to room.
2019-02-01 13:53:39 -06:00
jmacelroy
573cc64fcd Normalizing subdomain when checking JWTs; similar to room. 2019-02-01 13:19:33 -06:00
Saúl Ibarra Corretgé
53c232fd76 misc: fix off-by-one error
e729f0948c contained an off-by-one error:

URI_PROTOCOL_PATTERN includes the colon, so after applyting the regex we are
left with something like '//example.com/room' thus we only need to strip the
first 2 characters.

🤦
2019-02-01 15:22:17 +01:00
paweldomas
3b6e34e96b fix(JitsiMeetLogStorage): do not log whole message
The logMessage can be very long. Probably only it's length could be
important to why sendApplicationLog has failed. Stringify the error
though.
2019-02-01 10:34:03 +01:00
paweldomas
8fe5814831 ref(JitsiMeetLogStorage): move to base/logging 2019-02-01 10:34:03 +01:00
paweldomas
2305effa5c feat(RN): enable log collector on mobile
Stores the Logger.LogCollector instance in base/logging state instead of
global APP variable and enables it on mobile.
2019-02-01 10:34:03 +01:00
Saúl Ibarra Corretgé
e729f0948c android: fix deep-linking from web
Looks like custom-scheme links no longer work in all browsers. They do on
Firefox, but the don't in Chrome and other default browsers.

So, switch to intent links on Android:
https://developer.chrome.com/multidevice/android/intents

Example:
```
<a href="intent://meet.jit.si/test123#Intent;scheme=org.jitsi.meet;package=org.jitsi.meet;end">Open Jitsi Meet</a>
```
2019-02-01 09:30:09 +01:00
virtuacoplenny
6f57d58dd9 fix(participant): update local name only on explicit update (#3849)
Dominant speaker events can trigger local participant updates
without a display name. Do not update the name unless there
is an explicit update in the action.
2019-01-31 15:46:34 -08:00
paweldomas
d0858b95b8 update lib-jitsi-meet 2019-01-31 13:50:51 -06:00
Saúl Ibarra Corretgé
5afb057387 rn: polyfill callstats
Instead of bundling it in lib-jitsi-meet, which unnecessarily increases
lib-jitsi-meet's bundle size, polyfill it here so it's available in the global
scope, just like the web does.
2019-01-31 13:50:51 -06:00
Saúl Ibarra Corretgé
5d86e3b674 deps: update react-native-callstats to 3.57.1 2019-01-31 13:50:51 -06:00
Paweł Domas
f8294fb312 android: add ConnectionService
* feat(Android): implement ConnectionService

Adds basic integration with Android's ConnectionService by implementing
the outgoing call scenario.

* ref(callkit): rename _SET_CALLKIT_SUBSCRIPTIONS

* ref(callkit): move feature to call-integration directory

* feat(ConnectionService): synchronize video state

* ref(AudioMode): use ConnectionService on API >= 26

Not ready yet - few details left mentioned in the FIXMEs

* feat(ConnectionService): add debug logs

Adds logs to trace the calls.

* fix(ConnectionService): leaking ConnectionImpl instances

Turns out there is no callback fired back from the JavaScript side after
the disconnect or abort event is sent from the native. The connection
must be marked as disconnected and removed immediately.

* feat(ConnectionService): handle onCreateOutgoingConnectionFailed

* ref(ConnectionService): merge classes and move to the sdk package

* feat(CallIntegration): show Alert if outgoing call fails

* fix(ConnectionService): alternatively get call UUID from the account

Some Android flavours (or versions ?) do copy over extras to
the onCreateOutgoingConnectionFailed callback. But the call UUID is also
set as the PhoneAccount's label, so eventually it should be available
there.

* ref(ConnectionService): use call UUID as PhoneAccount ID.

The extra is not reliable on some custom Android flavours. It also makes
sense to use unique id for the account instead of the URL given that
it's created on the per call basis.

* fix(ConnectionService): abort the call when hold is requested

Turns out Android P can sometimes request HOLD even though there's no
HOLD capability added to the connection (what!?), so just abort the call
in that case.

* fix(ConnectionService): unregister account on call failure

Unregister the PhoneAccount onCreateOutgoingConnectionFailed. That's
before the ConnectionImpl instance is created which is normally
responsible for doing that.

* fix(AudioModeModule): make package private and run on the audio thread

* address other review comments
2019-01-31 17:20:53 +01:00
Aaron van Meerten
3ad99e24cf Merge pull request #3840 from jitsi/prosody-token-wildcard-subdomain
supports a '*' in the sub claim to allow access to any room
2019-01-29 13:48:33 -06:00
Saúl Ibarra Corretgé
14990a427a rn: set version to 19.0.0
This marks our switch to CalVer: http://calver.org/

Major: year
Minor: release number
Patch: build (in case we need to retry)
2019-01-29 17:25:00 +01:00
virtuacoplenny
a1383bf730 fix(local-recording): allow config override to enable (#3615)
* fix(local-recording): allow config override to enable

Config overrides are not set until some time after
APP_WILL_MOUNT has completed and not in the same execution
context as when APP_WILL_MOUNT is called. So instead
choose recording controller initialization at a later time.
The time chosen is after conference join because the
controller needs the conference instance to work.

* remove redundant conditional check
2019-01-29 08:22:50 -08:00
Saúl Ibarra Corretgé
9bfe54475b android: read Dropbox API from main package resources
This makes Dropbox work on apps using the SDK without needing to build it
themselves.
2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
d5a43426ed android: don't read Dropbox key from iOS files 2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
612028ce3c rn: fix showing Dropbox controls when recording
- remove unneeded dialog (it's taken care of by StartRecordingDialogContent)
- pass correct props so integrations (Dropbox) show up
2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
3cec4989fd ios: enable recording in dev mode
While Apple doesn't want to allow us to enable Dropbox, it's good to have it
available for testing.
2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
77f220753f ios: dynamically load Dropbox API key
Load it at build time from a "dropbox.key" file. The file should contain the API
key in a single line.
2019-01-29 15:39:20 +01:00
Aaron van Meerten
13165990fc supports a '*' in the sub claim to allow access to any room 2019-01-28 16:19:43 -06:00
Bettenbuk Zoltan
63ff0c27a9 [RN] Add display name to on-stage participant 2019-01-28 18:34:12 +01:00
Bettenbuk Zoltan
f2b2cfda44 Extract shouldRenderParticipantVideo from ParticipantView 2019-01-28 18:34:12 +01:00
Leonard Kim
1b59b21fa8 chore(deps): bump lib to get wireless screensharing service 2019-01-27 17:55:01 +01:00
virtuacoplenny
6241172af8 feat(screenshare): support remote wireless screensharing (#3809)
* feat(screenshare): support remote wireless screensharing

- Pass events to the ProxyConnectionService so it can
  handle establishing a peer connection so a remote
  participant, not in the conference, can send a
  video stream to the local participant to use as a
  local desktop stream.
- Modify the existing start screensharing flow to accept
  a desktop stream instead of always trying to create one.

* adjust ProxyConnectionService for lib review changes
2019-01-26 12:53:11 -08:00
Saúl Ibarra Corretgé
8e58ce7500 ios: re-enable live streaming on iOS 10
There was a missing delegate method call into RNGoogleSignIn, which fixed this.
2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
37d3625210 ios: fix Goggle Sign-In deep-lining 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
111397d944 ios: style 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
211b3b55b1 ios: simplify code 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
8c0317cac0 ios: fix compilation warnings 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
15c8f2b125 android: remove unused import 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
349b1ff70e android: fix warning 2019-01-25 11:06:35 +01:00
Leonard Kim
244f206a87 chore(deps): bump lib for old conference listener cleanup 2019-01-25 09:19:10 +01:00
Bettenbuk Zoltan
82963b0aaf Add VSCode files to gitignore 2019-01-23 20:22:37 +01:00
paweldomas
92a412f814 chore(ios): update Podfile.lock 2019-01-23 17:04:41 +01:00
Guus der Kinderen
4b99caa1a9 Android SDK build instructions: add 'clean'
I'm ashamed to admit that I spent several _days_ chasing a bug that was already fixed. :( This obvious instruction would have been a time-saver.
2019-01-23 16:46:51 +01:00
Saúl Ibarra Corretgé
6190173ea7 misc: fixup MD format 2019-01-21 15:36:12 +01:00
Saúl Ibarra Corretgé
54df0f5d0f misc: add some more issue templates 2019-01-21 11:58:08 +01:00
damencho
4d440f5f64 Fixes showing video after waiting for owner. Fixes #3671. 2019-01-20 14:17:50 -08:00
virtuacoplenny
10624e87f5 ref(conference): change when the room reference is removed (#3808)
Delay removing the room reference. This is in case a
consumer of the API is attempting to submit feedback
after hangup but before redirecting to another page.
If the room reference is removed, feedback submission
will fail during this period.
2019-01-17 16:04:35 -08:00
Leonard Kim
7f29b47f3a chore(deps): update lib for camera-as-screenshare feature 2019-01-17 09:51:15 +01:00
Saúl Ibarra Corretgé
7c69308270 android: remove unused ProGuard rules file 2019-01-16 14:55:58 -06:00
Saúl Ibarra Corretgé
50b4212463 android: add missing ProGuard rules 2019-01-16 14:55:58 -06:00
Saúl Ibarra Corretgé
bb8fc8770a android: fix packager in debug mode in API 28
These values must match these ones in React Native:
5939d078a0/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java (L20-L22)
2019-01-16 14:55:12 -06:00
Bettenbuk Zoltan
79209535ea Centralise display name normalisation 2019-01-16 11:03:29 +01:00
Bettenbuk Zoltan
4bddae0bdb Remove default value from openDisplayNamePrompt action 2019-01-16 11:03:29 +01:00
Saúl Ibarra Corretgé
c203a452f7 ios: add initial Fastlane integration
Used for building and deploying builds to TestFlight and the App Store.
2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
515d2f11ce ios: set Google reverse client ID at build time
Read it from the GoogleService-Info.plist file and apply it into Info.plist.
2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
f7134722d0 ios: let the system reorder the file 2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
d8fa52fcaf ios: use a proper URL scheme placeholder
No caps are allowed, so this would trigger a rejection when uploading to
TestFlight.
2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
f7162c1500 rn: add some cleanup tasks when a conference ends / changes
- unpin participant (if the local one was pinned it would remain)
- close any dialog (except if authentication is pending)
2019-01-16 11:00:37 +01:00
virtuacoplenny
998db80db1 Merge pull request #3782 from virtuacoplenny/lenny/camera-as-ss
feat(screenshare): use camera as a screenshare source
2019-01-15 12:05:09 -08:00
Hristo Terezov
4575e7e119 fix(calendar): Remove logs with invalid calendar items. 2019-01-15 17:00:23 +01:00
Bettenbuk Zoltan
230b2b02fa Chat render improbement 2019-01-15 13:38:26 +01:00
Bettenbuk Zoltan
8a241ba2b7 [RN] Add chat functionality
Co-authored-by: DimaG <dgeorgiev06@gmail.com>
2019-01-15 11:33:12 +01:00
Bettenbuk Zoltan
82f714b608 Move display name handling into redux 2019-01-15 10:15:02 +01:00
Leonard Kim
8c9ba325ca fix(pinning): send a participant id on unpin
Analytics is erroring when unpinning because the logged
event sends null for the objectId. The objectId should
be the id of the person getting unpinned.
2019-01-15 09:58:45 +01:00
virtuacoplenny
693d4357a0 Merge pull request #3788 from virtuacoplenny/lenny/alpha-main
ref: alphabetize translation strings
2019-01-14 08:22:00 -08:00
Bettenbuk Zoltan
eef31d05cf [RN] Add an abstraction layer to Modal 2019-01-12 15:51:50 +01:00
Bettenbuk Zoltan
d7475a44e4 [RN] Extract header components for reuse 2019-01-12 15:51:50 +01:00
Leonard Kim
d09bfa7c0a ref: alphabetize translation strings 2019-01-11 15:33:01 -08:00
Leonard Kim
69dfa30142 feat(screenshare): use camera as a screenshare source
This feature is intended for spot. Spot can have an
HDMI -> usb adapter hooked up to it. In that case,
attempting to screenshare should use that adapter
as a screensharing source. Jitsi-Meet should pass
a configured screenshare source into lib-jitsi-meet
so it can be used as a source.
2019-01-11 09:52:53 -08:00
damencho
c13424f7c0 Fixes some lint warnings. 2019-01-11 15:11:17 +01:00
srmcgann
e7d0bf7b66 feat(DeepLinkingMobilePage): fix nested iframe button 2019-01-11 09:42:52 +01:00
damencho
653471e1c0 Adds specific class name to kick button.
The class name is similar to the one used for the mute button and is used by the tests to locate and click the button.
2019-01-10 12:02:55 -06:00
Bettenbuk Zoltan
e960002c45 [RN] Fix InviteButton translation 2019-01-10 16:31:03 +01:00
Bettenbuk Zoltan
c503187dc1 [RN] Fix input dialog value bug 2019-01-10 16:10:07 +01:00
virtuacoplenny
71563fec5b Merge pull request #3773 from zbettenbuk/fix-file-location
Move DeviceSelectionPopup to its right place
2019-01-09 22:17:49 -08:00
virtuacoplenny
5bf692a386 Merge pull request #3775 from jitsi/ljm_2080e81
Update LJM
2019-01-09 22:17:31 -08:00
paweldomas
f25556e63a chore: update lib-jitsi-meet to 2080e81 2019-01-09 18:48:59 -06:00
Saúl Ibarra Corretgé
a5696bb3e4 ref(eslint): remove React deprecated method rule suppression 2019-01-09 14:28:59 +01:00
Leonard Kim
29809ab024 ref(app): set url prop to state in componentDidUpdate
This is done to kill off the last deprecated lifecycle
usage. There is special logic within index.native to get a
default meeting url by asynchronously fetching it, if
a url is not passed initially. The url is then put onto
state and overridable on subsequent prop updates.
2019-01-09 14:28:59 +01:00
Bettenbuk Zoltan
5c0ae10ccb Remote video menu post-PR improvements 2019-01-09 12:13:30 +01:00
Bettenbuk Zoltan
82f6931ee8 [RN] Fix a react warning on remote video menu 2019-01-09 12:13:30 +01:00
Bettenbuk Zoltan
2ea5f3c1aa [RN] Add avatar to remote video menu 2019-01-09 12:13:30 +01:00
Saúl Ibarra Corretgé
889644f7bd [iOS] Add support for Siri shortcuts
This is mostly implemented in the app, with the needed support in the SDK. Since
the app needs to donate intents and deal with creating NSUserActivity objects it
doesn't feel right to do this in a library. Instead, we donate the intents from
the app, but the SDK is ready to extract conference URLs from any intent which
was registered as a conference activity.

This also opens the door for eventually adding Handoff support.
2019-01-09 12:05:58 +01:00
Saúl Ibarra Corretgé
4898f81596 [iOS] Simplify code
Share the code for extracting the URL for conference from a NSUserActivity.
2019-01-09 12:05:58 +01:00
Bettenbuk Zoltan
20d597e402 Move DeviceSelectionPopup to its right place 2019-01-09 12:02:42 +01:00
virtuacoplenny
fcd12a9cf6 Merge pull request #3770 from virtuacoplenny/lenny/bump-lib-json-override
chore(deps): bump lib to fix json message type overrriding
2019-01-08 13:27:42 -08:00
Leonard Kim
03094030a5 chore(deps): bump lib to fix json message type overriding 2019-01-08 11:32:06 -08:00
Buddhika Jayawardhana
16f47e5e0a doc: mention Java version 8 is required
Ref: #3543
2019-01-08 17:45:05 +01:00
Saúl Ibarra Corretgé
634f304815 android: simplify handling of the back button
Provide a default and builtin default implementation which finishes the
Activity, same as before.

What this PR removes is the ability to provide a custom default handler because
applications can already take this decision when calling `onBackPressed`. In
addition, make `onBackPressed` return `void` because it's virtually impossible
for it to return `false` (that would mean that there is no
`ReactInstanceManager`, which means there is no app to begin with).

In addition, remove the use of `BackAndroid` since `BackHandler` contains an iOS
shim now.
2019-01-08 17:43:36 +01:00
Saúl Ibarra Corretgé
148d4ebb90 rn: add Firebase integration
This is done at the app level, not the SDK.

Currently 2 Firebase services are used:

  - Crashlytics
  - Dynamic Links

They are enabled in tandem, if the appropriate Google services file
(GoogleService-Info.plist on iOS or google-services.json on Android) is found.

Each service needs to be individually enabled in the Firebase console.
2019-01-08 17:42:59 +01:00
Saúl Ibarra Corretgé
a1ebba0ef7 android: fix compilation warning
The annotation processor is required for our Glide module to be included.
2019-01-08 17:42:59 +01:00
Saúl Ibarra Corretgé
7e231c2826 ios: fix compilation warning 2019-01-08 17:42:59 +01:00
Saúl Ibarra Corretgé
5cbddb4874 ios: update SDK installation instructions
Mention the availability of CocoaPods.
2019-01-08 09:41:27 +01:00
Hristo Terezov
5a5dd6f5c5 chore(lib-jitsi-meet): Update. 2019-01-07 19:48:48 +00:00
Hristo Terezov
c2b2b4eba4 fix(initAnalytics): Add catch. 2019-01-07 19:48:48 +00:00
Hristo Terezov
155c7ba633 chore(lib-jitsi-meet): Update. 2019-01-07 14:32:31 +00:00
Hristo Terezov
5ad98dd058 ref(config): Create 'analytics' section. 2019-01-07 14:32:31 +00:00
Hristo Terezov
e5a8d95f1f feat(Amplitude): Integration. 2019-01-07 14:32:31 +00:00
virtuacoplenny
2d57d22a3f Merge pull request #3762 from saghul/no-prop-types
misc: drop dependency on prop-types and polyfill
2019-01-04 08:37:18 -08:00
virtuacoplenny
132dd98ecf Merge pull request #3763 from saghul/update-loaders
deps: update webpack loaders
2019-01-04 08:36:34 -08:00
virtuacoplenny
9b47dd1403 Merge pull request #3753 from virtuacoplenny/lenny/hangup-clean-ui
ref(hangup): clean up some UI state on hangup
2019-01-04 08:29:40 -08:00
virtuacoplenny
5b45542009 Merge pull request #3764 from virtuacoplenny/lenny/no-cwrp-abstract-app
ref(app): move url change handling to componentDidUpdate
2019-01-04 07:58:21 -08:00
Saúl Ibarra Corretgé
0b6496bf4d misc: drop dependency on prop-types and polyfill 2019-01-04 10:53:07 +01:00
Saúl Ibarra Corretgé
8ac701ab74 deps: drop @atlaskit/layer-manager dependency
We no longer need it since Rect 16 takes care of passing the context around.
It's also deprecated: https://atlaskit.atlassian.com/packages/core/layer-manager
2019-01-04 10:52:31 +01:00
Saúl Ibarra Corretgé
80bfeb6613 deps: update webpack loaders
- bump expose-loader to 0.7.5
- remove unused file-loader

NOTE: The first incarnation of this commmit also removed string-replace-loader,
but alas lib-jitsi-meet lists it as a devDependency so it's not installed and we
are currently running webpack on install. This is arguably wrong, but that's a
discussion for another day.
2019-01-04 10:27:40 +01:00
Leonard Kim
22a1917107 ref(app): move url change handling to componentDidUpdate
Instead of handling the side effect of navigating to another
url from within componentWillReceiveProps, try to match the
same logic instead in componentDidUpdate.
2019-01-03 19:34:46 -08:00
Sebastian Safari
ce01b31514 doc: add notes to enable 32bit only mode 2019-01-03 13:43:11 +01:00
Saúl Ibarra Corretgé
274b7148b0 ios: update development team
Also update the bundle ID since we don't release with that anyway.
2019-01-03 13:11:41 +01:00
Saúl Ibarra Corretgé
937c74f49e rn: disable touch feedback on Thumbnail
Touch feedback manifests in some ugly black border bleeding out of the thumbnail
itself. Since we already provide feedback (be that by adding the blue border in
case of pinning, or showing the menu in case of long press) the perception is
the same, without the graphical glitch.
2019-01-03 13:10:51 +01:00
Leonard Kim
be2bd9e2e6 chore(deps): bump @atlaskit/flag to 9.1.8 from 6.1.0
- Change the existing overrides to move the flags
  so the first flag does not cover the toolbar.
- Add a new override to disable the slide in
  animation, as it will play for each flag once
  it becomes the first flag--instead of playing
  only once when the flag queue has items.
2019-01-03 11:20:46 +00:00
Leonard Kim
50d3f46934 fix(thumbnail): re-override atlaskit theme for top toolbar
A CSS override prevents atlaskit theme from setting a
dark background on the top toolbar. With the upgrade
of the theme package the CSS class names changed.
2019-01-03 11:42:31 +02:00
Leonard Kim
14cc4ea54a ref(hangup): clean up some UI state on hangup
- Reset some state on the singletons conference
  and VideoLayout.
- Add a way for LocalVideo to clean itself up
  by sharing logic with the other SmallVideos.
- Add clearing of chat messages so they don't
  linger.
- Remove some UI event listeners.
2019-01-02 09:54:05 -08:00
Leonard Kim
9215b1e8b2 ref(app): move initialization into componentDidMount
componentWillMount is a deprecated lifecycle method;
componentDidMount should be used to kick off things
like ajax. In the case of the _App hierarchy, a promise
chain is used to perform initialization, and it is
first started in the constructor by initializing
storage. However, by the time storage is initialized,
resolving the first promise, _App has already mounted.
So, move it all to the componentDidMount lifecycle.
2019-01-02 10:02:04 +01:00
Leonard Kim
d996d51653 chore(deps): bump to @atlaskit/icon 15.0.5, @atlaskit/theme 7.0.2 2019-01-02 09:46:03 +01:00
Leonard Kim
ebcde745ef feat(tile-view): double click to pin 2019-01-02 09:43:20 +01:00
Saúl Ibarra Corretgé
fc75adc6ff feat(DialInInfo): fix webpack warning 2019-01-02 09:26:05 +01:00
Leonard Kim
3c4907ee0a fix(dial-in): update jsdoc 2019-01-02 09:22:58 +01:00
virtuacoplenny
914a64df7a Merge pull request #3747 from virtuacoplenny/lenny/remove-tab-hacks
ref(welcome-page): remove unused atlaskit overrides
2018-12-30 10:39:00 -08:00
Leonard Kim
eb34b0b11d ref(welcome-page): remove unused atlaskit overrides
WelcomePage used to use @atlaskit/tabs to switch between
recent meetings and calendar meetings. @atlaskit/tabs is
no longer used there so remove the css hacks which made
it look more presentable.
2018-12-29 16:42:55 -08:00
michael-dev
f6d3ca23a5 Fix LoginDialog hidden by gUM-Overlay (#766)
* Fix LoginDialog hidden by gUM-Overlay

Running FF46 on Linux and Android. The gUM Dialog (zIndex 1013) hides the LoginDialog (zIndex 999 by default) , but the gUM Dialog will only be resolved when connection is completed (aka hideUserMediaPermissionsGuidanceOverlay is called once the Promise.all in createInitialLocalTracksAndConnect is resolved and that Promise includes "connect").

Fix this by increasing the connection dialog zIndex.

Alternatively this could by fixed by handling gUM and connection one after the other.

* remove whitespace change
2018-12-28 09:37:29 -08:00
virtuacoplenny
fe33ad5026 Merge pull request #3724 from virtuacoplenny/lenny/bump-modal-dialog
chore(dep): bump @atlaskit/modal-dialog 6.0.12 to 7.1.2
2018-12-28 08:13:42 -08:00
damencho
380d9c75d1 Simplifies logic and renames a method. 2018-12-28 13:54:29 +00:00
damencho
7a09befd87 Updates time to be in ms and sends update of stats when user joins. 2018-12-28 13:54:29 +00:00
damencho
3b4037553a Adds server-side speaker stats handling.
Adds the component which receives the messages from client and a module which enabled on a virtual host will start advertising the component. When clients discover the component they will send message to the component with the name of the room where the dominant speaker event happen.
2018-12-28 13:54:29 +00:00
Saúl Ibarra Corretgé
f1ca2cac96 build: fix setting webpack mode 2018-12-21 17:04:08 +01:00
Saúl Ibarra Corretgé
8d1fd9841e misc: shorten text description 2018-12-21 15:12:13 +01:00
Leonard Kim
8b399e8caf chore(dep): bump @atlaskit/modal-dialog 6.0.12 to 7.1.2
The package now requires using a ModalTransition component
to handle animations. The existing DialogContainer component
has been split into native and web implementations to support
this change.
2018-12-20 20:05:49 -08:00
Bettenbuk Zoltan
6b68fba220 [RN] Add remote video menu 2018-12-20 17:23:07 +01:00
Saúl Ibarra Corretgé
d4c0840659 deps: update react-native-webrtc 2018-12-20 16:31:33 +01:00
Saúl Ibarra Corretgé
46a9891763 deps: update react-native-linear-gradient 2018-12-20 14:55:59 +01:00
270 changed files with 7769 additions and 4993 deletions

View File

@@ -1,12 +1,13 @@
---
name: Bug Report
about: Before posting, please make sure you check https://community.jitsi.org
name: Bug report
about: Create a report to help us improve
---
*This Issue tracker is only for reporting bugs and tracking code related issues.*
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed. General questions, installation help, and feature requests can also be posted to community.jitsi.org.
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed.
General questions, installation help, and feature requests can also be posted to community.jitsi.org.
## Description
---

10
.github/ISSUE_TEMPLATE/2-help.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: Need help with Jitsi Meet?
about: Please ask it in our community at https://community.jitsi.org
---
If you have a question about Jitsi Meet that is not a bug report or feature
request, please post it in https://community.jitsi.org
Questions posted to this repository will be closed.

View File

@@ -0,0 +1,23 @@
---
name: "Feature request"
about: Suggest an idea for this project
---
<!--
Thank you for suggesting an idea to make Jitsi Meet better.
Please fill in as much of the template below as you're able.
Note that the ultimate decission for implementing features lies on the Jitsi team, not all feature requests shall be accepted.
-->
**Is your feature request related to a problem you are facing?**
Please describe the problem you are trying to solve.
**Describe the solution you'd like**
Please describe the desired behavior.
**Describe alternatives you've considered**
Please describe alternative solutions or features you have considered.

4
.gitignore vendored
View File

@@ -35,6 +35,7 @@ xcuserdata
DerivedData
*.hmap
*.ipa
*.dSYM.zip
*.xcuserstate
project.xcworkspace
@@ -76,3 +77,6 @@ buck-out/
.jshintignore
.jshintrc
# VSCode files
android/.project
android/.settings/org.eclipse.buildship.core.prefs

View File

@@ -43,6 +43,10 @@ deploy-appbundle:
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.map \
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.map \
$(BUILD_DIR)/analytics-amplitude.min.js \
$(BUILD_DIR)/analytics-amplitude.min.map \
$(DEPLOY_DIR)
deploy-lib-jitsi-meet:

View File

@@ -33,6 +33,19 @@ dependencies {
}
```
Also, enable 32bit mode for react-native, since react-native only supports 32bit apps. (If you have a 64bit device, it will not run unless this setting it set)
```gradle
android {
...
defaultConfig {
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
...
```
### Build and use your own SDK artifacts/binaries
<details>
@@ -62,7 +75,7 @@ In the same way, copy the JavaScriptCore dependency:
Alternatively, you can use the scripts located in the android/scripts directory to publish these dependencies to your Maven repo.
Third-party React Native _modules_, which Jitsi Meet SDK for Android depends on, are download by NPM in source code form. These need to be assembled into Maven artifacts, and then published to your local Maven repository. The SDK project facilitates this.
Third-party React Native _modules_, which Jitsi Meet SDK for Android depends on, are download by NPM in source code form. These need to be assembled into Maven artifacts, and then published to your local Maven repository. The SDK project facilitates this.
To prepare, Configure the Maven repositories in which you are going to publish the SDK artifacts/binaries. In `android/sdk/build.gradle` as well as in `android/build.gradle` modify the lines that contain:
@@ -74,9 +87,13 @@ Change this value (which represents the Maven repository location used internall
Make sure to do this in both files! Each file should require one line to be changed.
To prevent artifacts from previous builds affecting you're outcome, it's good to start with cleaning your work directories:
$ ./gradlew clean
To create the release assembly for any _specific_ third-party React Native module that you need, you can execture the following commands, replace the module name in the examples below.
$ ./gradlew :react-native-webrtc:assembleRelease
$ ./gradlew :react-native-webrtc:assembleRelease
$ ./gradlew :react-native-webrtc:publish
You build and publish the SDK itself in the same way:
@@ -86,7 +103,7 @@ You build and publish the SDK itself in the same way:
Alternatively, you can assemble and publish _all_ subprojects, which include the react-native modules, but also the SDK itself, with a single command:
$ ./gradlew assembleRelease publish
$ ./gradlew clean assembleRelease publish
You're now ready to use the artifacts. In _your_ project, add the Maven repository that you used above (`/tmp/repo`) into your top-level `build.gradle` file:
@@ -171,10 +188,7 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onBackPressed() {
if (!ReactActivityLifecycleCallbacks.onBackPressed()) {
// Invoke the default handler if it wasn't handled by React.
super.onBackPressed();
}
ReactActivityLifecycleCallbacks.onBackPressed();
}
@Override

View File

@@ -1,5 +1,14 @@
apply plugin: 'com.android.application'
boolean googleServicesEnabled = project.file('google-services.json').exists()
// Crashlytics integration is done as part of Firebase now, so it gets
// automagically activated with google-services.json
if (googleServicesEnabled) {
apply plugin: 'io.fabric'
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
@@ -31,10 +40,12 @@ android {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
}
}
@@ -44,11 +55,15 @@ android {
}
}
repositories {
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation 'com.google.android.gms:play-services-auth:15.0.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation project(':sdk')
@@ -62,24 +77,24 @@ dependencies {
implementation("com.github.bumptech.glide:annotations:${rootProject.ext.glideVersion}") {
exclude group: "com.android.support", module: "annotations"
}
annotationProcessor "com.github.bumptech.glide:compiler:${rootProject.ext.glideVersion}"
// Firebase
// - Crashlytics
// - Dynamic Links
implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
implementation 'com.google.firebase:firebase-dynamic-links:16.1.5'
}
gradle.projectsEvaluated {
// Dropbox integration
//
def plistParser = new XmlSlurper(
/* validating */ false,
/* namespaceAware */ false,
/* allowDocTypeDeclaration */ true)
plistParser.setFeature(
'http://apache.org/xml/features/nonvalidating/load-external-dtd',
false)
def plist = plistParser.parse('../ios/app/src/Info.plist')
def dropboxScheme = plist.dict.array.dict.array.string.find { string ->
string.text().startsWith('db-')
def dropboxAppKey
if (project.file('dropbox.key').exists()) {
dropboxAppKey = project.file('dropbox.key').text.trim() - 'db-'
}
def dropboxAppKey = dropboxScheme?.text() - 'db-'
if (dropboxAppKey) {
android.defaultConfig.resValue('string', 'dropbox_app_key', "${dropboxAppKey}")
@@ -95,7 +110,7 @@ gradle.projectsEvaluated {
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="db-${dropboxAppKey}" />
</intent-filter>
</activity>""";
</activity>"""
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
@@ -116,6 +131,6 @@ gradle.projectsEvaluated {
}
}
if (project.file('google-services.json').exists()) {
if (googleServicesEnabled) {
apply plugin: 'com.google.gms.google-services'
}

View File

@@ -73,6 +73,15 @@
public *;
}
# WebRTC
-keep class org.webrtc.** { *; }
-dontwarn org.chromium.build.BuildHooksAndroid
# Jisti Meet SDK
-keep class org.jitsi.meet.sdk.** { *; }
# We added the following when we switched minifyEnabled on. Probably because we
# ran the app and hit problems...
@@ -83,7 +92,6 @@
-keep class com.facebook.react.bridge.ReadableType { *; }
-keep class com.facebook.react.bridge.queue.NativeRunnable { *; }
-keep class com.facebook.react.devsupport.** { *; }
-keep class org.webrtc.** { *; }
-dontwarn com.facebook.react.devsupport.**
-dontwarn com.google.appengine.**

View File

@@ -16,6 +16,7 @@
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize">
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 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.
@@ -16,21 +17,21 @@
package org.jitsi.meet;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import org.jitsi.meet.sdk.invite.AddPeopleController;
import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
import org.jitsi.meet.sdk.invite.InviteController;
import org.jitsi.meet.sdk.invite.InviteControllerListener;
import com.crashlytics.android.Crashlytics;
import com.facebook.react.bridge.UiThreadUtil;
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
import io.fabric.sdk.android.Fabric;
import java.util.ArrayList;
import java.util.List;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
/**
@@ -46,13 +47,6 @@ import java.util.Map;
* {@code react-native run-android}.
*/
public class MainActivity extends JitsiMeetActivity {
/**
* The query to perform through {@link AddPeopleController} when the
* {@code InviteButton} is tapped in order to exercise the public API of the
* feature invite. If {@code null}, the {@code InviteButton} will not be
* rendered.
*/
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
@Override
protected JitsiMeetView initializeView() {
@@ -106,72 +100,11 @@ public class MainActivity extends JitsiMeetActivity {
}
});
// inviteController
final InviteController inviteController
= view.getInviteController();
inviteController.setListener(new InviteControllerListener() {
public void beginAddPeople(
AddPeopleController addPeopleController) {
onInviteControllerBeginAddPeople(
inviteController,
addPeopleController);
}
});
inviteController.setAddPeopleEnabled(
ADD_PEOPLE_CONTROLLER_QUERY != null);
inviteController.setDialOutEnabled(
inviteController.isAddPeopleEnabled());
}
return view;
}
private void onAddPeopleControllerInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
UiThreadUtil.assertOnUiThread();
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated InviteController
// and no subsequent InviteButton clicks/taps will be delivered.
// Technically, endAddPeople will automatically be invoked if there are
// no failedInviteees i.e. the invite succeeeded for all specified
// invitees.
addPeopleController.endAddPeople();
}
private void onAddPeopleControllerReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
UiThreadUtil.assertOnUiThread();
int size = results.size();
if (size > 0) {
// Exercise AddPeopleController's inviteById implementation.
List<String> ids = new ArrayList<>(size);
for (Map<String, Object> result : results) {
Object id = result.get("id");
if (id != null) {
ids.add(id.toString());
}
}
addPeopleController.inviteById(ids);
return;
}
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated InviteController
// and no subsequent InviteButton clicks/taps will be delivered.
addPeopleController.endAddPeople();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
@@ -182,49 +115,28 @@ public class MainActivity extends JitsiMeetActivity {
setWelcomePageEnabled(true);
super.onCreate(savedInstanceState);
}
private void onInviteControllerBeginAddPeople(
InviteController inviteController,
AddPeopleController addPeopleController) {
UiThreadUtil.assertOnUiThread();
// Setup Crashlytics and Firebase Dynamic Links
if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
Fabric.with(this, new Crashlytics());
// Log with the tag "ReactNative" in order to have the log visible in
// react-native log-android as well.
Log.d(
"ReactNative",
InviteControllerListener.class.getSimpleName() + ".beginAddPeople");
FirebaseDynamicLinks.getInstance().getDynamicLink(getIntent())
.addOnSuccessListener(this, pendingDynamicLinkData -> {
Uri dynamicLink = null;
String query = ADD_PEOPLE_CONTROLLER_QUERY;
if (query != null
&& (inviteController.isAddPeopleEnabled()
|| inviteController.isDialOutEnabled())) {
addPeopleController.setListener(new AddPeopleControllerListener() {
public void onInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
onAddPeopleControllerInviteSettled(
addPeopleController,
failedInvitees);
}
if (pendingDynamicLinkData != null) {
dynamicLink = pendingDynamicLinkData.getLink();
}
public void onReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
onAddPeopleControllerReceivedResults(
addPeopleController,
results, query);
}
});
addPeopleController.performQuery(query);
} else {
// XXX Explicitly invoke endAddPeople on addPeopleController;
// otherwise, it is going to be memory-leaked in the associated
// InviteController and no subsequent InviteButton clicks/taps will
// be delivered.
addPeopleController.endAddPeople();
if (dynamicLink != null) {
try {
loadURL(new URL(dynamicLink.toString()));
} catch (MalformedURLException e) {
Log.d("ReactNative", "Malformed dynamic link", e);
}
}
});
}
}
}

View File

@@ -1,5 +1,6 @@
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">10.0.2.2</domain>
</domain-config>
</network-security-config>

View File

@@ -7,10 +7,14 @@ buildscript {
repositories {
google()
jcenter()
repositories {
maven { url 'https://maven.fabric.io/public' }
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.google.gms:google-services:3.2.1'
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.27.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files.

View File

@@ -17,6 +17,6 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
buildNumber=2
appVersion=1.21.0
buildNumber=1
appVersion=19.0.0
sdkVersion=1.21.0

View File

@@ -124,6 +124,7 @@ android.libraryVariants.all { def variant ->
// Bundle sounds
//
copy {
from("${projectDir}/../../sounds/incomingMessage.wav")
from("${projectDir}/../../sounds/joined.wav")
from("${projectDir}/../../sounds/left.wav")
from("${projectDir}/../../sounds/outgoingRinging.wav")

View File

@@ -1,25 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/scorretge/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
@@ -26,5 +27,11 @@
android:supportsRtl="true">
<activity
android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name="org.jitsi.meet.sdk.ConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -25,6 +25,8 @@ import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.telecom.CallAudioState;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
@@ -100,6 +102,69 @@ class AudioModeModule
*/
static final String TAG = MODULE_NAME;
/**
* Converts any of the "DEVICE_" constants into the corresponding
* {@link CallAudioState} "ROUTE_" number.
*
* @param audioDevice one of the "DEVICE_" constants.
* @return a route number {@link CallAudioState#ROUTE_EARPIECE} if no match
* is found.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static int audioDeviceToRouteInt(String audioDevice) {
if (audioDevice == null) {
return CallAudioState.ROUTE_EARPIECE;
}
switch (audioDevice) {
case DEVICE_BLUETOOTH:
return CallAudioState.ROUTE_BLUETOOTH;
case DEVICE_EARPIECE:
return CallAudioState.ROUTE_EARPIECE;
case DEVICE_HEADPHONES:
return CallAudioState.ROUTE_WIRED_HEADSET;
case DEVICE_SPEAKER:
return CallAudioState.ROUTE_SPEAKER;
default:
Log.e(TAG, "Unsupported device name: " + audioDevice);
return CallAudioState.ROUTE_EARPIECE;
}
}
/**
* Populates given route mask into the "DEVICE_" list.
*
* @param supportedRouteMask an integer coming from
* {@link CallAudioState#getSupportedRouteMask()}.
* @return a list of device names.
*/
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
Set<String> devices = new HashSet<>();
if ((supportedRouteMask & CallAudioState.ROUTE_EARPIECE)
== CallAudioState.ROUTE_EARPIECE) {
devices.add(DEVICE_EARPIECE);
}
if ((supportedRouteMask & CallAudioState.ROUTE_BLUETOOTH)
== CallAudioState.ROUTE_BLUETOOTH) {
devices.add(DEVICE_BLUETOOTH);
}
if ((supportedRouteMask & CallAudioState.ROUTE_SPEAKER)
== CallAudioState.ROUTE_SPEAKER) {
devices.add(DEVICE_SPEAKER);
}
if ((supportedRouteMask & CallAudioState.ROUTE_WIRED_HEADSET)
== CallAudioState.ROUTE_WIRED_HEADSET) {
devices.add(DEVICE_HEADPHONES);
}
return devices;
}
/**
* Whether or not the ConnectionService is used for selecting audio devices.
*/
private static boolean useConnectionService() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
/**
* Indicator that we have lost audio focus.
*/
@@ -204,6 +269,15 @@ class AudioModeModule
*/
private String selectedDevice;
/**
* Used on API >= 26 to store the most recently reported audio devices.
* Makes it easier to compare for a change, because the devices are stored
* as a mask in the {@link CallAudioState}. The mask is populated into
* the {@link #availableDevices} on each update.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private int supportedRouteMask;
/**
* User selected device. When null the default is used depending on the
* mode.
@@ -224,21 +298,25 @@ class AudioModeModule
= (AudioManager)
reactContext.getSystemService(Context.AUDIO_SERVICE);
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
// Starting Oreo the ConnectionImpl from ConnectionService us used to
// detect the available devices.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
// Do an initial detection on Android >= M.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
runInAudioThread(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Do an initial detection on Android >= M.
runInAudioThread(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
}
@@ -354,6 +432,38 @@ class AudioModeModule
});
}
@RequiresApi(api = Build.VERSION_CODES.O)
void onCallAudioStateChange(final CallAudioState callAudioState) {
runInAudioThread(new Runnable() {
@Override
public void run() {
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
boolean audioDevicesChanged
= supportedRouteMask != newSupportedRoutes;
if (audioDevicesChanged) {
supportedRouteMask = newSupportedRoutes;
availableDevices = routesToDeviceNames(supportedRouteMask);
Log.d(TAG,
"Available audio devices: "
+ availableDevices.toString());
}
boolean audioRouteChanged
= audioDeviceToRouteInt(selectedDevice)
!= callAudioState.getRoute();
if (audioRouteChanged || audioDevicesChanged) {
// Reset user selection
userSelectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
}
});
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
@@ -417,6 +527,31 @@ class AudioModeModule
});
}
/**
* The API >= 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void setAudioRoute(String audioDevice) {
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
RNConnectionService.setAudioRoute(newAudioRoute);
}
/**
* The API < 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
private void setAudioRoutePreO(String audioDevice) {
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
}
/**
* Helper method to set the output route to a Bluetooth device.
*
@@ -475,7 +610,7 @@ class AudioModeModule
/**
* Setup the audio route change detection mechanism. We use the
* {@link android.media.AudioDeviceCallback} API on Android >= 23 only.
* {@link android.media.AudioDeviceCallback} on 23 >= Android API < 26.
*/
private void setupAudioRouteChangeDetection() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -486,7 +621,7 @@ class AudioModeModule
}
/**
* Audio route change detection mechanism for Android API >= 23.
* Audio route change detection mechanism for 23 >= Android API < 26.
*/
@TargetApi(Build.VERSION_CODES.M)
private void setupAudioRouteChangeDetectionM() {
@@ -542,27 +677,31 @@ class AudioModeModule
Log.d(TAG, "Update audio route for mode: " + mode);
if (mode == DEFAULT) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
if (!useConnectionService()) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
}
selectedDevice = null;
userSelectedDevice = null;
return true;
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (!useConnectionService()) {
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(
if (audioManager.requestAudioFocus(
this,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.d(TAG, "Audio focus request failed");
return false;
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.d(TAG, "Audio focus request failed");
return false;
}
}
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
@@ -596,11 +735,11 @@ class AudioModeModule
selectedDevice = audioDevice;
Log.d(TAG, "Selected audio device: " + audioDevice);
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
if (useConnectionService()) {
setAudioRoute(audioDevice);
} else {
setAudioRoutePreO(audioDevice);
}
return true;
}

View File

@@ -0,0 +1,435 @@
package org.jitsi.meet.sdk;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableNativeMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Jitsi Meet implementation of {@link ConnectionService}. At the time of this
* writing it implements only the outgoing call scenario.
*
* NOTE the class needs to be public, but is not part of the SDK API and should
* never be used directly.
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public class ConnectionService extends android.telecom.ConnectionService {
/**
* Tag used for logging.
*/
static final String TAG = "JitsiConnectionService";
/**
* The extra added to the {@link ConnectionImpl} and
* {@link ConnectionRequest} which stores the {@link PhoneAccountHandle}
* created for the call.
*/
static final String EXTRA_PHONE_ACCOUNT_HANDLE
= "org.jitsi.meet.sdk.connection_service.PHONE_ACCOUNT_HANDLE";
/**
* Connections mapped by call UUID.
*/
static private final Map<String, ConnectionImpl> connections
= new HashMap<>();
/**
* The start call Promises mapped by call UUID.
*/
static private final HashMap<String, Promise> startCallPromises
= new HashMap<>();
/**
* Adds {@link ConnectionImpl} to the list.
*
* @param connection - {@link ConnectionImpl}
*/
static void addConnection(ConnectionImpl connection) {
connections.put(connection.getCallUUID(), connection);
}
/**
* Returns all {@link ConnectionImpl} instances held in this list.
*
* @return a list of {@link ConnectionImpl}.
*/
static List<ConnectionImpl> getConnections() {
return new ArrayList<>(connections.values());
}
/**
* Registers a start call promise.
*
* @param uuid - the call UUID to which the start call promise belongs to.
* @param promise - the Promise instance to be stored for later use.
*/
static void registerStartCallPromise(String uuid, Promise promise) {
startCallPromises.put(uuid, promise);
}
/**
* Removes {@link ConnectionImpl} from the list.
*
* @param connection - {@link ConnectionImpl}
*/
static void removeConnection(ConnectionImpl connection) {
connections.remove(connection.getCallUUID());
}
/**
* Used to adjusts the connection's state to
* {@link android.telecom.Connection#STATE_ACTIVE}.
*
* @param callUUID the call UUID which identifies the connection.
*/
static void setConnectionActive(String callUUID) {
ConnectionImpl connection = connections.get(callUUID);
if (connection != null) {
connection.setActive();
} else {
Log.e(TAG, String.format(
"setConnectionActive - no connection for UUID: %s",
callUUID));
}
}
/**
* Used to adjusts the connection's state to
* {@link android.telecom.Connection#STATE_DISCONNECTED}.
*
* @param callUUID the call UUID which identifies the connection.
* @param cause disconnection reason.
*/
static void setConnectionDisconnected(String callUUID, DisconnectCause cause) {
ConnectionImpl connection = connections.get(callUUID);
if (connection != null) {
// Note that the connection is not removed from the list here, but
// in ConnectionImpl's state changed callback. It's a safer
// approach, because in case the app would crash on the JavaScript
// side the calls would be cleaned up by the system they would still
// be removed from the ConnectionList.
connection.setDisconnected(cause);
connection.destroy();
} else {
Log.e(TAG, "endCall no connection for UUID: " + callUUID);
}
}
/**
* Unregisters a start call promise. Must be called after the Promise is
* rejected or resolved.
*
* @param uuid the call UUID which identifies the call to which the promise
* belongs to.
* @return the unregistered Promise instance or <tt>null</tt> if there
* wasn't any for the given call UUID.
*/
static Promise unregisterStartCallPromise(String uuid) {
return startCallPromises.remove(uuid);
}
/**
* Used to adjusts the call's state.
*
* @param callUUID the call UUID which identifies the connection.
* @param callState a map which carries the properties to be modified. See
* "KEY_*" constants in {@link ConnectionImpl} for the list of keys.
*/
static void updateCall(String callUUID, ReadableMap callState) {
ConnectionImpl connection = connections.get(callUUID);
if (connection != null) {
if (callState.hasKey(ConnectionImpl.KEY_HAS_VIDEO)) {
boolean hasVideo
= callState.getBoolean(ConnectionImpl.KEY_HAS_VIDEO);
Log.d(TAG, String.format(
"updateCall: %s hasVideo: %s", callUUID, hasVideo));
connection.setVideoState(
hasVideo
? VideoProfile.STATE_BIDIRECTIONAL
: VideoProfile.STATE_AUDIO_ONLY);
}
} else {
Log.e(TAG, "updateCall no connection for UUID: " + callUUID);
}
}
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
ConnectionImpl connection = new ConnectionImpl();
connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
connection.setAddress(
request.getAddress(),
TelecomManager.PRESENTATION_ALLOWED);
connection.setExtras(request.getExtras());
// NOTE there's a time gap between the placeCall and this callback when
// things could get out of sync, but they are put back in sync once
// the startCall Promise is resolved below. That's because on
// the JavaScript side there's a logic to sync up in .then() callback.
connection.setVideoState(request.getVideoState());
Bundle moreExtras = new Bundle();
moreExtras.putParcelable(
EXTRA_PHONE_ACCOUNT_HANDLE,
Objects.requireNonNull(request.getAccountHandle(), "accountHandle"));
connection.putExtras(moreExtras);
addConnection(connection);
Promise startCallPromise
= unregisterStartCallPromise(connection.getCallUUID());
if (startCallPromise != null) {
Log.d(TAG,
"onCreateOutgoingConnection " + connection.getCallUUID());
startCallPromise.resolve(null);
} else {
Log.e(TAG, String.format(
"onCreateOutgoingConnection: no start call Promise for %s",
connection.getCallUUID()));
}
return connection;
}
@Override
public Connection onCreateIncomingConnection(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
throw new RuntimeException("Not implemented");
}
@Override
public void onCreateIncomingConnectionFailed(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
throw new RuntimeException("Not implemented");
}
@Override
public void onCreateOutgoingConnectionFailed(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
PhoneAccountHandle theAccountHandle = request.getAccountHandle();
String callUUID = theAccountHandle.getId();
Log.e(TAG, "onCreateOutgoingConnectionFailed " + callUUID);
if (callUUID != null) {
Promise startCallPromise = unregisterStartCallPromise(callUUID);
if (startCallPromise != null) {
startCallPromise.reject(
"CREATE_OUTGOING_CALL_FAILED",
"The request has been denied by the system");
} else {
Log.e(TAG, String.format(
"startCallFailed - no start call Promise for UUID: %s",
callUUID));
}
} else {
Log.e(TAG, "onCreateOutgoingConnectionFailed - no call UUID");
}
unregisterPhoneAccount(theAccountHandle);
}
private void unregisterPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
TelecomManager telecom = getSystemService(TelecomManager.class);
if (telecom != null) {
if (phoneAccountHandle != null) {
telecom.unregisterPhoneAccount(phoneAccountHandle);
} else {
Log.e(TAG, "unregisterPhoneAccount - account handle is null");
}
} else {
Log.e(TAG, "unregisterPhoneAccount - telecom is null");
}
}
/**
* Registers new {@link PhoneAccountHandle}.
*
* @param context the current Android context.
* @param address the phone account's address. At the time of this writing
* it's the call handle passed from the Java Script side.
* @param callUUID the call's UUID for which the account is to be created.
* It will be used as the account's id.
* @return {@link PhoneAccountHandle} described by the given arguments.
*/
static PhoneAccountHandle registerPhoneAccount(
Context context, Uri address, String callUUID) {
PhoneAccountHandle phoneAccountHandle
= new PhoneAccountHandle(
new ComponentName(context, ConnectionService.class),
callUUID);
PhoneAccount.Builder builder
= PhoneAccount.builder(phoneAccountHandle, address.toString())
.setAddress(address)
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
PhoneAccount.CAPABILITY_VIDEO_CALLING |
PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
.addSupportedUriScheme(PhoneAccount.SCHEME_SIP);
PhoneAccount account = builder.build();
TelecomManager telecomManager
= context.getSystemService(TelecomManager.class);
telecomManager.registerPhoneAccount(account);
return phoneAccountHandle;
}
/**
* Connection implementation for Jitsi Meet's {@link ConnectionService}.
*
* @author Pawel Domas
*/
class ConnectionImpl extends Connection {
/**
* The constant which defines the key for the "has video" property.
* The key is used in the map which carries the call's state passed as
* the argument of the {@link RNConnectionService#updateCall} method.
*/
static final String KEY_HAS_VIDEO = "hasVideo";
/**
* Called when system wants to disconnect the call.
*
* {@inheritDoc}
*/
@Override
public void onDisconnect() {
Log.d(TAG, "onDisconnect " + getCallUUID());
WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID());
ReactContextUtils.emitEvent(
null,
"org.jitsi.meet:features/connection_service#disconnect",
data);
// The JavaScript side will not go back to the native with
// 'endCall', so the Connection must be removed immediately.
setConnectionDisconnected(
getCallUUID(),
new DisconnectCause(DisconnectCause.LOCAL));
}
/**
* Called when system wants to abort the call.
*
* {@inheritDoc}
*/
@Override
public void onAbort() {
Log.d(TAG, "onAbort " + getCallUUID());
WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID());
ReactContextUtils.emitEvent(
null,
"org.jitsi.meet:features/connection_service#abort",
data);
// The JavaScript side will not go back to the native with
// 'endCall', so the Connection must be removed immediately.
setConnectionDisconnected(
getCallUUID(),
new DisconnectCause(DisconnectCause.CANCELED));
}
@Override
public void onHold() {
// What ?! Android will still call this method even if we do not add
// the HOLD capability, so do the same thing as on abort.
// TODO implement HOLD
Log.d(TAG, String.format(
"onHold %s - HOLD is not supported, aborting the call...",
getCallUUID()));
this.onAbort();
}
/**
* Called when there's change to the call audio state. Either by
* the system after the connection is initialized or in response to
* {@link #setAudioRoute(int)}.
*
* @param state the new {@link CallAudioState}
*/
@Override
public void onCallAudioStateChanged(CallAudioState state) {
Log.d(TAG, "onCallAudioStateChanged: " + state);
AudioModeModule audioModeModule
= ReactInstanceManagerHolder
.getNativeModule(AudioModeModule.class);
if (audioModeModule != null) {
audioModeModule.onCallAudioStateChange(state);
}
}
/**
* Unregisters the account when the call is disconnected.
*
* @param state - the new connection's state.
*/
@Override
public void onStateChanged(int state) {
Log.d(TAG,
String.format("onStateChanged: %s %s",
Connection.stateToString(state),
getCallUUID()));
if (state == STATE_DISCONNECTED) {
removeConnection(this);
unregisterPhoneAccount(getPhoneAccountHandle());
}
}
/**
* Retrieves the UUID of the call associated with this connection.
*
* @return call UUID
*/
String getCallUUID() {
return getPhoneAccountHandle().getId();
}
private PhoneAccountHandle getPhoneAccountHandle() {
return getExtras().getParcelable(
ConnectionService.EXTRA_PHONE_ACCOUNT_HANDLE);
}
@Override
public String toString() {
return String.format(
"ConnectionImpl[adress=%s, uuid=%s]@%d",
getAddress(), getCallUUID(), hashCode());
}
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 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.
@@ -28,8 +29,7 @@ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
* during a conference by leaving the conference and (2) not handle the
* invocation when not in a conference.
*/
public class DefaultHardwareBackBtnHandlerImpl
implements DefaultHardwareBackBtnHandler {
class DefaultHardwareBackBtnHandlerImpl implements DefaultHardwareBackBtnHandler {
/**
* The {@code Activity} to which the default handling of the back button

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 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.
@@ -26,7 +27,6 @@ import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
import java.net.URL;
@@ -52,12 +52,6 @@ public class JitsiMeetActivity
private static final int OVERLAY_PERMISSION_REQUEST_CODE
= (int) (Math.random() * Short.MAX_VALUE);
/**
* The default behavior of this {@code JitsiMeetActivity} upon invoking the
* back button if {@link #view} does not handle the invocation.
*/
private DefaultHardwareBackBtnHandler defaultBackButtonImpl;
/**
* The default base {@code URL} used to join a conference when a partial URL
* (e.g. a room name only) is specified. The value is used only while
@@ -185,19 +179,7 @@ public class JitsiMeetActivity
@Override
public void onBackPressed() {
if (!ReactActivityLifecycleCallbacks.onBackPressed()) {
// JitsiMeetView didn't handle the invocation of the back button.
// Generally, an Activity extender would very likely want to invoke
// Activity#onBackPressed(). For the sake of consistency with
// JitsiMeetView and within the Jitsi Meet SDK for Android though,
// JitsiMeetActivity does what JitsiMeetView would've done if it
// were able to handle the invocation.
if (defaultBackButtonImpl == null) {
super.onBackPressed();
} else {
defaultBackButtonImpl.invokeDefaultOnBackPressed();
}
}
ReactActivityLifecycleCallbacks.onBackPressed();
}
@Override
@@ -279,8 +261,7 @@ public class JitsiMeetActivity
protected void onResume() {
super.onResume();
defaultBackButtonImpl = new DefaultHardwareBackBtnHandlerImpl(this);
ReactActivityLifecycleCallbacks.onHostResume(this, defaultBackButtonImpl);
ReactActivityLifecycleCallbacks.onHostResume(this);
}
@Override
@@ -288,7 +269,6 @@ public class JitsiMeetActivity
super.onStop();
ReactActivityLifecycleCallbacks.onHostPause(this);
defaultBackButtonImpl = null;
}
@Override

View File

@@ -24,8 +24,6 @@ import android.util.Log;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.invite.InviteController;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Map;
@@ -78,12 +76,6 @@ public class JitsiMeetView
*/
private URL defaultURL;
/**
* The entry point into the invite feature of Jitsi Meet. The Java
* counterpart of the JavaScript {@code InviteButton}.
*/
private final InviteController inviteController;
/**
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
* {@code true} iff the Android platform supports Picture-in-Picture
@@ -106,10 +98,6 @@ public class JitsiMeetView
public JitsiMeetView(@NonNull Context context) {
super(context);
// The entry point into the invite feature of Jitsi Meet. The Java
// counterpart of the JavaScript InviteButton.
inviteController = new InviteController(externalAPIScope);
// Check if the parent Activity implements JitsiMeetActivityInterface,
// otherwise things may go wrong.
if (!(context instanceof JitsiMeetActivityInterface)) {
@@ -155,19 +143,6 @@ public class JitsiMeetView
return defaultURL;
}
/**
* Gets the {@link InviteController} which represents the entry point into
* the invite feature of Jitsi Meet and is the Java counterpart of the
* JavaScript {@code InviteButton}.
*
* @return the {@link InviteController} which represents the entry point
* into the invite feature of Jitsi Meet and is the Java counterpart of the
* JavaScript {@code InviteButton}
*/
public InviteController getInviteController() {
return inviteController;
}
/**
* Gets the URL of the current conference.
*
@@ -238,18 +213,6 @@ public class JitsiMeetView
props.putString("defaultURL", defaultURL.toString());
}
// inviteController
InviteController inviteController = getInviteController();
if (inviteController != null) {
props.putBoolean(
"addPeopleEnabled",
inviteController.isAddPeopleEnabled());
props.putBoolean(
"dialOutEnabled",
inviteController.isDialOutEnabled());
}
// pictureInPictureEnabled
props.putBoolean(
"pictureInPictureEnabled",

View File

@@ -16,6 +16,7 @@
package org.jitsi.meet.sdk;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.os.Build;
@@ -53,6 +54,7 @@ class PictureInPictureModule
* including when the activity is not visible (paused or stopped), if the
* screen is locked or if the user has an activity pinned.
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isPictureInPictureSupported()) {
throw new IllegalStateException("Picture-in-Picture not supported");

View File

@@ -0,0 +1,165 @@
package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
/**
* The react-native side of Jitsi Meet's {@link ConnectionService}. Exposes
* the Java Script API.
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
class RNConnectionService
extends ReactContextBaseJavaModule {
private final static String TAG = ConnectionService.TAG;
/**
* Sets the audio route on all existing {@link android.telecom.Connection}s
*
* @param audioRoute the new audio route to be set. See
* {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_".
*/
@RequiresApi(api = Build.VERSION_CODES.O)
static void setAudioRoute(int audioRoute) {
for (ConnectionService.ConnectionImpl c
: ConnectionService.getConnections()) {
c.setAudioRoute(audioRoute);
}
}
RNConnectionService(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Starts a new outgoing call.
*
* @param callUUID - unique call identifier assigned by Jitsi Meet to
* a conference call.
* @param handle - a call handle which by default is Jitsi Meet room's URL.
* @param hasVideo - whether or not user starts with the video turned on.
* @param promise - the Promise instance passed by the React-native bridge,
* so that this method returns a Promise on the JS side.
*
* NOTE regarding the "missingPermission" suppress - SecurityException will
* be handled as part of the Exception try catch block and the Promise will
* be rejected.
*/
@SuppressLint("MissingPermission")
@ReactMethod
public void startCall(
String callUUID,
String handle,
boolean hasVideo,
Promise promise) {
Log.d(TAG,
String.format("startCall UUID=%s, h=%s, v=%s",
callUUID,
handle,
hasVideo));
ReactApplicationContext ctx = getReactApplicationContext();
Uri address = Uri.fromParts(PhoneAccount.SCHEME_SIP, handle, null);
PhoneAccountHandle accountHandle
= ConnectionService.registerPhoneAccount(
getReactApplicationContext(), address, callUUID);
Bundle extras = new Bundle();
extras.putParcelable(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
accountHandle);
extras.putInt(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
hasVideo
? VideoProfile.STATE_BIDIRECTIONAL
: VideoProfile.STATE_AUDIO_ONLY);
ConnectionService.registerStartCallPromise(callUUID, promise);
try {
TelecomManager tm
= (TelecomManager) ctx.getSystemService(
Context.TELECOM_SERVICE);
tm.placeCall(address, extras);
} catch (Exception e) {
ConnectionService.unregisterStartCallPromise(callUUID);
promise.reject(e);
}
}
/**
* Called by the JS side of things to mark the call as failed.
*
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void reportCallFailed(String callUUID) {
Log.d(TAG, "reportCallFailed " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.ERROR));
}
/**
* Called by the JS side of things to mark the call as disconnected.
*
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void endCall(String callUUID) {
Log.d(TAG, "endCall " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.LOCAL));
}
/**
* Called by the JS side of things to mark the call as active.
*
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void reportConnectedOutgoingCall(String callUUID) {
Log.d(TAG, "reportConnectedOutgoingCall " + callUUID);
ConnectionService.setConnectionActive(callUUID);
}
@Override
public String getName() {
return "ConnectionService";
}
/**
* Called by the JS side to update the call's state.
*
* @param callUUID - the call's UUID.
* @param callState - the map which carries infor about the current call's
* state. See static fields in {@link ConnectionService.ConnectionImpl}
* prefixed with "KEY_" for the values supported by the Android
* implementation.
*/
@ReactMethod
public void updateCall(String callUUID, ReadableMap callState) {
ConnectionService.updateCall(callUUID, callState);
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2018 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.
@@ -24,7 +25,6 @@ import android.os.Build;
import com.calendarevents.CalendarEventsPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Callback;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
/**
@@ -73,15 +73,12 @@ public class ReactActivityLifecycleCallbacks {
* otherwise. If {@code false}, the application should call the
* {@code super}'s implementation.
*/
public static boolean onBackPressed() {
public static void onBackPressed() {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager == null) {
return false;
} else {
if (reactInstanceManager != null) {
reactInstanceManager.onBackPressed();
return true;
}
}
@@ -123,25 +120,11 @@ public class ReactActivityLifecycleCallbacks {
* @param activity {@code Activity} being resumed.
*/
public static void onHostResume(Activity activity) {
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
/**
* {@link Activity} lifecycle method which should be called from
* {@code Activity#onResume} so we can do the required internal processing.
*
* @param activity {@code Activity} being resumed.
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} to
* handle invoking the back button if no {@link BaseReactView} handles it.
*/
public static void onHostResume(
Activity activity,
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
if (permissionsCallback != null) {

View File

@@ -25,11 +25,17 @@ import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.LifecycleState;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class ReactInstanceManagerHolder {
/**
* FIXME (from linter): Do not place Android context classes in static
* fields (static reference to ReactInstanceManager which has field
* mApplicationContext pointing to Context); this is a memory leak (and
* also breaks Instant Run).
*
* React Native bridge. The instance manager allows embedding applications
* to create multiple root views off the same JavaScript bundle.
*/
@@ -37,19 +43,25 @@ class ReactInstanceManagerHolder {
private static List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new ExternalAPIModule(reactContext),
new LocaleDetector(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
new org.jitsi.meet.sdk.dropbox.Dropbox(reactContext),
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
);
List<NativeModule> nativeModules
= new ArrayList<>(Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new ExternalAPIModule(reactContext),
new LocaleDetector(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
new org.jitsi.meet.sdk.dropbox.Dropbox(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
if (android.os.Build.VERSION.SDK_INT
>= android.os.Build.VERSION_CODES.O) {
nativeModules.add(new RNConnectionService(reactContext));
}
return nativeModules;
}
/**
@@ -58,7 +70,7 @@ class ReactInstanceManagerHolder {
* @param eventName {@code String} containing the event name.
* @param data {@code Object} optional ancillary data for the event.
*/
public static boolean emitEvent(
static boolean emitEvent(
String eventName,
@Nullable Object data) {
ReactInstanceManager reactInstanceManager

View File

@@ -17,7 +17,6 @@
package org.jitsi.meet.sdk;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

View File

@@ -41,9 +41,11 @@ public class Dropbox
public Dropbox(ReactApplicationContext reactContext) {
super(reactContext);
String pkg = reactContext.getApplicationContext().getPackageName();
int resId = reactContext.getResources()
.getIdentifier("dropbox_app_key", "string", pkg);
appKey
= reactContext.getString(
org.jitsi.meet.sdk.R.string.dropbox_app_key);
= reactContext.getString(resId);
isEnabled = !TextUtils.isEmpty(appKey);
clientId = generateClientId();

View File

@@ -1,211 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.invite;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Controller object used by native code to query and submit user selections for
* the user invitation flow.
*/
public class AddPeopleController {
/**
* The AddPeopleControllerListener for this controller, used to pass query
* results back to the native code that initiated the query.
*/
private AddPeopleControllerListener listener;
/**
* Local cache of search query results. Used to re-hydrate the list of
* selected items based on their ids passed to inviteById in order to pass
* the full item maps back to the JitsiMeetView during submission.
*/
private final Map<String, ReadableMap> items = new HashMap<>();
private final WeakReference<InviteController> owner;
private final WeakReference<ReactApplicationContext> reactContext;
/**
* Randomly generated UUID, used for identification in the InviteModule.
*/
private final String uuid = UUID.randomUUID().toString();
public AddPeopleController(
InviteController owner,
ReactApplicationContext reactContext) {
this.owner = new WeakReference<>(owner);
this.reactContext = new WeakReference<>(reactContext);
}
/**
* Cancel the invitation flow and free memory allocated to the
* AddPeopleController. After calling this method, this object is invalid -
* a new AddPeopleController will be passed to the caller through
* beginAddPeople.
*/
public void endAddPeople() {
InviteController owner = this.owner.get();
if (owner != null) {
owner.endAddPeople(this);
}
}
/**
*
* @return the AddPeopleControllerListener for this controller, used to pass
* query results back to the native code that initiated the query.
*/
public AddPeopleControllerListener getListener() {
return listener;
}
final ReactApplicationContext getReactApplicationContext() {
return reactContext.get();
}
/**
*
* @return the unique identifier for this AddPeopleController
*/
public String getUuid() {
return uuid;
}
/**
* Send invites to selected users based on their item ids
*
* @param ids
*/
public void inviteById(List<String> ids) {
InviteController owner = this.owner.get();
if (owner != null) {
WritableArray invitees = new WritableNativeArray();
for(int i = 0, size = ids.size(); i < size; i++) {
String id = ids.get(i);
if(items.containsKey(id)) {
WritableNativeMap map = new WritableNativeMap();
map.merge(items.get(id));
invitees.pushMap(map);
} else {
// If the id doesn't exist in the map, we can't do anything,
// so just skip it.
}
}
owner.invite(this, invitees);
}
}
void inviteSettled(ReadableArray failedInvitees) {
AddPeopleControllerListener listener = getListener();
if (listener != null) {
ArrayList<Map<String, Object>> jFailedInvitees = new ArrayList<>();
for (int i = 0, size = failedInvitees.size(); i < size; ++i) {
jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
}
listener.onInviteSettled(this, jFailedInvitees);
}
}
/**
* Start a search for entities to invite with the given query. Results will
* be returned through the associated AddPeopleControllerListener's
* onReceivedResults method.
*
* @param query
*/
public void performQuery(String query) {
InviteController owner = this.owner.get();
if (owner != null) {
owner.performQuery(this, query);
}
}
/**
* Caches results received by the search into a local map for use later when
* the items are submitted. Submission requires the full map of
* information, but only the IDs are returned back to the delegate. Using
* this map means we don't have to send the whole map back to the delegate.
*
* @param results
* @param query
*/
void receivedResultsForQuery(ReadableArray results, String query) {
AddPeopleControllerListener listener = getListener();
if (listener != null) {
List<Map<String, Object>> jvmResults = new ArrayList<>();
// cache results for use in submission later
// convert to jvm array
for(int i = 0; i < results.size(); i++) {
ReadableMap map = results.getMap(i);
if(map.hasKey("id")) {
items.put(map.getString("id"), map);
} else if(map.hasKey("type")
&& map.getString("type").equals("phone")
&& map.hasKey("number")) {
items.put(map.getString("number"), map);
} else {
Log.w(
"AddPeopleController",
"Received result without id and that was not a phone number, so not adding it to suggestions: "
+ map);
}
jvmResults.add(map.toHashMap());
}
listener.onReceivedResults(this, jvmResults, query);
}
}
/**
* Sets the AddPeopleControllerListener for this controller, used to pass
* query results back to the native code that initiated the query.
*
* @param listener
*/
public void setListener(AddPeopleControllerListener listener) {
this.listener = listener;
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.invite;
import java.util.List;
import java.util.Map;
public interface AddPeopleControllerListener {
/**
* Called when the call to {@link AddPeopleController#inviteById(List)}
* completes.
*
* @param addPeopleController the active {@link AddPeopleController} for
* this invite flow. This object should be cleaned up by calling
* {@link AddPeopleController#endAddPeople()} if the user exits the invite
* flow. Otherwise, it can stay active if the user will attempt to invite
* @param failedInvitees a {@code List} of {@code Map<String, Object>}
* dictionaries that represent the invitations that failed. The data type of
* the objects is identical to the results returned in onReceivedResuls.
*/
void onInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees);
/**
* Called when results are received for a query called through
* AddPeopleController.query().
*
* @param addPeopleController
* @param results a List of Map<String, Object> objects that represent items
* returned by the query. The object at key "type" describes the type of
* item: "user", "videosipgw" (conference room), or "phone". "user" types
* have properties at "id", "name", and "avatar". "videosipgw" types have
* properties at "id" and "name". "phone" types have properties at "number",
* "title", "and "subtitle"
* @param query the query that generated the given results
*/
void onReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query);
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.invite;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeMap;
import org.jitsi.meet.sdk.ReactContextUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* Represents the entry point into the invite feature of Jitsi Meet and is the
* Java counterpart of the JavaScript {@code InviteButton}.
*/
public class InviteController {
private AddPeopleController addPeopleController;
/**
* Whether adding/inviting people by name (as opposed to phone number) is
* enabled.
*/
private Boolean addPeopleEnabled;
/**
* Whether adding/inviting people by phone number (as opposed to name) is
* enabled.
*/
private Boolean dialOutEnabled;
private final String externalAPIScope;
private InviteControllerListener listener;
public InviteController(String externalAPIScope) {
this.externalAPIScope = externalAPIScope;
}
void beginAddPeople(ReactApplicationContext reactContext) {
InviteControllerListener listener = getListener();
if (listener != null) {
// XXX For the sake of simplicity and in order to reduce the risk of
// memory leaks, allow a single AddPeopleController at a time.
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null) {
return;
}
// Initialize a new AddPeopleController to represent the click/tap
// on the InviteButton and notify the InviteControllerListener
// about the event.
addPeopleController = new AddPeopleController(this, reactContext);
boolean success = false;
this.addPeopleController = addPeopleController;
try {
listener.beginAddPeople(addPeopleController);
success = true;
} finally {
if (!success) {
endAddPeople(addPeopleController);
}
}
}
}
void endAddPeople(AddPeopleController addPeopleController) {
if (this.addPeopleController == addPeopleController) {
this.addPeopleController = null;
}
}
public InviteControllerListener getListener() {
return listener;
}
/**
* Sends JavaScript event to submit invitations to the given item ids
*
* @param invitees a WritableArray of WritableNativeMaps representing
* selected items. Each map representing a selected item should match the
* data passed back in the return from a query.
*/
boolean invite(
AddPeopleController addPeopleController,
WritableArray invitees) {
return
invite(
addPeopleController.getUuid(),
addPeopleController.getReactApplicationContext(),
invitees);
}
public Future<List<Map<String, Object>>> invite(
final List<Map<String, Object>> invitees) {
final boolean inviteBegan
= invite(
UUID.randomUUID().toString(),
/* reactContext */ null,
Arguments.makeNativeArray(invitees));
FutureTask futureTask
= new FutureTask(new Callable() {
@Override
public List<Map<String, Object>> call() {
if (inviteBegan) {
// TODO Complete the returned Future when the invite
// settles.
return Collections.emptyList();
} else {
// The invite failed to even begin so report that all
// invitees failed.
return invitees;
}
}
});
// If the invite failed to even begin, complete the returned Future
// already and the Future implementation will report that all invitees
// failed.
if (!inviteBegan) {
futureTask.run();
}
return futureTask;
}
private boolean invite(
String addPeopleControllerScope,
ReactContext reactContext,
WritableArray invitees) {
WritableNativeMap data = new WritableNativeMap();
data.putString("addPeopleControllerScope", addPeopleControllerScope);
data.putString("externalAPIScope", externalAPIScope);
data.putArray("invitees", invitees);
return
ReactContextUtils.emitEvent(
reactContext,
"org.jitsi.meet:features/invite#invite",
data);
}
void inviteSettled(
String addPeopleControllerScope,
ReadableArray failedInvitees) {
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null
&& addPeopleController.getUuid().equals(
addPeopleControllerScope)) {
try {
addPeopleController.inviteSettled(failedInvitees);
} finally {
if (failedInvitees.size() == 0) {
endAddPeople(addPeopleController);
}
}
}
}
public boolean isAddPeopleEnabled() {
Boolean b = this.addPeopleEnabled;
return
(b == null || b.booleanValue()) ? (getListener() != null) : false;
}
public boolean isDialOutEnabled() {
Boolean b = this.dialOutEnabled;
return
(b == null || b.booleanValue()) ? (getListener() != null) : false;
}
/**
* Starts a query for users to invite to the conference. Results will be
* returned through
* {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}.
*
* @param query {@code String} to use for the query
*/
void performQuery(AddPeopleController addPeopleController, String query) {
WritableNativeMap params = new WritableNativeMap();
params.putString("addPeopleControllerScope", addPeopleController.getUuid());
params.putString("externalAPIScope", externalAPIScope);
params.putString("query", query);
ReactContextUtils.emitEvent(
addPeopleController.getReactApplicationContext(),
"org.jitsi.meet:features/invite#performQuery",
params);
}
void receivedResultsForQuery(
String addPeopleControllerScope,
String query,
ReadableArray results) {
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null
&& addPeopleController.getUuid().equals(
addPeopleControllerScope)) {
addPeopleController.receivedResultsForQuery(results, query);
}
}
/**
* Sets whether the ability to add users to the call is enabled. If this is
* enabled, an add user button will appear on the {@link JitsiMeetView}. If
* enabled, and the user taps the add user button,
* {@link InviteControllerListener#beginAddPeople(AddPeopleController)}
* will be called.
*
* @param addPeopleEnabled {@code true} to enable the add people button;
* otherwise, {@code false}
*/
public void setAddPeopleEnabled(boolean addPeopleEnabled) {
this.addPeopleEnabled = Boolean.valueOf(addPeopleEnabled);
}
/**
* Sets whether the ability to add phone numbers to the call is enabled.
* Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to be
* effective.
*
* @param dialOutEnabled {@code true} to enable the ability to add phone
* numbers to the call; otherwise, {@code false}
*/
public void setDialOutEnabled(boolean dialOutEnabled) {
this.dialOutEnabled = Boolean.valueOf(dialOutEnabled);
}
public void setListener(InviteControllerListener listener) {
this.listener = listener;
}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.invite;
public interface InviteControllerListener {
/**
* Called when the add user button is tapped.
*
* @param addPeopleController {@code AddPeopleController} scoped for this
* user invite flow. The {@code AddPeopleController} is used to start user
* queries and accepts an {@code AddPeopleControllerListener} for receiving
* user query responses.
*/
void beginAddPeople(AddPeopleController addPeopleController);
}

View File

@@ -1,171 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk.invite;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.UiThreadUtil;
import org.jitsi.meet.sdk.BaseReactView;
import org.jitsi.meet.sdk.JitsiMeetView;
/**
* Implements the react-native module of the feature invite.
*/
public class InviteModule
extends ReactContextBaseJavaModule {
public InviteModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Signals that a click/tap has been performed on {@code InviteButton} and
* that the execution flow for adding/inviting people to the current
* conference/meeting is to begin
*
* @param externalAPIScope the unique identifier of the
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
*/
@ReactMethod
public void beginAddPeople(final String externalAPIScope) {
// Make sure InviteControllerListener (like all other listeners of the
// SDK) is invoked on the UI thread. It was requested by SDK consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
beginAddPeople(externalAPIScope);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController != null) {
inviteController.beginAddPeople(getReactApplicationContext());
}
}
private InviteController findInviteControllerByExternalAPIScope(
String externalAPIScope) {
JitsiMeetView view
= (JitsiMeetView)
BaseReactView.findViewByExternalAPIScope(externalAPIScope);
return view == null ? null : view.getInviteController();
}
@Override
public String getName() {
return "Invite";
}
/**
* Callback for invitation failures
*
* @param failedInvitees the items for which the invitation failed
* @param addPeopleControllerScope a string that represents a connection to
* a specific AddPeopleController
*/
@ReactMethod
public void inviteSettled(
final String externalAPIScope,
final String addPeopleControllerScope,
final ReadableArray failedInvitees) {
// Make sure AddPeopleControllerListener (like all other listeners of
// the SDK) is invoked on the UI thread. It was requested by SDK
// consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
inviteSettled(
externalAPIScope,
addPeopleControllerScope,
failedInvitees);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Invite settled, but failed to find active controller to notify");
} else {
inviteController.inviteSettled(
addPeopleControllerScope,
failedInvitees);
}
}
/**
* Callback for results received from the JavaScript invite search call
*
* @param results the results in a ReadableArray of ReadableMap objects
* @param query the query associated with the search
* @param addPeopleControllerScope a string that represents a connection to
* a specific AddPeopleController
*/
@ReactMethod
public void receivedResults(
final String externalAPIScope,
final String addPeopleControllerScope,
final String query,
final ReadableArray results) {
// Make sure AddPeopleControllerListener (like all other listeners of
// the SDK) is invoked on the UI thread. It was requested by SDK
// consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
receivedResults(
externalAPIScope,
addPeopleControllerScope,
query,
results);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Received results, but failed to find active controller to send results back");
} else {
inviteController.receivedResultsForQuery(
addPeopleControllerScope,
query,
results);
}
}
}

6
app.js
View File

@@ -1,11 +1,5 @@
/* application specific logic */
// FIXME: remove once atlaskit work with React 16
// It seems that @atlaskit/icon is importing PropTypes from React, but it
// happens through some glyph coffee script template. It could be that more
// things are broken there (not only the icon).
import './react/features/base/react/prop-types-polyfill.js';
import 'jquery';
import 'jquery-contextmenu';
import 'jQuery-Impromptu';

View File

@@ -76,10 +76,10 @@ import {
dominantSpeakerChanged,
getAvatarURLByParticipantId,
getLocalParticipant,
getNormalizedDisplayName,
getParticipantById,
localParticipantConnectionStatusChanged,
localParticipantRoleChanged,
MAX_DISPLAY_NAME_LENGTH,
participantConnectionStatusChanged,
participantPresenceChanged,
participantRoleChanged,
@@ -88,6 +88,7 @@ import {
import { updateSettings } from './react/features/base/settings';
import {
createLocalTracksF,
destroyLocalTracks,
isLocalTrackMuted,
replaceLocalTrack,
trackAdded,
@@ -346,7 +347,10 @@ class ConferenceConnector {
// not enough rights to create conference
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED: {
// Schedule reconnect to check if someone else created the room.
this.reconnectTimeout = setTimeout(() => room.join(), 5000);
this.reconnectTimeout = setTimeout(() => {
APP.store.dispatch(conferenceWillJoin(room));
room.join();
}, 5000);
const { password }
= APP.store.getState()['features/base/conference'];
@@ -1233,6 +1237,7 @@ export default {
= connection.initJitsiConference(
APP.conference.roomName,
this._getConferenceOptions());
APP.store.dispatch(conferenceWillJoin(room));
this._setLocalAudioVideoStreams(localTracks);
this._room = room; // FIXME do not use this
@@ -1396,6 +1401,8 @@ export default {
receiver.stop();
}
this._stopProxyConnection();
let promise = null;
if (didHaveVideo) {
@@ -1471,9 +1478,12 @@ export default {
/**
* Creates desktop (screensharing) {@link JitsiLocalTrack}
*
* @param {Object} [options] - Screen sharing options that will be passed to
* createLocalTracks.
*
* @param {Object} [options.desktopSharing]
* @param {Object} [options.desktopStream] - An existing desktop stream to
* use instead of creating a new desktop stream.
* @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
* {@link JitsiLocalTrack} for the screensharing or rejected with
* {@link JitsiTrackError}.
@@ -1486,47 +1496,52 @@ export default {
const didHaveVideo = Boolean(this.localVideo);
const wasVideoMuted = this.isLocalVideoMuted();
return createLocalTracksF({
desktopSharingSources: options.desktopSharingSources,
devices: [ 'desktop' ],
desktopSharingExtensionExternalInstallation: {
interval: 500,
checkAgain: () => DSExternalInstallationInProgress,
listener: (status, url) => {
switch (status) {
case 'waitingForExtension': {
DSExternalInstallationInProgress = true;
externalInstallation = true;
const listener = () => {
// Wait a little bit more just to be sure that we
// won't miss the extension installation
setTimeout(
() => {
const getDesktopStreamPromise = options.desktopStream
? Promise.resolve([ options.desktopStream ])
: createLocalTracksF({
desktopSharingSourceDevice: options.desktopSharingSources
? null : config._desktopSharingSourceDevice,
desktopSharingSources: options.desktopSharingSources,
devices: [ 'desktop' ],
desktopSharingExtensionExternalInstallation: {
interval: 500,
checkAgain: () => DSExternalInstallationInProgress,
listener: (status, url) => {
switch (status) {
case 'waitingForExtension': {
DSExternalInstallationInProgress = true;
externalInstallation = true;
const listener = () => {
// Wait a little bit more just to be sure that
// we won't miss the extension installation
setTimeout(() => {
DSExternalInstallationInProgress = false;
},
500);
APP.UI.removeListener(
APP.UI.removeListener(
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
listener);
};
APP.UI.addListener(
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
listener);
};
APP.UI.showExtensionExternalInstallationDialog(url);
break;
}
case 'extensionFound':
// Close the dialog.
externalInstallation && $.prompt.close();
break;
default:
APP.UI.addListener(
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
listener);
APP.UI.showExtensionExternalInstallationDialog(url);
break;
}
case 'extensionFound':
// Close the dialog.
externalInstallation && $.prompt.close();
break;
default:
// Unknown status
// Unknown status
}
}
}
}
}).then(([ desktopStream ]) => {
});
return getDesktopStreamPromise.then(([ desktopStream ]) => {
// Stores the "untoggle" handler which remembers whether was
// there any video before and whether was it muted.
this._untoggleScreenSharing
@@ -1644,6 +1659,7 @@ export default {
// Handling:
// JitsiTrackErrors.PERMISSION_DENIED
// JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
// JitsiTrackErrors.CONSTRAINT_FAILED
// JitsiTrackErrors.GENERAL
// and any other
let descriptionKey;
@@ -1667,6 +1683,9 @@ export default {
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
titleKey = 'dialog.screenSharingFailedToInstallTitle';
}
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
descriptionKey = 'dialog.cameraConstraintFailedError';
titleKey = 'deviceError.cameraError';
} else {
descriptionKey = 'dialog.screenSharingFailedToInstall';
titleKey = 'dialog.screenSharingFailedToInstallTitle';
@@ -1840,7 +1859,7 @@ export default {
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
(id, displayName) => {
const formattedDisplayName
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
= getNormalizedDisplayName(displayName);
APP.store.dispatch(participantUpdated({
conference: room,
@@ -2216,34 +2235,6 @@ export default {
* @returns {void}
*/
_onConferenceJoined() {
if (APP.logCollector) {
// Start the LogCollector's periodic "store logs" task
APP.logCollector.start();
APP.logCollectorStarted = true;
// Make an attempt to flush in case a lot of logs have been
// cached, before the collector was started.
APP.logCollector.flush();
// This event listener will flush the logs, before
// the statistics module (CallStats) is stopped.
//
// NOTE The LogCollector is not stopped, because this event can
// be triggered multiple times during single conference
// (whenever statistics module is stopped). That includes
// the case when Jicofo terminates the single person left in the
// room. It will then restart the media session when someone
// eventually join the room which will start the stats again.
APP.conference.addConferenceListener(
JitsiConferenceEvents.BEFORE_STATISTICS_DISPOSED,
() => {
if (APP.logCollector) {
APP.logCollector.flush();
}
}
);
}
APP.UI.initConference();
APP.keyboardshortcut.init();
@@ -2466,7 +2457,13 @@ export default {
*/
hangup(requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.removeLocalMedia();
this._stopProxyConnection();
APP.store.dispatch(destroyLocalTracks());
this._localTracksInitialized = false;
this.localVideo = null;
this.localAudio = null;
// Remove unnecessary event listeners from firing callbacks.
if (this.deviceChangeListener) {
@@ -2475,6 +2472,9 @@ export default {
this.deviceChangeListener);
}
APP.UI.removeAllListeners();
APP.remoteControl.removeAllListeners();
let requestFeedbackPromise;
if (requestFeedback) {
@@ -2495,6 +2495,9 @@ export default {
requestFeedbackPromise,
this.leaveRoomAndDisconnect()
]).then(values => {
this._room = undefined;
room = undefined;
APP.API.notifyReadyToClose();
maybeRedirectToWelcomePage(values[0]);
});
@@ -2508,7 +2511,8 @@ export default {
leaveRoomAndDisconnect() {
APP.store.dispatch(conferenceWillLeave(room));
return room.leave().then(disconnect, disconnect);
return room.leave()
.then(disconnect, disconnect);
},
/**
@@ -2614,8 +2618,7 @@ export default {
* @param nickname {string} the new display name
*/
changeLocalDisplayName(nickname = '') {
const formattedNickname
= nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH);
const formattedNickname = getNormalizedDisplayName(nickname);
const { id, name } = getLocalParticipant(APP.store.getState());
if (formattedNickname === name) {
@@ -2647,7 +2650,6 @@ export default {
});
if (room) {
room.setDisplayName(formattedNickname);
APP.UI.changeDisplayName(id, formattedNickname);
}
},
@@ -2675,6 +2677,65 @@ export default {
return this.localVideo.sourceType;
},
/**
* Callback invoked by the external api create or update a direct connection
* from the local client to an external client.
*
* @param {Object} event - The object containing information that should be
* passed to the {@code ProxyConnectionService}.
* @returns {void}
*/
onProxyConnectionEvent(event) {
if (!this._proxyConnection) {
this._proxyConnection = new JitsiMeetJS.ProxyConnectionService({
/**
* The proxy connection feature is currently tailored towards
* taking a proxied video stream and showing it as a local
* desktop screen.
*/
convertVideoToDesktop: true,
/**
* Callback invoked to pass messages from the local client back
* out to the external client.
*
* @param {string} peerJid - The jid of the intended recipient
* of the message.
* @param {Object} data - The message that should be sent. For
* screensharing this is an iq.
* @returns {void}
*/
onSendMessage: (peerJid, data) =>
APP.API.sendProxyConnectionEvent({
data,
to: peerJid
}),
/**
* Callback invoked when the remote peer of the proxy connection
* has provided a video stream, intended to be used as a local
* desktop stream.
*
* @param {JitsiLocalTrack} remoteProxyStream - The media
* stream to use as a local desktop stream.
* @returns {void}
*/
onRemoteStream: desktopStream => {
if (desktopStream.videoType !== 'desktop') {
logger.warn('Received a non-desktop stream to proxy.');
desktopStream.dispose();
return;
}
this.toggleScreenSharing(undefined, { desktopStream });
}
});
}
this._proxyConnection.processMessage(event);
},
/**
* Sets the video muted status.
*
@@ -2709,5 +2770,19 @@ export default {
if (score === -1 || (score >= 1 && score <= 5)) {
APP.store.dispatch(submitFeedback(score, message, room));
}
},
/**
* Terminates any proxy screensharing connection that is active.
*
* @private
* @returns {void}
*/
_stopProxyConnection() {
if (this._proxyConnection) {
this._proxyConnection.stop();
}
this._proxyConnection = null;
}
};

View File

@@ -334,14 +334,19 @@ var config = {
// backToP2PDelay: 5
},
// A list of scripts to load as lib-jitsi-meet "analytics handlers".
// analyticsScriptUrls: [
// "libs/analytics-ga.js", // google-analytics
// "https://example.com/my-custom-analytics.js"
// ],
analytics: {
// The Google Analytics Tracking ID:
// googleAnalyticsTrackingId: 'your-tracking-id-UA-123456-1'
// The Google Analytics Tracking ID
// googleAnalyticsTrackingId = 'your-tracking-id-here-UA-123456-1',
// The Amplitude APP Key:
// amplitudeAPPKey: '<APP_KEY>'
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
// scriptURLs: [
// "libs/analytics-ga.min.js", // google-analytics
// "https://example.com/my-custom-analytics.js"
// ],
},
// Information about the jitsi-meet instance we are connecting to, including
// the user region as seen by the server.
@@ -381,6 +386,13 @@ var config = {
// analyticsInterval: 60000,
// }
// If set, will attempt to use the provided video input device label when
// triggering a screenshare, instead of proceeding through the normal flow
// for obtaining a desktop stream.
// NOTE: This option is experimental and is currently intended for internal
// use only.
// _desktopSharingSourceDevice: 'sample-id-or-label'
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold

View File

@@ -1,42 +1,18 @@
/**
* Move Atlaskit Flag up a little bit so it does not cover the toolbar with the
* first notification.
* Move the @atlaskit/flag container up a little bit so it does not cover the
* toolbar with the first notification.
*/
.cxGWJB{
.cjMOOK{
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
}
.gXSEsl:nth-child(n+2) {
transform: translateX(0) translateY(100%) translateY(16px) !important;
-ms-transform: translateX(0) translateY(100%) translateY(16px) !important;
-webkit-transform: translateX(0) translateY(100%) translateY(16px) !important;
}
/**
* Welcome page tab color adjustments.
* Disable the slide-in animation for @atlaskit/flag due to the animation
* repeating for each queued flag once it becomes the top flag.
*/
.welcome {
/**
* The text color of the selected tab and hovered tabs.
*/
.bVobOt,
.bVobOt:hover,
.ebveIl:hover {
color: #172B4D;
}
/**
* The color of the inactive tab text.
*/
.ebveIl {
color: #FFFFFF;
}
/**
* The color of the underline of a selected tab.
*/
.kByArU {
background-color: #172B4D;
}
.mIBKA:first-child {
animation: cbfRuT 0s !important;
-webkit-animation: cbfRuT 0s !important;
}
.modal-dialog-form {
@@ -56,3 +32,11 @@
border: 1px solid #455166;
}
}
/**
* Override @atlaskit/theme styling for the top toolbar so it displays over
* the video thumbnail while obscuring as little as possible.
*/
.videocontainer .tOoji {
background: none;
}

View File

@@ -25,6 +25,9 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-chat-unread:before {
content: "\e0b7";
}
.icon-arrow_back:before {
content: "\e5c4";
}

View File

@@ -29,7 +29,7 @@
background: #0074E0;
border-radius: 4px;
color: #FFFFFF;
display: inline-block;
display: flex;
justify-content: center;
align-items: center;
padding: 5px 10px;

View File

@@ -80,15 +80,6 @@
float: left;
pointer-events: all;
}
/**
* Need to overwrite the background for the top toolbar dark theme div
* wrapper needed before we're able to move all top toolbar indicators
* creation to react.
*/
.sc-ifAKCX {
background: none;
}
}
&__toolbar {
@@ -505,18 +496,22 @@
display:none !important;
}
#dominantSpeakerAvatarContainer,
#dominantSpeakerAvatar,
.dynamic-shadow {
width: 200px;
height: 200px;
}
#dominantSpeakerAvatar {
#dominantSpeakerAvatarContainer {
top: 50px;
margin: auto;
position: relative;
border-radius: 100px;
overflow: hidden;
visibility: inherit;
}
#dominantSpeakerAvatar {
background-color: #000000;
}

View File

@@ -159,7 +159,7 @@ unzip jitsi-videobridge-linux-{arch-buildnum}.zip
Install JRE if missing:
```
apt-get install default-jre
apt-get install openjdk-8-jre
```
_NOTE: When installing on older Debian releases keep in mind that you need JRE >= 1.7._
@@ -182,7 +182,7 @@ Or autostart it by adding the line in `/etc/rc.local`:
Install JDK and Maven if missing:
```
apt-get install default-jdk maven
apt-get install openjdk-8-jdk maven
```
_NOTE: When installing on older Debian releases keep in mind that you need JDK >= 1.7._

View File

@@ -0,0 +1,22 @@
# Enabling speakerstats prosody module
To enable the speaker stats we need to enable speakerstats module under the main
virtual host, this is to enable the advertising the speaker stats component,
which address needs to be specified in `speakerstats_component` option.
We need to also enable the component with the address specified in `speakerstats_component`.
The component needs also to have the option with the muc component address in
`muc_component` option.
```lua
VirtualHost "jitsi.example.com"
speakerstats_component = "speakerstats.jitsi.example.com"
modules_enabled = {
"speakerstats";
}
Component "speakerstats.jitsi.example.com" "speakerstats_component"
muc_component = "conference.jitsi.example.com"
Component "conference.jitsi.example.com" "muc"
```

View File

@@ -1,35 +0,0 @@
// flow-typed signature: d9a983bb1ac458a256c31c139047bdbb
// flow-typed version: 927687984d/prop-types_v15.x.x/flow_>=v0.41.x
type $npm$propTypes$ReactPropsCheckType = (
props: any,
propName: string,
componentName: string,
href?: string) => ?Error;
declare module 'prop-types' {
declare var array: React$PropType$Primitive<Array<any>>;
declare var bool: React$PropType$Primitive<boolean>;
declare var func: React$PropType$Primitive<Function>;
declare var number: React$PropType$Primitive<number>;
declare var object: React$PropType$Primitive<Object>;
declare var string: React$PropType$Primitive<string>;
declare var symbol: React$PropType$Primitive<Symbol>;
declare var any: React$PropType$Primitive<any>;
declare var arrayOf: React$PropType$ArrayOf;
declare var element: React$PropType$Primitive<any>; /* TODO */
declare var instanceOf: React$PropType$InstanceOf;
declare var node: React$PropType$Primitive<any>; /* TODO */
declare var objectOf: React$PropType$ObjectOf;
declare var oneOf: React$PropType$OneOf;
declare var oneOfType: React$PropType$OneOfType;
declare var shape: React$PropType$Shape;
declare function checkPropTypes<V>(
propTypes: $Subtype<{[_: $Keys<V>]: $npm$propTypes$ReactPropsCheckType}>,
values: V,
location: string,
componentName: string,
getStack: ?(() => ?string)
) : void;
}

Binary file not shown.

View File

@@ -7,6 +7,7 @@
<font-face units-per-em="1024" ascent="1024" descent="0" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" />
<glyph unicode="&#xe0b7;" glyph-name="chat-unread" d="M768 682v86h-512v-86h512zM598 426v86h-342v-86h342zM256 640v-86h512v86h-512zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
<glyph unicode="&#xe0cd;" glyph-name="phone" d="M282 564c62-120 162-220 282-282l94 94c12 12 30 16 44 10 48-16 100-24 152-24 24 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-44z" />
<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" />

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -8,6 +8,12 @@
<link rel="stylesheet" href="css/all.css">
<script>
// IE11 and earlier can be identified via their user agent and be
// redirected to a page that is known to have no newer js syntax.
if (window.navigator.userAgent.match(/(MSIE|Trident)/)) {
window.location.href = "static/recommendedBrowsers.html";
}
window.indexLoadedTime = window.performance.now();
console.log("(TIME) index.html loaded:\t", indexLoadedTime);
// XXX the code below listeners for errors and displays an error message

View File

@@ -192,7 +192,12 @@ var interfaceConfig = {
/**
* Specify mobile app scheme for opening the app from the mobile browser.
*/
// APP_SCHEME: 'org.jitsi.meet'
// APP_SCHEME: 'org.jitsi.meet',
/**
* Specify the Android app package name.
*/
// ANDROID_APP_PACKAGE: 'org.jitsi.meet'
};
/* eslint-enable no-unused-vars, no-var, max-len */

View File

@@ -2,6 +2,15 @@ platform :ios, '10.0'
workspace 'jitsi-meet'
target 'jitsi-meet' do
project 'app/app.xcodeproj'
pod 'Crashlytics'
pod 'Fabric'
pod 'Firebase/Core'
pod 'Firebase/DynamicLinks'
end
target 'JitsiMeet' do
project 'sdk/sdk.xcodeproj'
@@ -34,6 +43,8 @@ target 'JitsiMeet' do
:path => '../node_modules/react-native-fast-image'
pod 'react-native-keep-awake',
:path => '../node_modules/react-native-keep-awake'
pod 'BVLinearGradient',
:path => '../node_modules/react-native-linear-gradient'
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'RNGoogleSignin',
:path => '../node_modules/react-native-google-signin'

View File

@@ -1,12 +1,51 @@
PODS:
- boost-for-react-native (1.63.0)
- BVLinearGradient (2.5.3):
- React
- Crashlytics (3.12.0):
- Fabric (~> 1.9.0)
- DoubleConversion (1.1.6)
- Fabric (1.9.0)
- Firebase/Core (5.15.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 5.4.0)
- Firebase/CoreOnly (5.15.0):
- FirebaseCore (= 5.1.10)
- Firebase/DynamicLinks (5.15.0):
- Firebase/CoreOnly
- FirebaseDynamicLinks (= 3.3.0)
- FirebaseAnalytics (5.4.0):
- FirebaseCore (~> 5.1)
- FirebaseInstanceID (~> 3.3)
- GoogleAppMeasurement (= 5.4.0)
- GoogleUtilities/AppDelegateSwizzler (~> 5.2)
- GoogleUtilities/MethodSwizzler (~> 5.2)
- GoogleUtilities/Network (~> 5.2)
- "GoogleUtilities/NSData+zlib (~> 5.2)"
- nanopb (~> 0.3)
- FirebaseAnalyticsInterop (1.1.0)
- FirebaseCore (5.1.10):
- GoogleUtilities/Logger (~> 5.2)
- FirebaseDynamicLinks (3.3.0):
- FirebaseAnalytics (~> 5.1)
- FirebaseAnalyticsInterop (~> 1.0)
- FirebaseCore (~> 5.1)
- FirebaseInstanceID (3.3.0):
- FirebaseCore (~> 5.1)
- GoogleUtilities/Environment (~> 5.3)
- GoogleUtilities/UserDefaults (~> 5.3)
- FLAnimatedImage (1.0.12)
- Folly (2016.10.31.00):
- boost-for-react-native
- DoubleConversion
- glog
- glog (0.3.5)
- GoogleAppMeasurement (5.4.0):
- GoogleUtilities/AppDelegateSwizzler (~> 5.2)
- GoogleUtilities/MethodSwizzler (~> 5.2)
- GoogleUtilities/Network (~> 5.2)
- "GoogleUtilities/NSData+zlib (~> 5.2)"
- nanopb (~> 0.3)
- GoogleSignIn (4.4.0):
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
- "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)"
@@ -19,8 +58,31 @@ PODS:
- GoogleToolboxForMac/Defines (= 2.2.0)
- "GoogleToolboxForMac/NSString+URLArguments (= 2.2.0)"
- "GoogleToolboxForMac/NSString+URLArguments (2.2.0)"
- GoogleUtilities/AppDelegateSwizzler (5.3.7):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (5.3.7)
- GoogleUtilities/Logger (5.3.7):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (5.3.7):
- GoogleUtilities/Logger
- GoogleUtilities/Network (5.3.7):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (5.3.7)"
- GoogleUtilities/Reachability (5.3.7):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (5.3.7):
- GoogleUtilities/Logger
- GTMSessionFetcher/Core (1.2.1)
- ObjectiveDropboxOfficial (3.9.2)
- nanopb (0.3.901):
- nanopb/decode (= 0.3.901)
- nanopb/encode (= 0.3.901)
- nanopb/decode (0.3.901)
- nanopb/encode (0.3.901)
- ObjectiveDropboxOfficial (3.9.4)
- React (0.57.8):
- React/Core (= 0.57.8)
- react-native-background-timer (2.1.1):
@@ -92,7 +154,12 @@ PODS:
- yoga (0.57.8.React)
DEPENDENCIES:
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- Crashlytics
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- Fabric
- Firebase/Core
- Firebase/DynamicLinks
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- ObjectiveDropboxOfficial
@@ -119,14 +186,27 @@ DEPENDENCIES:
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- boost-for-react-native
- Crashlytics
- Fabric
- Firebase
- FirebaseAnalytics
- FirebaseAnalyticsInterop
- FirebaseCore
- FirebaseDynamicLinks
- FirebaseInstanceID
- FLAnimatedImage
- GoogleAppMeasurement
- GoogleSignIn
- GoogleToolboxForMac
- GoogleUtilities
- GTMSessionFetcher
- nanopb
- ObjectiveDropboxOfficial
- SDWebImage
EXTERNAL SOURCES:
BVLinearGradient:
:path: "../node_modules/react-native-linear-gradient"
DoubleConversion:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
Folly:
@@ -156,14 +236,26 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: b0b70acf63ee888829b7c2ebbf6b50e227396e55
Crashlytics: 07fb167b1694128c1c9a5a5cc319b0e9c3ca0933
DoubleConversion: bb338842f62ab1d708ceb63ec3d999f0f3d98ecd
Fabric: f988e33c97f08930a413e08123064d2e5f68d655
Firebase: 8bb9268bff82374f2cbaaabb143e725743c316ae
FirebaseAnalytics: c06f9d70577d79074214700a71fd5d39de5550fb
FirebaseAnalyticsInterop: e5f21be9af6548372e2f0815834ff909bff395a2
FirebaseCore: 35747502d9e8c6ee217385ad04446c7c2aaf9c5c
FirebaseDynamicLinks: c713da5f75c324f38fb2d57164bbc1c461aa6739
FirebaseInstanceID: e2fa4cb35ef5558c200f7f0ad8a53e212215f93e
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
Folly: c89ac2d5c6ab169cd7397ef27485c44f35f742c7
glog: e8acf0ebbf99759d3ff18c86c292a5898282dcde
GoogleAppMeasurement: 98b71f5e04142793729a5ef23e5b96651ff4b70f
GoogleSignIn: 7ff245e1a7b26d379099d3243a562f5747e23d39
GoogleToolboxForMac: ff31605b7d66400dcec09bed5861689aebadda4d
GoogleUtilities: 111a012f4c3a29c9e7c954c082fafd6ee3c999c0
GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca
ObjectiveDropboxOfficial: aa792e0556ceb7b72955fa29a2709072f6e35fd9
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
React: 1fe0eb13d90b625d94c3b117c274dcfd2e760e11
react-native-background-timer: bb7a98c8e97fc7c290de2d423dd09ddb73dcbcbb
react-native-calendar-events: fe6fbc8ed337a7423c98f2c9012b25f20444de09
@@ -176,6 +268,6 @@ SPEC CHECKSUMS:
SDWebImage: c5594f1a19c48d526d321e548902b56b479cd508
yoga: b1ce48b6cf950b98deae82838f5173ea7cf89e85
PODFILE CHECKSUM: 2fa79bc1fe2fe42efb63895fdb0cb84d5f578884
PODFILE CHECKSUM: 7d1909450626f31f9ea2de80122a66a50af2e1ea
COCOAPODS: 1.5.3

View File

@@ -1,13 +1,25 @@
# Jitsi Meet SDK for iOS
## Build
The Jitsi Meet iOS SDK provides the same user experience as the Jitsi Meet app,
in a customizable way which you can embed in your apps.
## Usage
There are 2 ways to integrate the SDK into your project:
- Using CocoaPods
- Building it yourself
### Using CocoaPods
Follow the instructions [here](https://github.com/jitsi/jitsi-meet-ios-sdk-releases/blob/master/README.md).
### Builduing it yourself
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
2. `xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release archive`
## Install
After successfully building Jitsi Meet SDK for iOS, copy
`ios/sdk/JitsiMeet.framework` (if the path points to a symbolic link, follow the
symbolic link) and

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>correct_api_key</string>
<key>TRACKING_ID</key>
<string>correct_tracking_id</string>
<key>CLIENT_ID</key>
<string>correct_client_id</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.google.correct-reversed-client-id</string>
<key>GOOGLE_APP_ID</key>
<string>1:123:ios:123abc</string>
<key>GCM_SENDER_ID</key>
<string>correct_gcm_sender_id</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.google.FirebaseSDKTests</string>
<key>PROJECT_ID</key>
<string>abc-xyz-123</string>
<key>DATABASE_URL</key>
<string>https://abc-xyz-123.firebaseio.com</string>
<key>STORAGE_BUCKET</key>
<string>project-id-123.storage.firebase.com</string>
</dict>
</plist>

View File

@@ -7,5 +7,7 @@
<string>applinks:beta.meet.jit.si</string>
<string>applinks:meet.jit.si</string>
</array>
<key>com.apple.developer.siri</key>
<true/>
</dict>
</plist>

View File

@@ -17,6 +17,9 @@
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */; };
DE4C455E21DE1E4300EA0709 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DE4C455D21DE1E4300EA0709 /* GoogleService-Info.plist */; };
DE4C456121DE1E4E00EA0709 /* FIRUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -35,10 +38,12 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
09AA3B93E4CC62D84B424690 /* Pods-jitsi-meet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jitsi-meet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-jitsi-meet/Pods-jitsi-meet.release.xcconfig"; sourceTree = "<group>"; };
0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = JitsiMeet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
0B412F201EDEE95300B1A0A6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
0BBD021F212EB69D00CCB19F /* Types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Types.h; sourceTree = "<group>"; };
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
@@ -47,7 +52,12 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jitsi-meet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-jitsi-meet/Pods-jitsi-meet.debug.xcconfig"; sourceTree = "<group>"; };
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-jitsi-meet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
DE4C455D21DE1E4300EA0709 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; };
DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRUtilities.m; sourceTree = "<group>"; };
DE4C456021DE1E4E00EA0709 /* FIRUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRUtilities.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -57,6 +67,7 @@
files = (
0B26BE6E1EC5BC3C00EEFB41 /* JitsiMeet.framework in Frameworks */,
0BD6B4371EF82A6B00D1F4CD /* WebRTC.framework in Frameworks */,
695AF3ED6F686F9C5EE40F9A /* libPods-jitsi-meet.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -68,6 +79,7 @@
children = (
0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */,
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
489E8EFE2C720D10F5961AEF /* libPods-jitsi-meet.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -77,17 +89,30 @@
children = (
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
DE4C456021DE1E4E00EA0709 /* FIRUtilities.h */,
DE4C455F21DE1E4E00EA0709 /* FIRUtilities.m */,
DE4C455D21DE1E4300EA0709 /* GoogleService-Info.plist */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
0B412F201EDEE95300B1A0A6 /* Main.storyboard */,
0BBD021F212EB69D00CCB19F /* Types.h */,
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */,
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */,
);
path = src;
sourceTree = "<group>";
};
5E96ADD5E49F3B3822EF9A52 /* Pods */ = {
isa = PBXGroup;
children = (
4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */,
09AA3B93E4CC62D84B424690 /* Pods-jitsi-meet.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
@@ -95,6 +120,7 @@
0B26BE711EC5BC4D00EEFB41 /* Frameworks */,
83CBBA001A601CBA00E9B192 /* Products */,
13B07FAE1A68108700A75B9A /* src */,
5E96ADD5E49F3B3822EF9A52 /* Pods */,
);
indentWidth = 2;
sourceTree = "<group>";
@@ -115,6 +141,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */;
buildPhases = (
B6607F42A5CF0C76E98929E2 /* [CP] Check Pods Manifest.lock */,
0BBA83C41EC9F7600075A103 /* Run React packager */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
@@ -122,6 +149,9 @@
0B26BE701EC5BC3C00EEFB41 /* Embed Frameworks */,
B35383AD1DDA0083008F406A /* Adjust embedded framework architectures */,
0BB7DA181EC9E695007AAE98 /* Adjust ATS */,
DEC2069321CBBD6900072F03 /* Setup Fabric */,
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
);
buildRules = (
);
@@ -142,11 +172,14 @@
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = BQNXB4G3KQ;
DevelopmentTeam = FC967L3QRG;
SystemCapabilities = {
com.apple.SafariKeychain = {
enabled = 1;
};
com.apple.Siri = {
enabled = 1;
};
};
};
};
@@ -175,6 +208,7 @@
buildActionMask = 2147483647;
files = (
0B412F211EDEE95300B1A0A6 /* Main.storyboard in Resources */,
DE4C455E21DE1E4300EA0709 /* GoogleService-Info.plist in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
);
@@ -195,7 +229,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/fixup-ats.sh";
shellScript = "../scripts/fixup-ats.sh\n";
};
0BBA83C41EC9F7600075A103 /* Run React packager */ = {
isa = PBXShellScriptBuildPhase;
@@ -223,7 +257,83 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "../scripts/fixup-frameworks.sh";
shellScript = "../scripts/fixup-frameworks.sh\n";
};
B6607F42A5CF0C76E98929E2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-jitsi-meet-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Setup Google reverse URL handler";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nREVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c \"Print :REVERSED_CLIENT_ID:\" $PROJECT_DIR/GoogleService-Info.plist)\n\n/usr/libexec/PlistBuddy -c \"Set :CFBundleURLTypes:1:CFBundleURLSchemes:0 $REVERSED_CLIENT_ID\" $INFO_PLIST\n";
};
DE4F6D6E22005C0400DE699E /* Setup Dropbox */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Setup Dropbox";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "INFO_PLIST=\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nDROPBOX_KEY_FILE=\"$PROJECT_DIR/dropbox.key\"\n\nif [[ -f $DROPBOX_KEY_FILE ]]; then\n /usr/libexec/PlistBuddy -c \"Delete :LSApplicationQueriesSchemes\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:0 string 'dbapi-2'\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :LSApplicationQueriesSchemes:1 string 'dbapi-8-emm'\" $INFO_PLIST\n\n DROPBOX_KEY=$(head -n 1 $DROPBOX_KEY_FILE)\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLName string dropbox\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes array\" $INFO_PLIST\n /usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:2:CFBundleURLSchemes:0 string $DROPBOX_KEY\" $INFO_PLIST\nfi\n";
};
DEC2069321CBBD6900072F03 /* Setup Fabric */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Setup Fabric";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "${PODS_ROOT}/Fabric/run\n";
};
/* End PBXShellScriptBuildPhase section */
@@ -234,6 +344,7 @@
files = (
0B412F1F1EDEE6E800B1A0A6 /* ViewController.m in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
DE4C456121DE1E4E00EA0709 /* FIRUtilities.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -254,12 +365,14 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = FC967L3QRG;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -278,7 +391,7 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -286,11 +399,13 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 09AA3B93E4CC62D84B424690 /* Pods-jitsi-meet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
CODE_SIGN_ENTITLEMENTS = app.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = FC967L3QRG;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -309,7 +424,7 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet;
PRODUCT_NAME = "jitsi-meet";
TARGETED_DEVICE_FAMILY = "1,2";
};

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 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.
@@ -15,13 +16,32 @@
*/
#import "AppDelegate.h"
#import "FIRUtilities.h"
#import "Types.h"
#import <JitsiMeet/JitsiMeet.h>
@import Crashlytics;
@import Fabric;
@import Firebase;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize Crashlytics and Firebase if a valid GoogleService-Info.plist file was provided.
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
NSLog(@"Enablign Crashlytics and Firebase");
[FIRApp configure];
[Fabric with:@[[Crashlytics class]]];
}
// Set the conference activity type defined in this application.
// This cannot be defined by the SDK.
JitsiMeetView.conferenceActivityType = JitsiMeetConferenceActivityType;
return [JitsiMeetView application:application
didFinishLaunchingWithOptions:launchOptions];
}
@@ -30,18 +50,58 @@
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
// 1. Attempt to handle Universal Links through Firebase in order to support
// its Dynamic Links (which we utilize for the purposes of deferred deep
// linking).
BOOL handled
= [[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink, NSError * _Nullable error) {
NSURL *dynamicLinkURL = dynamicLink.url;
if (dynamicLinkURL) {
userActivity.webpageURL = dynamicLinkURL;
[JitsiMeetView application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
}];
if (handled) {
return handled;
}
}
// 2. Default to plain old, non-Firebase-assisted Universal Links.
return [JitsiMeetView application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSURL *openUrl = url;
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
// Process Firebase Dynamic Links
FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
if (dynamicLink != nil) {
NSURL *dynamicLinkURL = dynamicLink.url;
if (dynamicLinkURL != nil
&& (dynamicLink.matchType == FIRDLMatchTypeUnique
|| dynamicLink.matchType == FIRDLMatchTypeDefault)) {
// Strong match, process it.
openUrl = dynamicLinkURL;
}
}
}
return [JitsiMeetView application:app
openURL:url
openURL:openUrl
options:options];
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
* Copyright 2017 Google
*
* 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
* 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,
@@ -16,16 +16,8 @@
#import <Foundation/Foundation.h>
#import "AddPeopleControllerDelegate.h"
@interface FIRUtilities : NSObject
@interface JMAddPeopleController: NSObject
@property (nonatomic, nullable, weak) id<JMAddPeopleControllerDelegate> delegate;
- (void)endAddPeople;
- (void)inviteById:(NSArray<NSString *> * _Nonnull)ids;
- (void)performQuery:(NSString * _Nonnull)query;
+ (BOOL)appContainsRealServiceInfoPlist;
@end

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2017 Google
*
* 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 "FIRUtilities.h"
// Plist file name.
NSString *const kGoogleServiceInfoFileName = @"GoogleService-Info";
// Plist file type.
NSString *const kGoogleServiceInfoFileType = @"plist";
NSString *const kGoogleAppIDPlistKey = @"GOOGLE_APP_ID";
// Dummy plist GOOGLE_APP_ID
NSString *const kDummyGoogleAppID = @"1:123:ios:123abc";
@implementation FIRUtilities
+ (BOOL)appContainsRealServiceInfoPlist {
static BOOL containsRealServiceInfoPlist = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle mainBundle];
containsRealServiceInfoPlist = [self containsRealServiceInfoPlistInBundle:bundle];
});
return containsRealServiceInfoPlist;
}
+ (BOOL)containsRealServiceInfoPlistInBundle:(NSBundle *)bundle {
NSString *bundlePath = bundle.bundlePath;
if (!bundlePath.length) {
return NO;
}
NSString *plistFilePath = [bundle pathForResource:kGoogleServiceInfoFileName
ofType:kGoogleServiceInfoFileType];
if (!plistFilePath.length) {
return NO;
}
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
if (!plist) {
return NO;
}
// Perform a very naive validation by checking to see if the plist has the dummy google app id
NSString *googleAppID = plist[kGoogleAppIDPlistKey];
if (!googleAppID.length) {
return NO;
}
if ([googleAppID isEqualToString:kDummyGoogleAppID]) {
return NO;
}
return YES;
}
@end

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.21</string>
<string>19.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -39,12 +39,14 @@
<string>com.googleusercontent.apps</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.YOUR_ID_HERE</string>
<string>com.googleusercontent.apps.your-id-here</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>FirebaseScreenReportingEnabled</key>
<false/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@@ -68,6 +70,10 @@
<string></string>
<key>NSMicrophoneUsageDescription</key>
<string>Participate in meetings with voice.</string>
<key>NSUserActivityTypes</key>
<array>
<string>org.jitsi.JitsiMeet.ios.conference</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
@@ -93,5 +99,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>firebase_crashlytics_collection_enabled</key>
<string>false</string>
</dict>
</plist>

7
ios/app/src/Types.h Normal file
View File

@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
// This must match what's defined in the NSUserActivityTypes array in the
// Info.plist file.
static NSString *const JitsiMeetConferenceActivityType
= @"org.jitsi.JitsiMeet.ios.conference";

View File

@@ -18,10 +18,6 @@
#import <JitsiMeet/JitsiMeet.h>
@interface ViewController
: UIViewController<
JitsiMeetViewDelegate,
JMAddPeopleControllerDelegate,
JMInviteControllerDelegate>
@interface ViewController : UIViewController<JitsiMeetViewDelegate>
@end

View File

@@ -14,17 +14,18 @@
* limitations under the License.
*/
#import <Availability.h>
#import <CoreSpotlight/CoreSpotlight.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "Types.h"
#import "ViewController.h"
/**
* The query to perform through JMAddPeopleController when the InviteButton is
* tapped in order to exercise the public API of the feature invite. If nil, the
* InviteButton will not be rendered.
*/
static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
// Needed for NSUserActivity suggestedInvocationPhrase
@import Intents;
@interface ViewController ()
@end
@implementation ViewController
@@ -33,20 +34,8 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[super viewDidLoad];
JitsiMeetView *view = (JitsiMeetView *) self.view;
#ifdef DEBUG
view.delegate = self;
// inviteController
JMInviteController *inviteController = view.inviteController;
inviteController.delegate = self;
inviteController.addPeopleEnabled
= inviteController.dialOutEnabled
= ADD_PEOPLE_CONTROLLER_QUERY != nil;
#endif // #ifdef DEBUG
// 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
@@ -56,12 +45,13 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[view loadURL:nil];
}
#if DEBUG
// JitsiMeetViewDelegate
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
withData:(NSDictionary *)data {
#if DEBUG
NSLog(
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
__FILE__, __LINE__, name, data);
@@ -70,6 +60,7 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[NSThread isMainThread],
@"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
name);
#endif
}
- (void)conferenceFailed:(NSDictionary *)data {
@@ -78,6 +69,36 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
- (void)conferenceJoined:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
// Register a NSUserActivity for this conference so it can be invoked as a
// Siri shortcut. This is only supported in iOS >= 12.
#ifdef __IPHONE_12_0
if (@available(iOS 12.0, *)) {
NSUserActivity *userActivity
= [[NSUserActivity alloc] initWithActivityType:JitsiMeetConferenceActivityType];
NSString *urlStr = data[@"url"];
NSURL *url = [NSURL URLWithString:urlStr];
NSString *conference = [url.pathComponents lastObject];
userActivity.title = [NSString stringWithFormat:@"Join %@", conference];
userActivity.suggestedInvocationPhrase = @"Join my Jitsi meeting";
userActivity.userInfo = @{@"url": urlStr};
[userActivity setEligibleForSearch:YES];
[userActivity setEligibleForPrediction:YES];
[userActivity setPersistentIdentifier:urlStr];
// Subtitle
CSSearchableItemAttributeSet *attributes
= [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeItem];
attributes.contentDescription = urlStr;
userActivity.contentAttributeSet = attributes;
self.userActivity = userActivity;
[userActivity becomeCurrent];
}
#endif
}
- (void)conferenceLeft:(NSDictionary *)data {
@@ -96,87 +117,4 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
}
// JMInviteControllerDelegate
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {
NSLog(
@"[%s:%d] JMInviteControllerDelegate %s",
__FILE__, __LINE__, __FUNCTION__);
NSAssert(
[NSThread isMainThread],
@"JMInviteControllerDelegate beginAddPeople: invoked on a non-main thread");
NSString *query = ADD_PEOPLE_CONTROLLER_QUERY;
JitsiMeetView *view = (JitsiMeetView *) self.view;
JMInviteController *inviteController = view.inviteController;
if (query
&& (inviteController.addPeopleEnabled
|| inviteController.dialOutEnabled)) {
addPeopleController.delegate = self;
[addPeopleController performQuery:query];
} else {
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated JMInviteController
// and no subsequent InviteButton clicks/taps will be delivered.
[addPeopleController endAddPeople];
}
}
// JMAddPeopleControllerDelegate
- (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller
didReceiveResults:(NSArray<NSDictionary *> * _Nonnull)results
forQuery:(NSString * _Nonnull)query {
NSAssert(
[NSThread isMainThread],
@"JMAddPeopleControllerDelegate addPeopleController:didReceiveResults:forQuery: invoked on a non-main thread");
NSUInteger count = results.count;
if (count) {
// Exercise JMAddPeopleController's inviteById: implementation.
NSMutableArray *ids = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; ++i) {
ids[i] = results[i][@"id"];
}
[controller inviteById:ids];
// Exercise JMInviteController's invite:withCompletion: implementation.
//
// XXX Technically, only at most one of the two exercises will result in
// an actual invitation eventually.
JitsiMeetView *view = (JitsiMeetView *) self.view;
JMInviteController *inviteController = view.inviteController;
[inviteController invite:results withCompletion:nil];
return;
}
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
// is going to be memory-leaked in the associated JMInviteController and no
// subsequent InviteButton clicks/taps will be delivered.
[controller endAddPeople];
}
- (void) inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController {
NSAssert(
[NSThread isMainThread],
@"JMAddPeopleControllerDelegate inviteSettled:fromSearchController: invoked on a non-main thread");
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
// is going to be memory-leaked in the associated JMInviteController and no
// subsequent InviteButton clicks/taps will be delivered. Technically,
// endAddPeople will automatically be invoked if there are no
// failedInviteees i.e. the invite succeeeded for all specified invitees.
[addPeopleController endAddPeople];
}
#endif // #ifdef DEBUG
@end

4
ios/fastlane/Appfile Normal file
View File

@@ -0,0 +1,4 @@
app_identifier("com.atlassian.JitsiMeet.ios") # The bundle identifier of your app
itc_team_id("304592") # App Store Connect Team ID
team_id("FC967L3QRG") # Developer Portal Team ID

28
ios/fastlane/Fastfile Normal file
View File

@@ -0,0 +1,28 @@
default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
# Set the app identifier
update_app_identifier(
xcodeproj: "app/app.xcodeproj",
plist_path: "src/Info.plist",
app_identifier: "com.atlassian.JitsiMeet.ios"
)
# Inrement the build number by 1
increment_build_number(
build_number: latest_testflight_build_number + 1,
xcodeproj: "app/app.xcodeproj"
)
# Actually build the app
build_app(scheme: "jitsi-meet", include_bitcode: false)
# Upload the build to TestFlight (but don't distribute it)
upload_to_testflight(skip_submission: true, skip_waiting_for_build_processing: true)
# Cleanup
clean_build_artifacts
end
end

29
ios/fastlane/README.md Normal file
View File

@@ -0,0 +1,29 @@
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew cask install fastlane`
# Available Actions
## iOS
### ios beta
```
fastlane ios beta
```
Push a new beta build to TestFlight
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

View File

@@ -32,15 +32,9 @@
6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC920C06D530089C899 /* recordingOff.mp3 */; };
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
87FE6F3321E52437004A5DC7 /* incomingMessage.wav in Resources */ = {isa = PBXBuildFile; fileRef = 87FE6F3221E52437004A5DC7 /* incomingMessage.wav */; };
A4414AE020B37F1A003546E6 /* rejected.wav in Resources */ = {isa = PBXBuildFile; fileRef = A4414ADF20B37F1A003546E6 /* rejected.wav */; };
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A934E8212F3ADB001E9388 /* Dropbox.m */; };
B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85120981A74000DEF7A /* AddPeopleController.m */; };
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85320981A74000DEF7A /* AddPeopleController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85420981A74000DEF7A /* InviteControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
B386B85C20981A75000DEF7A /* InviteController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85520981A75000DEF7A /* InviteController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; };
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */; };
@@ -60,9 +54,6 @@
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingStart.wav; path = ../../sounds/outgoingStart.wav; sourceTree = "<group>"; };
0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingRinging.wav; path = ../../sounds/outgoingRinging.wav; sourceTree = "<group>"; };
0B6F414F20987DE600FF6789 /* Invite+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Invite+Private.h"; sourceTree = "<group>"; };
0B6F41502098840600FF6789 /* InviteController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "InviteController+Private.h"; sourceTree = "<group>"; };
0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AddPeopleController+Private.h"; 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>"; };
@@ -72,7 +63,7 @@
0BB9AD781F5EC6D7001C08DB /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallKit.m; sourceTree = "<group>"; };
0BB9AD7C1F60356D001C08DB /* AppInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppInfo.m; sourceTree = "<group>"; };
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = CallKitIcon.png; path = ../../react/features/mobile/callkit/CallKitIcon.png; sourceTree = "<group>"; };
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = CallKitIcon.png; path = ../../react/features/mobile/call-integration/CallKitIcon.png; sourceTree = "<group>"; };
0BCA495C1EC4B6C600B793EE /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioMode.m; sourceTree = "<group>"; };
0BCA495D1EC4B6C600B793EE /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = POSIX.m; sourceTree = "<group>"; };
0BCA495E1EC4B6C600B793EE /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Proximity.m; sourceTree = "<group>"; };
@@ -85,18 +76,12 @@
6C31EDC920C06D530089C899 /* recordingOff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOff.mp3; path = ../../sounds/recordingOff.mp3; sourceTree = "<group>"; };
75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
75635B0920751D6D00F29C9F /* left.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = left.wav; path = ../../sounds/left.wav; sourceTree = "<group>"; };
87FE6F3221E52437004A5DC7 /* incomingMessage.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = incomingMessage.wav; path = ../../sounds/incomingMessage.wav; sourceTree = "<group>"; };
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
A4414ADF20B37F1A003546E6 /* rejected.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = rejected.wav; path = ../../sounds/rejected.wav; sourceTree = "<group>"; };
A4A934E8212F3ADB001E9388 /* Dropbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Dropbox.m; sourceTree = "<group>"; };
A4A934EB21349A06001E9388 /* Dropbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Dropbox.h; sourceTree = "<group>"; };
B386B85020981A74000DEF7A /* InviteController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteController.m; sourceTree = "<group>"; };
B386B85120981A74000DEF7A /* AddPeopleController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddPeopleController.m; sourceTree = "<group>"; };
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleControllerDelegate.h; sourceTree = "<group>"; };
B386B85320981A74000DEF7A /* AddPeopleController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleController.h; sourceTree = "<group>"; };
B386B85420981A74000DEF7A /* InviteControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteControllerDelegate.h; sourceTree = "<group>"; };
B386B85520981A75000DEF7A /* InviteController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteController.h; sourceTree = "<group>"; };
B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; sourceTree = "<group>"; };
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitEmitter.swift; sourceTree = "<group>"; };
@@ -125,6 +110,7 @@
0BCA49681EC4BBE500B793EE /* Resources */ = {
isa = PBXGroup;
children = (
87FE6F3221E52437004A5DC7 /* incomingMessage.wav */,
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
@@ -168,7 +154,6 @@
A4A934E7212F3AB8001E9388 /* dropbox */,
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
0BD906E91EC0C00300C8C18E /* Info.plist */,
B386B84F20981A11000DEF7A /* invite */,
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
@@ -207,23 +192,6 @@
path = dropbox;
sourceTree = "<group>";
};
B386B84F20981A11000DEF7A /* invite */ = {
isa = PBXGroup;
children = (
B386B85320981A74000DEF7A /* AddPeopleController.h */,
B386B85120981A74000DEF7A /* AddPeopleController.m */,
0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */,
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */,
B386B85620981A75000DEF7A /* Invite.m */,
0B6F414F20987DE600FF6789 /* Invite+Private.h */,
B386B85520981A75000DEF7A /* InviteController.h */,
B386B85020981A74000DEF7A /* InviteController.m */,
0B6F41502098840600FF6789 /* InviteController+Private.h */,
B386B85420981A74000DEF7A /* InviteControllerDelegate.h */,
);
path = invite;
sourceTree = "<group>";
};
C5E72ADFC30ED96F9B35F076 /* Pods */ = {
isa = PBXGroup;
children = (
@@ -260,14 +228,10 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
B386B85C20981A75000DEF7A /* InviteController.h in Headers */,
B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */,
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */,
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */,
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -335,6 +299,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
87FE6F3321E52437004A5DC7 /* incomingMessage.wav in Resources */,
0B49424520AD8DBD00BD2DE0 /* outgoingStart.wav in Resources */,
6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */,
A4414AE020B37F1A003546E6 /* rejected.wav in Resources */,
@@ -462,12 +427,9 @@
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
B386B85D20981A75000DEF7A /* Invite.m in Sources */,
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
B386B85720981A75000DEF7A /* InviteController.m in Sources */,
DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */,
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */,
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,

View File

@@ -20,6 +20,12 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
typedef enum {
kAudioModeDefault,
kAudioModeAudioCall,
kAudioModeVideoCall
} JitsiMeetAudioMode;
@interface AudioMode : NSObject<RCTBridgeModule>
@property(nonatomic, strong) dispatch_queue_t workerQueue;
@@ -27,18 +33,13 @@
@end
@implementation AudioMode {
NSString *_category;
NSString *_mode;
NSString *_avCategory;
NSString *_avMode;
JitsiMeetAudioMode _mode;
}
RCT_EXPORT_MODULE();
typedef enum {
kAudioModeDefault,
kAudioModeAudioCall,
kAudioModeVideoCall
} JitsiMeetAudioMode;
+ (BOOL)requiresMainQueueSetup {
return NO;
}
@@ -54,13 +55,23 @@ typedef enum {
- (instancetype)init {
self = [super init];
if (self) {
_category = nil;
_mode = nil;
_avCategory = nil;
_avMode = nil;
_mode = kAudioModeDefault;
dispatch_queue_attr_t attributes =
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
QOS_CLASS_USER_INITIATED, -1);
_workerQueue = dispatch_queue_create("AudioMode.queue", attributes);
// AVAudioSession is a singleton and other parts of the application such as
// WebRTC may undo the settings. Make sure that the settings are reapplied
// upon undoes.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(routeChanged:)
name:AVAudioSessionRouteChangeNotification
object:nil];
}
return self;
}
@@ -82,7 +93,7 @@ typedef enum {
// needed. This notification is posted on a secondary thread, so make
// sure we switch to our worker thread.
dispatch_async(_workerQueue, ^{
[self setCategory:self->_category mode:self->_mode error:nil];
[self setCategory:self->_avCategory mode:self->_avMode error:nil];
});
break;
}
@@ -97,6 +108,18 @@ typedef enum {
error:(NSError * _Nullable *)outError {
AVAudioSession *session = [AVAudioSession sharedInstance];
// We don't want to touch the category when setting the default mode.
// This is to play well with other components which could be integrated
// into the final application.
if (_mode == kAudioModeDefault) {
return YES;
}
// Nothing to do.
if (category == nil && mode == nil) {
return YES;
}
if (session.category != category
&& ![session setCategory:category error:outError]) {
RCTLogError(@"Failed to (re)apply specified AVAudioSession category!");
@@ -114,8 +137,8 @@ typedef enum {
RCT_EXPORT_METHOD(setMode:(int)mode
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
NSString *avCategory;
NSString *avMode;
NSString *avCategory = nil;
NSString *avMode = nil;
NSError *error;
switch (mode) {
@@ -124,8 +147,6 @@ RCT_EXPORT_METHOD(setMode:(int)mode
avMode = AVAudioSessionModeVoiceChat;
break;
case kAudioModeDefault:
avCategory = AVAudioSessionCategorySoloAmbient;
avMode = AVAudioSessionModeDefault;
break;
case kAudioModeVideoCall:
avCategory = AVAudioSessionCategoryPlayAndRecord;
@@ -136,29 +157,17 @@ RCT_EXPORT_METHOD(setMode:(int)mode
return;
}
// Save the desired/specified category and mode so that they may be
// reapplied.
_avCategory = avCategory;
_avMode = avMode;
_mode = mode;
if (![self setCategory:avCategory mode:avMode error:&error] || error) {
reject(@"setMode", error.localizedDescription, error);
return;
} else {
resolve(nil);
}
// Even though the specified category and mode were successfully set, the
// AVAudioSession is a singleton and other parts of the application such as
// WebRTC may undo the settings. Make sure that the settings are reapplied
// upon undoes.
if (!_category || !_mode) {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(routeChanged:)
name:AVAudioSessionRouteChangeNotification
object:nil];
}
// Save the desired/specified category and mode so that they may be
// reapplied (upon undoes as described above).
_category = avCategory;
_mode = avMode;
resolve(nil);
}
@end

View File

@@ -18,11 +18,6 @@
<string>1.9.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-2</string>
<string>dbapi-8-emm</string>
</array>
<key>NSPrincipalClass</key>
<string></string>
</dict>

View File

@@ -17,9 +17,3 @@
// JitsiMeetView
#import <JitsiMeet/JitsiMeetView.h>
#import <JitsiMeet/JitsiMeetViewDelegate.h>
// invite/
#import <JitsiMeet/AddPeopleController.h>
#import <JitsiMeet/AddPeopleControllerDelegate.h>
#import <JitsiMeet/InviteController.h>
#import <JitsiMeet/InviteControllerDelegate.h>

View File

@@ -18,6 +18,7 @@
@interface JitsiMeetView ()
+ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity;
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope;
@end

View File

@@ -17,17 +17,16 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "InviteController.h"
#import "JitsiMeetViewDelegate.h"
@interface JitsiMeetView : UIView
@property (class, copy, nonatomic, nullable) NSString *conferenceActivityType;
@property (copy, nonatomic, nullable) NSURL *defaultURL;
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
@property (nonatomic, readonly, nonnull) JMInviteController *inviteController;
@property (nonatomic) BOOL pictureInPictureEnabled;
@property (nonatomic) BOOL welcomePageEnabled;

View File

@@ -22,9 +22,9 @@
#import <React/RCTLinkingManager.h>
#import <React/RCTRootView.h>
#import <RNGoogleSignin/RNGoogleSignin.h>
#import "Dropbox.h"
#import "Invite+Private.h"
#import "InviteController+Private.h"
#import "JitsiMeetView+Private.h"
#import "RCTBridgeWrapper.h"
@@ -88,6 +88,8 @@ void registerFatalErrorHandler() {
@dynamic pictureInPictureEnabled;
static NSString *_conferenceActivityType;
static RCTBridgeWrapper *bridgeWrapper;
/**
@@ -120,46 +122,18 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
{
NSString *activityType = userActivity.activityType;
// XXX At least twice we received bug reports about malfunctioning loadURL
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
// expected in our testing. But that was to be expected because the app does
// not exercise loadURL. In order to increase the test coverage of loadURL,
// channel Universal linking through loadURL.
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]
&& [self loadURLInViews:userActivity.webpageURL]) {
id url = [self conferenceURLFromUserActivity:userActivity];
if (url && [self loadURLObjectInViews:url]) {
return YES;
}
// Check for a CallKit intent.
if ([activityType isEqualToString:@"INStartAudioCallIntent"]
|| [activityType isEqualToString:@"INStartVideoCallIntent"]) {
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)) {
// Load the URL contained in the handle.
[self loadURLObjectInViews:@{
@"config": @{
@"startAudioOnly": @(startAudioOnly)
},
@"url": url
}];
return YES;
}
}
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
@@ -172,6 +146,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
return YES;
}
if ([RNGoogleSignin application:app
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]]) {
return YES;
}
// XXX At least twice we received bug reports about malfunctioning loadURL
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
// expected in our testing. But that was to be expected because the app does
@@ -254,9 +235,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
props[@"addPeopleEnabled"] = @(_inviteController.addPeopleEnabled);
props[@"dialOutEnabled"] = @(_inviteController.dialOutEnabled);
// XXX If urlObject is nil, then it must appear as undefined in the
// JavaScript source code so that we check the launchOptions there.
if (urlObject) {
@@ -305,6 +283,16 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
[self loadURLObject:urlString ? @{ @"url": urlString } : nil];
}
#pragma conferenceActivityType getter / setter
+ (NSString *)conferenceActivityType {
return _conferenceActivityType;
}
+ (void) setConferenceActivityType:(NSString *)conferenceActivityType {
_conferenceActivityType = conferenceActivityType;
}
#pragma pictureInPictureEnabled getter / setter
- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
@@ -359,6 +347,45 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
return handled;
}
+ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity {
NSString *activityType = userActivity.activityType;
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
// App was started by opening a URL in the browser
return @{ @"url" : 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)) {
return @{
@"config": @{@"startAudioOnly":@(startAudioOnly)},
@"url": url
};
}
} else if (_conferenceActivityType && [activityType isEqualToString:_conferenceActivityType]) {
// App was started by continuing a registered NSUserActivity (SiriKit, Handoff, ...)
NSString *url;
if ((url = userActivity.userInfo[@"url"])) {
return @{ @"url" : url };
}
}
return nil;
}
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
return [views objectForKey:externalAPIScope];
}
@@ -388,10 +415,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
externalAPIScope = [NSUUID UUID].UUIDString;
[views setObject:self forKey:externalAPIScope];
_inviteController
= [[JMInviteController alloc] initWithExternalAPIScope:externalAPIScope
bridgeWrapper:bridgeWrapper];
// Set a background color which is in accord with the JavaScript and Android
// parts of the application and causes less perceived visual flicker than
// the default background color.

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
#import <Intents/Intents.h>
#import "JitsiMeetView+Private.h"
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
@@ -36,6 +36,7 @@ RCT_EXPORT_MODULE();
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;
@@ -45,34 +46,7 @@ RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
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
};
}
}
initialURL = [JitsiMeetView conferenceURLFromUserActivity:userActivity];
}
}
@@ -80,4 +54,3 @@ RCT_EXPORT_METHOD(getInitialURL:(RCTPromiseResolveBlock)resolve
}
@end

View File

@@ -66,7 +66,7 @@ RCT_EXPORT_METHOD(authorize:(RCTPromiseResolveBlock)resolve
[DBClientsManager authorizeFromController:[UIApplication sharedApplication]
controller:[[self class] topMostController]
openURL:^(NSURL *url) {
[[UIApplication sharedApplication] openURL:url];
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
}];
});
}
@@ -132,26 +132,24 @@ RCT_EXPORT_METHOD(getSpaceUsage: (NSString *)token
if (authResult) {
if ([authResult isSuccess]) {
currentResolve(authResult.accessToken.accessToken);
currentResolve = nil;
currentReject = nil;
} else {
NSString *msg;
if ([authResult isError]) {
msg = [NSString stringWithFormat:@"%@, error type: %ld",[authResult errorDescription], [authResult errorType]];
msg = [NSString stringWithFormat:@"%@, error type: %zd",[authResult errorDescription], [authResult errorType]];
} else {
msg = @"OAuth canceled!";
}
currentReject(@"authorize", msg, nil);
currentResolve = nil;
currentReject = nil;
}
currentResolve = nil;
currentReject = nil;
return YES;
}
return NO;
}
+ (UIViewController *)topMostController
{
+ (UIViewController *)topMostController {
UIViewController *topController
= [UIApplication sharedApplication].keyWindow.rootViewController;

View File

@@ -1,33 +0,0 @@
/*
* 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 "AddPeopleController.h"
#import "InviteController.h"
@interface JMAddPeopleController ()
@property (nonatomic, strong) NSMutableDictionary* _Nonnull items;
@property (nonatomic, weak, nullable) JMInviteController *owner;
@property (nonatomic, readonly) NSString* _Nonnull uuid;
- (instancetype _Nonnull)initWithOwner:(JMInviteController * _Nonnull)owner;
- (void)inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees;
- (void)receivedResults:(NSArray<NSDictionary*> * _Nonnull)results
forQuery:(NSString * _Nonnull)query;
@end

View File

@@ -1,82 +0,0 @@
/*
* 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 "AddPeopleController+Private.h"
#import "InviteController+Private.h"
@implementation JMAddPeopleController
- (instancetype)initWithOwner:(JMInviteController *)owner {
self = [super init];
if (self) {
_uuid = [[NSUUID UUID] UUIDString];
_items = [[NSMutableDictionary alloc] init];
_owner = owner;
}
return self;
}
#pragma mark API
- (void)endAddPeople {
[self.owner endAddPeopleForController:self];
}
- (void)inviteById:(NSArray<NSString *> * _Nonnull)ids {
NSMutableArray* invitees = [[NSMutableArray alloc] init];
for (NSString* itemId in ids) {
id invitee = [self.items objectForKey:itemId];
if (invitee) {
[invitees addObject:invitee];
}
}
[self.owner invite:invitees forController:self];
}
- (void)performQuery:(NSString *)query {
[self.owner performQuery:query forController:self];
}
#pragma mark Internal API, used to call the delegate and report to the user
- (void)receivedResults:(NSArray<NSDictionary *> *)results
forQuery:(NSString *)query {
for (NSDictionary* item in results) {
NSString* itemId = item[@"id"];
NSString* itemType = item[@"type"];
if (itemId) {
[self.items setObject:item forKey:itemId];
} else if (itemType != nil && [itemType isEqualToString: @"phone"]) {
NSString* number = item[@"number"];
if (number) {
[self.items setObject:item forKey:number];
}
}
}
[self.delegate addPeopleController:self
didReceiveResults:results
forQuery:query];
}
- (void)inviteSettled:(NSArray<NSDictionary *> *)failedInvitees {
[self.delegate inviteSettled:failedInvitees fromSearchController:self];
}
@end

View File

@@ -1,41 +0,0 @@
/*
* 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 <Foundation/Foundation.h>
#import "AddPeopleController.h"
@class JMAddPeopleController;
@protocol JMAddPeopleControllerDelegate
/**
* Called when a JMAddPeopleController has results for a query that was
* previously provided.
*/
- (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller
didReceiveResults:(NSArray<NSDictionary *> * _Nonnull)results
forQuery:(NSString * _Nonnull)query;
/**
* Called when a JMAddPeopleController has finished the inviting process, either
* succesfully or not. In case of failure the failedInvitees array will contain
* the items for which invitations failed.
*/
- (void) inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController;
@end

View File

@@ -1,30 +0,0 @@
/*
* 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 <React/RCTBridge.h>
#import <React/RCTEventEmitter.h>
@interface Invite : RCTEventEmitter <RCTBridgeModule>
- (void) invite:(NSArray<NSDictionary *> * _Nonnull)invitees
externalAPIScope:(NSString * _Nonnull)externalAPIScope
addPeopleControllerScope:(NSString * _Nonnull)addPeopleControllerScope;
- (void) performQuery:(NSString * _Nonnull)query
externalAPIScope:(NSString * _Nonnull)externalAPIScope
addPeopleControllerScope:(NSString * _Nonnull)addPeopleControllerScope;
@end

View File

@@ -1,127 +0,0 @@
/*
* 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 "Invite+Private.h"
#import "InviteController+Private.h"
#import "JitsiMeetView+Private.h"
// The events emitted/supported by the Invite react-native module:
//
// XXX The event names are ridiculous on purpose. Even though iOS makes it look
// like it emits within the bounderies of a react-native module ony, it actually
// also emits through DeviceEventEmitter. (Of course, Android emits only through
// DeviceEventEmitter.)
static NSString * const InviteEmitterEvent
= @"org.jitsi.meet:features/invite#invite";
static NSString * const PerformQueryEmitterEvent
= @"org.jitsi.meet:features/invite#performQuery";
@implementation Invite
RCT_EXPORT_MODULE();
/**
* Make sure all methods in this module are invoked on the main/UI thread.
*/
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
- (NSArray<NSString *> *)supportedEvents {
return @[
InviteEmitterEvent,
PerformQueryEmitterEvent
];
}
/**
* Initiates the process to add people. This involves calling a delegate method
* in the JMInviteControllerDelegate so the native host application can start
* the query process.
*
* @param externalAPIScope - Scope identifying the JitsiMeetView where the
* calling JS code is being executed.
*/
RCT_EXPORT_METHOD(beginAddPeople:(NSString *)externalAPIScope) {
JitsiMeetView *view
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
JMInviteController *inviteController = view.inviteController;
[inviteController beginAddPeople];
}
/**
* Indicates the the invite process has settled / finished.
*
* @param externalAPIScope - Scope identifying the JitsiMeetView where the
* calling JS code is being executed.
* @param addPeopleControllerScope - Scope identifying the JMAddPeopleController
* wich was settled.
* @param failedInvitees - Array with the invitees which were not invited due
* to a failure.
*/
RCT_EXPORT_METHOD(inviteSettled:(NSString *)externalAPIScope
addPeopleControllerScope:(NSString *)addPeopleControllerScope
failedInvitees:(NSArray *)failedInvitees) {
JitsiMeetView *view
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
JMInviteController *inviteController = view.inviteController;
[inviteController inviteSettled:addPeopleControllerScope
failedInvitees:failedInvitees];
}
/**
* Process results received for the given query. This involves calling a
* delegate method in JMAddPeopleControllerDelegate so the native host
* application is made aware of the query results.
*
* @param externalAPIScope - Scope identifying the JitsiMeetView where the
* calling JS code is being executed.
* @param addPeopleControllerScope - Scope identifying the JMAddPeopleController
* for which the results were received.
* @param query - The actual query for which the results were received.
* @param results - The query results.
*/
RCT_EXPORT_METHOD(receivedResults:(NSString *)externalAPIScope
addPeopleControllerScope:(NSString *)addPeopleControllerScope
query:(NSString *)query
results:(NSArray *)results) {
JitsiMeetView *view
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
JMInviteController *inviteController = view.inviteController;
[inviteController receivedResults:addPeopleControllerScope
query:query
results:results];
}
- (void) invite:(NSArray<NSDictionary *> * _Nonnull)invitees
externalAPIScope:(NSString * _Nonnull)externalAPIScope
addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
[self sendEventWithName:InviteEmitterEvent
body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
@"externalAPIScope": externalAPIScope,
@"invitees": invitees }];
}
- (void) performQuery:(NSString * _Nonnull)query
externalAPIScope:(NSString * _Nonnull)externalAPIScope
addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
[self sendEventWithName:PerformQueryEmitterEvent
body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
@"externalAPIScope": externalAPIScope,
@"query": query }];
}
@end

View File

@@ -1,53 +0,0 @@
/*
* 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 "InviteController.h"
#import "AddPeopleController.h"
#import "Invite+Private.h"
#import "RCTBridgeWrapper.h"
@interface JMInviteController ()
@property (nonatomic, nullable) JMAddPeopleController *addPeopleController;
@property (nonatomic) NSString * _Nonnull externalAPIScope;
@property (nonatomic, nullable, weak) RCTBridgeWrapper *bridgeWrapper;
@property (nonatomic, readonly) Invite * _Nullable inviteModule;
- (instancetype _Nonnull)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
bridgeWrapper:(RCTBridgeWrapper * _Nullable)bridgeWrapper;
- (void)beginAddPeople;
- (void)endAddPeopleForController:(JMAddPeopleController * _Nonnull)controller;
- (void) invite:(NSArray * _Nonnull)invitees
forController:(JMAddPeopleController * _Nonnull)controller;
- (void)inviteSettled:(NSString * _Nonnull)addPeopleControllerScope
failedInvitees:(NSArray * _Nonnull)failedInvitees;
- (void)performQuery:(NSString * _Nonnull)query
forController:(JMAddPeopleController * _Nonnull)controller;
- (void)receivedResults:(NSString * _Nonnull)addPeopleControllerScope
query:(NSString * _Nonnull)query
results:(NSArray * _Nonnull)results;
@end

View File

@@ -1,32 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "InviteControllerDelegate.h"
@interface JMInviteController : NSObject
@property (nonatomic) BOOL addPeopleEnabled;
@property (nonatomic) BOOL dialOutEnabled;
@property (nonatomic, nullable, weak) id<JMInviteControllerDelegate> delegate;
- (void) invite:(NSArray * _Nonnull)invitees
withCompletion:(void (^ _Nullable)(NSArray<NSDictionary *> * _Nonnull failedInvitees))completion;
@end

View File

@@ -1,160 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "InviteController+Private.h"
#import "AddPeopleController+Private.h"
@implementation JMInviteController {
NSNumber *_addPeopleEnabled;
NSNumber *_dialOutEnabled;
}
@dynamic addPeopleEnabled;
@dynamic dialOutEnabled;
#pragma mark Constructor
-(instancetype)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
bridgeWrapper:(RCTBridgeWrapper * _Nullable)bridgeWrapper {
self = [super init];
if (self) {
self.externalAPIScope = externalAPIScope;
self.bridgeWrapper = bridgeWrapper;
}
return self;
}
#pragma mark Public API
-(Invite * _Nullable)inviteModule {
return [self.bridgeWrapper.bridge moduleForName:@"Invite"];
}
-(void)beginAddPeople {
if (_delegate == nil) {
return;
}
if (_addPeopleController != nil) {
return;
}
_addPeopleController = [[JMAddPeopleController alloc] initWithOwner:self];
@try {
if (self.delegate
&& [self.delegate respondsToSelector:@selector(beginAddPeople:)]) {
[self.delegate beginAddPeople:_addPeopleController];
}
} @catch (NSException *e) {
[self endAddPeopleForController:_addPeopleController];
}
}
-(void)endAddPeopleForController:(JMAddPeopleController *)controller {
if (self.addPeopleController == controller) {
self.addPeopleController = nil;
}
}
#pragma mark Property getters / setters
- (void) setAddPeopleEnabled:(BOOL)addPeopleEnabled {
_addPeopleEnabled = [NSNumber numberWithBool:addPeopleEnabled];
}
- (BOOL) addPeopleEnabled {
if (_addPeopleEnabled == nil || [_addPeopleEnabled boolValue]) {
return self.delegate
&& [self.delegate respondsToSelector:@selector(beginAddPeople:)];
}
return NO;
}
- (void) setDialOutEnabled:(BOOL)dialOutEnabled {
_dialOutEnabled = [NSNumber numberWithBool:dialOutEnabled];
}
- (BOOL) dialOutEnabled {
if (_dialOutEnabled == nil || [_dialOutEnabled boolValue]) {
return self.delegate
&& [self.delegate respondsToSelector:@selector(beginAddPeople:)];
}
return NO;
}
#pragma mark Result handling
- (void)inviteSettled:(NSString *)addPeopleControllerScope
failedInvitees:(NSArray *)failedInvitees {
JMAddPeopleController *controller = self.addPeopleController;
if (controller != nil
&& [controller.uuid isEqualToString:addPeopleControllerScope]) {
@try {
[controller inviteSettled:failedInvitees];
} @finally {
if ([failedInvitees count] == 0) {
[self endAddPeopleForController:controller];
}
}
}
}
- (void)receivedResults:(NSString *)addPeopleControllerScope
query:(NSString *)query
results:(NSArray *)results {
JMAddPeopleController *controller = self.addPeopleController;
if (controller != nil
&& [controller.uuid isEqualToString:addPeopleControllerScope]) {
[controller receivedResults:results forQuery:query];
}
}
#pragma mark Use the Invite react-native module to emit the search / submission events
- (void) invite:(NSArray *)invitees
forController:(JMAddPeopleController * _Nonnull)controller {
[self invite:invitees
forControllerScope:controller.uuid];
}
- (void) invite:(NSArray *)invitees
forControllerScope:(NSString * _Nonnull)controllerScope {
[self.inviteModule invite:invitees
externalAPIScope:self.externalAPIScope
addPeopleControllerScope:controllerScope];
}
- (void) invite:(NSArray *)invitees
withCompletion:(void (^)(NSArray<NSDictionary *> *failedInvitees))completion {
// TODO Execute the specified completion block when the invite settles.
[self invite:invitees
forControllerScope:[[NSUUID UUID] UUIDString]];
}
- (void)performQuery:(NSString * _Nonnull)query
forController:(JMAddPeopleController * _Nonnull)controller {
[self.inviteModule performQuery:query
externalAPIScope:self.externalAPIScope
addPeopleControllerScope:controller.uuid];
}
@end

View File

@@ -1,29 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "AddPeopleController.h"
@protocol JMInviteControllerDelegate <NSObject>
/**
* Called when the invite button in the conference is tapped.
*
* The search controller provided can be used to query user search within the
* conference.
*/
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController;
@end

26
lang/languages-af.json Normal file
View File

@@ -0,0 +1,26 @@
{
"en": "Engels",
"az": "Azerbeidjans",
"bg": "Bulgaars",
"cs": "Tsjeggies",
"de": "Duits",
"el": "Grieks",
"eo": "Esperanto",
"es": "Spaans",
"fr": "Frans",
"hy": "Armeens",
"it": "Italiaans",
"ja": "Japannees",
"ko": "Koreaans",
"nb": "Bokmal-Noorweegs",
"oc": "Oksitaans",
"pl": "Pools",
"ptBR": "Portugees (Brasilië)",
"ru": "Russies",
"sk": "Slowaaks",
"sl": "Sloweens",
"sv": "Sweeds",
"tr": "Turks",
"vi": "Viëtnamees",
"zhCN": "Sjinees (Sjina)"
}

740
lang/main-af.json Normal file
View File

@@ -0,0 +1,740 @@
{
"contactlist_plural": "__count__ lede",
"passwordSetRemotely": "ingestel deur n ander lid",
"poweredby": "aangedryf deur",
"inviteUrlDefaultMsg": "U konferensie word tans geskep...",
"me": "ek",
"speaker": "",
"raisedHand": "Wil praat",
"defaultNickname": "bv. Piet Pompies",
"defaultLink": "bv. __url__",
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Oorfone",
"phone": "Foon",
"speaker": "Luidspreker"
},
"audioOnly": {
"audioOnly": "Net klank",
"featureToggleDisabled": "Wisseling van __feature__ is gedeaktiveer tydens Net klank-modus"
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"electronGrantPermissions": "",
"firefoxGrantPermissions": "Kies <b><i>Deel gekose toestel</i></b> wanneer die blaaier vir toestemming vra.",
"operaGrantPermissions": "Kies <b><i>Allow</i></b> wanneer die blaaier vir toestemming vra.",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "Kies <b><i>OK</i></b> wanneer die blaaier vir toestemming vra.",
"nwjsGrantPermissions": "Gee asb. toestemming vir die gebruik van u kamera en mikrofoon",
"edgeGrantPermissions": "Kies <b><i>Yes</i></b> wanneer die blaaier vir toestemming vra."
},
"keyboardShortcuts": {
"keyboardShortcuts": "Sleutelbordkortpaaie",
"raiseHand": "Steek hand op of laat sak hom",
"pushToTalk": "Druk om te praat",
"toggleScreensharing": "Wissel tussen kamera- en skermdeling",
"toggleFilmstrip": "Wys of versteek duimnaels vir videos",
"toggleShortcuts": "Wys of versteek sleutelbordkortpaaie",
"focusLocal": "Fokus op u video",
"focusRemote": "Fokus op n ander persoon se video",
"toggleChat": "Maak gesels oop of toe",
"mute": "Demp of ontdemp jou mikrofoon",
"fullScreen": "Bekyk of verlaat volskerm",
"videoMute": "Begin of stop u kamera",
"showSpeakerStats": "Wys sprekerstatistiek",
"localRecording": "Wys of versteek kontroles vir plaaslike opname"
},
"welcomepage": {
"accessibilityLabel": {
"join": "Raak om aan te sluit",
"roomname": ""
},
"appDescription": "Hou gerus n videogesprek met die hele span. Om die waarheid te sê, nooi sommer almal. __app__ is n 100% oopbronoplossing vir geënkripteerde videokonferensies wat mens heeldag, elke dag gratis kan geniet — geen rekening nodig nie.",
"audioVideoSwitch": {
"audio": "Stem",
"video": "Video"
},
"calendar": "",
"connectCalendarText": "Koppel u kalender om alle vergaderings in __app__ te sien. Plus, voeg __app__-vergaderings by u kalender en begin hulle met een klik.",
"connectCalendarButton": "Koppel u kalender",
"enterRoomTitle": "Begin n nuwe vergadering",
"go": "GAAN",
"join": "SLUIT AAN",
"privacy": "Privaatheid",
"recentList": "Onlangs",
"recentListDelete": "Skrap",
"recentListEmpty": "Die lys van onlangse gesprekke is leeg. Gesels met u span al u onlangse gesprekke sal hier wees.",
"roomname": "Gee kamernaam",
"roomnameHint": "Gee die naam of URL van die kamer waar u wil aansluit. Dink gerus enige naam uit. Laat weet net die mense wat u ontmoet wat dit is sodat hulle die selfde naam gee.",
"sendFeedback": "Stuur terugvoer",
"terms": "Voorwaardes",
"title": "Veilige en volledig gratis videokonferensies propvol funksionaliteit"
},
"startupoverlay": {
"policyText": "",
"title": "__app__ benodig u mikrofoon en kamera."
},
"suspendedoverlay": {
"title": "U video-oproep is onderbreek omdat die rekenaar gaan slaap het.",
"text": "Druk die <i>Sluit weer aan</i>-knoppie om te herkoppel.",
"rejoinKeyTitle": "Sluit weer aan"
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "Wissel Net klank",
"audioRoute": "",
"callQuality": "",
"chat": "Wissel geselsvenster",
"cc": "Wissel onderskrifte",
"document": "Wissel gedeelde dokument",
"feedback": "",
"fullScreen": "Wissel volskerm",
"hangup": "Verlaat die oproep",
"invite": "",
"localRecording": "Wissel kontroles vir plaaslike opname",
"lockRoom": "Wissel kamerslot",
"moreActions": "Wissel kieslys vir meer aksies",
"moreActionsMenu": "Kieslys vir meer aksies",
"mute": "",
"pip": "Wissel Prent-in-Prent-modus",
"profile": "",
"raiseHand": "Wissel handopsteek",
"recording": "Wissel opname",
"Settings": "Wissel instellings",
"sharedvideo": "Wissel Youtube-videodeling",
"shareRoom": "Nooi iemand",
"shareYourScreen": "Wissel skermdeling",
"shortcuts": "Wissel kortpaaie",
"speakerStats": "Wissel sprekerstatistiek",
"toggleCamera": "",
"tileView": "",
"videomute": ""
},
"addPeople": "Voeg mense by die oproep",
"audioonly": "Aktiveer / deaktiveer Net klank-modus (spaar bandwydte)",
"audioOnlyOn": "Aktiveer Net klank-modus (spaar bandwydte)",
"audioOnlyOff": "Deaktiveer Net klank-modus",
"audioRoute": "Kies die klanktoestel",
"callQuality": "Bestuur oproepkwaliteit",
"enterFullScreen": "Volskermaansig",
"exitFullScreen": "Verlaat volskerm",
"feedback": "Laat terugvoer",
"moreActions": "Meer aksies",
"mute": "Demp / ontdemp",
"videomute": "Begin / stop kamera",
"authenticate": "Verifieer",
"lock": "Sluit / ontsluit kamer",
"chat": "Open / sluit gesels",
"etherpad": "Open / sluit gedeelde dokument",
"documentOpen": "Open gedeelde dokument",
"documentClose": "Sluit gedeelde dokument",
"shareRoom": "Deel kamer",
"sharedvideo": "Deel n YouTube-video",
"stopSharedVideo": "Stop YouTube-video",
"fullscreen": "",
"sip": "Bel SIP-nommer",
"Settings": "",
"hangup": "Verlaat",
"login": "Meld aan",
"logout": "",
"sharedVideoMutedPopup": "Die gedeelde video is gedemp sodat u met ander lede kan praat.",
"toggleCamera": "Wissel kamera",
"micMutedPopup": "Die mikrofoon is gedemp vir beste ervaring van die gedeelde video.",
"talkWhileMutedPopup": "Besig om te praat? U is gedemp.",
"unableToUnmutePopup": "",
"cameraDisabled": "Kamera is nie beskikbaar nie",
"micDisabled": "Mikrofoon is nie beskikbaar nie",
"filmstrip": "Wys / versteek videos",
"pip": "Betree Prent-in-Prent-modus",
"profile": "Redigeer u profiel",
"raiseHand": "Lig / laat sak u hand",
"shortcuts": "Sien kortpaaie",
"speakerStats": "Sprekerstatistiek",
"tileViewToggle": "Wissel teëlaansig",
"invite": "Nooi mense"
},
"chat": {
"nickname": {
"title": "Gee n bynaam in die boksie hieronder",
"popover": "Kies n bynaam"
},
"error": "",
"messagebox": "Tik teks..."
},
"settings": {
"calendar": {
"about": "",
"disconnect": "Ontkoppel",
"microsoftSignIn": "Meld aan met Microsoft",
"signedIn": "",
"title": ""
},
"title": "",
"update": "Werk by",
"name": "",
"startAudioMuted": "Almal begin gedemp",
"startVideoMuted": "Almal begin versteek",
"selectCamera": "Kamera",
"selectMic": "Mikrofoon",
"selectAudioOutput": "Klankafvoer",
"followMe": "Almal volg my",
"language": "Taal",
"loggedIn": "Aangemeld as __name__",
"noDevice": "",
"cameraAndMic": "Kamera en mikrofoon",
"moderator": "",
"more": "Meer",
"password": "STEL WAGWOORD",
"audioVideo": "KLANK EN VIDEO",
"devices": "Toestelle"
},
"profile": {
"title": "",
"setDisplayNameLabel": "Stel u vertoonnaam",
"setEmailLabel": "Stel u gravatar-e-posadres",
"setEmailInput": "Gee e-posadres"
},
"videothumbnail": {
"moderator": "",
"videomute": "Lid het die kamera gestop",
"mute": "Lid is gedemp",
"kick": "Skop uit",
"muted": "Gedemp",
"domute": "",
"flip": "Swaai om",
"remoteControl": ""
},
"connectionindicator": {
"header": "",
"connectedTo": "Gekoppel aan:",
"bitrate": "Bistempo:",
"bridgeCount": "Aantal bedieners: ",
"packetloss": "Pakkies verloor:",
"resolution": "Resolusie:",
"framerate": "Raampietempo:",
"less": "Wys minder",
"more": "Wys meer",
"address": "Adres:",
"remoteport": "Afgeleë poort:",
"remoteport_plural": "Afgeleë poorte:",
"localport": "Plaaslike poort:",
"localport_plural": "Plaaslike poorte:",
"localaddress": "Plaaslike adres:",
"localaddress_plural": "Plaaslike adresse:",
"remoteaddress": "Afgeleë adres:",
"remoteaddress_plural": "Afgeleë adresse:",
"transport": "",
"transport_plural": "",
"bandwidth": "Geraamde bandwydte:",
"na": "",
"turn": "",
"quality": {
"good": "",
"inactive": "Onaktief",
"lost": "",
"nonoptimal": "",
"poor": "Swak"
},
"status": "Verbinding:"
},
"notify": {
"disconnected": "ontkoppel",
"moderator": "",
"connectedOneMember": "__name__ het gekoppel",
"connectedTwoMembers": "__first__ en __second__ het gekoppel",
"connectedThreePlusMembers": "__name__ en __count__ ander het gekoppel",
"somebody": "Iemand",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "__to__ is nou moderator!",
"muted": "U het die gesprek gedemp begin.",
"mutedTitle": "U is gedemp!",
"raisedHand": "",
"suboptimalExperienceTitle": "Blaaierwaarskuwing",
"suboptimalExperienceDescription": "Gits... ons is bevrees u ervaring met __appName__ gaan nie so goed wees hier nie. Ons soek maniere om dit die hoof te bied, maar probeer intussen een van die <a href='static/recommendedBrowsers.html' target='_blank'>volledig ondersteunde blaaiers</a>."
},
"dialog": {
"accessibilityLabel": {
"liveStreaming": "Regstreekse stroom"
},
"allow": "Laat toe",
"confirm": "Bevestig",
"kickMessage": "Eina! U is uit die gesprek geskop!",
"kickTitle": "Uit vergadering geskop",
"popupErrorTitle": "Opspringer geblok",
"popupError": "U blaaier blokkeer opspringers vanaf hierdie werf. Aktiveer opspringers in die blaaier se sekuriteitopsies en probeer weer.",
"passwordErrorTitle": "Wagwoordfout",
"passwordError": "Hierdie gesprek word tans met n wagwoord beskerm. Slegs die eienaar van die konferensie kan n wagwoord instel.",
"passwordError2": "Hierdie gesprek word nie tans met n wagwoord beskerm nie. Slegs die eienaar van die konferensie kan n wagwoord instel.",
"connectError": "Oeps! Iets het skeefgeloop en ons kon nie aan die konferensie koppel nie.",
"connectErrorWithMsg": "Oeps! Iets het skeefgeloop en ons kon nie aan die konferensie koppel nie: __msg__",
"incorrectPassword": "Verkeerde gebruikernaam of wagwoord",
"connecting": "",
"copy": "Kopieer",
"contactSupport": "Kontak ondersteuning",
"error": "",
"detectext": "",
"failedpermissions": "Kon nie toestemming kry om die plaaslike mikrofoon en/of kamera te gebruik nie.",
"conferenceReloadTitle": "Iets het ongelukkig skeefgeloop.",
"conferenceReloadMsg": "Ons probeer om dit reg te stel. Gaan herkoppel oor __seconds__ sekondes...",
"conferenceDisconnectTitle": "Die verbinding is verbreek.",
"conferenceDisconnectMsg": "Kontroleer dalk die netwerkverbinding. Gaan oor __seconds__ sekondes weer koppel...",
"dismiss": "",
"rejoinNow": "Sluit nou weer aan",
"maxUsersLimitReachedTitle": "Maksimumlede-limiet bereik",
"maxUsersLimitReached": "Die limiet vir maksimum getal lede is bereik. Die konferensie is vol. Kontak asb. die eienaar van die vergadering of probeer weer later!",
"lockRoom": "Sluit kamer",
"lockTitle": "Sluit het misluk",
"lockMessage": "Kon nie die konferensie sluit nie.",
"warning": "",
"passwordNotSupportedTitle": "Wagwoord nie ondersteun nie",
"passwordNotSupported": "Die instel van n vergaderingwagwoord word nie ondersteun nie.",
"internalErrorTitle": "Interne fout",
"internalError": "Oeps! Iets het skeefgeloop. Die volgende fout het voorgekom: __error__",
"unableToSwitch": "Kan nie videostroom wissel nie.",
"SLDFailure": "",
"SRDFailure": "",
"oops": "Oeps!",
"currentPassword": "Die huidige wagwoord is",
"passwordLabel": "Wagwoord",
"defaultError": "Daar was een of ander soort fout",
"passwordRequired": "Wagwoord vereis",
"Ok": "Regso",
"done": "Klaar",
"Remove": "Verwyder",
"removePassword": "Verwyder wagwoord",
"shareVideoTitle": "Deel n video",
"shareVideoLinkError": "Gee asb. n korrekte YouTube-skakel.",
"removeSharedVideoTitle": "Verwyder gedeelde video",
"removeSharedVideoMsg": "Wil u definitief u gedeelde video verwyder?",
"alreadySharedVideoMsg": "n Ander lid deel reeds n video. Dié konferensie laat slegs een gedeelde video op n slag toe.",
"alreadySharedVideoTitle": "Slegs een gedeelde video op n slag word toegelaat",
"WaitingForHost": "Wag tans vir die gasheer ...",
"WaitForHostMsg": "Die konferensie <b>__room </b> het nog nie begin nie. As u die gasheer is, verifieer u identiteit. Wag andersins asb. vir die gasheer om op te daag.",
"IamHost": "Ek is die gasheer",
"Cancel": "Kanselleer",
"Submit": "Dien in",
"retry": "Herprobeer",
"logoutTitle": "Meld af",
"logoutQuestion": "Wil u definitief afmeld en die konferensie stop?",
"sessTerminated": "Oproep gestaak",
"hungUp": "U het neergesit",
"joinAgain": "Sluit weer aan",
"Share": "",
"Save": "Stoor",
"recording": "",
"recordingToken": "",
"Back": "Terug",
"serviceUnavailable": "Diens nie beskikbaar nie",
"gracefulShutdown": "Ons diens is tans buite werking t.w.v. onderhoud. Probeer gerus weer later.",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "Foutkode: __code__, boodskap: __msg__",
"password": "Tik wagwoord in",
"unlockRoom": "Ontsluit kamer",
"userPassword": "gebruikerwagwoord",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "Jammer! U mag nie by dié oproep aansluit nie.",
"displayNameRequired": "Vertoonnaam is nodig",
"enterDisplayName": "Gee asb. u vertoonnaam",
"feedbackHelp": "Terugvoer sal ons help om ons video-ervaring te verbeter.",
"feedbackQuestion": "Vertel ons oor die oproep!",
"thankYou": "Dankie dat u __appName__ gebruik!",
"sorryFeedback": "Dis jammer om te hoor. Wil u meer vertel?",
"liveStreaming": "",
"streamKey": "Sleutel vir regstreekse stroom",
"startLiveStreaming": "Begin regstreekse stroom",
"startRecording": "Begin opname",
"stopStreamingWarning": "Wil u definitief die regstreekse stroom stop?",
"stopRecordingWarning": "Wil u definitief die opname stop?",
"stopLiveStreaming": "Stop regstreekse stroom",
"stopRecording": "Stop opname",
"doNotShowMessageAgain": "Moenie weer dié boodskap wys nie",
"permissionDenied": "Toestemming gewyer",
"screenSharingFailedToInstall": "Oeps! Die uitbreiding vir skermdeling kon nie installeer nie.",
"screenSharingFailedToInstallTitle": "Uitbreiding vir skermdeling kon nie installeer nie",
"screenSharingFirefoxPermissionDeniedError": "",
"screenSharingFirefoxPermissionDeniedTitle": "Oeps! Ons kon nie skermdeling begin nie!",
"screenSharingPermissionDeniedError": "",
"cameraUnsupportedResolutionError": "Die kamera ondersteun nie die nodige videoresolusie nie.",
"cameraUnknownError": "Kan weens onbekende rede nie die kamera gebruik nie.",
"cameraPermissionDeniedError": "U het nie toestemming gegee om u kamera te gebruik nie. U kan steeds by die konferensie aansluit, maar ander sal u nie kan sien nie. Gebruik die kameraknoppie in die adresbalk om dit reg te stel.",
"cameraNotFoundError": "Kamera is nie gevind nie.",
"cameraConstraintFailedError": "Die kamera voldoen nie aan sekere van die vereistes nie.",
"micUnknownError": "Kan weens onbekende rede nie die mikrofoon gebruik nie.",
"micPermissionDeniedError": "U het nie toestemming gegee om u mikrofoon te gebruik nie. U kan steeds by die konferensie aansluit, maar ander sal u nie kan hoor nie. Gebruik die kameraknoppie in die adresbalk om dit reg te stel.",
"micNotFoundError": "Mikrofoon is nie gevind nie.",
"micConstraintFailedError": "Die mikrofoon voldoen nie aan sekere van die vereistes nie.",
"micNotSendingDataTitle": "Kan nie toegang tot mikrofoon kry nie",
"micNotSendingData": "Ons kry nie toegang tot u mikrofoon nie. Kies asb. n ander toestel by die instellingskieslys of probeer om die toepassing op nuut te laai.",
"cameraNotSendingDataTitle": "Kan nie toegang tot kamera kry nie",
"cameraNotSendingData": "Ons kry nie toegang tot u kamera nie. Kontroleer of n ander toepassing dié toestel gebruik, kies asb. n ander toestel by die instellingskieslys of probeer om die toepassing op nuut te laai.",
"goToStore": "Gaan na die webwinkel",
"externalInstallationTitle": "Uitbreiding is nodig",
"externalInstallationMsg": "",
"inlineInstallationMsg": "U moet ons uitbreiding vir werkskermdeling installeer.",
"inlineInstallExtension": "Installeer nou",
"muteParticipantTitle": "Demp dié lid?",
"muteParticipantBody": "U sal hulle nie kan ontdemp nie, maar hulle sal hulself enige tyd kan ontdemp.",
"muteParticipantButton": "Demp",
"liveStreamingDisabledTooltip": "Begin van regstreekse stroom gedeaktiveer.",
"liveStreamingDisabledForGuestTooltip": "Gaste kan nie regstreekse strome begin nie.",
"recordingDisabledTooltip": "Begin van opname gedeaktiveer.",
"recordingDisabledForGuestTooltip": "Gaste kan nie opnames begin nie.",
"remoteControlTitle": "",
"remoteControlRequestMessage": "",
"remoteControlShareScreenWarning": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"startRemoteControlErrorMessage": "",
"remoteControlStopMessage": "",
"close": "Sluit",
"shareYourScreen": "Deel u skerm",
"shareYourScreenDisabled": "Skermdeling gedeaktiveer.",
"shareYourScreenDisabledForGuest": "Gaste kan nie skerms deel nie.",
"yourEntireScreen": "U hele skerm",
"applicationWindow": "Toepassingsvenster",
"transcribing": "Transkribering"
},
"email": {
"sharedKey": [
"Dié konferensie word met n wagwoord beskerm. Gebruik asb. die volgende PIN om aan te sluit:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "Uitnodiging na n __appName__ (__conferenceName__)",
"body": [
"Hallo! Hierdie is n uitnodiging na n __appName__-konferensie wat ek pas opgestel het.",
"",
"",
"Klik gerus op die volgende skakel om by die konferensie aan te sluit.",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
" Let op dat __appName__ tans net deur die blaaiers __supportedBrowsers__ ondersteun word. Dus moet mens een van hulle gebruik.",
"",
"",
"Hopelik praat ons gou!"
],
"and": "en"
},
"share": {
"mainText": [
"Klik die volgende skakel om by die vergadering aan te sluit:",
"__roomUrl__"
],
"dialInfoText": [
"",
"",
"=====",
"",
"Wil u bloot met u foon inbel?",
"",
"__defaultDialInNumber__Klik dié skakel om die inbelfoonnommers vir dié vergadering te sien",
"__dialInfoPageUrl__"
]
},
"connection": {
"ERROR": "Fout",
"CONNECTING": "Koppel tans",
"RECONNECTING": "n Netwerkprobleem het voorgekom. Herkoppel tans...",
"CONNFAIL": "Koppeling het misluk",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "Ontkoppel tans",
"ATTACHED": ""
},
"recording": {
"beta": "",
"busy": "",
"busyTitle": "Alle opnemers is tans besig",
"buttonTooltip": "Begin / stop opname",
"error": "Opname het misluk. Probeer gerus weer.",
"expandedOff": "Opname het gestop",
"expandedOn": "Die vergadering word tans opgeneem.",
"expandedPending": "Opname word begin...",
"failedToStart": "Kon nie begin opneem nie",
"live": "",
"off": "Opname gestop",
"on": "Neem tans op",
"pending": "Berei voor om vergadering op te neem...",
"rec": "",
"authDropboxText": "Laai op na Dropbox",
"serviceName": "Opneemdiens",
"signOut": "Meld af",
"signIn": "meld aan",
"loggedIn": "Aangemeld as __name__",
"availableSpace": "Beskikbare spasie: __spaceLeft__ MB (ongeveer __duration__ minute se opname)",
"startRecordingBody": "Wil u definitief die opname begin?",
"unavailable": "",
"unavailableTitle": ""
},
"transcribing": {
"pending": "",
"off": "",
"error": "",
"expandedLabel": "",
"failedToStart": "",
"tr": "",
"labelToolTip": "",
"ccButtonTooltip": "",
"start": "",
"stop": ""
},
"liveStreaming": {
"busy": "",
"busyTitle": "",
"buttonTooltip": "Begin /stop regstreekse stroom",
"changeSignIn": "Wissel rekeninge.",
"choose": "Kies n regstreekse stroom",
"chooseCTA": "Kies n stroomopsie. U is tans aangemeld as __email__.",
"enterStreamKey": "Gee u sleutel vir regstreekse stroom by YouTube hier.",
"error": "Kon nie regstreeks stroom nie. Probeer gerus weer.",
"errorAPI": "n Fout het voorgekom tydens toegang tot u YouTube-uitsendings. Probeer om weer aan te meld.",
"errorLiveStreamNotEnabled": "Regstreekse stroom is nie geaktiveer op __email__ nie. Aktiveer asb. regstreekse strome of meld aan met n rekening met regstreekse strome geaktiveer.",
"expandedOff": "Die regstreekse stroom het gestop",
"expandedOn": "Die vergadering word tans gestroom na YouTube.",
"expandedPending": "Die regstreekse stroom begin tans...",
"failedToStart": "Regstreekse stroom kon nie begin nie",
"off": "Regstreekse stroom het gestop",
"on": "Regstreekse stroom",
"pending": "Begin tans regstreekse stroom...",
"serviceName": "Regstreekse stroomdiens",
"signedInAs": "U is tans aangemeld as:",
"signIn": "Meld aan met Google",
"signOut": "Meld af",
"signInCTA": "Meld aan of gee u sleutel vir regstreekse stroom vanaf YouTube.",
"start": "Begin n regstreekse stroom",
"streamIdHelp": "Wats dié?",
"unavailableTitle": "Regstreekse strome nie beskikbaar nie"
},
"videoSIPGW": {
"busy": "Ons probeer tans hulpbronne vry te stel. Probeer gerus weer oor n paar minute.",
"busyTitle": "Die Kamerdiens is tans besig",
"errorInvite": "Konferensie is nog nie gestig nie. Probeer gerus weer later.",
"errorInviteTitle": "",
"errorAlreadyInvited": "__displayName__ is reeds genooi",
"errorInviteFailedTitle": "Kon nie __displayName__ nooi nie",
"errorInviteFailed": "Ons werk aan n oplossing vir die probleem. Probeer gerus weer later.",
"pending": "__displayName__ is genooi",
"serviceName": "Kamerdiens",
"unavailableTitle": "Kamerdiens nie beskikbaar nie"
},
"speakerStats": {
"hours": "__count__h",
"minutes": "__count__m",
"name": "Naam",
"seconds": "__count__s",
"speakerStats": "Sprekerstatistiek",
"speakerTime": "Sprekertyd"
},
"deviceSelection": {
"deviceSettings": "Toestelinstellings",
"noPermission": "Toestemming nie gegee nie",
"previewUnavailable": "Voorskou nie beskikbaar nie",
"selectADevice": "Kies 'n toestel",
"testAudio": "Speel n toetsklank"
},
"videoStatus": {
"audioOnly": "",
"audioOnlyExpanded": "U is in Net klank-modus. Dié modus spaar bandwydte maar u sal nie videos van ander sien nie.",
"callQuality": "Oproepkwaliteit",
"hd": "HD",
"hdTooltip": "Sien tans hoëdefinisievideo",
"highDefinition": "Hoëdefinisie",
"labelTooltipAudioOnly": "Net klank-modus geaktiveer",
"labelTooiltipNoVideo": "Geen video",
"labelTooltipVideo": "Huidige videokwaliteit",
"ld": "LD",
"ldTooltip": "Sien tans laedefinisievideo",
"lowDefinition": "Laedefinisie",
"onlyAudioAvailable": "Net klank is beskikbaar",
"onlyAudioSupported": "Op dié blaaier ondersteun ons slegs klank.",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "SD",
"sdTooltip": "Sien tans standaarddefinisievideo",
"standardDefinition": "Standaarddefinisie",
"qualityButtonTip": ""
},
"dialOut": {
"statusMessage": "is nou __status__"
},
"addPeople": {
"add": "",
"countryNotSupported": "Ons ondersteun nog nie dié bestemming nie.",
"countryReminder": "",
"disabled": "",
"footerText": "",
"invite": "Nooi uit",
"loading": "",
"loadingNumber": "Valideer tans foonnommer",
"loadingPeople": "",
"noResults": "Geen soekresultate wat pas nie",
"noValidNumbers": "Gee asseblief n foonnommer",
"notAvailable": "U kan nie mense nooi nie.",
"searchNumbers": "Voeg foonnommers by",
"searchPeople": "Soek mense",
"searchPeopleAndNumbers": "Soek mense of voeg hulle foonnommers by",
"telephone": "Telefoon: __number__",
"title": "Nooi mense na dié vergadering",
"failedToAdd": "Kon nie lede byvoeg nie"
},
"inlineDialogFailure": {
"msg": "Ons het gestruikel.",
"retry": "Probeer weer",
"support": "Ondersteuning",
"supportMsg": "Indien dit aanhou, maak kontak met"
},
"deviceError": {
"cameraError": "Toegang na u kamera het misluk",
"microphoneError": "Toegang na u mikrofoon het misluk",
"cameraPermission": "Fout met verkryging van kameratoestemming",
"microphonePermission": "Fout met verkryging van mikrofoontoestemming"
},
"feedback": {
"average": "Gemiddeld",
"bad": "Sleg",
"good": "Goed",
"detailsLabel": "Vertel ons meer.",
"rateExperience": "",
"veryBad": "Baie sleg",
"veryGood": "Baie goed"
},
"info": {
"accessibilityLabel": "Wys inligting",
"addPassword": "Voeg wagwoord by",
"cancelPassword": "Kanselleer wagwoord",
"conferenceURL": "Skakel:",
"country": "Land",
"dialANumber": "Om by u vergadering aan te sluit, skakel een van dié nommers en gee dan dié PIN: __conferenceID__#",
"dialInNumber": "Inbel:",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Jammer. Inbel word nie tans ondersteun nie.",
"genericError": "Oeps! Iets het skeefgeloop.",
"inviteLiveStream": "Om die regstreekse stroom van dié vergadering te sien, klik dié skakel: __url__",
"invitePhone": "Om per telefoon aan te sluit, skakel __number__ en gee dié PIN: __conferenceID__#",
"invitePhoneAlternatives": "Om meer foonnommers te sien, klik dié skakel: __url__",
"inviteURL": "Om by die videovergadering aan te sluit, klik dié skakel: __url__",
"liveStreamURL": "Regstreekse stroom:",
"moreNumbers": "Meer nommers",
"noNumbers": "Geen inbelnommers.",
"noPassword": "Geen",
"noRoom": "Geen kamer is gegee om na in te bel nie.",
"numbers": "Inbelnommers",
"password": "Wagwoord:",
"title": "Deel",
"tooltip": "Deelskakel en inbelinligting vir dié vergadering"
},
"settingsView": {
"alertOk": "Regso",
"alertTitle": "Waarskuwing",
"alertURLText": "Die gegewe bediener-URL is ongeldig",
"conferenceSection": "Konferensie",
"displayName": "Vertoonnaam",
"email": "E-pos",
"header": "Instellings",
"profileSection": "Profiel",
"serverURL": "Bediener-URL",
"startWithAudioMuted": "Begin met klank gedemp",
"startWithVideoMuted": "Begin met video gedemp"
},
"calendarSync": {
"addMeetingURL": "Voeg n vergaderingskakel by",
"confirmAddLink": "Wil u n Jitsi-skakel by dié geleentheid voeg?",
"confirmAddLinkTitle": "Kalender",
"join": "Sluit aan",
"joinTooltip": "Sluit aan by die vergadering",
"nextMeeting": "volgende vergadering",
"noEvents": "Geen komende geleenthede is geskeduleer nie.",
"ongoingMeeting": "vergadering onderweg",
"permissionButton": "Open instellings",
"permissionMessage": "",
"refresh": "Verfris kalender",
"today": ""
},
"recentList": {
"joinPastMeeting": "Sluit by n vorige vergadering aan"
},
"sectionList": {
"pullToRefresh": ""
},
"deepLinking": {
"title": "",
"description": "",
"tryAgainButton": "",
"launchWebButton": "",
"appNotInstalled": "",
"downloadApp": "",
"openApp": ""
},
"presenceStatus": {
"invited": "Uitgenooi",
"ringing": "Lui tans...",
"calling": "Bel tans...",
"initializingCall": "Inisialiseer tans oproep...",
"connected": "Gekoppel",
"connecting": "Koppel tans...",
"connecting2": "Koppel tans*...",
"disconnected": "Ontkoppeld",
"busy": "Besig",
"rejected": "Geweier",
"ignored": "Geïgnoreer",
"expired": "Verval"
},
"dateUtils": {
"today": "Vandag",
"yesterday": "Gister",
"earlier": "Vroeër"
},
"incomingCall": {
"answer": "Antwoord",
"audioCallTitle": "Inkomende oproep",
"decline": "Weier",
"productLabel": "vanaf Jitsi Meet",
"videoCallTitle": "Inkomende video-oproep"
},
"localRecording": {
"localRecording": "Plaaslike opname",
"dialogTitle": "Kontroles vir plaaslike opname",
"start": "Begin opname",
"stop": "Stop opname",
"moderator": "Moderator",
"me": "Ek",
"duration": "Duur",
"durationNA": "",
"encoding": "Enkodering",
"participantStats": "Deelnemerstatistiek",
"participant": "Deelnemer",
"sessionToken": "",
"clientState": {
"on": "Aan",
"off": "Af",
"unknown": "Onbekend"
},
"messages": {
"engaged": "",
"finished": "",
"finishedModerator": "",
"notModerator": "U is nie die moderator nie. U kan nie n plaaslike opname begin of stop nie."
},
"yes": "Ja",
"no": "Nee",
"label": "",
"labelToolTip": ""
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -60,6 +60,9 @@ function initCommands() {
sendAnalytics(createApiEvent('display.name.changed'));
APP.conference.changeLocalDisplayName(displayName);
},
'proxy-connection-event': event => {
APP.conference.onProxyConnectionEvent(event);
},
'submit-feedback': feedback => {
sendAnalytics(createApiEvent('submit.feedback'));
APP.conference.submitFeedback(feedback.score, feedback.message);
@@ -260,6 +263,20 @@ class API {
});
}
/**
* Notifies the external application (spot) that the local jitsi-participant
* has a status update.
*
* @param {Object} event - The message to pass onto spot.
* @returns {void}
*/
sendProxyConnectionEvent(event: Object) {
this._sendEvent({
name: 'proxy-connection-event',
...event
});
}
/**
* Sends event to the external application.
*

View File

@@ -46,6 +46,7 @@ const events = {
'outgoing-message': 'outgoingMessage',
'participant-joined': 'participantJoined',
'participant-left': 'participantLeft',
'proxy-connection-event': 'proxyConnectionEvent',
'video-ready-to-close': 'readyToClose',
'video-conference-joined': 'videoConferenceJoined',
'video-conference-left': 'videoConferenceLeft',
@@ -743,6 +744,25 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
eventList.forEach(event => this.removeEventListener(event));
}
/**
* Passes an event along to the local conference participant to establish
* or update a direct peer connection. This is currently used for developing
* wireless screensharing with room integration and it is advised against to
* use as its api may change.
*
* @param {Object} event - An object with information to pass along.
* @param {Object} event.data - The payload of the event.
* @param {string} event.from - The jid of the sender of the event. Needed
* when a reply is to be sent regarding the event.
* @returns {void}
*/
sendProxyConnectionEvent(event) {
this._transport.sendEvent({
data: [ event ],
name: 'proxy-connection-event'
});
}
/**
* Returns the configuration for electron for the windows that are open
* from Jitsi Meet.

View File

@@ -18,7 +18,6 @@ import {
getLocalParticipant,
showParticipantJoinedNotification
} from '../../react/features/base/participants';
import { destroyLocalTracks } from '../../react/features/base/tracks';
import { toggleChat } from '../../react/features/chat';
import { openDisplayNamePrompt } from '../../react/features/display-name';
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
@@ -294,12 +293,6 @@ UI.start = function() {
UI.registerListeners
= () => UIListeners.forEach((value, key) => UI.addListener(key, value));
/**
* Unregister some UI event listeners.
*/
UI.unregisterListeners
= () => UIListeners.forEach((value, key) => UI.removeListener(key, value));
/**
* Setup some DOM event listeners.
*/
@@ -577,6 +570,15 @@ UI.addListener = function(type, listener) {
eventEmitter.on(type, listener);
};
/**
* Removes the all listeners for all events.
*
* @returns {void}
*/
UI.removeAllListeners = function() {
eventEmitter.removeAllListeners();
};
/**
* Removes the given listener for the given type of event.
*
@@ -595,17 +597,7 @@ UI.removeListener = function(type, listener) {
*/
UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
UI.clickOnVideo = function(videoNumber) {
const videos = $('#remoteVideos .videocontainer:not(#mixedstream)');
const videosLength = videos.length;
if (videosLength <= videoNumber) {
return;
}
const videoIndex = videoNumber === 0 ? 0 : videosLength - videoNumber;
videos[videoIndex].click();
};
UI.clickOnVideo = videoNumber => VideoLayout.togglePin(videoNumber);
// Used by torture.
UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
@@ -678,7 +670,7 @@ UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
* Prompt user for nickname.
*/
UI.promptDisplayName = () => {
APP.store.dispatch(openDisplayNamePrompt());
APP.store.dispatch(openDisplayNamePrompt(undefined));
};
/**
@@ -978,18 +970,6 @@ 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

@@ -87,7 +87,8 @@ const AudioLevels = {
// External circle audio level.
const ext = {
level: ((int.level * scale * level) + int.level).toFixed(0),
level: parseFloat(
((int.level * scale * level) + int.level).toFixed(0)),
color: interfaceConfig.AUDIO_LEVEL_SECONDARY_COLOR
};

View File

@@ -113,7 +113,8 @@ function LoginDialog(successCallback, cancelCallback) {
states,
{
closeText: '',
persistent: true
persistent: true,
zIndex: 1020
},
null
);

View File

@@ -301,8 +301,7 @@ export default class SharedVideoManager {
// FIXME The cat is out of the bag already or rather _room is
// not private because it is used in multiple other places
// already such as AbstractPageReloadOverlay and
// JitsiMeetLogStorage.
// already such as AbstractPageReloadOverlay.
conference: APP.conference._room,
id: self.url,
isFakeParticipant: true,

View File

@@ -15,11 +15,14 @@ export default function SharedVideoThumb(participant, videoType, VideoLayout) {
this.videoSpanId = 'sharedVideoContainer';
this.container = this.createContainer(this.videoSpanId);
this.$container = $(this.container);
this.container.onclick = this.videoClick.bind(this);
this.bindHoverHandler();
SmallVideo.call(this, VideoLayout);
this.isVideoMuted = true;
this.setDisplayName(participant.name);
this.container.onclick = this._onContainerClick;
this.container.ondblclick = this._onContainerDoubleClick;
}
SharedVideoThumb.prototype = Object.create(SmallVideo.prototype);
SharedVideoThumb.prototype.constructor = SharedVideoThumb;
@@ -61,25 +64,6 @@ SharedVideoThumb.prototype.createContainer = function(spanId) {
return container;
};
/**
* The thumb click handler.
*/
SharedVideoThumb.prototype.videoClick = function() {
this._togglePin();
};
/**
* Removes RemoteVideo from the page.
*/
SharedVideoThumb.prototype.remove = function() {
logger.log('Remove shared video thumb', this.id);
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
/**
* Sets the display name for the thumb.
*/

View File

@@ -6,6 +6,10 @@ import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { i18next } from '../../../react/features/base/i18n';
import {
Avatar,
getAvatarURLByParticipantId
} from '../../../react/features/base/participants';
import { PresenceLabel } from '../../../react/features/presence-status';
/* eslint-enable no-unused-vars */
@@ -14,9 +18,6 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
import {
JitsiParticipantConnectionStatus
} from '../../../react/features/base/lib-jitsi-meet';
import {
getAvatarURLByParticipantId
} from '../../../react/features/base/participants';
import {
updateKnownLargeVideoResolution
} from '../../../react/features/large-video';
@@ -95,6 +96,9 @@ export default class LargeVideoManager {
= this._onVideoResolutionUpdate.bind(this);
this.videoContainer.addResizeListener(this._onVideoResolutionUpdate);
this._dominantSpeakerAvatarContainer
= document.getElementById('dominantSpeakerAvatarContainer');
}
/**
@@ -108,6 +112,10 @@ export default class LargeVideoManager {
this._onVideoResolutionUpdate);
this.removePresenceLabel();
ReactDOM.unmountComponentAtNode(this._dominantSpeakerAvatarContainer);
this.$container.css({ display: 'none' });
}
/**
@@ -392,7 +400,17 @@ export default class LargeVideoManager {
* Updates the src of the dominant speaker avatar
*/
updateAvatar(avatarUrl) {
$('#dominantSpeakerAvatar').attr('src', avatarUrl);
if (avatarUrl) {
ReactDOM.render(
<Avatar
id = "dominantSpeakerAvatar"
uri = { avatarUrl } />,
this._dominantSpeakerAvatarContainer
);
} else {
ReactDOM.unmountComponentAtNode(
this._dominantSpeakerAvatarContainer);
}
}
/**

View File

@@ -61,7 +61,8 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
this.addAudioLevelIndicator();
this.updateIndicators();
this.container.onclick = this._onContainerClick.bind(this);
this.container.onclick = this._onContainerClick;
this.container.ondblclick = this._onContainerDoubleClick;
}
LocalVideo.prototype = Object.create(SmallVideo.prototype);
@@ -253,40 +254,6 @@ LocalVideo.prototype.updateDOMLocation = function() {
this._updateVideoElement();
};
/**
* Callback invoked when the thumbnail is clicked. Will directly call
* VideoLayout to handle thumbnail click if certain elements have not been
* clicked.
*
* @param {MouseEvent} event - The click event to intercept.
* @private
* @returns {void}
*/
LocalVideo.prototype._onContainerClick = function(event) {
// TODO Checking the classes is a workround to allow events to bubble into
// the DisplayName component if it was clicked. React's synthetic events
// will fire after jQuery handlers execute, so stop propogation at this
// point will prevent DisplayName from getting click events. This workaround
// should be removeable once LocalVideo is a React Component because then
// the components share the same eventing system.
const $source = $(event.target || event.srcElement);
const { classList } = event.target;
const clickedOnDisplayName
= $source.parents('.displayNameContainer').length > 0;
const clickedOnPopover = $source.parents('.popover').length > 0
|| classList.contains('popover');
const ignoreClick = clickedOnDisplayName || clickedOnPopover;
if (event.stopPropagation && !ignoreClick) {
event.stopPropagation();
}
if (!ignoreClick) {
this._togglePin();
}
};
/**
* Renders the React Element for displaying video in {@code LocalVideo}.
*

View File

@@ -22,8 +22,7 @@ import {
} from '../../../react/features/remote-video-menu';
import {
LAYOUTS,
getCurrentLayout,
shouldDisplayTileView
getCurrentLayout
} from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
@@ -89,7 +88,8 @@ function RemoteVideo(user, VideoLayout, emitter) {
this._setAudioVolume = this._setAudioVolume.bind(this);
this._stopRemoteControl = this._stopRemoteControl.bind(this);
this.container.onclick = this._onContainerClick.bind(this);
this.container.onclick = this._onContainerClick;
this.container.ondblclick = this._onContainerDoubleClick;
}
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
@@ -437,33 +437,10 @@ RemoteVideo.prototype.updateConnectionStatusIndicator = function() {
* Removes RemoteVideo from the page.
*/
RemoteVideo.prototype.remove = function() {
logger.log('Remove thumbnail', this.id);
this.removeAudioLevelIndicator();
const toolbarContainer
= this.container.querySelector('.videocontainer__toolbar');
if (toolbarContainer) {
ReactDOM.unmountComponentAtNode(toolbarContainer);
}
this.removeConnectionIndicator();
this.removeDisplayName();
this.removeAvatar();
SmallVideo.prototype.remove.call(this);
this.removePresenceLabel();
this._unmountIndicators();
this.removeRemoteVideoMenu();
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) {
@@ -613,36 +590,6 @@ RemoteVideo.prototype.removePresenceLabel = function() {
}
};
/**
* Callback invoked when the thumbnail is clicked. Will directly call
* VideoLayout to handle thumbnail click if certain elements have not been
* clicked.
*
* @param {MouseEvent} event - The click event to intercept.
* @private
* @returns {void}
*/
RemoteVideo.prototype._onContainerClick = function(event) {
const $source = $(event.target || event.srcElement);
const { classList } = event.target;
const ignoreClick = $source.parents('.popover').length > 0
|| classList.contains('popover');
if (!ignoreClick) {
this._togglePin();
}
// On IE we need to populate this handler on video <object> and it does not
// give event instance as an argument, so we check here for methods.
if (event.stopPropagation && event.preventDefault && !ignoreClick) {
event.stopPropagation();
event.preventDefault();
}
return false;
};
RemoteVideo.createContainer = function(spanId) {
const container = document.createElement('span');

View File

@@ -142,6 +142,9 @@ function SmallVideo(VideoLayout) {
// Bind event handlers so they are only bound once for every instance.
this._onPopoverHover = this._onPopoverHover.bind(this);
this.updateView = this.updateView.bind(this);
this._onContainerClick = this._onContainerClick.bind(this);
this._onContainerDoubleClick = this._onContainerDoubleClick.bind(this);
}
/**
@@ -742,6 +745,37 @@ SmallVideo.prototype.initBrowserSpecificProperties = function() {
}
};
/**
* Cleans up components on {@code SmallVideo} and removes itself from the DOM.
*
* @returns {void}
*/
SmallVideo.prototype.remove = function() {
logger.log('Remove thumbnail', this.id);
this.removeAudioLevelIndicator();
const toolbarContainer
= this.container.querySelector('.videocontainer__toolbar');
if (toolbarContainer) {
ReactDOM.unmountComponentAtNode(toolbarContainer);
}
this.removeConnectionIndicator();
this.removeDisplayName();
this.removeAvatar();
this._unmountIndicators();
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
/**
* Helper function for re-rendering multiple react components of the small
* video.
@@ -821,12 +855,71 @@ SmallVideo.prototype.updateIndicators = function() {
};
/**
* Pins the participant displayed by this thumbnail or unpins if already pinned.
* Callback invoked when the thumbnail is double clicked. Will pin the
* participant if in tile view.
*
* @param {MouseEvent} event - The click event to intercept.
* @private
* @returns {void}
*/
SmallVideo.prototype._togglePin = function() {
SmallVideo.prototype._onContainerDoubleClick = function(event) {
if (this._pinningRequiresDoubleClick() && this._shouldTriggerPin(event)) {
APP.store.dispatch(pinParticipant(this.id));
}
};
/**
* Callback invoked when the thumbnail is clicked and potentially trigger
* pinning of the participant.
*
* @param {MouseEvent} event - The click event to intercept.
* @private
* @returns {void}
*/
SmallVideo.prototype._onContainerClick = function(event) {
const triggerPin = this._shouldTriggerPin(event)
&& !this._pinningRequiresDoubleClick();
if (event.stopPropagation && triggerPin) {
event.stopPropagation();
event.preventDefault();
}
if (triggerPin) {
this.togglePin();
}
return false;
};
/**
* Returns whether or not a click event is targeted at certain elements which
* should not trigger a pin.
*
* @param {MouseEvent} event - The click event to intercept.
* @private
* @returns {boolean}
*/
SmallVideo.prototype._shouldTriggerPin = function(event) {
// TODO Checking the classes is a workround to allow events to bubble into
// the DisplayName component if it was clicked. React's synthetic events
// will fire after jQuery handlers execute, so stop propogation at this
// point will prevent DisplayName from getting click events. This workaround
// should be removeable once LocalVideo is a React Component because then
// the components share the same eventing system.
const $source = $(event.target || event.srcElement);
return $source.parents('.displayNameContainer').length === 0
&& $source.parents('.popover').length === 0
&& !event.target.classList.contains('popover');
};
/**
* Pins the participant displayed by this thumbnail or unpins if already pinned.
*
* @returns {void}
*/
SmallVideo.prototype.togglePin = function() {
const pinnedParticipant
= getPinnedParticipant(APP.store.getState()) || {};
const participantIdToPin
@@ -836,6 +929,17 @@ SmallVideo.prototype._togglePin = function() {
APP.store.dispatch(pinParticipant(participantIdToPin));
};
/**
* Returns whether or not clicking to pin the participant needs to be a double
* click instead of a single click.
*
* @private
* @returns {boolean}
*/
SmallVideo.prototype._pinningRequiresDoubleClick = function() {
return shouldDisplayTileView(APP.store.getState());
};
/**
* Removes the React element responsible for showing connection status, dominant
* speaker, and raised hand icons.
@@ -865,4 +969,5 @@ SmallVideo.prototype._onPopoverHover = function(popoverIsHovered) {
this.updateView();
};
export default SmallVideo;

View File

@@ -265,7 +265,7 @@ export class VideoContainer extends LargeContainer {
*/
this.$wrapperParent = this.$wrapper.parent();
this.avatarHeight = $('#dominantSpeakerAvatar').height();
this.avatarHeight = $('#dominantSpeakerAvatarContainer').height();
const onPlayingCallback = function(event) {
if (typeof resizeContainer === 'function') {
@@ -408,7 +408,7 @@ export class VideoContainer extends LargeContainer {
*/
_positionParticipantStatus($element) {
if (this.avatarDisplayed) {
const $avatarImage = $('#dominantSpeakerAvatar');
const $avatarImage = $('#dominantSpeakerAvatarContainer');
$element.css(
'top',

View File

@@ -10,6 +10,7 @@ import {
} from '../../../react/features/base/lib-jitsi-meet';
import { VIDEO_TYPE } from '../../../react/features/base/media';
import {
getLocalParticipant as getLocalParticipantFromStore,
getPinnedParticipant,
pinParticipant
} from '../../../react/features/base/participants';
@@ -75,6 +76,16 @@ function getAllThumbnails() {
];
}
/**
* Private helper to get the redux representation of the local participant.
*
* @private
* @returns {Object}
*/
function getLocalParticipant() {
return getLocalParticipantFromStore(APP.store.getState());
}
/**
* Returns the user ID of the remote participant that is current the dominant
* speaker.
@@ -132,18 +143,6 @@ const VideoLayout = {
this.registerListeners();
},
/**
* Cleans up any existing largeVideo instance.
*
* @returns {void}
*/
resetLargeVideo() {
if (largeVideo) {
largeVideo.destroy();
}
largeVideo = null;
},
/**
* Registering listeners for UI events in Video layout component.
*
@@ -154,8 +153,18 @@ const VideoLayout = {
onLocalFlipXChanged);
},
/**
* Cleans up state of this singleton {@code VideoLayout}.
*
* @returns {void}
*/
reset() {
this._resetLargeVideo();
this._resetFilmstrip();
},
initLargeVideo() {
this.resetLargeVideo();
this._resetLargeVideo();
largeVideo = new LargeVideoManager(eventEmitter);
if (localFlipX) {
@@ -183,7 +192,7 @@ const VideoLayout = {
},
changeLocalVideo(stream) {
const localId = APP.conference.getMyUserId();
const localId = getLocalParticipant().id;
this.onVideoTypeChanged(localId, stream.videoType);
@@ -200,7 +209,7 @@ const VideoLayout = {
*/
mucJoined() {
if (largeVideo && !largeVideo.id) {
this.updateLargeVideo(APP.conference.getMyUserId(), true);
this.updateLargeVideo(getLocalParticipant().id, true);
}
// FIXME: replace this call with a generic update call once SmallVideo
@@ -304,7 +313,7 @@ const VideoLayout = {
// Go with local video
logger.info('Fallback to local video...');
const id = APP.conference.getMyUserId();
const { id } = getLocalParticipant();
logger.info(`electLastVisibleVideo: ${id}`);
@@ -391,6 +400,19 @@ const VideoLayout = {
return id || null;
},
/**
* Triggers a thumbnail to pin or unpin itself.
*
* @param {number} videoNumber - The index of the video to toggle pin on.
* @private
*/
togglePin(videoNumber) {
const videos = getAllThumbnails();
const videoView = videos[videoNumber];
videoView && videoView.togglePin();
},
/**
* Callback invoked to update display when the pin participant has changed.
*
@@ -1152,6 +1174,40 @@ const VideoLayout = {
);
},
/**
* Cleans up any existing largeVideo instance.
*
* @private
* @returns {void}
*/
_resetLargeVideo() {
if (largeVideo) {
largeVideo.destroy();
}
largeVideo = null;
},
/**
* Cleans up filmstrip state. While a separate {@code Filmstrip} exists, its
* implementation is mainly for querying and manipulating the DOM while
* state mostly remains in {@code VideoLayout}.
*
* @private
* @returns {void}
*/
_resetFilmstrip() {
Object.keys(remoteVideos).forEach(remoteVideoId => {
this.removeParticipantContainer(remoteVideoId);
delete remoteVideos[remoteVideoId];
});
if (localVideoThumbnail) {
localVideoThumbnail.remove();
localVideoThumbnail = null;
}
},
/**
* Triggers an update of large video if the passed in participant is
* currently displayed on large video.

613
package-lock.json generated
View File

@@ -5,10 +5,12 @@
"requires": true,
"dependencies": {
"@atlaskit/analytics-next": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@atlaskit/analytics-next/-/analytics-next-3.0.7.tgz",
"integrity": "sha512-osUW2nntOjVKvJBQx2JE21cZ5H13mk8drQkSNw1KJWhV8bDIjZLuDBGqUI6kKbE+M0dGZP4se8YcwILcx1Vdaw==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/analytics-next/-/analytics-next-3.1.2.tgz",
"integrity": "sha512-bkYDvl3Ojsnim+bsc9BALfvOjiL7xdb2rTp/4yqUP9pfidtf5HudbOJ849+dKcRCmk/rFbfB/nhDBRU6rv1Ueg==",
"requires": {
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0",
"prop-types": "^15.5.10"
}
},
@@ -53,12 +55,24 @@
}
},
"@atlaskit/blanket": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-7.0.7.tgz",
"integrity": "sha512-e2tyj0bBPMVk4ORCGxOXZx4PhDuWXctKkq+aKWeUy6bHFIpNp+r227JAp+MGt4dBj80JFATJXT0SUtxTdLqpTA==",
"version": "7.0.12",
"resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-7.0.12.tgz",
"integrity": "sha512-IWnXU2N42M14kvTU1YhATiK7vGYPZPsk/c2A+b8tNhRJTcfcTxTPMfcmGOvWYPD128el2TSly4uOvn9B9WKc9A==",
"requires": {
"@atlaskit/analytics-next": "^3.0.6",
"@atlaskit/theme": "^6.0.2"
"@atlaskit/analytics-next": "^3.1.2",
"@atlaskit/theme": "^7.0.1",
"@babel/runtime": "^7.0.0"
},
"dependencies": {
"@atlaskit/theme": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-7.0.1.tgz",
"integrity": "sha512-wxXDnkUablJketNCrQuNUuazufYEA7kv0Y6Yzv6uvqfuyNpWUQt4H1psz/MW8DbZmCdku9dEYbNVK3nFP5TDGg==",
"requires": {
"@babel/runtime": "^7.0.0",
"prop-types": "^15.5.10"
}
}
}
},
"@atlaskit/button": {
@@ -522,98 +536,30 @@
}
},
"@atlaskit/flag": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@atlaskit/flag/-/flag-6.1.0.tgz",
"integrity": "sha1-IThcuWL1+Q0vhdTSPfuqmtDleos=",
"version": "9.1.8",
"resolved": "https://registry.npmjs.org/@atlaskit/flag/-/flag-9.1.8.tgz",
"integrity": "sha512-Uww6dYBZiz9SiRfJXqGgbkmI5YTLmN70f8rGVcD4ntoOGGIuqzDXvWe+MRa4BEOIs2TIkvY6EIUDrUSa8IQM/w==",
"requires": {
"@atlaskit/button": "^5.0.0",
"@atlaskit/icon": "^7.1.0",
"@atlaskit/theme": "^2.0.0",
"@atlaskit/util-shared-styles": "^2.10.3",
"babel-runtime": "^6.11.6",
"prop-types": "^15.5.10",
"react-transition-group": "^1.2.0",
"styled-components": "^1.3.0"
},
"dependencies": {
"@atlaskit/button": {
"version": "5.4.14",
"resolved": "https://registry.npmjs.org/@atlaskit/button/-/button-5.4.14.tgz",
"integrity": "sha1-YAl1FB/TVsy/cSiFoA9joTZ90o0=",
"requires": {
"@atlaskit/theme": "^2.0.0",
"babel-runtime": "^6.11.6",
"classnames": "^2.2.5",
"prop-types": "^15.5.10",
"styled-components": "^1.4.6"
}
},
"@atlaskit/icon": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@atlaskit/icon/-/icon-7.1.0.tgz",
"integrity": "sha1-czQVCEhzUmPeShO8mPTUTU8r6N8=",
"requires": {
"babel-runtime": "^6.11.6",
"prop-types": "^15.5.10",
"styled-components": "^1.3.0"
}
},
"@atlaskit/theme": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-2.4.1.tgz",
"integrity": "sha512-I4AMg+asUUwaruwsWDaUzub2zhEKYHhZ5tRlaNQ+8dhATpO1RSu6g9FSgwnAZ20M4Cf+YITVND/wyNpAON5HCQ==",
"requires": {
"prop-types": "^15.5.10",
"styled-components": "1.4.6 - 3"
}
},
"react-transition-group": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
"integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
"requires": {
"chain-function": "^1.0.0",
"dom-helpers": "^3.2.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.5.6",
"warning": "^3.0.0"
}
},
"styled-components": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-1.4.6.tgz",
"integrity": "sha1-WPMuimq1EPsUgekB6DjgR38UiwY=",
"requires": {
"buffer": "^5.0.2",
"css-to-react-native": "^1.0.6",
"fbjs": "^0.8.7",
"inline-style-prefixer": "^2.0.5",
"is-function": "^1.0.1",
"is-plain-object": "^2.0.1",
"prop-types": "^15.5.4",
"supports-color": "^3.1.2"
}
}
"@atlaskit/analytics-next": "^3.1.2",
"@atlaskit/button": "^10.1.1",
"@atlaskit/icon": "^15.0.2",
"@atlaskit/portal": "^0.0.17",
"@atlaskit/theme": "^7.0.1",
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0",
"react-transition-group": "^2.2.1",
"uuid": "^3.1.0"
}
},
"@atlaskit/icon": {
"version": "13.8.1",
"resolved": "https://registry.npmjs.org/@atlaskit/icon/-/icon-13.8.1.tgz",
"integrity": "sha512-6mjSZd8qQlPzEa04NB8dASNKes9H9dVdlg3J5q76g0sg2H5UjnpITiZTl3dz+94dEd15M2Lv7Z+pUDuhQad4fQ==",
"version": "15.0.3",
"resolved": "https://registry.npmjs.org/@atlaskit/icon/-/icon-15.0.3.tgz",
"integrity": "sha512-UAf7U0/+5giS2uMlOeVMYmhuWD4fQy0eRcp7r8oEDBqZXNH0yIuHrfu1bPgt2SbFotrjxZdPpOX1i1dXEu7y6g==",
"requires": {
"@atlaskit/theme": "^6.0.2",
"@atlaskit/theme": "^7.0.1",
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0",
"uuid": "^3.1.0"
},
"dependencies": {
"@atlaskit/theme": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-6.0.3.tgz",
"integrity": "sha512-s90qPe3S0fXtf1xHxH4uhT2IIbvGkMWLp1HW66rv0c8VSBj0f5VaJW+xVs7BLii5rN1QnOtsYS3ykx307mZOJQ==",
"requires": {
"prop-types": "^15.5.10"
}
}
}
},
"@atlaskit/inline-dialog": {
@@ -745,18 +691,6 @@
"react-scrolllock": "^3.0.2"
}
},
"@atlaskit/layer-manager": {
"version": "5.0.19",
"resolved": "https://registry.npmjs.org/@atlaskit/layer-manager/-/layer-manager-5.0.19.tgz",
"integrity": "sha512-2NvSNEERS9uW/oExF7ltj/h8akJvJRJnhEleKGx1JXqnbCUwQHtGkiP+06cX9JriGnhPEySt26dfQS5dAA0sKg==",
"requires": {
"@babel/runtime": "^7.0.0",
"prop-types": "^15.5.10",
"react-focus-lock": "^1.11.3",
"react-scrolllock": "^3.0.2",
"react-transition-group": "^2.2.1"
}
},
"@atlaskit/lozenge": {
"version": "6.2.4",
"resolved": "https://registry.npmjs.org/@atlaskit/lozenge/-/lozenge-6.2.4.tgz",
@@ -779,98 +713,44 @@
}
},
"@atlaskit/modal-dialog": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@atlaskit/modal-dialog/-/modal-dialog-6.0.12.tgz",
"integrity": "sha512-/64iftFdwTCcizNoGHw1PsIbKkEn0wE0ziMYnEc3IetBZWfJPOzW1SlyuulNGdJl8ZzMVrf9fTt4qMKAUHVbbw==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/modal-dialog/-/modal-dialog-7.1.2.tgz",
"integrity": "sha512-rY8ojmtr0/9MxqQ8Ab9mtgv1VLRuJMNPCCbstjJzv+NLBu5tIh/on+iuzsxBL84E5hgrvg0wf7JHzKyOoQJQsw==",
"requires": {
"@atlaskit/analytics-next": "^3.0.6",
"@atlaskit/blanket": "^7.0.7",
"@atlaskit/button": "^9.0.8",
"@atlaskit/icon": "^13.6.1",
"@atlaskit/layer-manager": "^5.0.9",
"@atlaskit/theme": "^6.0.2",
"@atlaskit/analytics-next": "^3.1.2",
"@atlaskit/blanket": "^7.0.12",
"@atlaskit/button": "^10.1.1",
"@atlaskit/icon": "^15.0.2",
"@atlaskit/portal": "^0.0.17",
"@atlaskit/theme": "^7.0.1",
"@babel/runtime": "^7.0.0",
"exenv": "^1.2.2",
"prop-types": "^15.5.10",
"raf-schd": "^2.1.0",
"react-focus-lock": "^1.11.3",
"react-scrolllock": "^3.0.1",
"react-transition-group": "^2.2.1"
"react-scrolllock": "^3.0.2",
"react-transition-group": "^2.2.1",
"tiny-invariant": "^0.0.3"
},
"dependencies": {
"@atlaskit/button": {
"version": "9.0.16",
"resolved": "https://registry.npmjs.org/@atlaskit/button/-/button-9.0.16.tgz",
"integrity": "sha512-VFk7Qyp+IM2AxsPseHubrqU4ORq5BQPRDafWHVFqg/yf2AzKU1sDavRIX8jQfchnu5rBdBgmQjffX5cArzCawg==",
"@atlaskit/icon": {
"version": "15.0.3",
"resolved": "https://registry.npmjs.org/@atlaskit/icon/-/icon-15.0.3.tgz",
"integrity": "sha512-UAf7U0/+5giS2uMlOeVMYmhuWD4fQy0eRcp7r8oEDBqZXNH0yIuHrfu1bPgt2SbFotrjxZdPpOX1i1dXEu7y6g==",
"requires": {
"@atlaskit/analytics-next": "^3.0.10",
"@atlaskit/spinner": "9.0.10",
"@atlaskit/theme": "^6.1.1",
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0"
},
"dependencies": {
"@atlaskit/analytics-next": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/analytics-next/-/analytics-next-3.1.2.tgz",
"integrity": "sha512-bkYDvl3Ojsnim+bsc9BALfvOjiL7xdb2rTp/4yqUP9pfidtf5HudbOJ849+dKcRCmk/rFbfB/nhDBRU6rv1Ueg==",
"requires": {
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0",
"prop-types": "^15.5.10"
}
},
"@atlaskit/theme": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-6.2.1.tgz",
"integrity": "sha512-6u0OxpnZ2n+G7Wc1wckgtOEiYl3wmJ2HvBd28N7d+Fi/fbi+C4TzfBvyeENUqtrp1UIEhweVbB2WLoVoKA5c/w==",
"requires": {
"@babel/runtime": "^7.0.0",
"prop-types": "^15.5.10"
}
}
}
},
"@atlaskit/spinner": {
"version": "9.0.10",
"resolved": "https://registry.npmjs.org/@atlaskit/spinner/-/spinner-9.0.10.tgz",
"integrity": "sha512-1akBXR6uC/cRWzwGPF4IfS7YpCsHdlncvo98p4W9ZGRn822SBQk9hiZBL0BfvkEFViAakPV7JZ5zZKc75UExhA==",
"requires": {
"@atlaskit/theme": "^6.1.1",
"@atlaskit/theme": "^7.0.1",
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0",
"react-transition-group": "^2.2.1"
},
"dependencies": {
"@atlaskit/theme": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-6.2.1.tgz",
"integrity": "sha512-6u0OxpnZ2n+G7Wc1wckgtOEiYl3wmJ2HvBd28N7d+Fi/fbi+C4TzfBvyeENUqtrp1UIEhweVbB2WLoVoKA5c/w==",
"requires": {
"@babel/runtime": "^7.0.0",
"prop-types": "^15.5.10"
}
}
"uuid": "^3.1.0"
}
},
"react-transition-group": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.4.0.tgz",
"integrity": "sha512-Xv5d55NkJUxUzLCImGSanK8Cl/30sgpOEMGc5m86t8+kZwrPxPCPcFqyx83kkr+5Lz5gs6djuvE5By+gce+VjA==",
"@atlaskit/theme": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-7.0.1.tgz",
"integrity": "sha512-wxXDnkUablJketNCrQuNUuazufYEA7kv0Y6Yzv6uvqfuyNpWUQt4H1psz/MW8DbZmCdku9dEYbNVK3nFP5TDGg==",
"requires": {
"dom-helpers": "^3.3.1",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"dependencies": {
"prop-types": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
"requires": {
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
}
}
"@babel/runtime": "^7.0.0",
"prop-types": "^15.5.10"
}
}
}
@@ -924,6 +804,17 @@
"react-popper": "1.0.2"
}
},
"@atlaskit/portal": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/@atlaskit/portal/-/portal-0.0.17.tgz",
"integrity": "sha512-nn7b0xd1f/zHaAVCl18vmInPS5/P3zJyDP5pVBeH6Lg4Xo60BR1SDOca9EzKqvxs0FFE84HWcjpWSBRxHZ5sHw==",
"requires": {
"@babel/runtime": "^7.0.0",
"babel-runtime": "^6.26.0",
"exenv": "^1.2.2",
"tiny-invariant": "^0.0.3"
}
},
"@atlaskit/spinner": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@atlaskit/spinner/-/spinner-9.0.13.tgz",
@@ -1037,10 +928,12 @@
}
},
"@atlaskit/theme": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-6.0.2.tgz",
"integrity": "sha512-GUnbTuxxcRaSubEFJJI6hWKwfCI6t1NKv1dVKCfIPhFdj/wBuyn/2rr8TKjwNRdhGHfY1kNq27fTX9Xt/sZ2QA==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-7.0.2.tgz",
"integrity": "sha512-e0kFRFmR31yLc28AMzU8Eyv0We9ncL+3H0voMQjiCqQOLxfahuNTNijnQ+tQNLnnvl9O3xWYFIIR+MZnQ3QJ9A==",
"requires": {
"@babel/runtime": "^7.0.0",
"exenv": "^1.2.2",
"prop-types": "^15.5.10"
}
},
@@ -1153,32 +1046,6 @@
"react": "^16.4.0"
}
},
"@atlaskit/util-shared-styles": {
"version": "2.10.6",
"resolved": "https://registry.npmjs.org/@atlaskit/util-shared-styles/-/util-shared-styles-2.10.6.tgz",
"integrity": "sha1-Vqol1ZzvAyT2U6boV9USpwvkfGU=",
"requires": {
"babel-runtime": "^6.11.6",
"styled-components": "^1.4.6"
},
"dependencies": {
"styled-components": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-1.4.6.tgz",
"integrity": "sha1-WPMuimq1EPsUgekB6DjgR38UiwY=",
"requires": {
"buffer": "^5.0.2",
"css-to-react-native": "^1.0.6",
"fbjs": "^0.8.7",
"inline-style-prefixer": "^2.0.5",
"is-function": "^1.0.1",
"is-plain-object": "^2.0.1",
"prop-types": "^15.5.4",
"supports-color": "^3.1.2"
}
}
}
},
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
@@ -2299,6 +2166,15 @@
}
}
},
"@expo/react-native-action-sheet": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@expo/react-native-action-sheet/-/react-native-action-sheet-1.1.2.tgz",
"integrity": "sha512-//2EvHVBFVGSAzuJvG0I1UoQVzGJBo2f1CkO+RMnEWdR0FeWYmV7+pCThIroL1czRm/oOtoMxiGS6FgXt6QgVA==",
"requires": {
"hoist-non-react-statics": "^2.2.2",
"prop-types": "^15.5.10"
}
},
"@jitsi/sdp-interop": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-0.1.13.tgz",
@@ -2324,6 +2200,15 @@
"isomorphic-fetch": "^2.2.1"
}
},
"@segment/top-domain": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@segment/top-domain/-/top-domain-3.0.0.tgz",
"integrity": "sha1-AuWlpP1CqfbPiGsF6C8QQBKjw6c=",
"requires": {
"component-cookie": "^1.1.2",
"component-url": "^0.2.1"
}
},
"@webassemblyjs/ast": {
"version": "1.7.11",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",
@@ -2593,6 +2478,24 @@
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true
},
"amplitude-js": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-4.5.2.tgz",
"integrity": "sha512-J075hRBuhCuBqwrhmuGIXg7zCLRO6TvONTJUESpTkM1LVL5bMcTx9BczW4Hh6p6kjBQjs2fD4rNbhPs48kYZwA==",
"requires": {
"@segment/top-domain": "^3.0.0",
"blueimp-md5": "^2.10.0",
"json3": "^3.3.2",
"lodash": "^4.17.4",
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
},
"dependencies": {
"ua-parser-js": {
"version": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d",
"from": "github:amplitude/ua-parser-js#ed538f1"
}
}
},
"ansi": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz",
@@ -3248,6 +3151,14 @@
"util.promisify": "^1.0.0"
}
},
"babel-plugin-check-es2015-constants": {
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
"integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
"requires": {
"babel-runtime": "^6.22.0"
}
},
"babel-plugin-syntax-trailing-function-commas": {
"version": "7.0.0-beta.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz",
@@ -3428,6 +3339,11 @@
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
"dev": true
},
"blueimp-md5": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.10.0.tgz",
"integrity": "sha512-EkNUOi7tpV68TqjpiUz9D9NcT8um2+qtgntmMbi5UKssVX2m/2PLqotcric0RE63pB3HPN/fjf3cKHN2ufGSUQ=="
},
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
@@ -3477,11 +3393,6 @@
"multicast-dns-service-types": "^1.1.0"
}
},
"bowser": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.2.tgz",
"integrity": "sha512-fuiANC1Bqbqa/S4gmvfCt7bGBmNELMsGZj4Wg3PrP6esP66Ttoj1JSlzFlXtHyduMv07kDNmDsX6VsMWT/MLGg=="
},
"bplist-creator": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz",
@@ -3765,11 +3676,6 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true
},
"chain-function": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz",
"integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg=="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
@@ -4153,11 +4059,6 @@
}
}
},
"colors": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
"integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q="
},
"combined-stream": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
@@ -4218,11 +4119,39 @@
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
},
"component-cookie": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/component-cookie/-/component-cookie-1.1.4.tgz",
"integrity": "sha512-j6rzl+vHDTowvYz7Al3V0ud84O2l4YqGdA9qMj1W1nlZ5yWi7EhOd7ZSPzWFM25gZgv2OxWh6JlJYfsz2+XYow==",
"requires": {
"debug": "2.2.0"
},
"dependencies": {
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"requires": {
"ms": "0.7.1"
}
},
"ms": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
}
}
},
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"component-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/component-url/-/component-url-0.2.1.tgz",
"integrity": "sha1-Tk9HmcQ+rZ/TzpG1owXSICCP7kc="
},
"compressible": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
@@ -4539,19 +4468,6 @@
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
"integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
},
"css-color-list": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/css-color-list/-/css-color-list-0.0.1.tgz",
"integrity": "sha1-hxjoaVrnosyHh76HFfHACKfyixU=",
"requires": {
"css-color-names": "0.0.1"
}
},
"css-color-names": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.1.tgz",
"integrity": "sha1-XQVI+iVkVu3kqaDCrHqxnT6xrYE="
},
"css-element-queries": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/css-element-queries/-/css-element-queries-0.3.2.tgz",
@@ -4603,16 +4519,6 @@
}
}
},
"css-to-react-native": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-1.0.6.tgz",
"integrity": "sha1-cox+d05WU2VYoOyqmQ2VB8Q6SsQ=",
"requires": {
"css-color-list": "0.0.1",
"fbjs": "^0.8.5",
"nearley": "^2.7.7"
}
},
"cssesc": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
@@ -4910,11 +4816,6 @@
"randombytes": "^2.0.0"
}
},
"discontinuous-range": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
"integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo="
},
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@@ -5784,9 +5685,9 @@
}
},
"expose-loader": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-0.7.4.tgz",
"integrity": "sha512-lweINkewAXcQtNjd7j1gO3cd8O/8lNYijsEwH4YZ+Dv3gT2Kh9/YvJov5Mdp2A75QIhgOvsSyRa/ZG3wYjNZpA==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-0.7.5.tgz",
"integrity": "sha512-iPowgKUZkTPX5PznYsmifVj9Bob0w2wTHVkt/eYNPSzyebkUgIedmskf/kcfEIWpiWjg3JRjnW+a17XypySMuw==",
"dev": true
},
"express": {
@@ -6020,16 +5921,6 @@
"object-assign": "^4.0.1"
}
},
"file-loader": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.5.tgz",
"integrity": "sha512-RzGHDatcVNpGISTvCpfUfOGpYuSR7HSsSg87ki+wF6rw1Hm0RALPTiAdsxAq1UwLf0RRhbe22/eHK6nhXspiOQ==",
"dev": true,
"requires": {
"loader-utils": "^1.0.2",
"schema-utils": "^0.3.0"
}
},
"filename-regex": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
@@ -7578,11 +7469,6 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
"dev": true
},
"hyphenate-style-name": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz",
"integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es="
},
"i18next": {
"version": "8.4.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-8.4.3.tgz",
@@ -7838,15 +7724,6 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"inline-style-prefixer": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz",
"integrity": "sha1-wVPH6I/YT+9cYC6VqBaLJ3BnH+c=",
"requires": {
"bowser": "^1.0.0",
"hyphenate-style-name": "^1.0.1"
}
},
"inquirer": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
@@ -8081,11 +7958,6 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"is-function": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz",
"integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU="
},
"is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
@@ -8452,8 +8324,7 @@
"json3": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
"integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
"dev": true
"integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE="
},
"json5": {
"version": "0.5.1",
@@ -8514,6 +8385,11 @@
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz",
"integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo="
},
"keymirror": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz",
"integrity": "sha1-kYiJ6hP40KQufFVyUO7nE63JXDU="
},
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -8555,8 +8431,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#75a556e3950dfc396d7aa8306b42de67f372e1cf",
"from": "github:jitsi/lib-jitsi-meet#75a556e3950dfc396d7aa8306b42de67f372e1cf",
"version": "github:jitsi/lib-jitsi-meet#2e1436e20d4d8fb6020497a87b2714dff38a6c86",
"from": "github:jitsi/lib-jitsi-meet#2e1436e20d4d8fb6020497a87b2714dff38a6c86",
"requires": {
"@jitsi/sdp-interop": "0.1.13",
"@jitsi/sdp-simulcast": "0.2.1",
@@ -8565,7 +8441,6 @@
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#6fff754a77a56ab52499f3559105a15886942a1e",
"js-utils": "github:jitsi/js-utils#446497893023aa8dec403e0e4e35a22cae6bc87d",
"lodash.isequal": "4.5.0",
"react-native-callstats": "3.53.4",
"sdp-transform": "2.3.0",
"strophe.js": "1.2.15",
"strophejs-plugin-disco": "0.0.2",
@@ -9607,17 +9482,6 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"nearley": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.11.1.tgz",
"integrity": "sha512-1azpqq1JvHKZNPEixS1jNEXf4kDilhFtr8AIZIGjP8N0TcAcUhKgi354niI5pM4JoOsMQ+H6vzCYWQa95LQjcw==",
"requires": {
"nomnom": "~1.6.2",
"railroad-diagrams": "^1.0.0",
"randexp": "0.4.6",
"semver": "^5.4.1"
}
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
@@ -9892,15 +9756,6 @@
}
}
},
"nomnom": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz",
"integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=",
"requires": {
"colors": "0.5.x",
"underscore": "~1.4.4"
}
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
@@ -11279,20 +11134,6 @@
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-2.1.2.tgz",
"integrity": "sha512-Orl0IEvMtUCgPddgSxtxreK77UiQz4nPYJy9RggVzu4mKsZkQWiAaG1y9HlYWdvm9xtN348xRaT37qkvL/+A+g=="
},
"railroad-diagrams": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234="
},
"randexp": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
"requires": {
"discontinuous-range": "1.0.0",
"ret": "~0.1.10"
}
},
"randomatic": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
@@ -11616,19 +11457,45 @@
"integrity": "sha512-By4lgWQG9eewS9WzhzxVHAewxX40v1CzLnNYXFMOqF06fYVCNykiiTMGlLzz3UXHVwwN1Drxw9uWroQfGRFMsw=="
},
"react-native-callstats": {
"version": "3.53.4",
"resolved": "https://registry.npmjs.org/react-native-callstats/-/react-native-callstats-3.53.4.tgz",
"integrity": "sha512-LN6CVKHSVTz+CJ6hGuGaxC2bbknoIACEMYLKZ/dcTMEepibaac3bZ+Evilgq0ot+E1BBBFkOo9hiOUU1cgqLXQ==",
"version": "3.57.1",
"resolved": "https://registry.npmjs.org/react-native-callstats/-/react-native-callstats-3.57.1.tgz",
"integrity": "sha512-IrPYu/Q4AgC2lMDVgORHNM5OpgIxwqPmYw6Rhqbr1RXgMIN91Ve/NG6hjM6logk+gxbacxeyXpl3XCmtaM6RPg==",
"requires": {
"base-64": "0.1.0",
"jssha": "^2.2.0"
}
},
"react-native-communications": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-native-communications/-/react-native-communications-2.2.1.tgz",
"integrity": "sha1-eIO1ayCgAu63kMET+GFuqGksp5U="
},
"react-native-fast-image": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.1.1.tgz",
"integrity": "sha512-kEzgZxbbXYhy27u5GnhrKitn+XDBFAHSDUJdYC6llMi5cDPjgcqhOAQABj0K+ga5pn+/xPZLmD882rrUGiwVVA=="
},
"react-native-gifted-chat": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/react-native-gifted-chat/-/react-native-gifted-chat-0.6.0.tgz",
"integrity": "sha512-KYI/okKUZmjcJM3I6BP10KG1WNkCKBZhY8N47wk407dr+KqLS4+LR13UKo7j3f++5SrX2Ex+7vYvIQ2pBdzCiA==",
"requires": {
"@expo/react-native-action-sheet": "^1.0.1",
"moment": "^2.19.0",
"react-native-communications": "2.2.1",
"react-native-lightbox": "^0.7.0",
"react-native-parsed-text": "^0.0.20",
"react-native-video": "^3.2.1",
"uuid": "3.3.0"
},
"dependencies": {
"uuid": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.0.tgz",
"integrity": "sha512-ijO9N2xY/YaOqQ5yz5c4sy2ZjWmA6AR6zASb/gdpeKZ8+948CxwfMW9RrKVk5may6ev8c0/Xguu32e2Llelpqw=="
}
}
},
"react-native-google-signin": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/react-native-google-signin/-/react-native-google-signin-1.0.2.tgz",
@@ -11644,14 +11511,28 @@
"resolved": "https://registry.npmjs.org/react-native-keep-awake/-/react-native-keep-awake-4.0.0.tgz",
"integrity": "sha512-0Fotox+eLXQooeibVs3P60yASYUWjtRw9MZNmbuHt5UZQrgUrAKsE4jm7gTr4tPU1m1RkwGzcgUFpcOkh/ec7g=="
},
"react-native-linear-gradient": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.4.0.tgz",
"integrity": "sha512-h4nwmcjfeedSiHGBmQkMmCSIqm3196YtT1AtbAqE93jgAcpib0btvoCx8nBUemmhfm+CA5mFEh8p5biA4wFw/A==",
"react-native-lightbox": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/react-native-lightbox/-/react-native-lightbox-0.7.0.tgz",
"integrity": "sha512-HS3T4WlCd0Gb3us2d6Jse5m6KjNhngnKm35Wapq30WtQa9s+/VMmtuktbGPGaWtswcDyOj6qByeJBw9W80iPCA==",
"requires": {
"prop-types": "^15.5.10"
}
},
"react-native-linear-gradient": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.3.tgz",
"integrity": "sha512-XdusrOXXlkI+yQpUW7YLeiq9cZiBwkvQX4XEkHPVrJ9H47gsKmdgBwObkZBzBQUP0dKK/Sg6aVpETEis4w43bQ=="
},
"react-native-parsed-text": {
"version": "0.0.20",
"resolved": "https://registry.npmjs.org/react-native-parsed-text/-/react-native-parsed-text-0.0.20.tgz",
"integrity": "sha512-n77hYu64Tr3oclzIXBXXaiLh1WbMKdA2Y0x6bX/yqwxAM4afcObENY5VrNB+EsTBJBEDqrypA9D1p2cLEIHkuQ==",
"requires": {
"babel-plugin-check-es2015-constants": "6.22.0",
"prop-types": "^15.5.10"
}
},
"react-native-sound": {
"version": "github:jitsi/react-native-sound#e4260ed7f641eeb0377d76eac7987aba72e1cf08",
"from": "github:jitsi/react-native-sound#e4260ed7f641eeb0377d76eac7987aba72e1cf08"
@@ -11707,9 +11588,18 @@
}
}
},
"react-native-video": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-3.2.1.tgz",
"integrity": "sha512-Xansfoo/to80FwhM1HKlf7pCxDZ5RtV+kG3piCVvsNAhPY4GGwiOGUH9y3Y+mFQIDEWcY8I9j16lsFYAbnue3g==",
"requires": {
"keymirror": "0.1.1",
"prop-types": "^15.5.10"
}
},
"react-native-webrtc": {
"version": "github:jitsi/react-native-webrtc#6322a9b5a38ce590cfaea4041072ea87c8dbf558",
"from": "github:jitsi/react-native-webrtc#6322a9b5a38ce590cfaea4041072ea87c8dbf558",
"version": "github:jitsi/react-native-webrtc#c1be0cb1c6e8a83dfd406e478082a5ff205a97ec",
"from": "github:jitsi/react-native-webrtc#c1be0cb1c6e8a83dfd406e478082a5ff205a97ec",
"requires": {
"base64-js": "^1.1.2",
"event-target-shim": "^1.0.5",
@@ -13875,13 +13765,49 @@
"dev": true
},
"string-replace-loader": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-1.3.0.tgz",
"integrity": "sha512-zj8J2ELc5HWOYFkS3MaRMgaHu+mPTG/EfHHaFesJqXjpaKOAruaONEWt3/Em5Urc6n8qDlvabIN6umiU8lH4QA==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-2.1.1.tgz",
"integrity": "sha512-0Nvw1LDclF45AFNuYPcD2Jvkv0mwb/dQSnJZMvhqGrT+zzmrpG3OJFD600qfQfNUd5aqfp7fCm2mQMfF7zLbyQ==",
"dev": true,
"requires": {
"loader-utils": "^1.1.0",
"lodash": "^4"
"schema-utils": "^0.4.5"
},
"dependencies": {
"ajv": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz",
"integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"schema-utils": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
"integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
"dev": true,
"requires": {
"ajv": "^6.1.0",
"ajv-keywords": "^3.1.0"
}
}
}
},
"string-replace-to-array": {
@@ -14684,11 +14610,6 @@
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
"integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po="
},
"underscore": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",

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