Compare commits
480 Commits
3371
...
android-sd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26ffde072e | ||
|
|
c8b20e037e | ||
|
|
00c8409e31 | ||
|
|
1b43c22940 | ||
|
|
efddb36164 | ||
|
|
385e1c1047 | ||
|
|
3cc181a2e5 | ||
|
|
bcc1be675f | ||
|
|
d1be5742ba | ||
|
|
1b27e331da | ||
|
|
c1f7bf75c1 | ||
|
|
382ec011eb | ||
|
|
8a3ddd8596 | ||
|
|
738a199b4c | ||
|
|
40a1af5302 | ||
|
|
be5dba7eea | ||
|
|
eb15f73e59 | ||
|
|
2f7b485b8f | ||
|
|
3a885c893a | ||
|
|
6c4901a826 | ||
|
|
91c1c91950 | ||
|
|
1091ac7e7d | ||
|
|
0c0bd001e5 | ||
|
|
256994e1f8 | ||
|
|
f6fb859531 | ||
|
|
7deb2006c3 | ||
|
|
2aea24ffad | ||
|
|
b5aae0b58d | ||
|
|
d436825a45 | ||
|
|
5276cb6bc8 | ||
|
|
f863733dd3 | ||
|
|
2ccd4968a4 | ||
|
|
c233433243 | ||
|
|
6861f463b3 | ||
|
|
ac065f0225 | ||
|
|
b4b33c94dd | ||
|
|
c8753230a4 | ||
|
|
dbf569e29e | ||
|
|
27205e3119 | ||
|
|
f2fdef8361 | ||
|
|
d4dd5ab46a | ||
|
|
902da8cc4f | ||
|
|
c0a5e4f203 | ||
|
|
55ff9dbe80 | ||
|
|
bd99108e8e | ||
|
|
0c042b4078 | ||
|
|
743bbcb846 | ||
|
|
4ef2f0211a | ||
|
|
86f765a01f | ||
|
|
9e8b8313e0 | ||
|
|
f4a8115b00 | ||
|
|
a93bd422d3 | ||
|
|
c1598b7376 | ||
|
|
bc403adb46 | ||
|
|
1941275f93 | ||
|
|
6ae9bbe0c5 | ||
|
|
2c70388a9e | ||
|
|
e0815de2ad | ||
|
|
02e058370e | ||
|
|
8a7b795d37 | ||
|
|
d24ca796ad | ||
|
|
af2c61fd96 | ||
|
|
5a934c071a | ||
|
|
bfe4237430 | ||
|
|
84c60a5fdf | ||
|
|
cfc7210ac8 | ||
|
|
57ac951192 | ||
|
|
36fee03d5e | ||
|
|
14b747e0a4 | ||
|
|
c113b2e765 | ||
|
|
560e65da37 | ||
|
|
861cf7d842 | ||
|
|
dd23ed09ad | ||
|
|
64897b9c91 | ||
|
|
0dc8c687f2 | ||
|
|
c65d29d1a7 | ||
|
|
6aa895b679 | ||
|
|
dae451e6fa | ||
|
|
3c1f056d2a | ||
|
|
663e0a6693 | ||
|
|
53f98df8f3 | ||
|
|
6616f728f6 | ||
|
|
1c1e8a942b | ||
|
|
9721d99918 | ||
|
|
69c21cbb11 | ||
|
|
abefc56750 | ||
|
|
a4a1685224 | ||
|
|
21fb225726 | ||
|
|
75ab890707 | ||
|
|
2ded8363ad | ||
|
|
c0a8a386a5 | ||
|
|
7af081ea99 | ||
|
|
8800cb4580 | ||
|
|
b658f20a30 | ||
|
|
c3e52f32f9 | ||
|
|
ab73d808fe | ||
|
|
e110460793 | ||
|
|
c0ee451ca1 | ||
|
|
67dca97d1d | ||
|
|
fd0ca76255 | ||
|
|
149e53af76 | ||
|
|
f3e7952e51 | ||
|
|
0f77cf9e0c | ||
|
|
66eb09fb63 | ||
|
|
b7f2c7d487 | ||
|
|
bed3f62536 | ||
|
|
e8eb7e1e1f | ||
|
|
1409d6fb5e | ||
|
|
a2cbd9229a | ||
|
|
bf50a110c7 | ||
|
|
467c9d36cf | ||
|
|
6ddd17c769 | ||
|
|
732f2c1963 | ||
|
|
e7144eb674 | ||
|
|
b5489b7c81 | ||
|
|
4d5332cadf | ||
|
|
5261f61110 | ||
|
|
8c7d6da5d5 | ||
|
|
6d91728a74 | ||
|
|
9b32fc95eb | ||
|
|
6bc1d87753 | ||
|
|
a5fc62b920 | ||
|
|
49f6010905 | ||
|
|
1c27f567ee | ||
|
|
7684b2bf98 | ||
|
|
8886bcdb73 | ||
|
|
437bdb92be | ||
|
|
6943659dc9 | ||
|
|
35f934e197 | ||
|
|
aca0469bd0 | ||
|
|
11f2e7291e | ||
|
|
7a985dd843 | ||
|
|
e09ea36055 | ||
|
|
ad3a087091 | ||
|
|
d379b4d53b | ||
|
|
7a99d57274 | ||
|
|
85bc0ed5fc | ||
|
|
f4dda6e7ce | ||
|
|
5c8dfabf7c | ||
|
|
20d03f3228 | ||
|
|
268137bb97 | ||
|
|
f87463780c | ||
|
|
6b2ced63c7 | ||
|
|
d6fbe55360 | ||
|
|
0feeb94fc7 | ||
|
|
c9c509ec51 | ||
|
|
f7ff457a3b | ||
|
|
72127aaa0b | ||
|
|
b8854a3b45 | ||
|
|
e6e7001857 | ||
|
|
6d4601fe66 | ||
|
|
8ecb01ec21 | ||
|
|
14ccb41015 | ||
|
|
348cde54e7 | ||
|
|
430bd3f141 | ||
|
|
b36f419e86 | ||
|
|
ed1d03db71 | ||
|
|
5d837571c0 | ||
|
|
f23532d7f4 | ||
|
|
0280294a8f | ||
|
|
fdd1418236 | ||
|
|
bae47434b1 | ||
|
|
3c98cfc136 | ||
|
|
17e5bc7b73 | ||
|
|
6092655099 | ||
|
|
721cca669f | ||
|
|
c62ff0939d | ||
|
|
ecf050c2d9 | ||
|
|
069137b02a | ||
|
|
f053cad66c | ||
|
|
4be3042cb0 | ||
|
|
593235d683 | ||
|
|
7bd8ddbba2 | ||
|
|
14232b1d93 | ||
|
|
ff4887a3c8 | ||
|
|
0289aacd4f | ||
|
|
99c13eaec0 | ||
|
|
1a713dfa27 | ||
|
|
7e84b51ee0 | ||
|
|
86fa442d0e | ||
|
|
93b88f1a16 | ||
|
|
2041d4fffb | ||
|
|
83c3e7c725 | ||
|
|
a34331891a | ||
|
|
56dd83740c | ||
|
|
debe50c3f4 | ||
|
|
d9985c786c | ||
|
|
ed910947d4 | ||
|
|
c4c5eace25 | ||
|
|
c6bf646fe8 | ||
|
|
ab2bb7686e | ||
|
|
12b53c38b9 | ||
|
|
ca31c8b199 | ||
|
|
1b331ce090 | ||
|
|
edd55e7798 | ||
|
|
9f4da84701 | ||
|
|
0097360396 | ||
|
|
b5cef05941 | ||
|
|
ddab9224b2 | ||
|
|
a5693c6e96 | ||
|
|
8dd345b192 | ||
|
|
130ab886ee | ||
|
|
c0e9f2b95a | ||
|
|
011972872e | ||
|
|
ef5720a3f0 | ||
|
|
ec30af2844 | ||
|
|
e08aeca28c | ||
|
|
ad7892ebce | ||
|
|
cbc7e1b6be | ||
|
|
da7d959b9a | ||
|
|
ada57ebcd0 | ||
|
|
1993ad10eb | ||
|
|
d305caf910 | ||
|
|
a25a504a59 | ||
|
|
250c61279b | ||
|
|
1e18af2d1a | ||
|
|
eb1fd4fd88 | ||
|
|
e0c8b6b3c0 | ||
|
|
9071cd4813 | ||
|
|
9bf650c700 | ||
|
|
49e3b03885 | ||
|
|
31e996ac3f | ||
|
|
d1c447e97b | ||
|
|
8a34c0a80f | ||
|
|
2f626ea474 | ||
|
|
0a76eebca7 | ||
|
|
0e9e695251 | ||
|
|
b86df7a8e3 | ||
|
|
5b25e02e26 | ||
|
|
0e6f14bb7c | ||
|
|
57b9954d9c | ||
|
|
249dd7b8b8 | ||
|
|
b31d7b4451 | ||
|
|
8dea3389ee | ||
|
|
e7f9e8e7f7 | ||
|
|
a53a86ee7a | ||
|
|
598b6f0598 | ||
|
|
b245945c4a | ||
|
|
301a8e319a | ||
|
|
820abfd059 | ||
|
|
1d42420a36 | ||
|
|
1b4bdb5142 | ||
|
|
f030a3f1fb | ||
|
|
8f79779ca7 | ||
|
|
c5111bb359 | ||
|
|
42814eac7d | ||
|
|
74d0013acc | ||
|
|
88e4850c4d | ||
|
|
87f9b1cf92 | ||
|
|
fe1187d7b7 | ||
|
|
a35b36d6df | ||
|
|
a04982fd96 | ||
|
|
658679f89e | ||
|
|
f90fc665f8 | ||
|
|
0c8130af41 | ||
|
|
e4c5968459 | ||
|
|
a148cd41a4 | ||
|
|
f8a049759d | ||
|
|
4b4225e14f | ||
|
|
3b0c5d0b6a | ||
|
|
3b750ddd5a | ||
|
|
6383d000a9 | ||
|
|
a48d67bdc7 | ||
|
|
2f92e72858 | ||
|
|
d2c85ada1b | ||
|
|
55b95c52d6 | ||
|
|
52362c4675 | ||
|
|
8da0552541 | ||
|
|
7ce0def995 | ||
|
|
48285e8a2d | ||
|
|
21dcc41d31 | ||
|
|
625d268373 | ||
|
|
681782ed20 | ||
|
|
1baa85b649 | ||
|
|
72137a2811 | ||
|
|
0734ce7ae3 | ||
|
|
2dc06c28e3 | ||
|
|
5848669552 | ||
|
|
c0376d238a | ||
|
|
979b773c3c | ||
|
|
3195a449ca | ||
|
|
d7483f07e3 | ||
|
|
9c1b802997 | ||
|
|
bb3a10b0fc | ||
|
|
97e8b31cee | ||
|
|
55218de779 | ||
|
|
714e0e045d | ||
|
|
0bc369afb4 | ||
|
|
f71ec55170 | ||
|
|
760885437a | ||
|
|
f77976b742 | ||
|
|
9e95e7cd97 | ||
|
|
9d94257e79 | ||
|
|
13cfd61c83 | ||
|
|
fa818bc386 | ||
|
|
a73a642c64 | ||
|
|
94b3f6410d | ||
|
|
3d30f6e9cd | ||
|
|
40c16f0bac | ||
|
|
a1db63a8c2 | ||
|
|
59a9c2d947 | ||
|
|
fc897b9bac | ||
|
|
96f013c549 | ||
|
|
742905e05a | ||
|
|
bde44a94e8 | ||
|
|
1786bfadce | ||
|
|
b2e840636a | ||
|
|
ddaa22048f | ||
|
|
3e77890387 | ||
|
|
1e39c12963 | ||
|
|
243fdba80f | ||
|
|
08c4933c1b | ||
|
|
d5e0dea469 | ||
|
|
033aa0dd6e | ||
|
|
803870ef8f | ||
|
|
bf67a4a675 | ||
|
|
ee2036a2a7 | ||
|
|
4c3ed190f3 | ||
|
|
a91b49c2c1 | ||
|
|
186ba70cb7 | ||
|
|
12c18657d5 | ||
|
|
9f8e7d4050 | ||
|
|
4cea7018f5 | ||
|
|
54a9b9199e | ||
|
|
4591b36c3e | ||
|
|
db862b5b3b | ||
|
|
70b864f00b | ||
|
|
73b6a7a134 | ||
|
|
8b5b112c6a | ||
|
|
2f7f9f24c4 | ||
|
|
1197c26529 | ||
|
|
6eb66b639e | ||
|
|
fa88db6897 | ||
|
|
64eb4b5609 | ||
|
|
ef2455caea | ||
|
|
ca11cbf6cc | ||
|
|
f15a2aea68 | ||
|
|
e6c3d7ded7 | ||
|
|
2861198251 | ||
|
|
226c0bb084 | ||
|
|
30c0bfc108 | ||
|
|
df50e7fa69 | ||
|
|
f85ac3ef91 | ||
|
|
e33b334307 | ||
|
|
ce6f7308ad | ||
|
|
f66478fa34 | ||
|
|
bf99051885 | ||
|
|
7234ca69c8 | ||
|
|
ae965877f3 | ||
|
|
ae3b70eb13 | ||
|
|
97e0303065 | ||
|
|
35ffbe1720 | ||
|
|
f7b92f65ca | ||
|
|
cf7b10d53d | ||
|
|
d798f93614 | ||
|
|
4ddfcaf584 | ||
|
|
431a221c63 | ||
|
|
477826089c | ||
|
|
a46369cf22 | ||
|
|
651791b8df | ||
|
|
09cc738219 | ||
|
|
6d8ec4d147 | ||
|
|
d65a068fdb | ||
|
|
cf23045f8d | ||
|
|
e47d2d13ce | ||
|
|
07b7f03aa7 | ||
|
|
7ce44f85ca | ||
|
|
41e0d782ce | ||
|
|
2a8fafdd36 | ||
|
|
faee1c139e | ||
|
|
eb644987ce | ||
|
|
de60a70daf | ||
|
|
2904dfa794 | ||
|
|
d51cf7c581 | ||
|
|
f25e6c6a5d | ||
|
|
5fb9422513 | ||
|
|
d01cfc8466 | ||
|
|
fa3888991f | ||
|
|
ded355a807 | ||
|
|
b655c8d54a | ||
|
|
42a6e6faaf | ||
|
|
c7954c284d | ||
|
|
251da1861a | ||
|
|
9712804040 | ||
|
|
fecbef0aff | ||
|
|
d65b71b584 | ||
|
|
579d291bca | ||
|
|
871026f4ba | ||
|
|
9a8a070c62 | ||
|
|
7cf4c7bd78 | ||
|
|
72a1def571 | ||
|
|
0dad99c3b7 | ||
|
|
840c0190c4 | ||
|
|
e0fdeea69b | ||
|
|
e3612929f8 | ||
|
|
70921bb6ef | ||
|
|
371ca4eef1 | ||
|
|
54fdb7066f | ||
|
|
85bcb0c757 | ||
|
|
d387cbe5bd | ||
|
|
1bc28e4904 | ||
|
|
cb3419ba2a | ||
|
|
a2f8e156da | ||
|
|
a4cf79c161 | ||
|
|
9352517705 | ||
|
|
47d5163c52 | ||
|
|
def22b01bb | ||
|
|
9445cf99fd | ||
|
|
96b226de24 | ||
|
|
5101f69e4e | ||
|
|
61b66e0edf | ||
|
|
700051f809 | ||
|
|
d16e10baec | ||
|
|
11e5c14f83 | ||
|
|
ded58d77d1 | ||
|
|
8642c372c4 | ||
|
|
edbf591059 | ||
|
|
ded4291d6a | ||
|
|
a14fead0f3 | ||
|
|
466e1e3eb8 | ||
|
|
8a90f0dab1 | ||
|
|
d7e0aa3f61 | ||
|
|
37b343a797 | ||
|
|
149485905c | ||
|
|
7f1df5629e | ||
|
|
f42d0411b1 | ||
|
|
8d1d573266 | ||
|
|
d86b60ea72 | ||
|
|
dfe5fbb702 | ||
|
|
09f881c0f5 | ||
|
|
f1546008f9 | ||
|
|
d8df7fde84 | ||
|
|
1c809eb428 | ||
|
|
e94edcd4ae | ||
|
|
b48651396f | ||
|
|
e2044074ad | ||
|
|
f060ac9db1 | ||
|
|
5a53d7f32a | ||
|
|
4eec13da1c | ||
|
|
cb8282dfe5 | ||
|
|
5cd0b1a9be | ||
|
|
504fadaf71 | ||
|
|
7187e540a8 | ||
|
|
34dffbfc5e | ||
|
|
a9637f93c3 | ||
|
|
0e8b0a9c5c | ||
|
|
e66b596a0d | ||
|
|
6f320f463d | ||
|
|
02955ab57c | ||
|
|
a9d76a2577 | ||
|
|
dcf31baf3a | ||
|
|
1e346f10ab | ||
|
|
a114d55fac | ||
|
|
afde717ca4 | ||
|
|
fb5a45f714 | ||
|
|
f5ac18da18 | ||
|
|
103ae363f6 | ||
|
|
9bde673397 | ||
|
|
ff6b27eafa | ||
|
|
8cb19ccbf6 | ||
|
|
198eba3682 | ||
|
|
a49f62238b | ||
|
|
a8233bdb84 | ||
|
|
ec2826e0fc | ||
|
|
3d9606f6da | ||
|
|
01458eeff9 | ||
|
|
0318568a30 | ||
|
|
4d04141f24 | ||
|
|
afbc622fb9 | ||
|
|
fbc7f865ec | ||
|
|
2a4bac7a27 | ||
|
|
b45a5da6e2 | ||
|
|
2fad9f9ba8 | ||
|
|
8b0e5b9d15 | ||
|
|
0889ffdf27 | ||
|
|
86d0d4fc22 | ||
|
|
7e9df74e60 | ||
|
|
3eca67e1ad | ||
|
|
c040b3a7dd | ||
|
|
3f4a71c26d |
76
.flowconfig
@@ -2,22 +2,27 @@
|
||||
; We fork some components by platform
|
||||
.*/*[.]android.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Ignore unexpected extra "@providesModule"
|
||||
.*/node_modules/.*/node_modules/fbjs/.*
|
||||
node_modules/react-native/Libraries/react-native/React.js
|
||||
|
||||
; Ignore duplicate module providers
|
||||
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
node_modules/react-native/Libraries/react-native/React.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
node_modules/react-native/Libraries/polyfills/.*
|
||||
|
||||
; Ignore metro
|
||||
.*/node_modules/metro/.*
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/HMRLoadingView.js
|
||||
|
||||
[untyped]
|
||||
.*/node_modules/@react-native-community/cli/.*/.*
|
||||
|
||||
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
|
||||
; seen to cause errors and we have chosen not to fix.
|
||||
@@ -40,6 +45,18 @@ emoji=true
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
|
||||
; well, not only on React Native. Unfortunately, Flow does not support .web.js
|
||||
; by default. Override Flow's defaults to include .web.js as well. Technically,
|
||||
; we have .native.js as well so the choice of .web.js may lead to errors.
|
||||
; Practically though, it is a potential future problem that we do not have at
|
||||
; the time of this writing.
|
||||
module.file_ext=.web.js
|
||||
; Flow's defaults:
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
module.file_ext=.ios.js
|
||||
|
||||
module.system=haste
|
||||
module.system.haste.use_name_reducers=true
|
||||
# get basename
|
||||
@@ -52,8 +69,11 @@ module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
|
||||
module.system.haste.paths.blacklist=.*/__tests__/.*
|
||||
module.system.haste.paths.blacklist=.*/__mocks__/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/RNTester/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/IntegrationTests/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation.js
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
@@ -64,22 +84,32 @@ suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
|
||||
; well, not only on React Native. Unfortunately, Flow does not support .web.js
|
||||
; by default. Override Flow's defaults to include .web.js as well. Technically,
|
||||
; we have .native.js as well so the choice of .web.js may lead to errors.
|
||||
; Practically though, it is a potential future problem that we do not have at
|
||||
; the time of this writing.
|
||||
module.file_ext=.web.js
|
||||
; Flow's defaults:
|
||||
module.file_ext=.js
|
||||
module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
nonstrict-import
|
||||
sketchy-null
|
||||
unclear-type
|
||||
unsafe-getters-setters
|
||||
untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.92.0
|
||||
^0.98.0
|
||||
|
||||
1
.gitignore
vendored
@@ -73,6 +73,7 @@ buck-out/
|
||||
# Build artifacts
|
||||
*.jsbundle
|
||||
*.framework
|
||||
android/app/debug
|
||||
android/app/release
|
||||
|
||||
# precommit-hook
|
||||
|
||||
@@ -2,3 +2,5 @@ osx_image: xcode10.2
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
after_script:
|
||||
- sleep 10
|
||||
|
||||
2
Makefile
@@ -45,6 +45,8 @@ deploy-appbundle:
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.map \
|
||||
$(BUILD_DIR)/video-blur-effect.min.js \
|
||||
$(BUILD_DIR)/video-blur-effect.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
|
||||
@@ -207,24 +207,6 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
|
||||
|
||||
</details>
|
||||
|
||||
Starting with SDK version 1.22, a Glide module must be provided by the host app.
|
||||
This makes it possible to use the Glide image processing library from both the
|
||||
SDK and the host app itself.
|
||||
|
||||
You can use the code in `JitsiGlideModule.java` and adjust the package name.
|
||||
When building, add the following code in your `app/build.gradle` file, adjusting
|
||||
the Glide version to match the one in https://github.com/jitsi/jitsi-meet/blob/master/android/build.gradle
|
||||
|
||||
```
|
||||
// Glide
|
||||
implementation("com.github.bumptech.glide:glide:${glideVersion}") {
|
||||
exclude group: "com.android.support", module: "glide"
|
||||
}
|
||||
implementation("com.github.bumptech.glide:annotations:${glideVersion}") {
|
||||
exclude group: "com.android.support", module: "annotations"
|
||||
}
|
||||
```
|
||||
|
||||
### JitsiMeetActivity
|
||||
|
||||
This class encapsulates a high level API in the form of an Android `FragmentActivity`
|
||||
|
||||
6
android/app/.classpath
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin/default"/>
|
||||
</classpath>
|
||||
23
android/app/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>app</name>
|
||||
<comment>Project app created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
2
android/app/.settings/org.eclipse.buildship.core.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
||||
@@ -34,8 +34,6 @@ android {
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
|
||||
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
|
||||
buildConfigField "boolean", "LIBRE_BUILD", "${rootProject.ext.libreBuild}"
|
||||
}
|
||||
@@ -70,8 +68,8 @@ repositories {
|
||||
|
||||
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 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
@@ -88,15 +86,6 @@ dependencies {
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
|
||||
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
|
||||
|
||||
// Glide
|
||||
implementation("com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}") {
|
||||
exclude group: "com.android.support", module: "glide"
|
||||
}
|
||||
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}"
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
-include proguard-rules.pro
|
||||
|
||||
# Disabling obfuscation is useful if you collect stack traces from production crashes
|
||||
# (unless you are using a system that supports de-obfuscate the stack traces).
|
||||
-dontobfuscate
|
||||
22
android/app/proguard-rules.pro
vendored
@@ -9,13 +9,6 @@
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
|
||||
# React Native
|
||||
|
||||
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||
@@ -60,19 +53,9 @@
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-keep class okio.** { *; }
|
||||
-dontwarn okio.**
|
||||
|
||||
# FastImage + Glide
|
||||
|
||||
-keep public class com.dylanvann.fastimage.* {*;}
|
||||
-keep public class com.dylanvann.fastimage.** {*;}
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
# WebRTC
|
||||
|
||||
-keep class org.webrtc.** { *; }
|
||||
@@ -100,3 +83,6 @@
|
||||
-dontwarn javax.servlet.**
|
||||
|
||||
# ^^^ We added the above when we switched minifyEnabled on.
|
||||
|
||||
# Rule to avoid build errors related to SVGs.
|
||||
-keep public class com.horcrux.svg.** {*;}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.jitsi.meet;
|
||||
|
||||
import com.bumptech.glide.annotation.GlideModule;
|
||||
import com.bumptech.glide.module.AppGlideModule;
|
||||
|
||||
/**
|
||||
* An AppGlideModule needs to be present for image loading events to work in
|
||||
* react-native-fast-image. However, if this is defined by the SDK it will cause trouble with
|
||||
* apps which are using Glide themselves.
|
||||
*
|
||||
* In order to avoid the problem, define a Jitsi Glide module here, so applications already using
|
||||
* it are not in trouble.
|
||||
*/
|
||||
@GlideModule
|
||||
public final class JitsiGlideModule extends AppGlideModule {
|
||||
}
|
||||
@@ -21,9 +21,9 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeet;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
@@ -106,7 +106,7 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
//
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
|
||||
&& canRequestOverlayPermission()) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
|
||||
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 659 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 379 B |
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 960 B |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#66A8DD</color>
|
||||
</resources>
|
||||
@@ -2,5 +2,6 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:navigationBarColor">#1081B2</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -25,9 +25,10 @@ allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from
|
||||
// npm.
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
// Android JSC is installed from npm.
|
||||
maven { url("$rootDir/../node_modules/jsc-android/dist") }
|
||||
}
|
||||
|
||||
// Make sure we use the react-native version in node_modules and not the one
|
||||
@@ -74,6 +75,13 @@ allprojects {
|
||||
def versionQualifierNumber = (int)(((new Date().getTime()/1000) - 1546297200) / 10)
|
||||
|
||||
afterEvaluate { project ->
|
||||
if (project.plugins.hasPlugin('android') || project.plugins.hasPlugin('android-library')) {
|
||||
project.android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
}
|
||||
}
|
||||
|
||||
if (project.name.startsWith('react-native-')) {
|
||||
def npmManifest = project.file('../package.json')
|
||||
def json = new JsonSlurper().parseText(npmManifest.text)
|
||||
@@ -83,17 +91,6 @@ allprojects {
|
||||
|
||||
project.version = "${json.version}-jitsi-${versionQualifierNumber}"
|
||||
|
||||
project.android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
if (rootProject.ext.has('buildToolsVersion')) {
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
}
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from android.sourceSets.main.java.source
|
||||
@@ -164,12 +161,8 @@ ext {
|
||||
mavenUser = System.env.MVN_USER ?: ""
|
||||
mavenPassword = System.env.MVN_PASSWORD ?: ""
|
||||
|
||||
// Glide
|
||||
excludeAppGlideModule = true
|
||||
glideVersion = "4.7.1" // keep in sync with react-native-fast-image
|
||||
|
||||
// Libre build
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "true").toBoolean()
|
||||
libreBuild = (System.env.LIBRE_BUILD ?: "false").toBoolean()
|
||||
}
|
||||
|
||||
// If Android SDK is not installed, accept its license so that it
|
||||
|
||||
@@ -17,5 +17,8 @@
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
appVersion=19.2.0
|
||||
sdkVersion=2.1.0
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=19.3.0
|
||||
sdkVersion=2.3.1
|
||||
|
||||
@@ -10,6 +10,8 @@ MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
MVN_HTTP=1
|
||||
@@ -36,14 +38,20 @@ if [[ $MVN_HTTP == 1 ]]; then
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom || true
|
||||
popd
|
||||
# Push JSC
|
||||
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-DrepositoryId=${MVN_REPO_ID} \
|
||||
-Dfile=android-jsc-${JSC_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=android-jsc-${JSC_VERSION}.pom || true
|
||||
popd
|
||||
else
|
||||
# Check if an SDK with that same version has already been released
|
||||
if [[ -d ${MVN_REPO}/org/jitsi/react/jitsi-meet-sdk/${SDK_VERSION} ]]; then
|
||||
echo "There is already a release with that version in the Maven repo!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# First push React Native, if necessary
|
||||
# Push React Native, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/com/facebook/react/react-native/${RN_VERSION} ]]; then
|
||||
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
|
||||
@@ -56,6 +64,26 @@ else
|
||||
-DpomFile=react-native-${RN_VERSION}.pom
|
||||
popd
|
||||
fi
|
||||
|
||||
# Push JSC, if necessary
|
||||
if [[ ! -d ${MVN_REPO}/org/webkit/android-jsc/${JSC_VERSION} ]]; then
|
||||
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/jsc-android/dist/org/webkit/android-jsc/${JSC_VERSION}
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-Dfile=android-jsc-${JSC_VERSION}.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=android-jsc-${JSC_VERSION}.pom
|
||||
popd
|
||||
fi
|
||||
|
||||
# Check if an SDK with that same version has already been released
|
||||
if [[ -d ${MVN_REPO}/org/jitsi/react/jitsi-meet-sdk/${SDK_VERSION} ]]; then
|
||||
echo "There is already a release with that version in the Maven repo!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Now build and publish the Jitsi Meet SDK and its dependencies
|
||||
@@ -64,17 +92,15 @@ pushd ${THIS_DIR}/../
|
||||
./gradlew clean assembleRelease publish
|
||||
popd
|
||||
|
||||
if [[ $MVN_HTTP == 0 ]]; then
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
pushd ${MVN_REPO_PATH}
|
||||
if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]]; then
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies"
|
||||
fi
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
git tag -a android-sdk-${SDK_VERSION}
|
||||
git tag android-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
# Done!
|
||||
|
||||
@@ -8,6 +8,8 @@ THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOUR
|
||||
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}"
|
||||
echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${THIS_DIR}/../../node_modules/react-native/scripts/.packager.env"
|
||||
|
||||
adb reverse tcp:8081 tcp:8081
|
||||
|
||||
if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
|
||||
if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then
|
||||
echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly"
|
||||
|
||||
6
android/sdk/.classpath
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin/default"/>
|
||||
</classpath>
|
||||
23
android/sdk/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>sdk</name>
|
||||
<comment>Project sdk created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
2
android/sdk/.settings/org.eclipse.buildship.core.prefs
Normal file
@@ -0,0 +1,2 @@
|
||||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
||||
@@ -36,32 +36,35 @@ android {
|
||||
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 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.fragment:fragment:1.0.0'
|
||||
|
||||
api 'com.facebook.react:react-native:+'
|
||||
implementation 'org.webkit:android-jsc:+'
|
||||
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.amplitude:android-sdk:2.14.1'
|
||||
implementation(project(":react-native-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'com.android.support'
|
||||
exclude group: 'androidx'
|
||||
}
|
||||
}
|
||||
|
||||
implementation project(':react-native-background-timer')
|
||||
implementation project(':react-native-calendar-events')
|
||||
implementation(project(':react-native-fast-image')) {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
implementation project(':react-native-community-async-storage')
|
||||
implementation project(':react-native-community_netinfo')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-linear-gradient')
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':react-native-svg')
|
||||
implementation project(':react-native-webrtc')
|
||||
implementation project(':react-native-webview')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
@@ -137,19 +140,14 @@ android.libraryVariants.all { def variant ->
|
||||
mergeAssetsTask.doLast {
|
||||
def assetsDir = mergeAssetsTask.outputDir
|
||||
|
||||
// Bundle fonts
|
||||
//
|
||||
copy {
|
||||
from("${projectDir}/../../fonts/jitsi.ttf")
|
||||
into("${assetsDir}/fonts")
|
||||
}
|
||||
|
||||
// Bundle sounds
|
||||
//
|
||||
copy {
|
||||
from("${projectDir}/../../sounds/incomingMessage.wav")
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/liveStreamingOn.mp3")
|
||||
from("${projectDir}/../../sounds/liveStreamingOff.mp3")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
from("${projectDir}/../../sounds/recordingOn.mp3")
|
||||
@@ -205,8 +203,7 @@ publishing {
|
||||
def groupId = it.moduleGroup
|
||||
def artifactId = it.moduleName
|
||||
|
||||
if (artifactId.startsWith('react-native-')
|
||||
&& groupId.equals('jitsi-meet')) {
|
||||
if (artifactId.startsWith('react-native-') && groupId.equals('jitsi-meet')) {
|
||||
groupId = rootProject.ext.moduleGroupId
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
@@ -44,6 +45,8 @@
|
||||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -24,6 +24,7 @@ import com.facebook.react.bridge.ReadableMap;
|
||||
import com.amplitude.api.Amplitude;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -52,6 +53,17 @@ class AmplitudeModule
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user ID for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userId The new value for the user ID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserId(String instanceName, String userId) {
|
||||
Amplitude.getInstance(instanceName).setUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user properties for an Amplitude instance.
|
||||
*
|
||||
@@ -79,7 +91,7 @@ class AmplitudeModule
|
||||
JSONObject eventProps = new JSONObject(eventPropsString);
|
||||
Amplitude.getInstance(instanceName).logEvent(eventType, eventProps);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
JitsiMeetLogger.e(e, "Error logging event");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@ import android.content.pm.PackageManager;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -37,6 +36,8 @@ import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -121,7 +122,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
case DEVICE_SPEAKER:
|
||||
return android.telecom.CallAudioState.ROUTE_SPEAKER;
|
||||
default:
|
||||
Log.e(TAG, "Unsupported device name: " + audioDevice);
|
||||
JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
|
||||
return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
||||
}
|
||||
}
|
||||
@@ -218,7 +219,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
availableDevices = devices;
|
||||
Log.d(TAG, "Available audio devices: " +
|
||||
JitsiMeetLogger.i(TAG + " Available audio devices: " +
|
||||
availableDevices.toString());
|
||||
|
||||
// Reset user selection
|
||||
@@ -230,19 +231,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link Runnable} for running update operation on the main thread.
|
||||
*/
|
||||
private final Runnable updateAudioRouteRunner
|
||||
= new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mode != -1) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Audio mode currently in use.
|
||||
*/
|
||||
@@ -256,6 +244,11 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
private static final String DEVICE_HEADPHONES = "HEADPHONES";
|
||||
private static final String DEVICE_SPEAKER = "SPEAKER";
|
||||
|
||||
/**
|
||||
* Device change event.
|
||||
*/
|
||||
private static final String DEVICE_CHANGE_EVENT = "org.jitsi.meet:features/audio-mode#devices-update";
|
||||
|
||||
/**
|
||||
* List of currently available audio devices.
|
||||
*/
|
||||
@@ -303,7 +296,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Do an initial detection on Android >= M.
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
onAudioDeviceChange();
|
||||
} else {
|
||||
// On Android < M, detect if we have an earpiece.
|
||||
PackageManager pm = reactContext.getPackageManager();
|
||||
@@ -327,6 +320,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
public Map<String, Object> getConstants() {
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put("DEVICE_CHANGE_EVENT", DEVICE_CHANGE_EVENT);
|
||||
constants.put("AUDIO_CALL", AUDIO_CALL);
|
||||
constants.put("DEFAULT", DEFAULT);
|
||||
constants.put("VIDEO_CALL", VIDEO_CALL);
|
||||
@@ -335,31 +329,26 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of available audio device categories, i.e. 'bluetooth',
|
||||
* 'earpiece ', 'speaker', 'headphones'.
|
||||
*
|
||||
* @param promise a {@link Promise} which will be resolved with an object
|
||||
* containing a 'devices' key with a list of devices, plus a
|
||||
* 'selected' key with the selected one.
|
||||
* Notifies JS land that the devices list has changed.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getAudioDevices(final Promise promise) {
|
||||
private void notifyDevicesChanged() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("selected", selectedDevice);
|
||||
WritableArray devices = Arguments.createArray();
|
||||
WritableArray data = Arguments.createArray();
|
||||
final boolean hasHeadphones = availableDevices.contains(DEVICE_HEADPHONES);
|
||||
for (String device : availableDevices) {
|
||||
if (mode == VIDEO_CALL && device.equals(DEVICE_EARPIECE)) {
|
||||
// Skip earpiece when in video call mode.
|
||||
if (hasHeadphones && device.equals(DEVICE_EARPIECE)) {
|
||||
// Skip earpiece when headphones are plugged in.
|
||||
continue;
|
||||
}
|
||||
devices.pushString(device);
|
||||
WritableMap deviceInfo = Arguments.createMap();
|
||||
deviceInfo.putString("type", device);
|
||||
deviceInfo.putBoolean("selected", device.equals(selectedDevice));
|
||||
data.pushMap(deviceInfo);
|
||||
}
|
||||
map.putArray("devices", devices);
|
||||
|
||||
promise.resolve(map);
|
||||
ReactInstanceManagerHolder.emitEvent(DEVICE_CHANGE_EVENT, data);
|
||||
JitsiMeetLogger.i(TAG + " Updating audio device list");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -442,8 +431,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
if (audioDevicesChanged) {
|
||||
supportedRouteMask = newSupportedRoutes;
|
||||
availableDevices = routesToDeviceNames(supportedRouteMask);
|
||||
Log.d(TAG,
|
||||
"Available audio devices: "
|
||||
JitsiMeetLogger.i(TAG + " Available audio devices: "
|
||||
+ availableDevices.toString());
|
||||
}
|
||||
|
||||
@@ -477,7 +465,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN: {
|
||||
Log.d(TAG, "Audio focus gained");
|
||||
JitsiMeetLogger.d(TAG + " Audio focus gained");
|
||||
// Some other application potentially stole our audio focus
|
||||
// temporarily. Restore our mode.
|
||||
if (audioFocusLost) {
|
||||
@@ -489,7 +477,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
|
||||
Log.d(TAG, "Audio focus lost");
|
||||
JitsiMeetLogger.d(TAG + " Audio focus lost");
|
||||
audioFocusLost = true;
|
||||
break;
|
||||
}
|
||||
@@ -516,13 +504,13 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
@Override
|
||||
public void run() {
|
||||
if (!availableDevices.contains(device)) {
|
||||
Log.d(TAG, "Audio device not available: " + device);
|
||||
JitsiMeetLogger.w(TAG + " Audio device not available: " + device);
|
||||
userSelectedDevice = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode != -1) {
|
||||
Log.d(TAG, "User selected device set to: " + device);
|
||||
JitsiMeetLogger.i(TAG + " User selected device set to: " + device);
|
||||
userSelectedDevice = device;
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
@@ -584,7 +572,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean success;
|
||||
@@ -593,10 +581,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
success = updateAudioRoute(mode);
|
||||
} catch (Throwable e) {
|
||||
success = false;
|
||||
Log.e(
|
||||
TAG,
|
||||
"Failed to update audio route for mode: " + mode,
|
||||
e);
|
||||
JitsiMeetLogger.e(e, TAG + " Failed to update audio route for mode: " + mode);
|
||||
}
|
||||
if (success) {
|
||||
AudioModeModule.this.mode = mode;
|
||||
@@ -607,8 +592,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
"Failed to set audio mode to " + mode);
|
||||
}
|
||||
}
|
||||
};
|
||||
runInAudioThread(r);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,14 +617,14 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
@Override
|
||||
public void onAudioDevicesAdded(
|
||||
AudioDeviceInfo[] addedDevices) {
|
||||
Log.d(TAG, "Audio devices added");
|
||||
JitsiMeetLogger.d(TAG + " Audio devices added");
|
||||
onAudioDeviceChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(
|
||||
AudioDeviceInfo[] removedDevices) {
|
||||
Log.d(TAG, "Audio devices removed");
|
||||
JitsiMeetLogger.d(TAG + " Audio devices removed");
|
||||
onAudioDeviceChange();
|
||||
}
|
||||
};
|
||||
@@ -659,7 +643,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "Wired headset added / removed");
|
||||
JitsiMeetLogger.d(TAG + " Wired headset added / removed");
|
||||
onHeadsetDeviceChange();
|
||||
}
|
||||
};
|
||||
@@ -677,7 +661,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
private boolean updateAudioRoute(int mode) {
|
||||
Log.d(TAG, "Update audio route for mode: " + mode);
|
||||
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
|
||||
|
||||
if (mode == DEFAULT) {
|
||||
if (!useConnectionService()) {
|
||||
@@ -690,6 +674,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
selectedDevice = null;
|
||||
userSelectedDevice = null;
|
||||
|
||||
notifyDevicesChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -702,13 +687,12 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
AudioManager.STREAM_VOICE_CALL,
|
||||
AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
Log.d(TAG, "Audio focus request failed");
|
||||
JitsiMeetLogger.w(TAG + " Audio focus request failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
|
||||
boolean earpieceAvailable = availableDevices.contains(DEVICE_EARPIECE);
|
||||
boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
|
||||
|
||||
// Pick the desired device based on what's available and the mode.
|
||||
@@ -717,8 +701,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
audioDevice = DEVICE_BLUETOOTH;
|
||||
} else if (headsetAvailable) {
|
||||
audioDevice = DEVICE_HEADPHONES;
|
||||
} else if (mode == AUDIO_CALL && earpieceAvailable) {
|
||||
audioDevice = DEVICE_EARPIECE;
|
||||
} else {
|
||||
audioDevice = DEVICE_SPEAKER;
|
||||
}
|
||||
@@ -736,7 +718,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
selectedDevice = audioDevice;
|
||||
Log.d(TAG, "Selected audio device: " + audioDevice);
|
||||
JitsiMeetLogger.i(TAG + " Selected audio device: " + audioDevice);
|
||||
|
||||
if (useConnectionService()) {
|
||||
setAudioRoute(audioDevice);
|
||||
@@ -744,6 +726,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
||||
setAudioRoutePreO(audioDevice);
|
||||
}
|
||||
|
||||
notifyDevicesChanged();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,16 @@ package org.jitsi.meet.sdk;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -77,6 +78,15 @@ public abstract class BaseReactView<ListenerT>
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all registered React views.
|
||||
*
|
||||
* @return An {@link ArrayList} containing all views currently held by React.
|
||||
*/
|
||||
static ArrayList<BaseReactView> getViews() {
|
||||
return new ArrayList<>(views);
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code BaseReactView} within the process
|
||||
* for the purposes of {@link ExternalAPIModule}. The name scope was
|
||||
@@ -101,8 +111,7 @@ public abstract class BaseReactView<ListenerT>
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||
((Activity) context).getApplication());
|
||||
ReactInstanceManagerHolder.initReactInstanceManager((Activity)context);
|
||||
|
||||
// Hook this BaseReactView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
|
||||
@@ -24,7 +24,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* Helper class to detect and handle Bluetooth device changes. It monitors
|
||||
@@ -77,7 +78,7 @@ class BluetoothHeadsetMonitor {
|
||||
= (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
if (!audioManager.isBluetoothScoAvailableOffCall()) {
|
||||
Log.w(AudioModeModule.TAG, "Bluetooth SCO is not available");
|
||||
JitsiMeetLogger.w(AudioModeModule.TAG + " Bluetooth SCO is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +94,7 @@ class BluetoothHeadsetMonitor {
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
if (adapter == null) {
|
||||
Log.w(AudioModeModule.TAG, "Device doesn't support Bluetooth");
|
||||
JitsiMeetLogger.w(AudioModeModule.TAG + " Device doesn't support Bluetooth");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -148,9 +149,7 @@ class BluetoothHeadsetMonitor {
|
||||
switch (state) {
|
||||
case BluetoothHeadset.STATE_CONNECTED:
|
||||
case BluetoothHeadset.STATE_DISCONNECTED:
|
||||
Log.d(
|
||||
AudioModeModule.TAG,
|
||||
"BT headset connection state changed: " + state);
|
||||
JitsiMeetLogger.d(AudioModeModule.TAG + " BT headset connection state changed: " + state);
|
||||
updateDevices();
|
||||
break;
|
||||
}
|
||||
@@ -164,9 +163,7 @@ class BluetoothHeadsetMonitor {
|
||||
switch (state) {
|
||||
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
|
||||
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
|
||||
Log.d(
|
||||
AudioModeModule.TAG,
|
||||
"BT SCO connection state changed: " + state);
|
||||
JitsiMeetLogger.d(AudioModeModule.TAG + " BT SCO connection state changed: " + state);
|
||||
updateDevices();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -14,12 +13,14 @@ import android.telecom.PhoneAccount;
|
||||
import android.telecom.PhoneAccountHandle;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.telecom.VideoProfile;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -63,6 +64,16 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
static private final HashMap<String, Promise> startCallPromises
|
||||
= new HashMap<>();
|
||||
|
||||
/**
|
||||
* Aborts all ongoing connections. This is a last resort mechanism which forces all resources to
|
||||
* be freed on the system in case of fatal error.
|
||||
*/
|
||||
static void abortConnections() {
|
||||
for (ConnectionImpl connection: getConnections()) {
|
||||
connection.onAbort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@link ConnectionImpl} to the list.
|
||||
*
|
||||
@@ -119,9 +130,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
if (connection != null) {
|
||||
connection.setActive();
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"setConnectionActive - no connection for UUID: %s",
|
||||
callUUID));
|
||||
JitsiMeetLogger.e("%s setConnectionActive - no connection for UUID: %s", TAG, callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +161,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
connection.setDisconnected(cause);
|
||||
connection.destroy();
|
||||
} else {
|
||||
Log.e(TAG, "endCall no connection for UUID: " + callUUID);
|
||||
JitsiMeetLogger.e(TAG + " endCall no connection for UUID: " + callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,15 +193,14 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
boolean hasVideo
|
||||
= callState.getBoolean(ConnectionImpl.KEY_HAS_VIDEO);
|
||||
|
||||
Log.d(TAG, String.format(
|
||||
"updateCall: %s hasVideo: %s", callUUID, hasVideo));
|
||||
JitsiMeetLogger.i(" %s updateCall: %s hasVideo: %s", TAG, callUUID, hasVideo);
|
||||
connection.setVideoState(
|
||||
hasVideo
|
||||
? VideoProfile.STATE_BIDIRECTIONAL
|
||||
: VideoProfile.STATE_AUDIO_ONLY);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "updateCall no connection for UUID: " + callUUID);
|
||||
JitsiMeetLogger.e(TAG + " updateCall no connection for UUID: " + callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,13 +236,11 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
= unregisterStartCallPromise(connection.getCallUUID());
|
||||
|
||||
if (startCallPromise != null) {
|
||||
Log.d(TAG,
|
||||
"onCreateOutgoingConnection " + connection.getCallUUID());
|
||||
JitsiMeetLogger.d(TAG + " onCreateOutgoingConnection " + connection.getCallUUID());
|
||||
startCallPromise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, String.format(
|
||||
"onCreateOutgoingConnection: no start call Promise for %s",
|
||||
connection.getCallUUID()));
|
||||
JitsiMeetLogger.e(
|
||||
TAG + " onCreateOutgoingConnection: no start call Promise for " + connection.getCallUUID());
|
||||
}
|
||||
|
||||
return connection;
|
||||
@@ -258,7 +264,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
PhoneAccountHandle theAccountHandle = request.getAccountHandle();
|
||||
String callUUID = theAccountHandle.getId();
|
||||
|
||||
Log.e(TAG, "onCreateOutgoingConnectionFailed " + callUUID);
|
||||
JitsiMeetLogger.e(TAG + " onCreateOutgoingConnectionFailed " + callUUID);
|
||||
|
||||
if (callUUID != null) {
|
||||
Promise startCallPromise = unregisterStartCallPromise(callUUID);
|
||||
@@ -268,12 +274,10 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
"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));
|
||||
JitsiMeetLogger.e(TAG + " startCallFailed - no start call Promise for UUID: " + callUUID);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "onCreateOutgoingConnectionFailed - no call UUID");
|
||||
JitsiMeetLogger.e(TAG + " onCreateOutgoingConnectionFailed - no call UUID");
|
||||
}
|
||||
|
||||
unregisterPhoneAccount(theAccountHandle);
|
||||
@@ -285,10 +289,10 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
if (phoneAccountHandle != null) {
|
||||
telecom.unregisterPhoneAccount(phoneAccountHandle);
|
||||
} else {
|
||||
Log.e(TAG, "unregisterPhoneAccount - account handle is null");
|
||||
JitsiMeetLogger.e(TAG + " unregisterPhoneAccount - account handle is null");
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "unregisterPhoneAccount - telecom is null");
|
||||
JitsiMeetLogger.e(TAG + " unregisterPhoneAccount - telecom is null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +351,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onDisconnect() {
|
||||
Log.d(TAG, "onDisconnect " + getCallUUID());
|
||||
JitsiMeetLogger.i(TAG + " onDisconnect " + getCallUUID());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
@@ -367,7 +371,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onAbort() {
|
||||
Log.d(TAG, "onAbort " + getCallUUID());
|
||||
JitsiMeetLogger.i(TAG + " onAbort " + getCallUUID());
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
data.putString("callUUID", getCallUUID());
|
||||
ReactInstanceManagerHolder.emitEvent(
|
||||
@@ -385,9 +389,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
// 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()));
|
||||
JitsiMeetLogger.w(TAG + " onHold %s - HOLD is not supported, aborting the call...", getCallUUID());
|
||||
this.onAbort();
|
||||
}
|
||||
|
||||
@@ -400,7 +402,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onCallAudioStateChanged(CallAudioState state) {
|
||||
Log.d(TAG, "onCallAudioStateChanged: " + state);
|
||||
JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
|
||||
AudioModeModule audioModeModule
|
||||
= ReactInstanceManagerHolder
|
||||
.getNativeModule(AudioModeModule.class);
|
||||
@@ -416,10 +418,8 @@ public class ConnectionService extends android.telecom.ConnectionService {
|
||||
*/
|
||||
@Override
|
||||
public void onStateChanged(int state) {
|
||||
Log.d(TAG,
|
||||
String.format("onStateChanged: %s %s",
|
||||
Connection.stateToString(state),
|
||||
getCallUUID()));
|
||||
JitsiMeetLogger.d(
|
||||
"%s onStateChanged: %s %s", TAG, Connection.stateToString(state), getCallUUID());
|
||||
|
||||
if (state == STATE_DISCONNECTED) {
|
||||
removeConnection(this);
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
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.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* Module implementing an API for sending events from JavaScript to native code.
|
||||
*/
|
||||
@@ -67,16 +67,20 @@ class ExternalAPIModule
|
||||
*/
|
||||
@ReactMethod
|
||||
public void sendEvent(String name, ReadableMap data, String scope) {
|
||||
// Keep track of the current ongoing conference.
|
||||
OngoingConferenceTracker.getInstance().onExternalAPIEvent(name, data);
|
||||
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native ExternalAPI module so that the latter may match the
|
||||
// former to the native BaseReactView which hosts it.
|
||||
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view != null) {
|
||||
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
|
||||
try {
|
||||
view.onExternalAPIEvent(name, data);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "onExternalAPIEvent: error sending event", e);
|
||||
JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,15 @@ public class JitsiMeet {
|
||||
defaultConferenceOptions = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current conference URL as a string.
|
||||
*
|
||||
* @return the current conference URL.
|
||||
*/
|
||||
public static String getCurrentConference() {
|
||||
return OngoingConferenceTracker.getInstance().getCurrentConference();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the default conference options as a {@link Bundle}.
|
||||
*
|
||||
|
||||
@@ -20,12 +20,13 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@@ -38,8 +39,8 @@ public class JitsiMeetActivity extends FragmentActivity
|
||||
|
||||
protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
|
||||
|
||||
public static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
|
||||
public static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
|
||||
private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
|
||||
private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
|
||||
|
||||
// Helpers for starting the activity
|
||||
//
|
||||
@@ -71,6 +72,24 @@ public class JitsiMeetActivity extends FragmentActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// Here we are trying to handle the following corner case: an application using the SDK
|
||||
// is using this Activity for displaying meetings, but there is another "main" Activity
|
||||
// with other content. If this Activity is "swiped out" from the recent list we will get
|
||||
// Activity#onDestroy() called without warning. At this point we can try to leave the
|
||||
// current meeting, but when our view is detached from React the JS <-> Native bridge won't
|
||||
// be operational so the external API won't be able to notify the native side that the
|
||||
// conference terminated. Thus, try our best to clean up.
|
||||
leave();
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
ConnectionService.abortConnections();
|
||||
}
|
||||
JitsiMeetOngoingConferenceService.abort(this);
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
leave();
|
||||
@@ -143,6 +162,11 @@ public class JitsiMeetActivity extends FragmentActivity
|
||||
// Activity lifecycle methods
|
||||
//
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
JitsiMeetActivityDelegate.onActivityResult(this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
JitsiMeetActivityDelegate.onBackPressed();
|
||||
@@ -150,6 +174,8 @@ public class JitsiMeetActivity extends FragmentActivity
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
JitsiMeetConferenceOptions options;
|
||||
|
||||
if ((options = getConferenceOptions(intent)) != null) {
|
||||
@@ -183,17 +209,19 @@ public class JitsiMeetActivity extends FragmentActivity
|
||||
|
||||
@Override
|
||||
public void onConferenceJoined(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference joined: " + data);
|
||||
JitsiMeetLogger.i("Conference joined: " + data);
|
||||
// Launch the service for the ongoing notification.
|
||||
JitsiMeetOngoingConferenceService.launch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceTerminated(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference terminated: " + data);
|
||||
JitsiMeetLogger.i("Conference terminated: " + data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConferenceWillJoin(Map<String, Object> data) {
|
||||
Log.d(TAG, "Conference will join: " + data);
|
||||
JitsiMeetLogger.i("Conference will join: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.os.Build;
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,15 @@ public class JitsiMeetActivityDelegate {
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* Tells whether or not the permissions request is currently in progress.
|
||||
*
|
||||
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
|
||||
*/
|
||||
static boolean arePermissionsBeingRequested() {
|
||||
return permissionListener != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onActivityResult} so we are notified about results of external intents
|
||||
@@ -108,7 +118,13 @@ public class JitsiMeetActivityDelegate {
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
// Try to avoid a crash because some devices trip on this assert:
|
||||
// https://github.com/facebook/react-native/blob/df4e67fe75d781d1eb264128cadf079989542755/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java#L512
|
||||
// Why this happens is a mystery wrapped in an enigma.
|
||||
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
||||
if (activity == reactContext.getCurrentActivity()) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* Room name.
|
||||
*/
|
||||
private String room;
|
||||
/**
|
||||
* Conference subject.
|
||||
*/
|
||||
private String subject;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
@@ -50,6 +54,11 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
*/
|
||||
private Bundle colorScheme;
|
||||
|
||||
/**
|
||||
* Feature flags. See: https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js
|
||||
*/
|
||||
private Bundle featureFlags;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to join the conference with audio / video muted or to start in audio
|
||||
* only mode respectively.
|
||||
@@ -59,10 +68,9 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
private Boolean videoMuted;
|
||||
|
||||
/**
|
||||
* Set to {@code true} to enable the welcome page. Typically SDK users won't need this enabled
|
||||
* since the host application decides which meeting to join.
|
||||
* USer information, to be used when no token is specified.
|
||||
*/
|
||||
private Boolean welcomePageEnabled;
|
||||
private JitsiMeetUserInfo userInfo;
|
||||
|
||||
/**
|
||||
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
|
||||
@@ -70,17 +78,20 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
public static class Builder {
|
||||
private URL serverURL;
|
||||
private String room;
|
||||
private String subject;
|
||||
private String token;
|
||||
|
||||
private Bundle colorScheme;
|
||||
private Bundle featureFlags;
|
||||
|
||||
private Boolean audioMuted;
|
||||
private Boolean audioOnly;
|
||||
private Boolean videoMuted;
|
||||
|
||||
private Boolean welcomePageEnabled;
|
||||
private JitsiMeetUserInfo userInfo;
|
||||
|
||||
public Builder() {
|
||||
featureFlags = new Bundle();
|
||||
}
|
||||
|
||||
/**\
|
||||
@@ -105,6 +116,17 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the conference subject.
|
||||
* @param subject - Subject for the conference.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JWT token to be used for authentication when joining a conference.
|
||||
* @param token - The JWT token to be used for authentication.
|
||||
@@ -170,7 +192,31 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setWelcomePageEnabled(boolean enabled) {
|
||||
this.welcomePageEnabled = enabled;
|
||||
this.featureFlags.putBoolean("welcomepage.enabled", enabled);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFeatureFlag(String flag, boolean value) {
|
||||
this.featureFlags.putBoolean(flag, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFeatureFlag(String flag, String value) {
|
||||
this.featureFlags.putString(flag, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFeatureFlag(String flag, int value) {
|
||||
this.featureFlags.putInt(flag, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUserInfo(JitsiMeetUserInfo userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -185,12 +231,14 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
options.serverURL = this.serverURL;
|
||||
options.room = this.room;
|
||||
options.subject = this.subject;
|
||||
options.token = this.token;
|
||||
options.colorScheme = this.colorScheme;
|
||||
options.featureFlags = this.featureFlags;
|
||||
options.audioMuted = this.audioMuted;
|
||||
options.audioOnly = this.audioOnly;
|
||||
options.videoMuted = this.videoMuted;
|
||||
options.welcomePageEnabled = this.welcomePageEnabled;
|
||||
options.userInfo = this.userInfo;
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -201,32 +249,33 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
private JitsiMeetConferenceOptions(Parcel in) {
|
||||
room = in.readString();
|
||||
subject = in.readString();
|
||||
token = in.readString();
|
||||
colorScheme = in.readBundle();
|
||||
featureFlags = in.readBundle();
|
||||
userInfo = new JitsiMeetUserInfo(in.readBundle());
|
||||
byte tmpAudioMuted = in.readByte();
|
||||
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
|
||||
byte tmpAudioOnly = in.readByte();
|
||||
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
|
||||
byte tmpVideoMuted = in.readByte();
|
||||
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
|
||||
byte tmpWelcomePageEnabled = in.readByte();
|
||||
welcomePageEnabled = tmpWelcomePageEnabled == 0 ? null : tmpWelcomePageEnabled == 1;
|
||||
}
|
||||
|
||||
Bundle asProps() {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
// Android always has the PiP flag set by default.
|
||||
if (!featureFlags.containsKey("pip.enabled")) {
|
||||
featureFlags.putBoolean("pip.enabled", true);
|
||||
}
|
||||
|
||||
props.putBundle("flags", featureFlags);
|
||||
|
||||
if (colorScheme != null) {
|
||||
props.putBundle("colorScheme", colorScheme);
|
||||
}
|
||||
|
||||
if (welcomePageEnabled != null) {
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
}
|
||||
|
||||
// TODO: get rid of this.
|
||||
props.putBoolean("pictureInPictureEnabled", true);
|
||||
|
||||
Bundle config = new Bundle();
|
||||
|
||||
if (audioMuted != null) {
|
||||
@@ -238,6 +287,9 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
if (videoMuted != null) {
|
||||
config.putBoolean("startWithVideoMuted", videoMuted);
|
||||
}
|
||||
if (subject != null) {
|
||||
config.putString("subject", subject);
|
||||
}
|
||||
|
||||
Bundle urlProps = new Bundle();
|
||||
|
||||
@@ -257,6 +309,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
urlProps.putString("jwt", token);
|
||||
}
|
||||
|
||||
if (token == null && userInfo != null) {
|
||||
props.putBundle("userInfo", userInfo.asBundle());
|
||||
}
|
||||
|
||||
urlProps.putBundle("config", config);
|
||||
props.putBundle("url", urlProps);
|
||||
|
||||
@@ -281,12 +337,14 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(room);
|
||||
dest.writeString(subject);
|
||||
dest.writeString(token);
|
||||
dest.writeBundle(colorScheme);
|
||||
dest.writeBundle(featureFlags);
|
||||
dest.writeBundle(userInfo != null ? userInfo.asBundle() : new Bundle());
|
||||
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
||||
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
|
||||
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
|
||||
dest.writeByte((byte) (welcomePageEnabled == null ? 0 : welcomePageEnabled ? 1 : 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,15 +19,14 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
|
||||
/**
|
||||
* This class implements an Android {@link Service}, a foreground one specifically, and it's
|
||||
* responsible for presenting an ongoing notification when a conference is in progress.
|
||||
* The service will help keep the app running while in the background.
|
||||
*
|
||||
* See: https://developer.android.com/guide/components/services
|
||||
*/
|
||||
public class JitsiMeetOngoingConferenceService extends Service
|
||||
implements OngoingConferenceTracker.OngoingConferenceListener {
|
||||
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
|
||||
|
||||
static final class Actions {
|
||||
static final String START = TAG + ":START";
|
||||
static final String HANGUP = TAG + ":HANGUP";
|
||||
}
|
||||
|
||||
static void launch(Context context) {
|
||||
OngoingNotification.createOngoingConferenceNotificationChannel();
|
||||
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
intent.setAction(Actions.START);
|
||||
|
||||
ComponentName componentName;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
componentName = context.startForegroundService(intent);
|
||||
} else {
|
||||
componentName = context.startService(intent);
|
||||
}
|
||||
if (componentName == null) {
|
||||
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
|
||||
}
|
||||
}
|
||||
|
||||
static void abort(Context context) {
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
context.stopService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
OngoingConferenceTracker.getInstance().addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
OngoingConferenceTracker.getInstance().removeListener(this);
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
final String action = intent.getAction();
|
||||
if (action.equals(Actions.START)) {
|
||||
Notification notification = OngoingNotification.buildOngoingConferenceNotification();
|
||||
if (notification == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
|
||||
} else {
|
||||
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
|
||||
JitsiMeetLogger.i(TAG + " Service started");
|
||||
}
|
||||
} else if (action.equals(Actions.HANGUP)) {
|
||||
JitsiMeetLogger.i(TAG + " Hangup requested");
|
||||
// Abort all ongoing calls
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
ConnectionService.abortConnections();
|
||||
}
|
||||
stopSelf();
|
||||
} else {
|
||||
JitsiMeetLogger.w(TAG + " Unknown action received: " + action);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCurrentConferenceChanged(String conferenceUrl) {
|
||||
if (conferenceUrl == null) {
|
||||
stopSelf();
|
||||
JitsiMeetLogger.i(TAG + "Service stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
class JitsiMeetUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||||
private final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
|
||||
|
||||
public static void register() {
|
||||
Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
|
||||
JitsiMeetUncaughtExceptionHandler uncaughtExceptionHandler
|
||||
= new JitsiMeetUncaughtExceptionHandler(defaultUncaughtExceptionHandler);
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
|
||||
}
|
||||
|
||||
private JitsiMeetUncaughtExceptionHandler(Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler) {
|
||||
this.defaultUncaughtExceptionHandler = defaultUncaughtExceptionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
JitsiMeetLogger.e(e, this.getClass().getSimpleName() + " FATAL ERROR");
|
||||
|
||||
// Abort all ConnectionService ongoing calls
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
ConnectionService.abortConnections();
|
||||
}
|
||||
|
||||
if (defaultUncaughtExceptionHandler != null) {
|
||||
defaultUncaughtExceptionHandler.uncaughtException(t, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* This class represents user information to be passed to {@link JitsiMeetConferenceOptions} for
|
||||
* identifying a user.
|
||||
*/
|
||||
public class JitsiMeetUserInfo {
|
||||
/**
|
||||
* User's display name.
|
||||
*/
|
||||
private String displayName;
|
||||
|
||||
/**
|
||||
* User's email address.
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* User's avatar URL.
|
||||
*/
|
||||
private URL avatar;
|
||||
|
||||
public JitsiMeetUserInfo() {}
|
||||
|
||||
public JitsiMeetUserInfo(Bundle b) {
|
||||
super();
|
||||
|
||||
if (b.containsKey("displayName")) {
|
||||
displayName = b.getString("displayName");
|
||||
}
|
||||
|
||||
if (b.containsKey("email")) {
|
||||
email = b.getString("email");
|
||||
}
|
||||
|
||||
if (b.containsKey("avatarURL")) {
|
||||
String avatarURL = b.getString("avatarURL");
|
||||
try {
|
||||
avatar = new URL(avatarURL);
|
||||
} catch (MalformedURLException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public URL getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(URL avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
Bundle asBundle() {
|
||||
Bundle b = new Bundle();
|
||||
|
||||
if (displayName != null) {
|
||||
b.putString("displayName", displayName);
|
||||
}
|
||||
|
||||
if (email != null) {
|
||||
b.putString("email", email);
|
||||
}
|
||||
|
||||
if (avatar != null) {
|
||||
b.putString("avatarURL", avatar.toString());
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
}
|
||||
@@ -19,17 +19,19 @@ package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
implements OngoingConferenceTracker.OngoingConferenceListener {
|
||||
|
||||
/**
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
@@ -38,12 +40,6 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
private static final Map<String, Method> LISTENER_METHODS
|
||||
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
|
||||
|
||||
/**
|
||||
* The {@link Log} tag which identifies the source of the log messages of
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
private static final String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* The URL of the current conference.
|
||||
*/
|
||||
@@ -106,6 +102,14 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
if (!(context instanceof JitsiMeetActivityInterface)) {
|
||||
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
|
||||
}
|
||||
|
||||
OngoingConferenceTracker.getInstance().addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
OngoingConferenceTracker.getInstance().removeListener(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,11 +127,12 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
PictureInPictureModule.class);
|
||||
if (pipModule != null
|
||||
&& PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
|
||||
&& this.url != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "failed to enter PiP mode", re);
|
||||
JitsiMeetLogger.e(re, "Failed to enter PiP mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,27 +177,17 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal processing for the URL of the current conference set on the
|
||||
* associated {@link JitsiMeetView}.
|
||||
*
|
||||
* @param eventName the name of the external API event to be processed
|
||||
* @param eventData the details/specifics of the event to process determined
|
||||
* by/associated with the specified {@code eventName}.
|
||||
* Handler for {@link OngoingConferenceTracker} events.
|
||||
* @param conferenceUrl
|
||||
*/
|
||||
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
|
||||
String url = eventData.getString("url");
|
||||
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
this.url = url;
|
||||
break;
|
||||
|
||||
case "CONFERENCE_TERMINATED":
|
||||
if (url != null && url.equals(this.url)) {
|
||||
this.url = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@Override
|
||||
public void onCurrentConferenceChanged(String conferenceUrl) {
|
||||
// This property was introduced in order to address
|
||||
// an exception in the Picture-in-Picture functionality which arose
|
||||
// because of delays related to bridging between JavaScript and Java. To
|
||||
// reduce these delays do not wait for the call to be transferred to the
|
||||
// UI thread.
|
||||
this.url = conferenceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,13 +199,6 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
*/
|
||||
@Override
|
||||
protected void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
// XXX The JitsiMeetView property URL was introduced in order to address
|
||||
// an exception in the Picture-in-Picture functionality which arose
|
||||
// because of delays related to bridging between JavaScript and Java. To
|
||||
// reduce these delays do not wait for the call to be transferred to the
|
||||
// UI thread.
|
||||
maybeSetViewURL(name, data);
|
||||
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Module implementing a "bridge" between the JS loggers and the native one.
|
||||
*/
|
||||
@ReactModule(name = LogBridgeModule.NAME)
|
||||
class LogBridgeModule extends ReactContextBaseJavaModule {
|
||||
public static final String NAME = "LogBridge";
|
||||
|
||||
public LogBridgeModule(@Nonnull ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void trace(final String message) {
|
||||
JitsiMeetLogger.v(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void debug(final String message) {
|
||||
JitsiMeetLogger.d(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void info(final String message) {
|
||||
JitsiMeetLogger.i(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void log(final String message) {
|
||||
JitsiMeetLogger.i(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void warn(final String message) {
|
||||
JitsiMeetLogger.w(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void error(final String message) {
|
||||
JitsiMeetLogger.e(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to keep track of what the current conference is.
|
||||
*/
|
||||
class OngoingConferenceTracker {
|
||||
private static final OngoingConferenceTracker instance = new OngoingConferenceTracker();
|
||||
|
||||
private static final String CONFERENCE_WILL_JOIN = "CONFERENCE_WILL_JOIN";
|
||||
private static final String CONFERENCE_TERMINATED = "CONFERENCE_TERMINATED";
|
||||
|
||||
private final Collection<OngoingConferenceListener> listeners =
|
||||
Collections.synchronizedSet(new HashSet<OngoingConferenceListener>());
|
||||
private String currentConference;
|
||||
|
||||
public OngoingConferenceTracker() {
|
||||
}
|
||||
|
||||
public static OngoingConferenceTracker getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current active conference URL.
|
||||
*
|
||||
* @return - The current conference URL as a String.
|
||||
*/
|
||||
synchronized String getCurrentConference() {
|
||||
return currentConference;
|
||||
}
|
||||
|
||||
synchronized void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
if (!data.hasKey("url")) {
|
||||
return;
|
||||
}
|
||||
|
||||
String url = data.getString("url");
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(name) {
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
currentConference = url;
|
||||
updateListeners();
|
||||
break;
|
||||
|
||||
case CONFERENCE_TERMINATED:
|
||||
if (url.equals(currentConference)) {
|
||||
currentConference = null;
|
||||
updateListeners();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void addListener(OngoingConferenceListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
void removeListener(OngoingConferenceListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
private void updateListeners() {
|
||||
synchronized (listeners) {
|
||||
for (OngoingConferenceListener listener : listeners) {
|
||||
listener.onCurrentConferenceChanged(currentConference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OngoingConferenceListener {
|
||||
void onCurrentConferenceChanged(String conferenceUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for creating the ongoing notification which is used with
|
||||
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
|
||||
* and to hangup from within the notification itself.
|
||||
*/
|
||||
class OngoingNotification {
|
||||
private static final String TAG = OngoingNotification.class.getSimpleName();
|
||||
|
||||
private static final String CHANNEL_ID = "JitsiNotificationChannel";
|
||||
private static final String CHANNEL_NAME = "Ongoing Conference Notifications";
|
||||
|
||||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
|
||||
|
||||
|
||||
static void createOngoingConferenceNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationManager notificationManager
|
||||
= (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel channel
|
||||
= notificationManager.getNotificationChannel(CHANNEL_ID);
|
||||
if (channel != null) {
|
||||
// The channel was already created, no need to do it again.
|
||||
return;
|
||||
}
|
||||
|
||||
channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
channel.setShowBadge(false);
|
||||
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
static Notification buildOngoingConferenceNotification() {
|
||||
Context context = ReactInstanceManagerHolder.getCurrentActivity();
|
||||
if (context == null) {
|
||||
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
|
||||
return null;
|
||||
}
|
||||
|
||||
Intent notificationIntent = new Intent(context, context.getClass());
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||
|
||||
NotificationCompat.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder = new NotificationCompat.Builder(context, CHANNEL_ID);
|
||||
} else {
|
||||
builder = new NotificationCompat.Builder(context);
|
||||
}
|
||||
|
||||
builder
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setContentTitle(context.getString(R.string.ongoing_notification_title))
|
||||
.setContentText(context.getString(R.string.ongoing_notification_text))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setUsesChronometer(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
|
||||
|
||||
// Add a "hang-up" action only if we are using ConnectionService.
|
||||
if (AudioModeModule.useConnectionService()) {
|
||||
Intent hangupIntent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
hangupIntent.setAction(JitsiMeetOngoingConferenceService.Actions.HANGUP);
|
||||
PendingIntent hangupPendingIntent
|
||||
= PendingIntent.getService(context, 0, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action hangupAction = new NotificationCompat.Action(0, "Hang up", hangupPendingIntent);
|
||||
|
||||
builder.addAction(hangupAction);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -29,6 +28,8 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
@ReactModule(name = PictureInPictureModule.NAME)
|
||||
class PictureInPictureModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
@@ -70,7 +71,7 @@ class PictureInPictureModule
|
||||
throw new IllegalStateException("No current Activity!");
|
||||
}
|
||||
|
||||
Log.d(TAG, "Entering Picture-in-Picture");
|
||||
JitsiMeetLogger.i(TAG + " Entering Picture-in-Picture");
|
||||
|
||||
PictureInPictureParams.Builder builder
|
||||
= new PictureInPictureParams.Builder()
|
||||
|
||||
@@ -5,13 +5,12 @@ 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 androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
@@ -20,6 +19,8 @@ import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
/**
|
||||
* The react-native side of Jitsi Meet's {@link ConnectionService}. Exposes
|
||||
* the Java Script API.
|
||||
@@ -74,11 +75,11 @@ class RNConnectionService
|
||||
String handle,
|
||||
boolean hasVideo,
|
||||
Promise promise) {
|
||||
Log.d(TAG,
|
||||
String.format("startCall UUID=%s, h=%s, v=%s",
|
||||
JitsiMeetLogger.d("%s startCall UUID=%s, h=%s, v=%s",
|
||||
TAG,
|
||||
callUUID,
|
||||
handle,
|
||||
hasVideo));
|
||||
hasVideo);
|
||||
|
||||
ReactApplicationContext ctx = getReactApplicationContext();
|
||||
|
||||
@@ -118,7 +119,7 @@ class RNConnectionService
|
||||
*/
|
||||
@ReactMethod
|
||||
public void reportCallFailed(String callUUID) {
|
||||
Log.d(TAG, "reportCallFailed " + callUUID);
|
||||
JitsiMeetLogger.d(TAG + " reportCallFailed " + callUUID);
|
||||
ConnectionService.setConnectionDisconnected(
|
||||
callUUID,
|
||||
new DisconnectCause(DisconnectCause.ERROR));
|
||||
@@ -131,7 +132,7 @@ class RNConnectionService
|
||||
*/
|
||||
@ReactMethod
|
||||
public void endCall(String callUUID) {
|
||||
Log.d(TAG, "endCall " + callUUID);
|
||||
JitsiMeetLogger.d(TAG + " endCall " + callUUID);
|
||||
ConnectionService.setConnectionDisconnected(
|
||||
callUUID,
|
||||
new DisconnectCause(DisconnectCause.LOCAL));
|
||||
@@ -143,9 +144,10 @@ class RNConnectionService
|
||||
* @param callUUID - the call's UUID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void reportConnectedOutgoingCall(String callUUID) {
|
||||
Log.d(TAG, "reportConnectedOutgoingCall " + callUUID);
|
||||
public void reportConnectedOutgoingCall(String callUUID, Promise promise) {
|
||||
JitsiMeetLogger.d(TAG + " reportConnectedOutgoingCall " + callUUID);
|
||||
ConnectionService.setConnectionActive(callUUID);
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.app.Activity;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
@@ -27,7 +27,20 @@ import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.devsupport.DevInternalSettings;
|
||||
import com.facebook.react.jscexecutor.JSCExecutorFactory;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.oney.WebRTCModule.RTCVideoViewManager;
|
||||
import com.oney.WebRTCModule.WebRTCModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.webrtc.SoftwareVideoDecoderFactory;
|
||||
import org.webrtc.SoftwareVideoEncoderFactory;
|
||||
import org.webrtc.VideoDecoderFactory;
|
||||
import org.webrtc.VideoEncoderFactory;
|
||||
import org.webrtc.audio.AudioDeviceModule;
|
||||
import org.webrtc.audio.JavaAudioDeviceModule;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
@@ -46,8 +59,7 @@ class ReactInstanceManagerHolder {
|
||||
*/
|
||||
private static ReactInstanceManager reactInstanceManager;
|
||||
|
||||
private static List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
private static List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> nativeModules
|
||||
= new ArrayList<>(Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
@@ -56,6 +68,7 @@ class ReactInstanceManagerHolder {
|
||||
new DropboxModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new LocaleDetector(reactContext),
|
||||
new LogBridgeModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
@@ -65,8 +78,23 @@ class ReactInstanceManagerHolder {
|
||||
nativeModules.add(new RNConnectionService(reactContext));
|
||||
}
|
||||
|
||||
// Initialize the WebRTC module by hand, since we want to override some
|
||||
// initialization options.
|
||||
WebRTCModule.Options options = new WebRTCModule.Options();
|
||||
|
||||
AudioDeviceModule adm = JavaAudioDeviceModule.builder(reactContext)
|
||||
.createAudioDeviceModule();
|
||||
VideoDecoderFactory videoDecoderFactory = new SoftwareVideoDecoderFactory();
|
||||
VideoEncoderFactory videoEncoderFactory = new SoftwareVideoEncoderFactory();
|
||||
|
||||
options.setAudioDeviceModule(adm);
|
||||
options.setVideoDecoderFactory(videoDecoderFactory);
|
||||
options.setVideoEncoderFactory(videoEncoderFactory);
|
||||
|
||||
nativeModules.add(new WebRTCModule(reactContext, options));
|
||||
|
||||
try {
|
||||
Class<?> amplitudeModuleClass = Class.forName("AmplitudeModule");
|
||||
Class<?> amplitudeModuleClass = Class.forName("org.jitsi.meet.sdk.AmplitudeModule");
|
||||
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
|
||||
nativeModules.add((NativeModule)constructor.newInstance(reactContext));
|
||||
} catch (Exception e) {
|
||||
@@ -76,6 +104,13 @@ class ReactInstanceManagerHolder {
|
||||
return nativeModules;
|
||||
}
|
||||
|
||||
private static List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Arrays.<ViewManager>asList(
|
||||
// WebRTC, see createNativeModules for details.
|
||||
new RTCVideoViewManager()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to send an event to JavaScript.
|
||||
*
|
||||
@@ -120,6 +155,18 @@ class ReactInstanceManagerHolder {
|
||||
? reactContext.getNativeModule(nativeModuleClass) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current {@link Activity} linked to React Native.
|
||||
*
|
||||
* @return An activity attached to React Native.
|
||||
*/
|
||||
static Activity getCurrentActivity() {
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager != null
|
||||
? reactInstanceManager.getCurrentReactContext() : null;
|
||||
return reactContext != null ? reactContext.getCurrentActivity() : null;
|
||||
}
|
||||
|
||||
static ReactInstanceManager getReactInstanceManager() {
|
||||
return reactInstanceManager;
|
||||
}
|
||||
@@ -130,23 +177,26 @@ class ReactInstanceManagerHolder {
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param application {@code Application} instance which is running.
|
||||
* @param activity {@code Activity} current running Activity.
|
||||
*/
|
||||
static void initReactInstanceManager(Application application) {
|
||||
static void initReactInstanceManager(Activity activity) {
|
||||
if (reactInstanceManager != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SoLoader.init(activity, /* native exopackage */ false);
|
||||
|
||||
List<ReactPackage> packages
|
||||
= new ArrayList<>(Arrays.asList(
|
||||
new com.BV.LinearGradient.LinearGradientPackage(),
|
||||
new com.calendarevents.CalendarEventsPackage(),
|
||||
new com.corbt.keepawake.KCKeepAwakePackage(),
|
||||
new com.dylanvann.fastimage.FastImageViewPackage(),
|
||||
new com.facebook.react.shell.MainReactPackage(),
|
||||
new com.oblador.vectoricons.VectorIconsPackage(),
|
||||
new com.horcrux.svg.SvgPackage(),
|
||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage(),
|
||||
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
||||
new com.reactnativecommunity.netinfo.NetInfoPackage(),
|
||||
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
||||
new com.rnimmersive.RNImmersivePackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
new ReactPackageAdapter() {
|
||||
@@ -154,6 +204,10 @@ class ReactInstanceManagerHolder {
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return ReactInstanceManagerHolder.createNativeModules(reactContext);
|
||||
}
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return ReactInstanceManagerHolder.createViewManagers(reactContext);
|
||||
}
|
||||
}));
|
||||
|
||||
try {
|
||||
@@ -164,11 +218,17 @@ class ReactInstanceManagerHolder {
|
||||
// Ignore any error, the module is not compiled when LIBRE_BUILD is enabled.
|
||||
}
|
||||
|
||||
// Keep on using JSC, the jury is out on Hermes.
|
||||
JSCExecutorFactory jsFactory
|
||||
= new JSCExecutorFactory("", "");
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setApplication(activity.getApplication())
|
||||
.setCurrentActivity(activity)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.setJavaScriptExecutorFactory(jsFactory)
|
||||
.addPackages(packages)
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
@@ -180,5 +240,8 @@ class ReactInstanceManagerHolder {
|
||||
if (devSettings != null) {
|
||||
devSettings.setBundleDeltasEnabled(false);
|
||||
}
|
||||
|
||||
// Register our uncaught exception handler.
|
||||
JitsiMeetUncaughtExceptionHandler.register();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jitsi.meet.sdk;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
@@ -27,6 +26,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -144,8 +144,7 @@ class WiFiStatsModule
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("rssi", rssi)
|
||||
.put("signal", signalLevel)
|
||||
.put("timestamp",
|
||||
String.valueOf(System.currentTimeMillis()));
|
||||
.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
JSONArray addresses = new JSONArray();
|
||||
|
||||
@@ -185,17 +184,15 @@ class WiFiStatsModule
|
||||
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
Log.wtf(TAG,
|
||||
"Unable to NetworkInterface.getNetworkInterfaces()"
|
||||
);
|
||||
JitsiMeetLogger.e(e, TAG + " Unable to NetworkInterface.getNetworkInterfaces()");
|
||||
}
|
||||
|
||||
result.put("addresses", addresses);
|
||||
promise.resolve(result.toString());
|
||||
|
||||
Log.d(TAG, "WiFi stats: " + result.toString());
|
||||
JitsiMeetLogger.d(TAG + " WiFi stats: " + result.toString());
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to obtain wifi stats", e);
|
||||
JitsiMeetLogger.e(e, TAG + " Failed to obtain wifi stats");
|
||||
promise.reject(
|
||||
new Exception("Failed to obtain wifi stats"));
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class IncomingCallInfo {
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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.log;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Base class for all custom log handlers. Implementations must inherit from this class and
|
||||
* implement a `doLog` method which does the actual logging, in addition with a `getTag` method
|
||||
* with which to tag all logs coming into this logger.
|
||||
*
|
||||
* See {@link JitsiMeetDefaultLogHandler} for an example.
|
||||
*/
|
||||
public abstract class JitsiMeetBaseLogHandler extends Timber.Tree {
|
||||
@Override
|
||||
protected void log(int priority, @Nullable String tag, @NotNull String msg, @Nullable Throwable t) {
|
||||
String errmsg = Log.getStackTraceString(t);
|
||||
if (errmsg.isEmpty()) {
|
||||
doLog(priority, getDefaultTag(), msg);
|
||||
} else {
|
||||
doLog(priority, getDefaultTag(), MessageFormat.format("{0}\n{1}", msg, errmsg));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void doLog(int priority, @NotNull String tag, @NotNull String msg);
|
||||
|
||||
protected abstract String getDefaultTag();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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.log;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link JitsiMeetBaseLogHandler}. This is the main SDK logger, which
|
||||
* logs using the Android util.Log module.
|
||||
*/
|
||||
public class JitsiMeetDefaultLogHandler extends JitsiMeetBaseLogHandler {
|
||||
private static final String TAG = "JitsiMeetSDK";
|
||||
|
||||
@Override
|
||||
protected void doLog(int priority, @NotNull String tag, @NotNull String msg) {
|
||||
Log.println(priority, tag, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultTag() {
|
||||
return TAG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
*
|
||||
* 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.log;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class JitsiMeetLogger {
|
||||
static {
|
||||
addHandler(new JitsiMeetDefaultLogHandler());
|
||||
}
|
||||
|
||||
public static void addHandler(JitsiMeetBaseLogHandler handler) {
|
||||
Timber.plant(handler);
|
||||
}
|
||||
|
||||
public static void removeHandler(JitsiMeetBaseLogHandler handler) {
|
||||
Timber.uproot(handler);
|
||||
}
|
||||
|
||||
public static void v(String message, Object... args) {
|
||||
Timber.v(message, args);
|
||||
}
|
||||
|
||||
public static void v(Throwable t, String message, Object... args) {
|
||||
Timber.v(t, message, args);
|
||||
}
|
||||
|
||||
public static void v(Throwable t) {
|
||||
Timber.v(t);
|
||||
}
|
||||
|
||||
public static void d(String message, Object... args) {
|
||||
Timber.d(message, args);
|
||||
}
|
||||
|
||||
public static void d(Throwable t, String message, Object... args) {
|
||||
Timber.d(t, message, args);
|
||||
}
|
||||
|
||||
public static void d(Throwable t) {
|
||||
Timber.d(t);
|
||||
}
|
||||
|
||||
public static void i(String message, Object... args) {
|
||||
Timber.i(message, args);
|
||||
}
|
||||
|
||||
public static void i(Throwable t, String message, Object... args) {
|
||||
Timber.i(t, message, args);
|
||||
}
|
||||
|
||||
public static void i(Throwable t) {
|
||||
Timber.i(t);
|
||||
}
|
||||
|
||||
public static void w(String message, Object... args) {
|
||||
Timber.w(message, args);
|
||||
}
|
||||
|
||||
public static void w(Throwable t, String message, Object... args) {
|
||||
Timber.w(t, message, args);
|
||||
}
|
||||
|
||||
public static void w(Throwable t) {
|
||||
Timber.w(t);
|
||||
}
|
||||
|
||||
public static void e(String message, Object... args) {
|
||||
Timber.e(message, args);
|
||||
}
|
||||
|
||||
public static void e(Throwable t, String message, Object... args) {
|
||||
Timber.e(t, message, args);
|
||||
}
|
||||
|
||||
public static void e(Throwable t) {
|
||||
Timber.e(t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package org.jitsi.meet.sdk.net;
|
||||
|
||||
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.module.annotations.ReactModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
@@ -97,7 +97,7 @@ public class NAT64AddrInfoModule
|
||||
try {
|
||||
info = NAT64AddrInfo.discover(host);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "NAT64AddrInfo.discover: " + host, e);
|
||||
JitsiMeetLogger.e(e, TAG + " NAT64AddrInfo.discover: " + host);
|
||||
}
|
||||
infoTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public class NAT64AddrInfoModule
|
||||
try {
|
||||
result = info == null ? null : info.getIPv6Address(ipv4Address);
|
||||
} catch (IllegalArgumentException exc) {
|
||||
Log.e(TAG, "Failed to get IPv6 address for: " + ipv4Address, exc);
|
||||
JitsiMeetLogger.e(exc, TAG + " Failed to get IPv6 address for: " + ipv4Address);
|
||||
|
||||
// We don't want to reject. It's not a big deal if there's no IPv6
|
||||
// address resolved.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">Jitsi Meet SDK</string>
|
||||
<string name="dropbox_app_key"></string>
|
||||
<string name="ongoing_notification_title">Ongoing meeting</string>
|
||||
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,8 +3,12 @@ rootProject.name = 'jitsi-meet'
|
||||
include ':app', ':sdk'
|
||||
include ':react-native-background-timer'
|
||||
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
|
||||
include ':react-native-fast-image'
|
||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-community-async-storage'
|
||||
project(':react-native-community-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android')
|
||||
include ':react-native-community_netinfo'
|
||||
project(':react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android')
|
||||
include ':react-native-immersive'
|
||||
@@ -15,9 +19,9 @@ include ':react-native-linear-gradient'
|
||||
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
|
||||
include ':react-native-sound'
|
||||
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
|
||||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-webrtc'
|
||||
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-webview'
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
|
||||
382
conference.js
@@ -16,13 +16,15 @@ import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||
|
||||
import {
|
||||
createDeviceChangedEvent,
|
||||
createStartSilentEvent,
|
||||
createScreenSharingEvent,
|
||||
createStreamSwitchDelayEvent,
|
||||
createTrackMutedEvent,
|
||||
sendAnalytics
|
||||
} from './react/features/analytics';
|
||||
import {
|
||||
redirectWithStoredParams,
|
||||
maybeRedirectToWelcomePage,
|
||||
redirectToStaticPage,
|
||||
reloadWithStoredParams
|
||||
} from './react/features/app';
|
||||
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelOpened,
|
||||
kickedOut,
|
||||
lockStateChanged,
|
||||
onStartMutedPolicyChanged,
|
||||
p2pStatusChanged,
|
||||
@@ -51,6 +54,8 @@ import {
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
notifyCameraError,
|
||||
notifyMicError,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
@@ -76,13 +81,14 @@ import {
|
||||
import { showNotification } from './react/features/notifications';
|
||||
import {
|
||||
dominantSpeakerChanged,
|
||||
getAvatarURLByParticipantId,
|
||||
getLocalParticipant,
|
||||
getNormalizedDisplayName,
|
||||
getParticipantById,
|
||||
localParticipantConnectionStatusChanged,
|
||||
localParticipantRoleChanged,
|
||||
participantConnectionStatusChanged,
|
||||
participantKicked,
|
||||
participantMutedUs,
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
@@ -92,14 +98,12 @@ import {
|
||||
createLocalTracksF,
|
||||
destroyLocalTracks,
|
||||
isLocalTrackMuted,
|
||||
isUserInteractionRequiredForUnmute,
|
||||
replaceLocalTrack,
|
||||
trackAdded,
|
||||
trackRemoved
|
||||
} from './react/features/base/tracks';
|
||||
import {
|
||||
getLocationContextRoot,
|
||||
getJitsiMeetGlobalNS
|
||||
} from './react/features/base/util';
|
||||
import { getJitsiMeetGlobalNS } from './react/features/base/util';
|
||||
import { addMessage } from './react/features/chat';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
@@ -107,10 +111,8 @@ import {
|
||||
maybeOpenFeedbackDialog,
|
||||
submitFeedback
|
||||
} from './react/features/feedback';
|
||||
import {
|
||||
mediaPermissionPromptVisibilityChanged,
|
||||
suspendDetected
|
||||
} from './react/features/overlay';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||
import { isButtonEnabled } from './react/features/toolbox';
|
||||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
@@ -206,77 +208,6 @@ function muteLocalVideo(muted) {
|
||||
APP.store.dispatch(setVideoMuted(muted));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the welcome page is enabled and redirects to it.
|
||||
* If requested show a thank you dialog before that.
|
||||
* If we have a close page enabled, redirect to it without
|
||||
* showing any other dialog.
|
||||
*
|
||||
* @param {object} options used to decide which particular close page to show
|
||||
* or if close page is disabled, whether we should show the thankyou dialog
|
||||
* @param {boolean} options.showThankYou - whether we should
|
||||
* show thank you dialog
|
||||
* @param {boolean} options.feedbackSubmitted - whether feedback was submitted
|
||||
*/
|
||||
function maybeRedirectToWelcomePage(options) {
|
||||
// if close page is enabled redirect to it, without further action
|
||||
if (config.enableClosePage) {
|
||||
const { isGuest } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
// save whether current user is guest or not, before navigating
|
||||
// to close page
|
||||
window.sessionStorage.setItem('guest', isGuest);
|
||||
redirectToStaticPage(`static/${
|
||||
options.feedbackSubmitted ? 'close.html' : 'close2.html'}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// else: show thankYou dialog only if there is no feedback
|
||||
if (options.showThankYou) {
|
||||
APP.store.dispatch(showNotification({
|
||||
titleArguments: { appName: interfaceConfig.APP_NAME },
|
||||
titleKey: 'dialog.thankYou'
|
||||
}));
|
||||
}
|
||||
|
||||
// if Welcome page is enabled redirect to welcome page after 3 sec, if
|
||||
// there is a thank you message to be shown, 0.5s otherwise.
|
||||
if (config.enableWelcomePage) {
|
||||
setTimeout(
|
||||
() => {
|
||||
APP.store.dispatch(redirectWithStoredParams('/'));
|
||||
},
|
||||
options.showThankYou ? 3000 : 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a specific pathname to window.location.pathname taking into account
|
||||
* the context root of the Web app.
|
||||
*
|
||||
* @param {string} pathname - The pathname to assign to
|
||||
* window.location.pathname. If the specified pathname is relative, the context
|
||||
* root of the Web app will be prepended to the specified pathname before
|
||||
* assigning it to window.location.pathname.
|
||||
* @return {void}
|
||||
*/
|
||||
function redirectToStaticPage(pathname) {
|
||||
const windowLocation = window.location;
|
||||
let newPathname = pathname;
|
||||
|
||||
if (!newPathname.startsWith('/')) {
|
||||
// A pathname equal to ./ specifies the current directory. It will be
|
||||
// fine but pointless to include it because contextRoot is the current
|
||||
// directory.
|
||||
newPathname.startsWith('./')
|
||||
&& (newPathname = newPathname.substring(2));
|
||||
newPathname = getLocationContextRoot(windowLocation) + newPathname;
|
||||
}
|
||||
|
||||
windowLocation.pathname = newPathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* A queue for the async replaceLocalTrack action so that multiple audio
|
||||
* replacements cannot happen simultaneously. This solves the issue where
|
||||
@@ -342,7 +273,7 @@ class ConferenceConnector {
|
||||
|
||||
case JitsiConferenceErrors.NOT_ALLOWED_ERROR: {
|
||||
// let's show some auth not allowed page
|
||||
redirectToStaticPage('static/authError.html');
|
||||
APP.store.dispatch(redirectToStaticPage('static/authError.html'));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -373,13 +304,6 @@ class ConferenceConnector {
|
||||
APP.UI.notifyGracefulShutdown();
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.JINGLE_FATAL_ERROR: {
|
||||
const [ error ] = params;
|
||||
|
||||
APP.UI.notifyInternalError(error);
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
||||
const [ reason ] = params;
|
||||
|
||||
@@ -401,6 +325,7 @@ class ConferenceConnector {
|
||||
|
||||
case JitsiConferenceErrors.FOCUS_LEFT:
|
||||
case JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
// FIXME the conference should be stopped by the library and not by
|
||||
@@ -486,10 +411,13 @@ class ConferenceConnector {
|
||||
* call in hangup() to resolve when all operations are finished.
|
||||
*/
|
||||
function disconnect() {
|
||||
connection.disconnect();
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
const onDisconnected = () => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
|
||||
return Promise.resolve();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return connection.disconnect().then(onDisconnected, onDisconnected);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -627,8 +555,7 @@ export default {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF(
|
||||
{ devices: initialDevices }, true)
|
||||
tryCreateLocalTracks = createLocalTracksF({ devices: initialDevices }, true)
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
@@ -694,13 +621,14 @@ export default {
|
||||
// If both requests for 'audio' + 'video' and 'audio'
|
||||
// only failed, we assume that there are some problems
|
||||
// with user's microphone and show corresponding dialog.
|
||||
APP.UI.showMicErrorNotification(audioOnlyError);
|
||||
APP.UI.showCameraErrorNotification(videoOnlyError);
|
||||
APP.store.dispatch(notifyMicError(audioOnlyError));
|
||||
APP.store.dispatch(notifyCameraError(videoOnlyError));
|
||||
} else {
|
||||
// If request for 'audio' + 'video' failed, but request
|
||||
// for 'audio' only was OK, we assume that we had
|
||||
// problems with camera and show corresponding dialog.
|
||||
APP.UI.showCameraErrorNotification(audioAndVideoError);
|
||||
APP.store.dispatch(
|
||||
notifyCameraError(audioAndVideoError));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,13 +646,25 @@ export default {
|
||||
this.roomName = options.roomName;
|
||||
|
||||
return (
|
||||
this.createInitialLocalTracksAndConnect(
|
||||
|
||||
// Initialize the device list first. This way, when creating tracks
|
||||
// based on preferred devices, loose label matching can be done in
|
||||
// cases where the exact ID match is no longer available, such as
|
||||
// when the camera device has switched USB ports.
|
||||
// when in startSilent mode we want to start with audio muted
|
||||
this._initDeviceList()
|
||||
.catch(error => logger.warn(
|
||||
'initial device list initialization failed', error))
|
||||
.then(() => this.createInitialLocalTracksAndConnect(
|
||||
options.roomName, {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: config.startWithAudioMuted,
|
||||
startWithAudioMuted: config.startWithAudioMuted
|
||||
|| config.startSilent
|
||||
|| isUserInteractionRequiredForUnmute(APP.store.getState()),
|
||||
startWithVideoMuted: config.startWithVideoMuted
|
||||
})
|
||||
|| isUserInteractionRequiredForUnmute(APP.store.getState())
|
||||
}))
|
||||
.then(([ tracks, con ]) => {
|
||||
tracks.forEach(track => {
|
||||
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
||||
@@ -769,12 +709,23 @@ export default {
|
||||
this.setVideoMuteStatus(true);
|
||||
}
|
||||
|
||||
this._initDeviceList();
|
||||
// Initialize device list a second time to ensure device labels
|
||||
// get populated in case of an initial gUM acceptance; otherwise
|
||||
// they may remain as empty strings.
|
||||
this._initDeviceList(true);
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
this.recorder = new Recorder();
|
||||
}
|
||||
|
||||
if (config.startSilent) {
|
||||
sendAnalytics(createStartSilentEvent());
|
||||
APP.store.dispatch(showNotification({
|
||||
descriptionKey: 'notify.startSilentDescription',
|
||||
titleKey: 'notify.startSilentTitle'
|
||||
}));
|
||||
}
|
||||
|
||||
// XXX The API will take care of disconnecting from the XMPP
|
||||
// server (and, thus, leaving the room) on unload.
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -813,6 +764,13 @@ export default {
|
||||
* dialogs in case of media permissions error.
|
||||
*/
|
||||
muteAudio(mute, showUI = true) {
|
||||
if (!mute
|
||||
&& isUserInteractionRequiredForUnmute(APP.store.getState())) {
|
||||
logger.error('Unmuting audio requires user interaction');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Not ready to modify track's state yet
|
||||
if (!this._localTracksInitialized) {
|
||||
// This will only modify base/media.audio.muted which is then synced
|
||||
@@ -828,7 +786,7 @@ export default {
|
||||
|
||||
if (!this.localAudio && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showMicErrorNotification(error);
|
||||
showUI && APP.store.dispatch(notifyMicError(error));
|
||||
};
|
||||
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, false)
|
||||
@@ -876,6 +834,13 @@ export default {
|
||||
* dialogs in case of media permissions error.
|
||||
*/
|
||||
muteVideo(mute, showUI = true) {
|
||||
if (!mute
|
||||
&& isUserInteractionRequiredForUnmute(APP.store.getState())) {
|
||||
logger.error('Unmuting video requires user interaction');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If not ready to modify track's state yet adjust the base/media
|
||||
if (!this._localTracksInitialized) {
|
||||
// This will only modify base/media.video.muted which is then synced
|
||||
@@ -891,7 +856,7 @@ export default {
|
||||
|
||||
if (!this.localVideo && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showCameraErrorNotification(error);
|
||||
showUI && APP.store.dispatch(notifyCameraError(error));
|
||||
};
|
||||
|
||||
// Try to create local video if there wasn't any.
|
||||
@@ -995,17 +960,15 @@ export default {
|
||||
* Returns the connection times stored in the library.
|
||||
*/
|
||||
getConnectionTimes() {
|
||||
return this._room.getConnectionTimes();
|
||||
return room.getConnectionTimes();
|
||||
},
|
||||
|
||||
// used by torture currently
|
||||
isJoined() {
|
||||
return this._room
|
||||
&& this._room.isJoined();
|
||||
return room && room.isJoined();
|
||||
},
|
||||
getConnectionState() {
|
||||
return this._room
|
||||
&& this._room.getConnectionState();
|
||||
return room && room.getConnectionState();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1014,8 +977,7 @@ export default {
|
||||
* P2P connection
|
||||
*/
|
||||
getP2PConnectionState() {
|
||||
return this._room
|
||||
&& this._room.getP2PConnectionState();
|
||||
return room && room.getP2PConnectionState();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1024,7 +986,7 @@ export default {
|
||||
*/
|
||||
_startP2P() {
|
||||
try {
|
||||
this._room && this._room.startP2PSession();
|
||||
room && room.startP2PSession();
|
||||
} catch (error) {
|
||||
logger.error('Start P2P failed', error);
|
||||
throw error;
|
||||
@@ -1037,7 +999,7 @@ export default {
|
||||
*/
|
||||
_stopP2P() {
|
||||
try {
|
||||
this._room && this._room.stopP2PSession();
|
||||
room && room.stopP2PSession();
|
||||
} catch (error) {
|
||||
logger.error('Stop P2P failed', error);
|
||||
throw error;
|
||||
@@ -1052,7 +1014,7 @@ export default {
|
||||
* false otherwise.
|
||||
*/
|
||||
isConnectionInterrupted() {
|
||||
return this._room.isConnectionInterrupted();
|
||||
return room.isConnectionInterrupted();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1113,7 +1075,7 @@ export default {
|
||||
},
|
||||
|
||||
getMyUserId() {
|
||||
return this._room && this._room.myUserId();
|
||||
return room && room.myUserId();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1136,7 +1098,7 @@ export default {
|
||||
* least one track.
|
||||
*/
|
||||
getNumberOfParticipantsWithTracks() {
|
||||
return this._room.getParticipants()
|
||||
return room.getParticipants()
|
||||
.filter(p => p.getTracks().length > 0)
|
||||
.length;
|
||||
},
|
||||
@@ -1274,17 +1236,34 @@ export default {
|
||||
const options = config;
|
||||
|
||||
const nick = APP.store.getState()['features/base/settings'].displayName;
|
||||
const { locationURL } = APP.store.getState()['features/base/connection'];
|
||||
|
||||
if (nick) {
|
||||
options.displayName = nick;
|
||||
}
|
||||
|
||||
options.applicationName = interfaceConfig.APP_NAME;
|
||||
options.getWiFiStatsMethod = getJitsiMeetGlobalNS().getWiFiStats;
|
||||
options.getWiFiStatsMethod = this._getWiFiStatsMethod;
|
||||
options.confID = `${locationURL.host}${locationURL.pathname}`;
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the result of getWiFiStats from the global NS or does nothing
|
||||
* (returns empty result).
|
||||
* Fixes a concurrency problem where we need to pass a function when creating
|
||||
* JitsiConference, but that method is added to the context later.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
_getWiFiStatsMethod() {
|
||||
const gloabalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
return gloabalNS.getWiFiStats ? gloabalNS.getWiFiStats() : Promise.resolve('{}');
|
||||
},
|
||||
|
||||
/**
|
||||
* Start using provided video stream.
|
||||
* Stops previous video stream.
|
||||
@@ -1300,7 +1279,7 @@ export default {
|
||||
this.localVideo = newStream;
|
||||
this._setSharingScreen(newStream);
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
APP.UI.addLocalVideoStream(newStream);
|
||||
}
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
})
|
||||
@@ -1351,9 +1330,6 @@ export default {
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
this.localAudio = newStream;
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
||||
})
|
||||
.then(resolve)
|
||||
@@ -1369,8 +1345,7 @@ export default {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAudioOnly() {
|
||||
return Boolean(
|
||||
APP.store.getState()['features/base/conference'].audioOnly);
|
||||
return Boolean(APP.store.getState()['features/base/audio-only'].enabled);
|
||||
},
|
||||
|
||||
videoSwitchInProgress: false,
|
||||
@@ -1482,7 +1457,9 @@ export default {
|
||||
return this._switchToScreenSharing(options);
|
||||
}
|
||||
|
||||
return this._untoggleScreenSharing();
|
||||
return this._untoggleScreenSharing
|
||||
? this._untoggleScreenSharing()
|
||||
: Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1734,14 +1711,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const displayName = user.getDisplayName();
|
||||
|
||||
logger.log(`USER ${id} connnected:`, user);
|
||||
APP.API.notifyUserJoined(id, {
|
||||
displayName,
|
||||
formattedDisplayName: appendSuffix(
|
||||
displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
|
||||
});
|
||||
APP.UI.addUser(user);
|
||||
|
||||
// check the roles for the new user and reflect them
|
||||
@@ -1757,12 +1727,7 @@ export default {
|
||||
}
|
||||
|
||||
logger.log(`USER ${id} LEFT:`, user);
|
||||
APP.API.notifyUserLeft(id);
|
||||
APP.UI.messageHandler.participantNotification(
|
||||
user.getDisplayName(),
|
||||
'notify.somebody',
|
||||
'disconnected',
|
||||
'notify.disconnected');
|
||||
|
||||
APP.UI.onSharedVideoStop(id);
|
||||
});
|
||||
|
||||
@@ -1831,9 +1796,12 @@ export default {
|
||||
APP.UI.setAudioLevel(id, newLvl);
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
|
||||
APP.UI.showToolbar(6000);
|
||||
room.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, (track, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
APP.store.dispatch(participantMutedUs(participantThatMutedUs));
|
||||
}
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.SUBJECT_CHANGED,
|
||||
subject => APP.store.dispatch(conferenceSubjectChanged(subject)));
|
||||
|
||||
@@ -1931,40 +1899,19 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.KICKED, () => {
|
||||
room.on(JitsiConferenceEvents.KICKED, participant => {
|
||||
APP.UI.hideStats();
|
||||
APP.UI.notifyKicked();
|
||||
APP.store.dispatch(kickedOut(room, participant));
|
||||
|
||||
// FIXME close
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.PARTICIPANT_KICKED, (kicker, kicked) => {
|
||||
APP.store.dispatch(participantKicked(kicker, kicked));
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.SUSPEND_DETECTED, () => {
|
||||
APP.store.dispatch(suspendDetected());
|
||||
|
||||
// After wake up, we will be in a state where conference is left
|
||||
// there will be dialog shown to user.
|
||||
// We do not want video/audio as we show an overlay and after it
|
||||
// user need to rejoin or close, while waking up we can detect
|
||||
// camera wakeup as a problem with device.
|
||||
// We also do not care about device change, which happens
|
||||
// on resume after suspending PC.
|
||||
if (this.deviceChangeListener) {
|
||||
JitsiMeetJS.mediaDevices.removeEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
// stop local video
|
||||
if (this.localVideo) {
|
||||
this.localVideo.dispose();
|
||||
this.localVideo = null;
|
||||
}
|
||||
|
||||
// stop local audio
|
||||
if (this.localAudio) {
|
||||
this.localAudio.dispose();
|
||||
this.localAudio = null;
|
||||
}
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
|
||||
@@ -2098,7 +2045,7 @@ export default {
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showCameraErrorNotification(err);
|
||||
APP.store.dispatch(notifyCameraError(err));
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -2131,7 +2078,7 @@ export default {
|
||||
this._updateAudioDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showMicErrorNotification(err);
|
||||
APP.store.dispatch(notifyMicError(err));
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -2220,6 +2167,27 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanups local conference on suspend.
|
||||
*/
|
||||
onSuspendDetected() {
|
||||
// After wake up, we will be in a state where conference is left
|
||||
// there will be dialog shown to user.
|
||||
// We do not want video/audio as we show an overlay and after it
|
||||
// user need to rejoin or close, while waking up we can detect
|
||||
// camera wakeup as a problem with device.
|
||||
// We also do not care about device change, which happens
|
||||
// on resume after suspending PC.
|
||||
if (this.deviceChangeListener) {
|
||||
JitsiMeetJS.mediaDevices.removeEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
this.localVideo = null;
|
||||
this.localAudio = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback invoked when the conference has been successfully joined.
|
||||
* Initializes the UI and various other features.
|
||||
@@ -2232,29 +2200,12 @@ export default {
|
||||
|
||||
APP.keyboardshortcut.init();
|
||||
|
||||
if (config.requireDisplayName
|
||||
&& !APP.conference.getLocalDisplayName()) {
|
||||
APP.UI.promptDisplayName();
|
||||
}
|
||||
|
||||
APP.store.dispatch(conferenceJoined(room));
|
||||
|
||||
const displayName
|
||||
= APP.store.getState()['features/base/settings'].displayName;
|
||||
|
||||
APP.UI.changeDisplayName('localVideoContainer', displayName);
|
||||
APP.API.notifyConferenceJoined(
|
||||
this.roomName,
|
||||
this._room.myUserId(),
|
||||
{
|
||||
displayName,
|
||||
formattedDisplayName: appendSuffix(
|
||||
displayName,
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME),
|
||||
avatarURL: getAvatarURLByParticipantId(
|
||||
APP.store.getState(), this._room.myUserId())
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2277,20 +2228,23 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* Inits list of current devices and event listener for device change.
|
||||
* Updates the list of current devices.
|
||||
* @param {boolean} setDeviceListChangeHandler - Whether to add the deviceList change handlers.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initDeviceList() {
|
||||
_initDeviceList(setDeviceListChangeHandler = false) {
|
||||
const { mediaDevices } = JitsiMeetJS;
|
||||
|
||||
if (mediaDevices.isDeviceListAvailable()
|
||||
&& mediaDevices.isDeviceChangeAvailable()) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
if (setDeviceListChangeHandler) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
const { dispatch } = APP.store;
|
||||
|
||||
@@ -2388,17 +2342,26 @@ export default {
|
||||
// Let's handle unknown/non-preferred devices
|
||||
const newAvailDevices
|
||||
= APP.store.getState()['features/base/devices'].availableDevices;
|
||||
let newAudioDevices = [];
|
||||
let oldAudioDevices = [];
|
||||
|
||||
if (typeof newDevices.audiooutput === 'undefined') {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.audioOutput, oldDevices.audioOutput));
|
||||
newAudioDevices = newAvailDevices.audioOutput;
|
||||
oldAudioDevices = oldDevices.audioOutput;
|
||||
}
|
||||
|
||||
if (!requestedInput.audio) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.audioInput, oldDevices.audioInput));
|
||||
newAudioDevices = newAudioDevices.concat(newAvailDevices.audioInput);
|
||||
oldAudioDevices = oldAudioDevices.concat(oldDevices.audioInput);
|
||||
}
|
||||
|
||||
// check for audio
|
||||
if (newAudioDevices.length > 0) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
|
||||
}
|
||||
|
||||
// check for video
|
||||
if (!requestedInput.video) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
|
||||
@@ -2565,7 +2528,7 @@ export default {
|
||||
room = undefined;
|
||||
|
||||
APP.API.notifyReadyToClose();
|
||||
maybeRedirectToWelcomePage(values[0]);
|
||||
APP.store.dispatch(maybeRedirectToWelcomePage(values[0]));
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2577,8 +2540,11 @@ export default {
|
||||
leaveRoomAndDisconnect() {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
return room.leave()
|
||||
.then(disconnect, disconnect);
|
||||
if (room.isJoined()) {
|
||||
return room.leave().then(disconnect, disconnect);
|
||||
}
|
||||
|
||||
return disconnect();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2707,14 +2673,6 @@ export default {
|
||||
displayName: formattedNickname
|
||||
}));
|
||||
|
||||
APP.API.notifyDisplayNameChanged(id, {
|
||||
displayName: formattedNickname,
|
||||
formattedDisplayName:
|
||||
appendSuffix(
|
||||
formattedNickname,
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME)
|
||||
});
|
||||
|
||||
if (room) {
|
||||
APP.UI.changeDisplayName(id, formattedNickname);
|
||||
}
|
||||
@@ -2768,6 +2726,18 @@ export default {
|
||||
*/
|
||||
convertVideoToDesktop: true,
|
||||
|
||||
/**
|
||||
* Callback invoked when the connection has been closed
|
||||
* automatically. Triggers cleanup of screensharing if active.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onConnectionClosed: () => {
|
||||
if (this._untoggleScreenSharing) {
|
||||
this._untoggleScreenSharing();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback invoked to pass messages from the local client back
|
||||
* out to the external client.
|
||||
|
||||
27
config.js
@@ -90,6 +90,10 @@ var config = {
|
||||
// applied locally. FIXME: having these 2 options is confusing.
|
||||
// startWithAudioMuted: false,
|
||||
|
||||
// Enabling it (with #params) will disable local audio output of remote
|
||||
// participants and to enable it back a reload is needed.
|
||||
// startSilent: false
|
||||
|
||||
// Video
|
||||
|
||||
// Sets the preferred resolution (height) for local video. Defaults to 720.
|
||||
@@ -119,10 +123,6 @@ var config = {
|
||||
// are requested again.
|
||||
// enableLayerSuspension: false,
|
||||
|
||||
// Suspend sending video if bandwidth estimation is too low. This may cause
|
||||
// problems with audio playback. Disabled until these are fixed.
|
||||
disableSuspendVideo: true,
|
||||
|
||||
// Every participant after the Nth will start video muted.
|
||||
// startVideoMuted: 10,
|
||||
|
||||
@@ -197,6 +197,9 @@ var config = {
|
||||
// subtitles and buttons can be configured)
|
||||
// transcribingEnabled: false,
|
||||
|
||||
// Enables automatic turning on captions when recording is started
|
||||
// autoCaptionOnRecord: false,
|
||||
|
||||
// Misc
|
||||
|
||||
// Default value for the channel "last N" attribute. -1 for unlimited.
|
||||
@@ -266,6 +269,13 @@ var config = {
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Enable lock room for all moderators, even when userRolesBasedOnToken is enabled and participants are guests.
|
||||
// lockRoomGuestEnabled: false,
|
||||
|
||||
// When enabled the password used for locking a room is restricted to up to the number of digits specified
|
||||
// roomPasswordNumberOfDigits: 10,
|
||||
// default: roomPasswordNumberOfDigits: false,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
@@ -407,6 +417,14 @@ var config = {
|
||||
// use only.
|
||||
// _desktopSharingSourceDevice: 'sample-id-or-label'
|
||||
|
||||
// If true, any checks to handoff to another application will be prevented
|
||||
// and instead the app will continue to display in the current browser.
|
||||
// disableDeepLinking: false
|
||||
|
||||
// A property to disable the right click context menu for localVideo
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
@@ -421,7 +439,6 @@ var config = {
|
||||
dialOutCodesUrl
|
||||
disableRemoteControl
|
||||
displayJids
|
||||
enableLocalVideoFlip
|
||||
etherpad_base
|
||||
externalConnectUrl
|
||||
firefox_fake_device
|
||||
|
||||
31
css/_avatar.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
.avatar {
|
||||
align-items: center;
|
||||
background-color: #AAA;
|
||||
display: flex;
|
||||
border-radius: 50%;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 100;
|
||||
justify-content: center;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-foreign {
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
font-size: 40pt;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.avatar-svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.defaultAvatar {
|
||||
opacity: 0.6
|
||||
}
|
||||
@@ -33,6 +33,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
/**
|
||||
* AtlasKitThemeProvider sets a background color on an app-wrapping div, thereby
|
||||
* preventing transparency in filmstrip-only mode. The selector chosen to
|
||||
|
||||
194
css/_chat.scss
@@ -1,6 +1,9 @@
|
||||
#sideToolbarContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/**
|
||||
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||
* the toolbar and subtract from 100%.
|
||||
@@ -21,20 +24,6 @@
|
||||
&.slideInExt {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sideToolbarContainer__inner {
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: $sidebarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
#chat_container * {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
@@ -42,9 +31,8 @@
|
||||
flex: 1;
|
||||
font-size: 10pt;
|
||||
line-height: 20px;
|
||||
margin-top: $desktopAppDragBarHeight + 5px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
@@ -92,26 +80,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-close {
|
||||
background: gray;
|
||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 100%;
|
||||
color: white;
|
||||
cursor:pointer;
|
||||
height: 10px;
|
||||
line-height: 10px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
text-align: center;
|
||||
top: $desktopAppDragBarHeight;
|
||||
width: 10px;
|
||||
.chat-header {
|
||||
background-color: $chatHeaderBackgroundColor;
|
||||
height: 70px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
.chat-close {
|
||||
align-items: center;
|
||||
bottom: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
line-height: 15px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
width: 40px;
|
||||
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
@@ -123,16 +126,13 @@
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 10pt;
|
||||
font-size: 15px;
|
||||
line-height: 30px;
|
||||
padding: 5px;
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
@@ -145,64 +145,47 @@
|
||||
}
|
||||
|
||||
#nickname {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
font-size: 18px;
|
||||
top: 100px;
|
||||
margin-top: 30px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
#chat_container .display-name {
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 95%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sideToolbarContainer {
|
||||
* {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#chat_container .timestamp {
|
||||
float: right;
|
||||
padding-right: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
padding-top: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.chatArrow {
|
||||
border-color:
|
||||
transparent $newToolbarBackgroundColor transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 0 10px 10px 0;
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
.display-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
width: 93%;
|
||||
margin-left: 9px;
|
||||
margin-right: auto;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0px;
|
||||
margin-top: 3px;
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
margin-top: 3px;
|
||||
max-width: 100%;
|
||||
padding-bottom: 3px;
|
||||
position: relative;
|
||||
|
||||
&.localuser .display-name {
|
||||
color: #4C9AFF
|
||||
&.localuser {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatArrow,
|
||||
border-radius: 0px;
|
||||
|
||||
.timestamp,
|
||||
.display-name {
|
||||
display: none;
|
||||
@@ -231,7 +214,6 @@
|
||||
}
|
||||
|
||||
#smileysarea {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
display: flex;
|
||||
max-height: 150px;
|
||||
min-height: 35px;
|
||||
@@ -246,14 +228,22 @@
|
||||
.smileys-panel {
|
||||
bottom: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 0;
|
||||
height: auto;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
transition: height 0.3s;
|
||||
width: $sidebarWidth;
|
||||
|
||||
/**
|
||||
* CSS transitions do not apply for auto dimensions. So to produce the css
|
||||
* accordion effect for showing and hiding the smiley-panel, while allowing
|
||||
* for variable panel, height, use a very large max-height and animate off
|
||||
* of that.
|
||||
*/
|
||||
transition: max-height 0.3s;
|
||||
|
||||
&.show-smileys {
|
||||
height: 146px;
|
||||
max-height: 500%;
|
||||
}
|
||||
|
||||
#smileysContainer {
|
||||
@@ -287,3 +277,49 @@
|
||||
#usermsg::-webkit-scrollbar-track-piece {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.chat-message-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.local {
|
||||
align-items: flex-end;
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatmessage {
|
||||
border-radius: 0px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage-wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
display: inline-block;
|
||||
margin-top: 3px;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
219
css/_font.scss
@@ -1,219 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'jitsi';
|
||||
src: url('../fonts/jitsi.eot?3vw865');
|
||||
src: url('../fonts/jitsi.eot?3vw865#iefix') format('embedded-opentype'),
|
||||
url('../fonts/jitsi.ttf?3vw865') format('truetype'),
|
||||
url('../fonts/jitsi.woff?3vw865') format('woff'),
|
||||
url('../fonts/jitsi.svg?3vw865#jitsi') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^="icon-"], [class*=" icon-"] {
|
||||
font-family: 'jitsi';
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1.22em;
|
||||
font-size: 1.22em;
|
||||
cursor: default;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-signal_cellular_0:before {
|
||||
content: "\e901";
|
||||
}
|
||||
.icon-signal_cellular_1:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-signal_cellular_2:before {
|
||||
content: "\e907";
|
||||
}
|
||||
.icon-phone:before {
|
||||
content: "\e0cd";
|
||||
}
|
||||
.icon-radio_button_unchecked:before {
|
||||
content: "\e836";
|
||||
}
|
||||
.icon-radio_button_checked:before {
|
||||
content: "\e837";
|
||||
}
|
||||
.icon-search:before {
|
||||
content: "\e8b6";
|
||||
}
|
||||
.icon-chat-unread:before {
|
||||
content: "\e0b7";
|
||||
}
|
||||
.icon-closed_caption:before {
|
||||
content: "\e930";
|
||||
}
|
||||
.icon-tiles-many:before {
|
||||
content: "\e92e";
|
||||
}
|
||||
.icon-close:before {
|
||||
content: "\e5cd";
|
||||
}
|
||||
.icon-open_in_new:before {
|
||||
content: "\e89e";
|
||||
}
|
||||
.icon-restore:before {
|
||||
content: "\e8b3";
|
||||
}
|
||||
.icon-navigate_next:before {
|
||||
content: "\e409";
|
||||
}
|
||||
.icon-menu:before {
|
||||
content: "\e5d2";
|
||||
}
|
||||
.icon-arrow_back:before {
|
||||
content: "\e5c4";
|
||||
}
|
||||
.icon-public:before {
|
||||
content: "\e80b";
|
||||
}
|
||||
.icon-event_note:before {
|
||||
content: "\e616";
|
||||
}
|
||||
.icon-bluetooth:before {
|
||||
content: "\e1aa";
|
||||
}
|
||||
.icon-headset:before {
|
||||
content: "\e310";
|
||||
}
|
||||
.icon-phone-talk:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
.icon-thumb-menu:before {
|
||||
content: "\e5d4";
|
||||
}
|
||||
.icon-ninja:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-invite:before {
|
||||
content: "\e145";
|
||||
}
|
||||
.icon-add:before {
|
||||
content: "\e146";
|
||||
}
|
||||
.icon-play:before {
|
||||
content: "\f04b";
|
||||
}
|
||||
.icon-stop:before {
|
||||
content: "\f04d";
|
||||
}
|
||||
.icon-dominant-speaker:before {
|
||||
content: "\f0a1";
|
||||
}
|
||||
.icon-speaker:before {
|
||||
content: "\e92d";
|
||||
}
|
||||
.icon-rec:before {
|
||||
content: "\e92b";
|
||||
}
|
||||
.icon-camera-take-picture:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
.icon-AUD:before {
|
||||
content: "\e900";
|
||||
}
|
||||
.icon-HD:before {
|
||||
content: "\e927";
|
||||
}
|
||||
.icon-LD:before {
|
||||
content: "\e928";
|
||||
}
|
||||
.icon-SD:before {
|
||||
content: "\e929";
|
||||
}
|
||||
.icon-gsm-bars:before {
|
||||
content: "\e926";
|
||||
}
|
||||
.icon-info:before {
|
||||
content: "\e922";
|
||||
}
|
||||
.icon-mic-camera-combined:before {
|
||||
content: "\e903";
|
||||
}
|
||||
.icon-feedback:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
.icon-hangup:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-chat:before {
|
||||
content: "\e906";
|
||||
}
|
||||
.icon-share-doc:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-kick:before {
|
||||
content: "\e904";
|
||||
}
|
||||
.icon-menu-up:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
.icon-menu-down:before {
|
||||
content: "\e920";
|
||||
}
|
||||
.icon-full-screen:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
.icon-exit-full-screen:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
.icon-security:before {
|
||||
content: "\e90d";
|
||||
}
|
||||
.icon-security-locked:before {
|
||||
content: "\e90e";
|
||||
}
|
||||
.icon-microphone:before {
|
||||
content: "\e910";
|
||||
}
|
||||
.icon-mic-disabled:before {
|
||||
content: "\e912";
|
||||
}
|
||||
.icon-raised-hand:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
.icon-link:before {
|
||||
content: "\e913";
|
||||
}
|
||||
.icon-shared-video:before {
|
||||
content: "\e914";
|
||||
}
|
||||
.icon-settings:before {
|
||||
content: "\e915";
|
||||
}
|
||||
.icon-star:before {
|
||||
content: "\e916";
|
||||
}
|
||||
.icon-switch-camera:before {
|
||||
content: "\e921";
|
||||
}
|
||||
.icon-share-desktop:before {
|
||||
content: "\e917";
|
||||
}
|
||||
.icon-camera:before {
|
||||
content: "\e918";
|
||||
}
|
||||
.icon-camera-disabled:before {
|
||||
content: "\e919";
|
||||
}
|
||||
.icon-volume:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
.icon-presentation:before {
|
||||
content: "\e603";
|
||||
}
|
||||
.icon-visibility:before {
|
||||
content: "\e923";
|
||||
}
|
||||
.icon-visibility-off:before {
|
||||
content: "\e924";
|
||||
}
|
||||
@@ -73,8 +73,62 @@
|
||||
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
.toolbox-icon {
|
||||
margin: 0px 4px;
|
||||
|
||||
.toolbox-button {
|
||||
|
||||
.toolbox-icon {
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #d1dbe8;
|
||||
margin: 0px 4px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
|
||||
&:hover {
|
||||
background-color: #daebfa;
|
||||
border: 1px solid #daebfa;
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: #2a3a4b;
|
||||
border: 1px solid #5e6d7a;
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #5e6d7a;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled, .disabled & {
|
||||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #5e6d7a;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
background-color: $hangupColor;
|
||||
border: 1px solid $hangupColor;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
&:hover {
|
||||
background-color: $hangupColor;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,75 +136,6 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
i {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: inherit;
|
||||
height: 100%;
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
background-color: #e12d2d;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
&:hover {
|
||||
background-color: #e54b4b;
|
||||
}
|
||||
}
|
||||
i.disabled, .disabled i {
|
||||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
|
||||
.icon-mic-disabled, .icon-microphone, .icon-camera-disabled, .icon-camera {
|
||||
background-color: #fff;
|
||||
color: #5e6d7a;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #d1dbe8;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
|
||||
&:hover {
|
||||
background-color: #daebfa;
|
||||
border: 1px solid #daebfa;
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: #2a3a4b;
|
||||
color: #fff;
|
||||
border: 1px solid #5e6d7a;
|
||||
|
||||
&:hover {
|
||||
background-color: #5e6d7a;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled, .disabled & {
|
||||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
}
|
||||
|
||||
.overflow-menu {
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
@@ -191,14 +176,6 @@
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
i.toggled:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
@@ -227,6 +204,10 @@
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #B8C7E0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
@@ -265,9 +246,26 @@
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
height: $newToolbarSize;
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
flex-direction: column;
|
||||
font-size: 24px;
|
||||
height: $newToolbarSize;
|
||||
justify-content: center;
|
||||
width: $newToolbarSize;
|
||||
|
||||
&:hover, &.toggled {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial !important;
|
||||
background-color: #a4b8d1 !important;
|
||||
|
||||
svg {
|
||||
fill: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,10 +295,6 @@
|
||||
background-color: $AOTToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
color: $hangupColor;
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
@@ -325,10 +319,6 @@
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
|
||||
.icon-hangup {
|
||||
font-size: $newToolbarHangupFontSize;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
@@ -83,11 +83,19 @@ $modalMockAKInputBorder: 1px solid #f4f5f7;
|
||||
$modalTextColor: #333;
|
||||
|
||||
/**
|
||||
* Chat
|
||||
*/
|
||||
$chatHeaderBackgroundColor: rgba(42, 58, 75, 0.9);
|
||||
$chatInputSeparatorColor: #A4B8D1;
|
||||
$chatLocalMessageBackgroundColor: rgba(26, 108, 180, 1);
|
||||
$chatRemoteMessageBackgroundColor: rgba(240, 243, 247, 0.15);
|
||||
$sidebarWidth: 375px;
|
||||
|
||||
/**
|
||||
* Misc.
|
||||
*/
|
||||
$borderRadius: 4px;
|
||||
$defaultWatermarkLink: '../images/watermark.png';
|
||||
$sidebarWidth: 220px;
|
||||
$popoverMenuPadding: 13px;
|
||||
$happySoftwareBackground: transparent;
|
||||
$desktopAppDragBarHeight: 25px;
|
||||
|
||||
@@ -23,14 +23,9 @@
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
&.fit-full-height #largeVideoBackground {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fit-full-width #largeVideoBackground {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
#largeVideoBackground {
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
#largeVideoBackgroundContainer {
|
||||
@@ -349,8 +344,11 @@
|
||||
/**
|
||||
* Toolbar icon internal i elements (font icons).
|
||||
*/
|
||||
.toolbar-icon>i {
|
||||
line-height: $thumbnailToolbarHeight;
|
||||
.toolbar-icon>div {
|
||||
height: $thumbnailToolbarHeight;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,7 +496,6 @@
|
||||
}
|
||||
|
||||
#dominantSpeakerAvatarContainer,
|
||||
#dominantSpeakerAvatar,
|
||||
.dynamic-shadow {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
@@ -508,13 +505,9 @@
|
||||
top: 50px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
overflow: hidden;
|
||||
visibility: inherit;
|
||||
}
|
||||
#dominantSpeakerAvatar {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.dynamic-shadow {
|
||||
border-radius: 50%;
|
||||
@@ -525,24 +518,36 @@
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.userAvatar {
|
||||
.avatar-container {
|
||||
@include maxSize(60px);
|
||||
@include absoluteAligning();
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
|
||||
.userAvatar {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#videoNotAvailableScreen {
|
||||
text-align: center;
|
||||
#avatarContainer {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 50vh;
|
||||
display:inline-block;
|
||||
margin-top: 25vh;
|
||||
overflow: hidden;
|
||||
width: 50vh;
|
||||
|
||||
#avatar {
|
||||
border-radius: 50%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/**
|
||||
* Let the avatar grow with the tile.
|
||||
*/
|
||||
.userAvatar {
|
||||
.avatar-container {
|
||||
max-height: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,6 @@ $flagsImagePath: "../images/";
|
||||
@import "../node_modules/bc-css-flags/dist/css/bc-css-flags.scss";
|
||||
/* Flags END */
|
||||
|
||||
/* Fonts BEGIN */
|
||||
|
||||
@import 'font';
|
||||
|
||||
/* Fonts END */
|
||||
|
||||
/* Modules BEGIN */
|
||||
|
||||
@import 'aui_reset';
|
||||
@@ -86,5 +80,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'navigate_section_list';
|
||||
@import 'third-party-branding/google';
|
||||
@import 'third-party-branding/microsoft';
|
||||
@import 'avatar';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
font-size: 12px;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
padding: 15pt;
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
|
||||
7
debian/rules
vendored
@@ -17,8 +17,15 @@ override_dh_install: $(LANGUAGES)
|
||||
dh_install -X/config.js -X/package.json
|
||||
|
||||
$(LANGUAGES):
|
||||
LOCALE=$$(echo $@ | cut -c1-2) ; \
|
||||
if [ -f $(COUNTRIES_DIR)/$@.json ] ; \
|
||||
then \
|
||||
dh_install -pjitsi-meet-web $(COUNTRIES_DIR)/$@.json usr/share/jitsi-meet/lang/; \
|
||||
mv debian/jitsi-meet-web/usr/share/jitsi-meet/lang/$@.json debian/jitsi-meet-web/usr/share/jitsi-meet/lang/countries-$@.json; \
|
||||
else \
|
||||
if [ -f $(COUNTRIES_DIR)/$$LOCALE.json ] ; \
|
||||
then \
|
||||
dh_install -pjitsi-meet-web $(COUNTRIES_DIR)/$$LOCALE.json usr/share/jitsi-meet/lang/; \
|
||||
mv debian/jitsi-meet-web/usr/share/jitsi-meet/lang/$$LOCALE.json debian/jitsi-meet-web/usr/share/jitsi-meet/lang/countries-$@.json; \
|
||||
fi; \
|
||||
fi;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
### Adding an icon to the font file (e.g. for the floating menu)
|
||||
1. Go to https://icomoon.io/app/
|
||||
2. Go to "Manage Projects" from the menu on the top left.
|
||||
3. Use "Import project" and select <code>fonts/selection.json</code> from Jitsi Meet.
|
||||
4. Click "load".
|
||||
5. Add the new icons using the "Add icons from library" button...
|
||||
6. Go to "generate font" and make sure the identifiers for the new icons are correct.
|
||||
7. Download the result in a zip file using the "download" button.
|
||||
8. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
|
||||
9. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/_font.scss</code> in Jitsi Meet (do *not* copy the whole file)
|
||||
|
||||
Sample commit: https://github.com/jitsi/jitsi-meet/commit/68bc819b89aec12364fcf07b81efa83a1900eed6
|
||||
73
doc/api.md
@@ -192,6 +192,20 @@ The `command` parameter is String object with the name of the command. The follo
|
||||
api.executeCommand('displayName', 'New Nickname');
|
||||
```
|
||||
|
||||
* **password** - Sets the password for the room. This command requires one argument - the password name to be set.
|
||||
```javascript
|
||||
api.executeCommand('password', 'The Password');
|
||||
```
|
||||
|
||||
* **sendTones** - Play touch tones.
|
||||
```javascript
|
||||
api.executeCommand('sendTones', {
|
||||
tones: string, // The dial pad touch tones to play. For example, '12345#'.
|
||||
duration: number, // Optional. The number of milliseconds each tone should play. The default is 200.
|
||||
pause: number // Optional. The number of milliseconds between each tone. The default is 200.
|
||||
});
|
||||
```
|
||||
|
||||
* **subject** - Sets the subject of the conference. This command requires one argument - the new subject to be set.
|
||||
```javascript
|
||||
api.executeCommand('subject', 'New Conference Subject');
|
||||
@@ -222,6 +236,11 @@ api.executeCommand('toggleChat');
|
||||
api.executeCommand('toggleShareScreen');
|
||||
```
|
||||
|
||||
* **toggleTileView** - Enter / exit tile view layout mode. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleTileView');
|
||||
```
|
||||
|
||||
* **hangup** - Hangups the call. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('hangup');
|
||||
@@ -259,6 +278,14 @@ The `event` parameter is a String object with the name of the event.
|
||||
The `listener` parameter is a Function object with one argument that will be notified when the event occurs with data related to the event.
|
||||
|
||||
The following events are currently supported:
|
||||
* **cameraError** - event notifications about Jitsi-Meet having failed to access the camera. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
type: string, // A constant representing the overall type of the error.
|
||||
message: string // Additional information about the error.
|
||||
}
|
||||
```
|
||||
|
||||
* **avatarChanged** - event notifications about avatar
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
@@ -282,6 +309,14 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **micError** - event notifications about Jitsi-Meet having failed to access the mic. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
type: string, // A constant representing the overall type of the error.
|
||||
message: string // Additional information about the error.
|
||||
}
|
||||
```
|
||||
|
||||
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
@@ -296,6 +331,20 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **dominantSpeakerChanged** - receives event notifications about change in the dominant speaker. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
id: string //participantId of the new dominant speaker
|
||||
}
|
||||
```
|
||||
|
||||
* **tileViewChanged** - event notifications about tile view layout mode being entered or exited. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
enabled: boolean, // whether tile view is not displayed or not
|
||||
}
|
||||
```
|
||||
|
||||
* **incomingMessage** - Event notifications about incoming
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
@@ -339,6 +388,13 @@ changes. The listener will receive an object with the following structure:
|
||||
email: string // the new email
|
||||
}
|
||||
```
|
||||
* **feedbackSubmitted** - event notifications about conference feedback submission
|
||||
```javascript
|
||||
{
|
||||
error: string // The error which occurred during submission, if any.
|
||||
}
|
||||
```
|
||||
|
||||
* **filmstripDisplayChanged** - event notifications about the visibility of the filmstrip being updated.
|
||||
```javascript
|
||||
{
|
||||
@@ -354,6 +410,19 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **participantKickedOut** - event notifications about a participants being removed from the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
kicked: {
|
||||
id: string, // the id of the participant removed from the room
|
||||
local: boolean // whether or not the participant is the local particiapnt
|
||||
},
|
||||
kicker: {
|
||||
id: string // the id of the participant who kicked out the other participant
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
@@ -361,6 +430,8 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **passwordRequired** - event notifications fired when failing to join a room because it has a password.
|
||||
|
||||
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
@@ -402,6 +473,8 @@ The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **suspendDetected** - event notifications about detecting suspend event in host computer.
|
||||
|
||||
You can also add multiple event listeners by using `addEventListeners`.
|
||||
This method requires one argument of type Object. The object argument must
|
||||
have the names of the events as keys and the listeners of the events as values.
|
||||
|
||||
@@ -5,7 +5,7 @@ signed Android build for that, that can be a debug self-signed build too, just
|
||||
retrieve the signing hash. The key hash of an already signed ap can be obtained
|
||||
as follows (on macOS): ```keytool -list -printcert -jarfile the-app.apk```
|
||||
- Place the generated ```google-services.json``` file in ```android/app```
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app/src``` for
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app``` for
|
||||
iOS (you can stop at that step, no need for the driver and the code changes they
|
||||
suggest in the wizard).
|
||||
- You may want to exclude these files in YOUR GIT config (do not exclude them in
|
||||
|
||||
4
flow-typed/npm/flow-bin_v0.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583
|
||||
// flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x
|
||||
// flow-typed signature: 4e6a5da3290fe9ea49e6bcdced64f358
|
||||
// flow-typed version: c6154227d1/flow-bin_v0.x.x/flow_>=v0.25.x <=v0.103.x
|
||||
|
||||
declare module "flow-bin" {
|
||||
declare module.exports: string;
|
||||
|
||||
4
flow-typed/npm/jquery_v3.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: f26fda66e3a551aef37d3b0f53058e6a
|
||||
// flow-typed version: 44ad941b7a/jquery_v3.x.x/flow_>=v0.28.x
|
||||
// flow-typed signature: 538d762382091f2239d2d55cab1b574d
|
||||
// flow-typed version: c6154227d1/jquery_v3.x.x/flow_>=v0.28.x <=v0.103.x
|
||||
|
||||
/* eslint-disable max-len, no-unused-vars, flowtype/no-weak-types */
|
||||
|
||||
|
||||
413
flow-typed/npm/lodash_v4.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: a9b75804169260d49cda34b56dcfabe1
|
||||
// flow-typed version: e9dac1347c/lodash_v4.x.x/flow_>=v0.63.x
|
||||
// flow-typed signature: 96e97db746b98786dbff6de500b4b862
|
||||
// flow-typed version: 5fe02f287a/lodash_v4.x.x/flow_>=v0.63.x <=v0.103.x
|
||||
|
||||
declare module "lodash" {
|
||||
declare type Path = $ReadOnlyArray<string | number> | string | number;
|
||||
@@ -149,6 +149,11 @@ declare module "lodash" {
|
||||
separator?: RegExp | string
|
||||
};
|
||||
|
||||
declare type Cancelable = {
|
||||
cancel: () => void,
|
||||
flush: () => mixed
|
||||
};
|
||||
|
||||
declare type DebounceOptions = {
|
||||
leading?: boolean,
|
||||
maxWait?: number,
|
||||
@@ -265,14 +270,14 @@ declare module "lodash" {
|
||||
): -1;
|
||||
// alias of _.head
|
||||
first<T>(array: ?$ReadOnlyArray<T>): T;
|
||||
flatten<T, X>(array?: ?Array<Array<T> | X>): Array<T | X>;
|
||||
flatten<T, X>(array?: ?$ReadOnlyArray<$ReadOnlyArray<T> | X>): Array<T | X>;
|
||||
flattenDeep<T>(array?: ?(any[])): Array<T>;
|
||||
flattenDepth(array?: ?(any[]), depth?: ?number): any[];
|
||||
fromPairs<A, B>(pairs?: ?Array<[A, B]>): { [key: A]: B };
|
||||
head<T>(array: ?$ReadOnlyArray<T>): T;
|
||||
indexOf<T>(array: Array<T>, value: T, fromIndex?: number): number;
|
||||
indexOf<T>(array: $ReadOnlyArray<T>, value: T, fromIndex?: number): number;
|
||||
indexOf<T>(array: void | null, value?: ?T, fromIndex?: ?number): -1;
|
||||
initial<T>(array: ?Array<T>): Array<T>;
|
||||
initial<T>(array: ?$ReadOnlyArray<T>): Array<T>;
|
||||
intersection<T>(...arrays?: Array<$ReadOnlyArray<T>>): Array<T>;
|
||||
//Workaround until (...parameter: T, parameter2: U) works
|
||||
intersectionBy<T>(
|
||||
@@ -320,10 +325,10 @@ declare module "lodash" {
|
||||
a4?: ?$ReadOnlyArray<T>,
|
||||
comparator?: ?Comparator<T>
|
||||
): Array<T>;
|
||||
join<T>(array: Array<T>, separator?: ?string): string;
|
||||
join<T>(array: $ReadOnlyArray<T>, separator?: ?string): string;
|
||||
join<T>(array: void | null, separator?: ?string): "";
|
||||
last<T>(array: ?$ReadOnlyArray<T>): T;
|
||||
lastIndexOf<T>(array: Array<T>, value?: ?T, fromIndex?: ?number): number;
|
||||
lastIndexOf<T>(array: $ReadOnlyArray<T>, value?: ?T, fromIndex?: ?number): number;
|
||||
lastIndexOf<T>(array: void | null, value?: ?T, fromIndex?: ?number): -1;
|
||||
nth<T>(array: T[], n?: ?number): T;
|
||||
nth(array: void | null, n?: ?number): void;
|
||||
@@ -357,10 +362,10 @@ declare module "lodash" {
|
||||
start?: ?number,
|
||||
end?: ?number
|
||||
): Array<T>;
|
||||
sortedIndex<T>(array: Array<T>, value: T): number;
|
||||
sortedIndex<T>(array: $ReadOnlyArray<T>, value: T): number;
|
||||
sortedIndex<T>(array: void | null, value: ?T): 0;
|
||||
sortedIndexBy<T>(
|
||||
array: Array<T>,
|
||||
array: $ReadOnlyArray<T>,
|
||||
value?: ?T,
|
||||
iteratee?: ?ValueOnlyIteratee<T>
|
||||
): number;
|
||||
@@ -369,12 +374,12 @@ declare module "lodash" {
|
||||
value?: ?T,
|
||||
iteratee?: ?ValueOnlyIteratee<T>
|
||||
): 0;
|
||||
sortedIndexOf<T>(array: Array<T>, value: T): number;
|
||||
sortedIndexOf<T>(array: $ReadOnlyArray<T>, value: T): number;
|
||||
sortedIndexOf<T>(array: void | null, value?: ?T): -1;
|
||||
sortedLastIndex<T>(array: Array<T>, value: T): number;
|
||||
sortedLastIndex<T>(array: $ReadOnlyArray<T>, value: T): number;
|
||||
sortedLastIndex<T>(array: void | null, value?: ?T): 0;
|
||||
sortedLastIndexBy<T>(
|
||||
array: Array<T>,
|
||||
array: $ReadOnlyArray<T>,
|
||||
value: T,
|
||||
iteratee?: ValueOnlyIteratee<T>
|
||||
): number;
|
||||
@@ -383,16 +388,16 @@ declare module "lodash" {
|
||||
value?: ?T,
|
||||
iteratee?: ?ValueOnlyIteratee<T>
|
||||
): 0;
|
||||
sortedLastIndexOf<T>(array: Array<T>, value: T): number;
|
||||
sortedLastIndexOf<T>(array: $ReadOnlyArray<T>, value: T): number;
|
||||
sortedLastIndexOf<T>(array: void | null, value?: ?T): -1;
|
||||
sortedUniq<T>(array?: ?Array<T>): Array<T>;
|
||||
sortedUniq<T>(array?: ?$ReadOnlyArray<T>): Array<T>;
|
||||
sortedUniqBy<T>(
|
||||
array?: ?Array<T>,
|
||||
array?: ?$ReadOnlyArray<T>,
|
||||
iteratee?: ?ValueOnlyIteratee<T>
|
||||
): Array<T>;
|
||||
tail<T>(array?: ?Array<T>): Array<T>;
|
||||
take<T>(array?: ?Array<T>, n?: ?number): Array<T>;
|
||||
takeRight<T>(array?: ?Array<T>, n?: ?number): Array<T>;
|
||||
tail<T>(array?: ?$ReadOnlyArray<T>): Array<T>;
|
||||
take<T>(array?: ?$ReadOnlyArray<T>, n?: ?number): Array<T>;
|
||||
takeRight<T>(array?: ?$ReadOnlyArray<T>, n?: ?number): Array<T>;
|
||||
takeRightWhile<T>(array?: ?Array<T>, predicate?: ?Predicate<T>): Array<T>;
|
||||
takeWhile<T>(array?: ?Array<T>, predicate?: ?Predicate<T>): Array<T>;
|
||||
union<T>(...arrays?: Array<$ReadOnlyArray<T>>): Array<T>;
|
||||
@@ -442,59 +447,59 @@ declare module "lodash" {
|
||||
uniq<T>(array?: ?$ReadOnlyArray<T>): Array<T>;
|
||||
uniqBy<T>(array?: ?$ReadOnlyArray<T>, iteratee?: ?ValueOnlyIteratee<T>): Array<T>;
|
||||
uniqWith<T>(array?: ?$ReadOnlyArray<T>, comparator?: ?Comparator<T>): Array<T>;
|
||||
unzip<T>(array?: ?Array<T>): Array<T>;
|
||||
unzip<T>(array?: ?$ReadOnlyArray<T>): Array<T>;
|
||||
unzipWith<T>(array: ?Array<T>, iteratee?: ?Iteratee<T>): Array<T>;
|
||||
without<T>(array?: ?$ReadOnlyArray<T>, ...values?: Array<?T>): Array<T>;
|
||||
xor<T>(...array: Array<Array<T>>): Array<T>;
|
||||
//Workaround until (...parameter: T, parameter2: U) works
|
||||
xorBy<T>(a1?: ?Array<T>, iteratee?: ?ValueOnlyIteratee<T>): Array<T>;
|
||||
xorBy<T>(a1?: ?$ReadOnlyArray<T>, iteratee?: ?ValueOnlyIteratee<T>): Array<T>;
|
||||
xorBy<T>(
|
||||
a1: Array<T>,
|
||||
a2: Array<T>,
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>,
|
||||
iteratee?: ValueOnlyIteratee<T>
|
||||
): Array<T>;
|
||||
xorBy<T>(
|
||||
a1: Array<T>,
|
||||
a2: Array<T>,
|
||||
a3: Array<T>,
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>,
|
||||
a3: $ReadOnlyArray<T>,
|
||||
iteratee?: ValueOnlyIteratee<T>
|
||||
): Array<T>;
|
||||
xorBy<T>(
|
||||
a1: Array<T>,
|
||||
a2: Array<T>,
|
||||
a3: Array<T>,
|
||||
a4: Array<T>,
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>,
|
||||
a3: $ReadOnlyArray<T>,
|
||||
a4: $ReadOnlyArray<T>,
|
||||
iteratee?: ValueOnlyIteratee<T>
|
||||
): Array<T>;
|
||||
//Workaround until (...parameter: T, parameter2: U) works
|
||||
xorWith<T>(a1?: ?Array<T>, comparator?: ?Comparator<T>): Array<T>;
|
||||
xorWith<T>(a1?: ?$ReadOnlyArray<T>, comparator?: ?Comparator<T>): Array<T>;
|
||||
xorWith<T>(
|
||||
a1: Array<T>,
|
||||
a2: Array<T>,
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>,
|
||||
comparator?: Comparator<T>
|
||||
): Array<T>;
|
||||
xorWith<T>(
|
||||
a1: Array<T>,
|
||||
a2: Array<T>,
|
||||
a3: Array<T>,
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>,
|
||||
a3: $ReadOnlyArray<T>,
|
||||
comparator?: Comparator<T>
|
||||
): Array<T>;
|
||||
xorWith<T>(
|
||||
a1: Array<T>,
|
||||
a2: Array<T>,
|
||||
a3: Array<T>,
|
||||
a4: Array<T>,
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>,
|
||||
a3: $ReadOnlyArray<T>,
|
||||
a4: $ReadOnlyArray<T>,
|
||||
comparator?: Comparator<T>
|
||||
): Array<T>;
|
||||
zip<A, B>(a1?: ?(A[]), a2?: ?(B[])): Array<[A, B]>;
|
||||
zip<A, B, C>(a1: A[], a2: B[], a3: C[]): Array<[A, B, C]>;
|
||||
zip<A, B, C, D>(a1: A[], a2: B[], a3: C[], a4: D[]): Array<[A, B, C, D]>;
|
||||
zip<A, B>(a1?: ?($ReadOnlyArray<A>), a2?: ?($ReadOnlyArray<B>)): Array<[A, B]>;
|
||||
zip<A, B, C>(a1: $ReadOnlyArray<A>, a2: $ReadOnlyArray<B>, a3: $ReadOnlyArray<C>): Array<[A, B, C]>;
|
||||
zip<A, B, C, D>(a1: $ReadOnlyArray<A>, a2: $ReadOnlyArray<B>, a3: $ReadOnlyArray<C>, a4: $ReadOnlyArray<D>): Array<[A, B, C, D]>;
|
||||
zip<A, B, C, D, E>(
|
||||
a1: A[],
|
||||
a2: B[],
|
||||
a3: C[],
|
||||
a4: D[],
|
||||
a5: E[]
|
||||
a1: $ReadOnlyArray<A>,
|
||||
a2: $ReadOnlyArray<B>,
|
||||
a3: $ReadOnlyArray<C>,
|
||||
a4: $ReadOnlyArray<D>,
|
||||
a5: $ReadOnlyArray<E>
|
||||
): Array<[A, B, C, D, E]>;
|
||||
|
||||
zipObject<K, V>(props: Array<K>, values?: ?Array<V>): { [key: K]: V };
|
||||
@@ -502,44 +507,44 @@ declare module "lodash" {
|
||||
zipObjectDeep(props: any[], values?: ?any): Object;
|
||||
zipObjectDeep(props: void | null, values?: ?any): {};
|
||||
|
||||
zipWith<A>(a1?: ?Array<A>): Array<[A]>;
|
||||
zipWith<T, A>(a1: Array<A>, iteratee: (A) => T): Array<T>;
|
||||
zipWith<A>(a1?: ?$ReadOnlyArray<A>): Array<[A]>;
|
||||
zipWith<T, A>(a1: $ReadOnlyArray<A>, iteratee: (A) => T): Array<T>;
|
||||
|
||||
zipWith<A, B>(a1: Array<A>, a2: Array<B>): Array<[A, B]>;
|
||||
zipWith<A, B>(a1: $ReadOnlyArray<A>, a2: $ReadOnlyArray<B>): Array<[A, B]>;
|
||||
zipWith<T, A, B>(
|
||||
a1: Array<A>,
|
||||
a2: Array<B>,
|
||||
a1: $ReadOnlyArray<A>,
|
||||
a2: $ReadOnlyArray<B>,
|
||||
iteratee: (A, B) => T
|
||||
): Array<T>;
|
||||
): $ReadOnlyArray<T>;
|
||||
|
||||
zipWith<A, B, C>(
|
||||
a1: Array<A>,
|
||||
a2: Array<B>,
|
||||
a3: Array<C>
|
||||
a1: $ReadOnlyArray<A>,
|
||||
a2: $ReadOnlyArray<B>,
|
||||
a3: $ReadOnlyArray<C>
|
||||
): Array<[A, B, C]>;
|
||||
zipWith<T, A, B, C>(
|
||||
a1: Array<A>,
|
||||
a2: Array<B>,
|
||||
a3: Array<C>,
|
||||
a1: $ReadOnlyArray<A>,
|
||||
a2: $ReadOnlyArray<B>,
|
||||
a3: $ReadOnlyArray<C>,
|
||||
iteratee: (A, B, C) => T
|
||||
): Array<T>;
|
||||
|
||||
zipWith<A, B, C, D>(
|
||||
a1: Array<A>,
|
||||
a2: Array<B>,
|
||||
a3: Array<C>,
|
||||
a4: Array<D>
|
||||
a1: $ReadOnlyArray<A>,
|
||||
a2: $ReadOnlyArray<B>,
|
||||
a3: $ReadOnlyArray<C>,
|
||||
a4: $ReadOnlyArray<D>
|
||||
): Array<[A, B, C, D]>;
|
||||
zipWith<T, A, B, C, D>(
|
||||
a1: Array<A>,
|
||||
a2: Array<B>,
|
||||
a3: Array<C>,
|
||||
a4: Array<D>,
|
||||
a1: $ReadOnlyArray<A>,
|
||||
a2: $ReadOnlyArray<B>,
|
||||
a3: $ReadOnlyArray<C>,
|
||||
a4: $ReadOnlyArray<D>,
|
||||
iteratee: (A, B, C, D) => T
|
||||
): Array<T>;
|
||||
|
||||
// Collection
|
||||
countBy<T>(array: Array<T>, iteratee?: ?ValueOnlyIteratee<T>): Object;
|
||||
countBy<T>(array: $ReadOnlyArray<T>, iteratee?: ?ValueOnlyIteratee<T>): Object;
|
||||
countBy<T>(array: void | null, iteratee?: ?ValueOnlyIteratee<T>): {};
|
||||
countBy<T: Object>(object: T, iteratee?: ?ValueOnlyIteratee<T>): Object;
|
||||
// alias of _.forEach
|
||||
@@ -675,7 +680,7 @@ declare module "lodash" {
|
||||
iteratees?: ?$ReadOnlyArray<Iteratee<T>> | ?string,
|
||||
orders?: ?$ReadOnlyArray<"asc" | "desc"> | ?string
|
||||
): Array<T>;
|
||||
orderBy<V, T: Object>(
|
||||
orderBy<V, T: {}>(
|
||||
object: T,
|
||||
iteratees?: $ReadOnlyArray<OIteratee<*>> | string,
|
||||
orders?: $ReadOnlyArray<"asc" | "desc"> | string
|
||||
@@ -743,11 +748,11 @@ declare module "lodash" {
|
||||
object?: ?T,
|
||||
predicate?: ?OPredicate<A, T>
|
||||
): Array<V>;
|
||||
sample<T>(array: ?Array<T>): T;
|
||||
sample<T>(array: ?$ReadOnlyArray<T>): T;
|
||||
sample<V, T: Object>(object: T): V;
|
||||
sampleSize<T>(array?: ?Array<T>, n?: ?number): Array<T>;
|
||||
sampleSize<T>(array?: ?$ReadOnlyArray<T>, n?: ?number): Array<T>;
|
||||
sampleSize<V, T: Object>(object: T, n?: number): Array<V>;
|
||||
shuffle<T>(array: ?Array<T>): Array<T>;
|
||||
shuffle<T>(array: ?$ReadOnlyArray<T>): Array<T>;
|
||||
shuffle<V, T: Object>(object: T): Array<V>;
|
||||
size(collection: $ReadOnlyArray<any> | Object | string): number;
|
||||
some<T>(array: void | null, predicate?: ?Predicate<T>): false;
|
||||
@@ -785,7 +790,11 @@ declare module "lodash" {
|
||||
curry: Curry;
|
||||
curry(func: Function, arity?: number): Function;
|
||||
curryRight(func: Function, arity?: number): Function;
|
||||
debounce<F: (...any[]) => any>(func: F, wait?: number, options?: DebounceOptions): F;
|
||||
debounce<F: (...any[]) => any>(
|
||||
func: F,
|
||||
wait?: number,
|
||||
options?: DebounceOptions
|
||||
): F & Cancelable;
|
||||
defer(func: (...any[]) => any, ...args?: Array<any>): TimeoutID;
|
||||
delay(func: Function, wait: number, ...args?: Array<any>): TimeoutID;
|
||||
flip<R>(func: (...any[]) => R): (...any[]) => R;
|
||||
@@ -805,7 +814,7 @@ declare module "lodash" {
|
||||
func: F,
|
||||
wait?: number,
|
||||
options?: ThrottleOptions
|
||||
): F;
|
||||
): F & Cancelable;
|
||||
unary<F: (...any[]) => any>(func: F): F;
|
||||
wrap(value?: any, wrapper?: ?Function): Function;
|
||||
|
||||
@@ -830,13 +839,13 @@ declare module "lodash" {
|
||||
gte(value: any, other: any): boolean;
|
||||
isArguments(value: void | null): false;
|
||||
isArguments(value: any): boolean;
|
||||
isArray(value: Array<any>): true;
|
||||
isArray(value: $ReadOnlyArray<any>): true;
|
||||
isArray(value: any): false;
|
||||
isArrayBuffer(value: ArrayBuffer): true;
|
||||
isArrayBuffer(value: any): false;
|
||||
isArrayLike(value: Array<any> | string | { length: number }): true;
|
||||
isArrayLike(value: $ReadOnlyArray<any> | string | { length: number }): true;
|
||||
isArrayLike(value: any): false;
|
||||
isArrayLikeObject(value: { length: number } | Array<any>): true;
|
||||
isArrayLikeObject(value: { length: number } | $ReadOnlyArray<any>): true;
|
||||
isArrayLikeObject(value: any): false;
|
||||
isBoolean(value: boolean): true;
|
||||
isBoolean(value: any): false;
|
||||
@@ -939,16 +948,16 @@ declare module "lodash" {
|
||||
ceil(number: number, precision?: number): number;
|
||||
divide(dividend: number, divisor: number): number;
|
||||
floor(number: number, precision?: number): number;
|
||||
max<T>(array: ?Array<T>): T;
|
||||
max<T>(array: ?$ReadOnlyArray<T>): T;
|
||||
maxBy<T>(array: ?$ReadOnlyArray<T>, iteratee?: Iteratee<T>): T;
|
||||
mean(array: Array<*>): number;
|
||||
mean(array: $ReadOnlyArray<*>): number;
|
||||
meanBy<T>(array: Array<T>, iteratee?: Iteratee<T>): number;
|
||||
min<T>(array: ?Array<T>): T;
|
||||
min<T>(array: ?$ReadOnlyArray<T>): T;
|
||||
minBy<T>(array: ?$ReadOnlyArray<T>, iteratee?: Iteratee<T>): T;
|
||||
multiply(multiplier: number, multiplicand: number): number;
|
||||
round(number: number, precision?: number): number;
|
||||
subtract(minuend: number, subtrahend: number): number;
|
||||
sum(array: Array<*>): number;
|
||||
sum(array: $ReadOnlyArray<*>): number;
|
||||
sumBy<T>(array: $ReadOnlyArray<T>, iteratee?: Iteratee<T>): number;
|
||||
|
||||
// number
|
||||
@@ -1066,8 +1075,8 @@ declare module "lodash" {
|
||||
source: A | B | C | D
|
||||
) => any | void
|
||||
): Object;
|
||||
at(object?: ?Object, ...paths: Array<string>): Array<any>;
|
||||
at(object?: ?Object, paths: Array<string>): Array<any>;
|
||||
at(object?: ?Object, ...paths: $ReadOnlyArray<string>): Array<any>;
|
||||
at(object?: ?Object, paths: $ReadOnlyArray<string>): Array<any>;
|
||||
create(prototype: void | null, properties: void | null): {};
|
||||
create<T>(prototype: T, properties: Object): T;
|
||||
create(prototype: any, properties: void | null): {};
|
||||
@@ -1237,15 +1246,15 @@ declare module "lodash" {
|
||||
source: A | B | C | D
|
||||
) => any | void
|
||||
): Object;
|
||||
omit(object?: ?Object, ...props: Array<string>): Object;
|
||||
omit(object?: ?Object, props: Array<string>): Object;
|
||||
omit(object?: ?Object, ...props: $ReadOnlyArray<string>): Object;
|
||||
omit(object?: ?Object, props: $ReadOnlyArray<string>): Object;
|
||||
omitBy<A, T: { [id: any]: A } | { [id: number]: A }>(
|
||||
object: T,
|
||||
predicate?: ?OPredicate<A, T>
|
||||
): Object;
|
||||
omitBy<A, T>(object: void | null, predicate?: ?OPredicate<A, T>): {};
|
||||
pick(object?: ?Object, ...props: Array<string>): Object;
|
||||
pick(object?: ?Object, props: Array<string>): Object;
|
||||
pick(object?: ?Object, ...props: $ReadOnlyArray<string>): Object;
|
||||
pick(object?: ?Object, props: $ReadOnlyArray<string>): Object;
|
||||
pickBy<A, T: { [id: any]: A } | { [id: number]: A }>(
|
||||
object: T,
|
||||
predicate?: ?OPredicate<A, T>
|
||||
@@ -1758,65 +1767,65 @@ declare module "lodash/fp" {
|
||||
): number;
|
||||
// alias of _.head
|
||||
first<T>(array: $ReadOnlyArray<T>): T;
|
||||
flatten<T, X>(array: Array<Array<T> | X>): Array<T | X>;
|
||||
flatten<T, X>(array: $ReadOnlyArray<$ReadOnlyArray<T> | X>): Array<T | X>;
|
||||
unnest<T, X>(array: Array<Array<T> | X>): Array<T | X>;
|
||||
flattenDeep<T>(array: any[]): Array<T>;
|
||||
flattenDepth(depth: number): (array: any[]) => any[];
|
||||
flattenDepth(depth: number, array: any[]): any[];
|
||||
fromPairs<A, B>(pairs: Array<[A, B]>): { [key: A]: B };
|
||||
head<T>(array: $ReadOnlyArray<T>): T;
|
||||
indexOf<T>(value: T): (array: Array<T>) => number;
|
||||
indexOf<T>(value: T, array: Array<T>): number;
|
||||
indexOf<T>(value: T): (array: $ReadOnlyArray<T>) => number;
|
||||
indexOf<T>(value: T, array: $ReadOnlyArray<T>): number;
|
||||
indexOfFrom<T>(
|
||||
value: T
|
||||
): ((fromIndex: number) => (array: Array<T>) => number) &
|
||||
((fromIndex: number, array: Array<T>) => number);
|
||||
indexOfFrom<T>(value: T, fromIndex: number): (array: Array<T>) => number;
|
||||
indexOfFrom<T>(value: T, fromIndex: number, array: Array<T>): number;
|
||||
initial<T>(array: Array<T>): Array<T>;
|
||||
): ((fromIndex: number) => (array: $ReadOnlyArray<T>) => number) &
|
||||
((fromIndex: number, array: $ReadOnlyArray<T>) => number);
|
||||
indexOfFrom<T>(value: T, fromIndex: number): (array: $ReadOnlyArray<T>) => number;
|
||||
indexOfFrom<T>(value: T, fromIndex: number, array: $ReadOnlyArray<T>): number;
|
||||
initial<T>(array: $ReadOnlyArray<T>): Array<T>;
|
||||
init<T>(array: Array<T>): Array<T>;
|
||||
intersection<T>(a1: Array<T>): (a2: Array<T>) => Array<T>;
|
||||
intersection<T>(a1: Array<T>, a2: Array<T>): Array<T>;
|
||||
intersection<T>(a1: $ReadOnlyArray<T>): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
intersection<T>(a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>): Array<T>;
|
||||
intersectionBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
intersectionBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
intersectionBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>,
|
||||
a2: Array<T>
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>
|
||||
): Array<T>;
|
||||
intersectionWith<T>(
|
||||
comparator: Comparator<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
intersectionWith<T>(
|
||||
comparator: Comparator<T>,
|
||||
a1: Array<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
intersectionWith<T>(
|
||||
comparator: Comparator<T>,
|
||||
a1: Array<T>,
|
||||
a2: Array<T>
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>
|
||||
): Array<T>;
|
||||
join<T>(separator: string): (array: Array<T>) => string;
|
||||
join<T>(separator: string, array: Array<T>): string;
|
||||
last<T>(array: Array<T>): T;
|
||||
lastIndexOf<T>(value: T): (array: Array<T>) => number;
|
||||
lastIndexOf<T>(value: T, array: Array<T>): number;
|
||||
join<T>(separator: string): (array: $ReadOnlyArray<T>) => string;
|
||||
join<T>(separator: string, array: $ReadOnlyArray<T>): string;
|
||||
last<T>(array: $ReadOnlyArray<T>): T;
|
||||
lastIndexOf<T>(value: T): (array: $ReadOnlyArray<T>) => number;
|
||||
lastIndexOf<T>(value: T, array: $ReadOnlyArray<T>): number;
|
||||
lastIndexOfFrom<T>(
|
||||
value: T
|
||||
): ((fromIndex: number) => (array: Array<T>) => number) &
|
||||
((fromIndex: number, array: Array<T>) => number);
|
||||
): ((fromIndex: number) => (array: $ReadOnlyArray<T>) => number) &
|
||||
((fromIndex: number, array: $ReadOnlyArray<T>) => number);
|
||||
lastIndexOfFrom<T>(
|
||||
value: T,
|
||||
fromIndex: number
|
||||
): (array: Array<T>) => number;
|
||||
lastIndexOfFrom<T>(value: T, fromIndex: number, array: Array<T>): number;
|
||||
): (array: $ReadOnlyArray<T>) => number;
|
||||
lastIndexOfFrom<T>(value: T, fromIndex: number, array: $ReadOnlyArray<T>): number;
|
||||
nth<T>(n: number): (array: T[]) => T;
|
||||
nth<T>(n: number, array: T[]): T;
|
||||
pull<T>(value: T): (array: Array<T>) => Array<T>;
|
||||
@@ -1849,154 +1858,154 @@ declare module "lodash/fp" {
|
||||
reverse<T>(array: Array<T>): Array<T>;
|
||||
slice<T>(
|
||||
start: number
|
||||
): ((end: number) => (array: Array<T>) => Array<T>) &
|
||||
((end: number, array: Array<T>) => Array<T>);
|
||||
slice<T>(start: number, end: number): (array: Array<T>) => Array<T>;
|
||||
slice<T>(start: number, end: number, array: Array<T>): Array<T>;
|
||||
sortedIndex<T>(value: T): (array: Array<T>) => number;
|
||||
sortedIndex<T>(value: T, array: Array<T>): number;
|
||||
): ((end: number) => (array: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((end: number, array: $ReadOnlyArray<T>) => Array<T>);
|
||||
slice<T>(start: number, end: number): (array: $ReadOnlyArray<T>) => Array<T>;
|
||||
slice<T>(start: number, end: number, array: $ReadOnlyArray<T>): Array<T>;
|
||||
sortedIndex<T>(value: T): (array: $ReadOnlyArray<T>) => number;
|
||||
sortedIndex<T>(value: T, array: $ReadOnlyArray<T>): number;
|
||||
sortedIndexBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>
|
||||
): ((value: T) => (array: Array<T>) => number) &
|
||||
): ((value: T) => (array: $ReadOnlyArray<T>) => number) &
|
||||
((value: T, array: Array<T>) => number);
|
||||
sortedIndexBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
value: T
|
||||
): (array: Array<T>) => number;
|
||||
): (array: $ReadOnlyArray<T>) => number;
|
||||
sortedIndexBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
value: T,
|
||||
array: Array<T>
|
||||
array: $ReadOnlyArray<T>
|
||||
): number;
|
||||
sortedIndexOf<T>(value: T): (array: Array<T>) => number;
|
||||
sortedIndexOf<T>(value: T, array: Array<T>): number;
|
||||
sortedLastIndex<T>(value: T): (array: Array<T>) => number;
|
||||
sortedLastIndex<T>(value: T, array: Array<T>): number;
|
||||
sortedIndexOf<T>(value: T): (array: $ReadOnlyArray<T>) => number;
|
||||
sortedIndexOf<T>(value: T, array: $ReadOnlyArray<T>): number;
|
||||
sortedLastIndex<T>(value: T): (array: $ReadOnlyArray<T>) => number;
|
||||
sortedLastIndex<T>(value: T, array: $ReadOnlyArray<T>): number;
|
||||
sortedLastIndexBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>
|
||||
): ((value: T) => (array: Array<T>) => number) &
|
||||
): ((value: T) => (array: $ReadOnlyArray<T>) => number) &
|
||||
((value: T, array: Array<T>) => number);
|
||||
sortedLastIndexBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
value: T
|
||||
): (array: Array<T>) => number;
|
||||
): (array: $ReadOnlyArray<T>) => number;
|
||||
sortedLastIndexBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
value: T,
|
||||
array: Array<T>
|
||||
array: $ReadOnlyArray<T>
|
||||
): number;
|
||||
sortedLastIndexOf<T>(value: T): (array: Array<T>) => number;
|
||||
sortedLastIndexOf<T>(value: T, array: Array<T>): number;
|
||||
sortedUniq<T>(array: Array<T>): Array<T>;
|
||||
sortedUniqBy<T>(iteratee: ValueOnlyIteratee<T>, array: Array<T>): Array<T>;
|
||||
tail<T>(array: Array<T>): Array<T>;
|
||||
take<T>(n: number): (array: Array<T>) => Array<T>;
|
||||
take<T>(n: number, array: Array<T>): Array<T>;
|
||||
takeRight<T>(n: number): (array: Array<T>) => Array<T>;
|
||||
takeRight<T>(n: number, array: Array<T>): Array<T>;
|
||||
takeLast<T>(n: number): (array: Array<T>) => Array<T>;
|
||||
takeLast<T>(n: number, array: Array<T>): Array<T>;
|
||||
sortedLastIndexOf<T>(value: T): (array: $ReadOnlyArray<T>) => number;
|
||||
sortedLastIndexOf<T>(value: T, array: $ReadOnlyArray<T>): number;
|
||||
sortedUniq<T>(array: $ReadOnlyArray<T>): Array<T>;
|
||||
sortedUniqBy<T>(iteratee: ValueOnlyIteratee<T>, array: $ReadOnlyArray<T>): Array<T>;
|
||||
tail<T>(array: $ReadOnlyArray<T>): Array<T>;
|
||||
take<T>(n: number): (array: $ReadOnlyArray<T>) => Array<T>;
|
||||
take<T>(n: number, array: $ReadOnlyArray<T>): Array<T>;
|
||||
takeRight<T>(n: number): (array: $ReadOnlyArray<T>) => Array<T>;
|
||||
takeRight<T>(n: number, array: $ReadOnlyArray<T>): Array<T>;
|
||||
takeLast<T>(n: number): (array: $ReadOnlyArray<T>) => Array<T>;
|
||||
takeLast<T>(n: number, array: $ReadOnlyArray<T>): Array<T>;
|
||||
takeRightWhile<T>(predicate: Predicate<T>): (array: Array<T>) => Array<T>;
|
||||
takeRightWhile<T>(predicate: Predicate<T>, array: Array<T>): Array<T>;
|
||||
takeLastWhile<T>(predicate: Predicate<T>): (array: Array<T>) => Array<T>;
|
||||
takeLastWhile<T>(predicate: Predicate<T>, array: Array<T>): Array<T>;
|
||||
takeWhile<T>(predicate: Predicate<T>): (array: Array<T>) => Array<T>;
|
||||
takeWhile<T>(predicate: Predicate<T>, array: Array<T>): Array<T>;
|
||||
union<T>(a1: Array<T>): (a2: Array<T>) => Array<T>;
|
||||
union<T>(a1: Array<T>, a2: Array<T>): Array<T>;
|
||||
union<T>(a1: $ReadOnlyArray<T>): (a2: Array<T>) => Array<T>;
|
||||
union<T>(a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>): Array<T>;
|
||||
unionBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
unionBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
unionBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>,
|
||||
a2: Array<T>
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>
|
||||
): Array<T>;
|
||||
unionWith<T>(
|
||||
comparator: Comparator<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
unionWith<T>(
|
||||
comparator: Comparator<T>,
|
||||
a1: Array<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
unionWith<T>(
|
||||
comparator: Comparator<T>,
|
||||
a1: Array<T>,
|
||||
a2: Array<T>
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>
|
||||
): Array<T>;
|
||||
uniq<T>(array: Array<T>): Array<T>;
|
||||
uniqBy<T>(iteratee: ValueOnlyIteratee<T>): (array: Array<T>) => Array<T>;
|
||||
uniqBy<T>(iteratee: ValueOnlyIteratee<T>, array: Array<T>): Array<T>;
|
||||
uniqWith<T>(comparator: Comparator<T>): (array: Array<T>) => Array<T>;
|
||||
uniqWith<T>(comparator: Comparator<T>, array: Array<T>): Array<T>;
|
||||
unzip<T>(array: Array<T>): Array<T>;
|
||||
uniq<T>(array: $ReadOnlyArray<T>): Array<T>;
|
||||
uniqBy<T>(iteratee: ValueOnlyIteratee<T>): (array: $ReadOnlyArray<T>) => Array<T>;
|
||||
uniqBy<T>(iteratee: ValueOnlyIteratee<T>, array: $ReadOnlyArray<T>): Array<T>;
|
||||
uniqWith<T>(comparator: Comparator<T>): (array: $ReadOnlyArray<T>) => Array<T>;
|
||||
uniqWith<T>(comparator: Comparator<T>, array: $ReadOnlyArray<T>): Array<T>;
|
||||
unzip<T>(array: $ReadOnlyArray<T>): Array<T>;
|
||||
unzipWith<T>(iteratee: Iteratee<T>): (array: Array<T>) => Array<T>;
|
||||
unzipWith<T>(iteratee: Iteratee<T>, array: Array<T>): Array<T>;
|
||||
without<T>(values: Array<T>): (array: Array<T>) => Array<T>;
|
||||
without<T>(values: Array<T>, array: Array<T>): Array<T>;
|
||||
xor<T>(a1: Array<T>): (a2: Array<T>) => Array<T>;
|
||||
xor<T>(a1: Array<T>, a2: Array<T>): Array<T>;
|
||||
symmetricDifference<T>(a1: Array<T>): (a2: Array<T>) => Array<T>;
|
||||
symmetricDifference<T>(a1: Array<T>, a2: Array<T>): Array<T>;
|
||||
without<T>(values: $ReadOnlyArray<T>): (array: $ReadOnlyArray<T>) => Array<T>;
|
||||
without<T>(values: $ReadOnlyArray<T>, array: $ReadOnlyArray<T>): Array<T>;
|
||||
xor<T>(a1: $ReadOnlyArray<T>): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
xor<T>(a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>): Array<T>;
|
||||
symmetricDifference<T>(a1: $ReadOnlyArray<T>): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
symmetricDifference<T>(a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>): Array<T>;
|
||||
xorBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
xorBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
xorBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>,
|
||||
a2: Array<T>
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>
|
||||
): Array<T>;
|
||||
symmetricDifferenceBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
symmetricDifferenceBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
symmetricDifferenceBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
a1: Array<T>,
|
||||
a2: Array<T>
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>
|
||||
): Array<T>;
|
||||
xorWith<T>(
|
||||
comparator: Comparator<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
xorWith<T>(
|
||||
comparator: Comparator<T>,
|
||||
a1: Array<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
xorWith<T>(comparator: Comparator<T>, a1: Array<T>, a2: Array<T>): Array<T>;
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
xorWith<T>(comparator: Comparator<T>, a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>): Array<T>;
|
||||
symmetricDifferenceWith<T>(
|
||||
comparator: Comparator<T>
|
||||
): ((a1: Array<T>) => (a2: Array<T>) => Array<T>) &
|
||||
((a1: Array<T>, a2: Array<T>) => Array<T>);
|
||||
): ((a1: $ReadOnlyArray<T>) => (a2: $ReadOnlyArray<T>) => Array<T>) &
|
||||
((a1: $ReadOnlyArray<T>, a2: $ReadOnlyArray<T>) => Array<T>);
|
||||
symmetricDifferenceWith<T>(
|
||||
comparator: Comparator<T>,
|
||||
a1: Array<T>
|
||||
): (a2: Array<T>) => Array<T>;
|
||||
a1: $ReadOnlyArray<T>
|
||||
): (a2: $ReadOnlyArray<T>) => Array<T>;
|
||||
symmetricDifferenceWith<T>(
|
||||
comparator: Comparator<T>,
|
||||
a1: Array<T>,
|
||||
a2: Array<T>
|
||||
a1: $ReadOnlyArray<T>,
|
||||
a2: $ReadOnlyArray<T>
|
||||
): Array<T>;
|
||||
zip<A, B>(a1: A[]): (a2: B[]) => Array<[A, B]>;
|
||||
zip<A, B>(a1: A[], a2: B[]): Array<[A, B]>;
|
||||
zipAll(arrays: Array<Array<any>>): Array<any>;
|
||||
zipObject<K, V>(props?: Array<K>): (values?: Array<V>) => { [key: K]: V };
|
||||
zipObject<K, V>(props?: Array<K>, values?: Array<V>): { [key: K]: V };
|
||||
zip<A, B>(a1: $ReadOnlyArray<A>): (a2: $ReadOnlyArray<B>) => Array<[A, B]>;
|
||||
zip<A, B>(a1: $ReadOnlyArray<A>, a2: $ReadOnlyArray<B>): Array<[A, B]>;
|
||||
zipAll(arrays: $ReadOnlyArray<$ReadOnlyArray<any>>): Array<any>;
|
||||
zipObject<K, V>(props?: $ReadOnlyArray<K>): (values?: $ReadOnlyArray<V>) => { [key: K]: V };
|
||||
zipObject<K, V>(props?: $ReadOnlyArray<K>, values?: $ReadOnlyArray<V>): { [key: K]: V };
|
||||
zipObj(props: Array<any>): (values: Array<any>) => Object;
|
||||
zipObj(props: Array<any>, values: Array<any>): Object;
|
||||
zipObjectDeep(props: any[]): (values: any) => Object;
|
||||
@@ -2017,10 +2026,10 @@ declare module "lodash/fp" {
|
||||
// Collection
|
||||
countBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>
|
||||
): (collection: Array<T> | { [id: any]: T }) => { [string]: number };
|
||||
): (collection: $ReadOnlyArray<T> | { [id: any]: T }) => { [string]: number };
|
||||
countBy<T>(
|
||||
iteratee: ValueOnlyIteratee<T>,
|
||||
collection: Array<T> | { [id: any]: T }
|
||||
collection: $ReadOnlyArray<T> | { [id: any]: T }
|
||||
): { [string]: number };
|
||||
// alias of _.forEach
|
||||
each<T>(
|
||||
@@ -2946,8 +2955,10 @@ declare module "lodash/fp" {
|
||||
predicate: OPredicate<A>
|
||||
): (object: T) => Object;
|
||||
omitBy<A, T: { [id: any]: A }>(predicate: OPredicate<A>, object: T): Object;
|
||||
pick(props: Array<string>): (object: Object) => Object;
|
||||
pick(props: Array<string>, object: Object): Object;
|
||||
pick(...props: Array<string | {}>): Object;
|
||||
pick(props: $ReadOnlyArray<string>, object: Object): Object;
|
||||
pick(...props: Array<string>): (object: Object) => Object;
|
||||
pick(props: $ReadOnlyArray<string>): (object: Object) => Object;
|
||||
pickAll(props: Array<string>): (object: Object) => Object;
|
||||
pickAll(props: Array<string>, object: Object): Object;
|
||||
pickBy<A, T: { [id: any]: A }>(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: f06f00c3ad0cfedb90c0c6de04b219f3
|
||||
// flow-typed version: 3a6d556e4b/react-redux_v5.x.x/flow_>=v0.89.x
|
||||
// flow-typed signature: d2ddacbbca9700881249a9435381e689
|
||||
// flow-typed version: c6154227d1/react-redux_v7.x.x/flow_>=v0.89.x <=v0.103.x
|
||||
|
||||
/**
|
||||
The order of type arguments for connect() is as follows:
|
||||
@@ -27,6 +27,7 @@ Decrypting the abbreviations:
|
||||
RMP = Returned merge props
|
||||
CP = Props for returned component
|
||||
Com = React Component
|
||||
SS = Selected state
|
||||
ST = Static properties of Com
|
||||
EFO = Extra factory options (used only in connectAdvanced)
|
||||
*/
|
||||
@@ -38,7 +39,7 @@ declare module "react-redux" {
|
||||
|
||||
declare export type Options<S, OP, SP, MP> = {|
|
||||
pure?: boolean,
|
||||
withRef?: boolean,
|
||||
forwardRef?: boolean,
|
||||
areStatesEqual?: (next: S, prev: S) => boolean,
|
||||
areOwnPropsEqual?: (next: OP, prev: OP) => boolean,
|
||||
areStatePropsEqual?: (next: SP, prev: SP) => boolean,
|
||||
@@ -198,6 +199,19 @@ declare module "react-redux" {
|
||||
options?: ?Options<S, OP, SP, P>,
|
||||
): Connector<P, OP, P>;
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Typings for Hooks
|
||||
// ------------------------------------------------------------
|
||||
|
||||
declare export function useDispatch<D>(): D;
|
||||
|
||||
declare export function useSelector<S, SS>(
|
||||
selector: (state: S) => SS,
|
||||
equalityFn?: (a: SS, b: SS) => boolean,
|
||||
): SS;
|
||||
|
||||
declare export function useStore<Store>(): Store;
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Typings for Provider
|
||||
// ------------------------------------------------------------
|
||||
@@ -222,7 +236,7 @@ declare module "react-redux" {
|
||||
renderCountProp?: string,
|
||||
shouldHandleStateChanges?: boolean,
|
||||
storeKey?: string,
|
||||
withRef?: boolean,
|
||||
forwardRef?: boolean,
|
||||
};
|
||||
|
||||
declare type SelectorFactoryOptions<Com> = {
|
||||
@@ -231,7 +245,7 @@ declare module "react-redux" {
|
||||
renderCountProp: ?string,
|
||||
shouldHandleStateChanges: boolean,
|
||||
storeKey: string,
|
||||
withRef: boolean,
|
||||
forwardRef: boolean,
|
||||
displayName: string,
|
||||
wrappedComponentName: string,
|
||||
WrappedComponent: Com,
|
||||
@@ -272,5 +286,8 @@ declare module "react-redux" {
|
||||
createProvider: typeof createProvider,
|
||||
connect: typeof connect,
|
||||
connectAdvanced: typeof connectAdvanced,
|
||||
useDispatch: typeof useDispatch,
|
||||
useSelector: typeof useSelector,
|
||||
useStore: typeof useStore,
|
||||
};
|
||||
}
|
||||
89
flow-typed/npm/redux_v4.x.x.js
vendored
@@ -1,8 +1,7 @@
|
||||
// flow-typed signature: df80bdd535bfed9cf3223e077f3b4543
|
||||
// flow-typed version: c4c8963c9c/redux_v4.x.x/flow_>=v0.55.x
|
||||
// flow-typed signature: 99b2d8ebd0ab4be20976dc62a3bbf54c
|
||||
// flow-typed version: c6154227d1/redux_v4.x.x/flow_>=v0.89.x <=v0.103.x
|
||||
|
||||
declare module 'redux' {
|
||||
|
||||
/*
|
||||
|
||||
S = State
|
||||
@@ -11,49 +10,91 @@ declare module 'redux' {
|
||||
|
||||
*/
|
||||
|
||||
declare export type Action<T> = {
|
||||
type: T
|
||||
}
|
||||
|
||||
declare export type DispatchAPI<A> = (action: A) => A;
|
||||
declare export type Dispatch<A: { type: $Subtype<string> }> = DispatchAPI<A>;
|
||||
|
||||
declare export type Dispatch<A: { type: * }> = DispatchAPI<A>;
|
||||
|
||||
declare export type MiddlewareAPI<S, A, D = Dispatch<A>> = {
|
||||
dispatch: D;
|
||||
getState(): S;
|
||||
dispatch: D,
|
||||
getState(): S,
|
||||
};
|
||||
|
||||
declare export type Store<S, A, D = Dispatch<A>> = {
|
||||
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
|
||||
dispatch: D;
|
||||
getState(): S;
|
||||
subscribe(listener: () => void): () => void;
|
||||
replaceReducer(nextReducer: Reducer<S, A>): void
|
||||
dispatch: D,
|
||||
getState(): S,
|
||||
subscribe(listener: () => void): () => void,
|
||||
replaceReducer(nextReducer: Reducer<S, A>): void,
|
||||
};
|
||||
|
||||
declare export type Reducer<S, A> = (state: S | void, action: A) => S;
|
||||
|
||||
declare export type CombinedReducer<S, A> = (state: $Shape<S> & {} | void, action: A) => S;
|
||||
declare export type CombinedReducer<S, A> = (
|
||||
state: ($Shape<S> & {}) | void,
|
||||
action: A
|
||||
) => S;
|
||||
|
||||
declare export type Middleware<S, A, D = Dispatch<A>> =
|
||||
(api: MiddlewareAPI<S, A, D>) =>
|
||||
(next: D) => D;
|
||||
declare export type Middleware<S, A, D = Dispatch<A>> = (
|
||||
api: MiddlewareAPI<S, A, D>
|
||||
) => (next: D) => D;
|
||||
|
||||
declare export type StoreCreator<S, A, D = Dispatch<A>> = {
|
||||
(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
|
||||
(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
|
||||
(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>,
|
||||
(
|
||||
reducer: Reducer<S, A>,
|
||||
preloadedState: S,
|
||||
enhancer?: StoreEnhancer<S, A, D>
|
||||
): Store<S, A, D>,
|
||||
};
|
||||
|
||||
declare export type StoreEnhancer<S, A, D = Dispatch<A>> = (next: StoreCreator<S, A, D>) => StoreCreator<S, A, D>;
|
||||
declare export type StoreEnhancer<S, A, D = Dispatch<A>> = (
|
||||
next: StoreCreator<S, A, D>
|
||||
) => StoreCreator<S, A, D>;
|
||||
|
||||
declare export function createStore<S, A, D>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
|
||||
declare export function createStore<S, A, D>(reducer: Reducer<S, A>, preloadedState?: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
|
||||
declare export function createStore<S, A, D>(
|
||||
reducer: Reducer<S, A>,
|
||||
enhancer?: StoreEnhancer<S, A, D>
|
||||
): Store<S, A, D>;
|
||||
declare export function createStore<S, A, D>(
|
||||
reducer: Reducer<S, A>,
|
||||
preloadedState?: S,
|
||||
enhancer?: StoreEnhancer<S, A, D>
|
||||
): Store<S, A, D>;
|
||||
|
||||
declare export function applyMiddleware<S, A, D>(...middlewares: Array<Middleware<S, A, D>>): StoreEnhancer<S, A, D>;
|
||||
declare export function applyMiddleware<S, A, D>(
|
||||
...middlewares: Array<Middleware<S, A, D>>
|
||||
): StoreEnhancer<S, A, D>;
|
||||
|
||||
declare export type ActionCreator<A, B> = (...args: Array<B>) => A;
|
||||
declare export type ActionCreators<K, A> = { [key: K]: ActionCreator<A, any> };
|
||||
declare export type ActionCreators<K, A> = {
|
||||
[key: K]: ActionCreator<A, any>,
|
||||
};
|
||||
|
||||
declare export function bindActionCreators<A, C: ActionCreator<A, any>, D: DispatchAPI<A>>(actionCreator: C, dispatch: D): C;
|
||||
declare export function bindActionCreators<A, K, C: ActionCreators<K, A>, D: DispatchAPI<A>>(actionCreators: C, dispatch: D): C;
|
||||
declare export function bindActionCreators<
|
||||
A,
|
||||
C: ActionCreator<A, any>,
|
||||
D: DispatchAPI<A>
|
||||
>(
|
||||
actionCreator: C,
|
||||
dispatch: D
|
||||
): C;
|
||||
declare export function bindActionCreators<
|
||||
A,
|
||||
K,
|
||||
C: ActionCreators<K, A>,
|
||||
D: DispatchAPI<A>
|
||||
>(
|
||||
actionCreators: C,
|
||||
dispatch: D
|
||||
): C;
|
||||
|
||||
declare export function combineReducers<O: Object, A>(reducers: O): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
|
||||
declare export function combineReducers<O: {}, A>(
|
||||
reducers: O
|
||||
): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
|
||||
|
||||
declare export var compose: $Compose;
|
||||
}
|
||||
|
||||
4
flow-typed/npm/uuid_v3.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: 3cf668e64747095cab0bb360cf2fb34f
|
||||
// flow-typed version: d659bd0cb8/uuid_v3.x.x/flow_>=v0.32.x
|
||||
// flow-typed signature: 609c1622fc97de96d59519934aa5ce87
|
||||
// flow-typed version: c6154227d1/uuid_v3.x.x/flow_>=v0.32.x <=v0.103.x
|
||||
|
||||
declare module "uuid" {
|
||||
declare class uuid {
|
||||
|
||||