mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-01 04:12:27 +00:00
Compare commits
542 Commits
2802
...
node_10_bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e48ddc28eb | ||
|
|
71edea8aac | ||
|
|
2b1cb75e40 | ||
|
|
216782d606 | ||
|
|
c707b82419 | ||
|
|
3fdf944763 | ||
|
|
56100d0d5c | ||
|
|
486e8e35d9 | ||
|
|
554974a36d | ||
|
|
cd943319d6 | ||
|
|
837f496e8f | ||
|
|
c43f7c8979 | ||
|
|
c9c9f7eac0 | ||
|
|
5ccc397e47 | ||
|
|
00cd82d976 | ||
|
|
61deb74444 | ||
|
|
b30008e3a5 | ||
|
|
62b6737a3f | ||
|
|
cd77a9176c | ||
|
|
2a61968566 | ||
|
|
be4813e10d | ||
|
|
ae890dc093 | ||
|
|
9407f562f6 | ||
|
|
011a46ce2d | ||
|
|
8e0bd36ece | ||
|
|
6f8743af3a | ||
|
|
58d220d645 | ||
|
|
d3c5756f7a | ||
|
|
5ff1ce5a60 | ||
|
|
843f08f38e | ||
|
|
418575136f | ||
|
|
8c97ce2ee9 | ||
|
|
b2245729cc | ||
|
|
cc2b5a261b | ||
|
|
13c4ec884b | ||
|
|
7162080d00 | ||
|
|
b71adbdf70 | ||
|
|
2ae2f04f0a | ||
|
|
4424c456a9 | ||
|
|
cfa1e2f90d | ||
|
|
d290d28248 | ||
|
|
0474031a78 | ||
|
|
a57a5ca49d | ||
|
|
d8c1f107da | ||
|
|
9d27c36d80 | ||
|
|
057b300074 | ||
|
|
e164a23cf0 | ||
|
|
61456b0d99 | ||
|
|
df55448a2c | ||
|
|
6953569629 | ||
|
|
4d2614660c | ||
|
|
60f7ba7301 | ||
|
|
e5cc732b72 | ||
|
|
d604cdfe27 | ||
|
|
dc90800e50 | ||
|
|
6f17988d17 | ||
|
|
c54db8337d | ||
|
|
9a1e9fff98 | ||
|
|
a214be0dfe | ||
|
|
39a22effb1 | ||
|
|
ca600928f5 | ||
|
|
60decf7692 | ||
|
|
467452d110 | ||
|
|
af37141e3d | ||
|
|
ae7a882188 | ||
|
|
a49e590e7c | ||
|
|
1928efda11 | ||
|
|
57bf165ebd | ||
|
|
38517127c3 | ||
|
|
b7b43e8d9c | ||
|
|
8adc8a090a | ||
|
|
8c23d43a3a | ||
|
|
5773bc48ed | ||
|
|
9613755055 | ||
|
|
1fbc68d0cc | ||
|
|
2cbe7922f6 | ||
|
|
a712e26ee2 | ||
|
|
b673c4a11a | ||
|
|
8282873de5 | ||
|
|
2101f70a09 | ||
|
|
717fade79c | ||
|
|
959e687ed4 | ||
|
|
5f5adc3fa8 | ||
|
|
f317f993fd | ||
|
|
b2baab573e | ||
|
|
12ed711cce | ||
|
|
dfbd8d71ad | ||
|
|
d051d3450d | ||
|
|
7c88de20fe | ||
|
|
7b71482b03 | ||
|
|
2339f232a5 | ||
|
|
3bb3b4500d | ||
|
|
0fca0f392d | ||
|
|
c25d6eb9a8 | ||
|
|
37ff77cd5b | ||
|
|
fd30481ac2 | ||
|
|
2d87757aaa | ||
|
|
126e2d6e14 | ||
|
|
32fbcb17b9 | ||
|
|
d3bf0b7862 | ||
|
|
17f4b24a3f | ||
|
|
e63cd8c81b | ||
|
|
282e66b2dc | ||
|
|
72922130a2 | ||
|
|
514175b1af | ||
|
|
ceb8d7b03d | ||
|
|
22803f36e9 | ||
|
|
7674e90d4d | ||
|
|
1d128e027a | ||
|
|
ee9f304345 | ||
|
|
f148b50100 | ||
|
|
95785a9585 | ||
|
|
26d906fa46 | ||
|
|
eac069c930 | ||
|
|
5119f41af6 | ||
|
|
e2771b53bb | ||
|
|
008fb868a6 | ||
|
|
6dea107bcd | ||
|
|
d10d61fb7a | ||
|
|
9fe2b834eb | ||
|
|
a327a5d804 | ||
|
|
288bb59f71 | ||
|
|
f3d623e0ca | ||
|
|
388c906312 | ||
|
|
2043845d52 | ||
|
|
024671165a | ||
|
|
aba0912abf | ||
|
|
e446acb045 | ||
|
|
3927f29ba8 | ||
|
|
dafcde5060 | ||
|
|
3b754fa219 | ||
|
|
4283d8b342 | ||
|
|
31cc63b757 | ||
|
|
79bd5cce00 | ||
|
|
4fd8172126 | ||
|
|
fe7652ec90 | ||
|
|
72776e3a23 | ||
|
|
8addf0f436 | ||
|
|
73146e77cc | ||
|
|
28115b963d | ||
|
|
15819f7974 | ||
|
|
deb58798ba | ||
|
|
07ccb0a386 | ||
|
|
8d6e1b1872 | ||
|
|
80dadd0218 | ||
|
|
955e0a3382 | ||
|
|
1354731fc5 | ||
|
|
3ca704d81d | ||
|
|
3ad27961e5 | ||
|
|
86caf52d08 | ||
|
|
f2cb15ba44 | ||
|
|
d62974b433 | ||
|
|
8ff33684f7 | ||
|
|
b8179102c5 | ||
|
|
c23c798f7a | ||
|
|
3c27d2ee54 | ||
|
|
7267f386dc | ||
|
|
dba7f2d429 | ||
|
|
a896d8f076 | ||
|
|
99d285519d | ||
|
|
2704b2f822 | ||
|
|
62544188bd | ||
|
|
df0e107ea6 | ||
|
|
f10d42f8e4 | ||
|
|
7eda31315f | ||
|
|
87c010a9bd | ||
|
|
8d0d92a437 | ||
|
|
faada0abae | ||
|
|
1d99abc4a4 | ||
|
|
9aed4df6d2 | ||
|
|
d92b720704 | ||
|
|
25aaa74edc | ||
|
|
195462a1a8 | ||
|
|
9c03e95bf1 | ||
|
|
c353e9377f | ||
|
|
913c56c408 | ||
|
|
2f1223f721 | ||
|
|
4f1aaf89bf | ||
|
|
df6df1c6c3 | ||
|
|
1e804e552e | ||
|
|
b284f25fde | ||
|
|
49bdd53bee | ||
|
|
0827e02de9 | ||
|
|
0410af9e5e | ||
|
|
5a051024e6 | ||
|
|
e2def5f88b | ||
|
|
1078fa9d05 | ||
|
|
dda7568a48 | ||
|
|
4550848eac | ||
|
|
7822831b1e | ||
|
|
e03126e422 | ||
|
|
61652c69b3 | ||
|
|
b6e1a49d33 | ||
|
|
e0ac3efb5c | ||
|
|
65c76dcde5 | ||
|
|
5daa91ec1b | ||
|
|
473ba28171 | ||
|
|
52b55d65a0 | ||
|
|
8ebf2b7e47 | ||
|
|
cc38fcc5d0 | ||
|
|
a277421ecb | ||
|
|
2f2e69a6f5 | ||
|
|
0490a3cf73 | ||
|
|
bfc8ecfaa6 | ||
|
|
42c827434c | ||
|
|
0f3b67e53e | ||
|
|
2dfb107c57 | ||
|
|
f8c01646c7 | ||
|
|
0f0f9ea1b2 | ||
|
|
ce308eaa8b | ||
|
|
337cea6488 | ||
|
|
e125861b29 | ||
|
|
3241c7a929 | ||
|
|
55a2ef30a0 | ||
|
|
ae0bd9e64e | ||
|
|
9c769a650e | ||
|
|
07bc70c2f5 | ||
|
|
2ee1bf9351 | ||
|
|
7e1d97665a | ||
|
|
b978851a0f | ||
|
|
ef49817eaf | ||
|
|
cac8888b37 | ||
|
|
81853d971a | ||
|
|
b9c5ed3b03 | ||
|
|
0892e0b644 | ||
|
|
b41bf22be7 | ||
|
|
a1cc9bce91 | ||
|
|
8d3cecad86 | ||
|
|
bd8559fad6 | ||
|
|
fb75180632 | ||
|
|
046b06e436 | ||
|
|
af7c69a1aa | ||
|
|
7ad0639f7a | ||
|
|
54a1853e60 | ||
|
|
27021ea271 | ||
|
|
f5a667ad9e | ||
|
|
2b9ce40533 | ||
|
|
d3dd833f21 | ||
|
|
1cc372868b | ||
|
|
a6956c7c34 | ||
|
|
aaaa3e05d1 | ||
|
|
467a5aaae3 | ||
|
|
243dd16285 | ||
|
|
92001f4d37 | ||
|
|
6a31c59081 | ||
|
|
11c5b220a1 | ||
|
|
590ad90cd1 | ||
|
|
ca62e902bc | ||
|
|
34d1eb6768 | ||
|
|
b6b21e5410 | ||
|
|
b8daf0a9f9 | ||
|
|
39f1958300 | ||
|
|
0b1224495b | ||
|
|
ee7d180cbb | ||
|
|
4d3383c620 | ||
|
|
fd78203ff8 | ||
|
|
a36b341865 | ||
|
|
3d6e18394e | ||
|
|
9a6e5c67f5 | ||
|
|
50ea847905 | ||
|
|
b54a9e2bf7 | ||
|
|
918fb1dfc6 | ||
|
|
9f015df61d | ||
|
|
2cd1b7f80b | ||
|
|
afd2aea79c | ||
|
|
c62f761d67 | ||
|
|
acda279111 | ||
|
|
ccf9e2a362 | ||
|
|
3154c6f936 | ||
|
|
8ff3ae0ab2 | ||
|
|
ea22d12581 | ||
|
|
39e236a42c | ||
|
|
01c2786c95 | ||
|
|
9972e88b67 | ||
|
|
cd1c384cc8 | ||
|
|
f97f294d1a | ||
|
|
d3dd54ac3b | ||
|
|
13ee67d15c | ||
|
|
b25caedce7 | ||
|
|
5d4a2e87f8 | ||
|
|
44baca3185 | ||
|
|
b9f28a1beb | ||
|
|
6b7a883331 | ||
|
|
944cf4272d | ||
|
|
cc27e96b22 | ||
|
|
4e4755f91e | ||
|
|
0dcf8ef2f6 | ||
|
|
1ee71be961 | ||
|
|
bfdfb5321c | ||
|
|
dc246960df | ||
|
|
3bfab7718f | ||
|
|
980648df4d | ||
|
|
f2f991e969 | ||
|
|
96a837801e | ||
|
|
9c4da125c8 | ||
|
|
c203215c54 | ||
|
|
155e02bbfb | ||
|
|
d189888902 | ||
|
|
5aee082bf9 | ||
|
|
453c4b99dc | ||
|
|
961e1d611f | ||
|
|
bd449be20d | ||
|
|
9331b0870b | ||
|
|
00d1edcdef | ||
|
|
769e782c6f | ||
|
|
485ff81443 | ||
|
|
d12afc5c07 | ||
|
|
20444adbc9 | ||
|
|
63c017f8e6 | ||
|
|
afe7c4470d | ||
|
|
3f3a957f40 | ||
|
|
26c0164f1e | ||
|
|
b48c897d9b | ||
|
|
e59761baa2 | ||
|
|
baa2c217de | ||
|
|
5dc2aca081 | ||
|
|
84b589719f | ||
|
|
1c6d22b75e | ||
|
|
2547ee3a04 | ||
|
|
7328dd9125 | ||
|
|
0aa2d81844 | ||
|
|
e1f7d4585e | ||
|
|
bdae4b9493 | ||
|
|
8f688c3535 | ||
|
|
60ae8497c0 | ||
|
|
7c1b7a588e | ||
|
|
fd05f120ff | ||
|
|
d9fa05f42e | ||
|
|
12901be6be | ||
|
|
009eeccf3c | ||
|
|
e01acd9cf0 | ||
|
|
ac63a0fa73 | ||
|
|
1d296f9704 | ||
|
|
98e3bcb691 | ||
|
|
880d3525db | ||
|
|
c958c64ba8 | ||
|
|
cc319ad5e9 | ||
|
|
75b2eb0f99 | ||
|
|
acb3bd7ad7 | ||
|
|
7fcc95c9da | ||
|
|
87fa8de815 | ||
|
|
7164cd49e4 | ||
|
|
4ac367d403 | ||
|
|
ffd0827354 | ||
|
|
00f18e9369 | ||
|
|
401c43ee02 | ||
|
|
6ae5adcb3d | ||
|
|
535e5b4f64 | ||
|
|
a5d0bfe1d4 | ||
|
|
67d7d4fc14 | ||
|
|
342a00a6af | ||
|
|
bf14e66142 | ||
|
|
c3f602b7b6 | ||
|
|
9a06d2bf52 | ||
|
|
2951fefef9 | ||
|
|
361e5f0fad | ||
|
|
682169e44c | ||
|
|
c65ccb0af5 | ||
|
|
f035861617 | ||
|
|
045dad3354 | ||
|
|
b5e9c71865 | ||
|
|
7b0a6a2ee5 | ||
|
|
333a0f5f90 | ||
|
|
47aa14e9f6 | ||
|
|
ec8ad6190d | ||
|
|
42b85f73bd | ||
|
|
2bd0f77671 | ||
|
|
11c9d5f0ef | ||
|
|
942d7d7e56 | ||
|
|
e431acda18 | ||
|
|
7ee63a44c5 | ||
|
|
e3bc333115 | ||
|
|
750d6cf534 | ||
|
|
fecd138a3c | ||
|
|
1f8fa3b6d4 | ||
|
|
0acc9187ed | ||
|
|
675eea7b99 | ||
|
|
146ffb0918 | ||
|
|
57b302da3e | ||
|
|
b2f76f3ed6 | ||
|
|
85fbaac9b2 | ||
|
|
022954b40b | ||
|
|
7fa941cb8c | ||
|
|
ad259988b9 | ||
|
|
8c7a3f16b1 | ||
|
|
84c1c3dfd3 | ||
|
|
fccd0d6b29 | ||
|
|
12dda7acb9 | ||
|
|
28861c0054 | ||
|
|
0d3fac7c0f | ||
|
|
dcfebf746f | ||
|
|
4ab8d98cd1 | ||
|
|
fc643f06e4 | ||
|
|
c89791069b | ||
|
|
e06ad6cea9 | ||
|
|
ac834326e7 | ||
|
|
0cf585860b | ||
|
|
259066e2c6 | ||
|
|
fa0dacf7c8 | ||
|
|
ad4e73cc0a | ||
|
|
89eacd6982 | ||
|
|
4a9fdb8a10 | ||
|
|
602c8610bf | ||
|
|
07613aa856 | ||
|
|
db6f2c8868 | ||
|
|
c7fc26d864 | ||
|
|
a5f2cb8bd9 | ||
|
|
78866b0dd7 | ||
|
|
31011b24c2 | ||
|
|
8df54d2cb3 | ||
|
|
0f9c7d8697 | ||
|
|
9d62ecb742 | ||
|
|
bda1d7fdab | ||
|
|
d35a1d60a0 | ||
|
|
90d2340609 | ||
|
|
d70ca48728 | ||
|
|
a82ed4653e | ||
|
|
81be082fe7 | ||
|
|
546651e51f | ||
|
|
79b31543c5 | ||
|
|
7c8fa57bba | ||
|
|
880fb59b2c | ||
|
|
89160e55f0 | ||
|
|
ccf0c8a363 | ||
|
|
84f303dd3c | ||
|
|
3e79926ad4 | ||
|
|
ff0d42a95b | ||
|
|
2c0ef822ae | ||
|
|
83720a4ed5 | ||
|
|
01899b1dfd | ||
|
|
c62809d910 | ||
|
|
de725404ef | ||
|
|
f30bdb3dd9 | ||
|
|
2b20c55bfe | ||
|
|
c6d553738f | ||
|
|
c700261852 | ||
|
|
cbd510bf7d | ||
|
|
0817482b9c | ||
|
|
9ac5aafe10 | ||
|
|
efc9cc9f50 | ||
|
|
2b7976380e | ||
|
|
e93c9dde5d | ||
|
|
9aa12f7d14 | ||
|
|
e367490839 | ||
|
|
91323ebfec | ||
|
|
60c68b624e | ||
|
|
92414a346a | ||
|
|
6f962be322 | ||
|
|
1e3dc20b44 | ||
|
|
ec0439cbb1 | ||
|
|
05801711a7 | ||
|
|
57f7abc6dd | ||
|
|
c4b31435fb | ||
|
|
6a1e9e256d | ||
|
|
a4cfe97b38 | ||
|
|
b4983cfe04 | ||
|
|
4a680e11ac | ||
|
|
ce69ee60ca | ||
|
|
fa9a4480e6 | ||
|
|
f604b1c82d | ||
|
|
701552ec8f | ||
|
|
d26d1ff925 | ||
|
|
bcb955ea72 | ||
|
|
dbd1091364 | ||
|
|
acc41e6d0b | ||
|
|
4d21c28421 | ||
|
|
d15753f719 | ||
|
|
bced38cefc | ||
|
|
5499599720 | ||
|
|
118250750a | ||
|
|
9eb9306e87 | ||
|
|
5579464951 | ||
|
|
693b1c392f | ||
|
|
bbf505c076 | ||
|
|
ead62a5dde | ||
|
|
455660c891 | ||
|
|
7de8b96a07 | ||
|
|
ca4c7129f1 | ||
|
|
ad7728a599 | ||
|
|
5c7b7cd625 | ||
|
|
7186a9c79c | ||
|
|
c672ffd435 | ||
|
|
8cfc83f18c | ||
|
|
fa9549582f | ||
|
|
11b7144ad0 | ||
|
|
92fee790f2 | ||
|
|
72487fa7c7 | ||
|
|
380909371f | ||
|
|
d4d5ef202a | ||
|
|
c95cb0e9cf | ||
|
|
ab83a97fd5 | ||
|
|
9e2a101089 | ||
|
|
c8c198b0b1 | ||
|
|
4bb6e5aefd | ||
|
|
3dbb663922 | ||
|
|
592305df74 | ||
|
|
357b206831 | ||
|
|
d557a6505d | ||
|
|
f12ba37cf3 | ||
|
|
d4d2c25499 | ||
|
|
8b91afd80d | ||
|
|
d89227829f | ||
|
|
2b1c875b91 | ||
|
|
5b4a16cf6b | ||
|
|
480fe53001 | ||
|
|
2ecacf6c3e | ||
|
|
ee9fcbb735 | ||
|
|
db21e97c19 | ||
|
|
8cd2bd272b | ||
|
|
771d60f954 | ||
|
|
37cd5bb5b9 | ||
|
|
983e62ffe9 | ||
|
|
fcca15c827 | ||
|
|
6b3da4a4a4 | ||
|
|
8ecd6e6ced | ||
|
|
f7c4133fb7 | ||
|
|
bbb1dce42a | ||
|
|
31864b95ab | ||
|
|
33a748b569 | ||
|
|
83ede66037 | ||
|
|
2128c84212 | ||
|
|
a72463a302 | ||
|
|
62cc44f791 | ||
|
|
b4c95bec39 | ||
|
|
7012c77fe9 | ||
|
|
a8b1ca38dc | ||
|
|
99b5a41269 | ||
|
|
f608ad4e5e | ||
|
|
0d81e677bf | ||
|
|
22ce001f14 | ||
|
|
9650404099 | ||
|
|
7704809c4c | ||
|
|
75c7cfd9e1 | ||
|
|
ef7fb1a7b0 | ||
|
|
ccbf3efa38 | ||
|
|
39e46bacf6 | ||
|
|
12d7ab9026 | ||
|
|
9131d2448d | ||
|
|
44c498a566 | ||
|
|
192f1d44f5 | ||
|
|
b57dad576a | ||
|
|
494e8eb8d9 |
42
.flowconfig
42
.flowconfig
@@ -16,18 +16,14 @@
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
|
||||
; Ignore metro
|
||||
.*/node_modules/metro/.*
|
||||
|
||||
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
|
||||
; seen to cause errors and we have chosen not to fix.
|
||||
.*/node_modules/@atlaskit/.*/*.js.flow
|
||||
.*/node_modules/@atlassian
|
||||
.*/node_modules/bower/lib/node_modules/bower-json/test/.*
|
||||
.*/node_modules/immutable/dist/immutable.js.flow
|
||||
.*/node_modules/jsonlint/test/.*
|
||||
|
||||
; FIXME Remove once we update past commit
|
||||
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
|
||||
.*/node_modules/react-native/local-cli/link/link.js
|
||||
.*/node_modules/react-native-keep-awake/.*
|
||||
.*/node_modules/react-native-permissions/.*
|
||||
.*/node_modules/styled-components/.*
|
||||
|
||||
.*/\.git/.*
|
||||
@@ -37,33 +33,43 @@
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow/
|
||||
node_modules/react-native/flow-github/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
module.system=haste
|
||||
module.system.haste.use_name_reducers=true
|
||||
# get basename
|
||||
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
|
||||
# strip .js or .js.flow suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
|
||||
# strip .ios suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
|
||||
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/.*
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
; FIXME Remove once we update past commit
|
||||
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
|
||||
module.name_mapper='^./link/link$' -> 'emptyObject'
|
||||
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$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\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
; 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,
|
||||
@@ -77,4 +83,4 @@ module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
|
||||
[version]
|
||||
^0.57.0
|
||||
^0.78.0
|
||||
|
||||
27
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Before posting, please make sure you check https://community.jitsi.org
|
||||
|
||||
---
|
||||
|
||||
*This Issue tracker is only for reporting bugs and tracking code related issues.*
|
||||
|
||||
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed. General questions, installation help, and feature requests can also be posted to community.jitsi.org.
|
||||
|
||||
## Description
|
||||
---
|
||||
|
||||
## Current behavior
|
||||
---
|
||||
|
||||
## Expected Behavior
|
||||
---
|
||||
|
||||
## Possible Solution
|
||||
---
|
||||
|
||||
## Steps to reproduce
|
||||
---
|
||||
|
||||
# Environment details
|
||||
---
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -68,3 +68,11 @@ buck-out/
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
# precommit-hook
|
||||
.jshintignore
|
||||
.jshintrc
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
osx_image: xcode9.3
|
||||
osx_image: xcode10
|
||||
language: objective-c
|
||||
script:
|
||||
- "./ios/travis-ci/build-ipa.sh"
|
||||
|
||||
13
Makefile
13
Makefile
@@ -2,6 +2,7 @@ BUILD_DIR = build
|
||||
CLEANCSS = ./node_modules/.bin/cleancss
|
||||
DEPLOY_DIR = libs
|
||||
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
|
||||
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
|
||||
NODE_SASS = ./node_modules/.bin/node-sass
|
||||
NPM = npm
|
||||
OUTPUT_DIR = .
|
||||
@@ -19,7 +20,7 @@ compile:
|
||||
clean:
|
||||
rm -fr $(BUILD_DIR)
|
||||
|
||||
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
|
||||
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
|
||||
|
||||
deploy-init:
|
||||
rm -fr $(DEPLOY_DIR)
|
||||
@@ -33,6 +34,8 @@ deploy-appbundle:
|
||||
$(BUILD_DIR)/do_external_connect.min.map \
|
||||
$(BUILD_DIR)/external_api.min.js \
|
||||
$(BUILD_DIR)/external_api.min.map \
|
||||
$(BUILD_DIR)/flacEncodeWorker.min.js \
|
||||
$(BUILD_DIR)/flacEncodeWorker.min.map \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.js \
|
||||
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js \
|
||||
@@ -50,6 +53,12 @@ deploy-lib-jitsi-meet:
|
||||
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-libflac:
|
||||
cp \
|
||||
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js \
|
||||
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-css:
|
||||
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
|
||||
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
|
||||
@@ -58,7 +67,7 @@ deploy-css:
|
||||
deploy-local:
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
dev: deploy-init deploy-css deploy-lib-jitsi-meet
|
||||
dev: deploy-init deploy-css deploy-lib-jitsi-meet deploy-libflac
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package:
|
||||
|
||||
@@ -10,7 +10,7 @@ Jitsi Meet allows for very efficient collaboration. It allows users to stream th
|
||||
|
||||
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing the Jitsi Meet suite on your server and hosting your own conferencing service.
|
||||
|
||||
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system.
|
||||
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
|
||||
|
||||
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
|
||||
|
||||
@@ -33,7 +33,7 @@ You can get our mobile versions from here:
|
||||
|
||||
## Building the sources
|
||||
|
||||
Node.js >= 8 is required.
|
||||
Node.js >= 8 and npm >= 6 are required.
|
||||
|
||||
On Debian/Ubuntu systems, the required packages can be installed with:
|
||||
```
|
||||
@@ -130,7 +130,7 @@ equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
|
||||
Jitsi Meet in terms of security.
|
||||
|
||||
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
|
||||
at [Atlassian](https://atlassian.com).
|
||||
at [8x8](https://8x8.com).
|
||||
|
||||
## Mobile app
|
||||
Jitsi Meet is also available as a React Native app for Android and iOS.
|
||||
|
||||
@@ -126,6 +126,15 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const ignoredEvents
|
||||
= [ 'e2e_rtt', 'rtp.stats', 'rtt.by.region', 'available.device',
|
||||
'stream.switch.delay', 'ice.state.changed', 'ice.duration' ];
|
||||
|
||||
// Temporary removing some of the events that are too noisy.
|
||||
if (ignoredEvents.indexOf(event.action) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gaEvent = {
|
||||
'eventCategory': 'jitsi-meet',
|
||||
'eventAction': this._extractAction(event),
|
||||
|
||||
@@ -1,24 +1,58 @@
|
||||
# Jitsi Meet SDK for Android
|
||||
|
||||
## Build
|
||||
## Build your own, or use a pre-build SDK artifacts/binaries
|
||||
Jitsi conveniently provides a pre-build SDK artifacts/binaries in its Maven repository. When you do not require any modification to the SDK itself, it's suggested to use the pre-build SDK. This avoids the complexity of building and installing your own SDK artifacts/binaries.
|
||||
|
||||
### Use pre-build SDK artifacts/binaries
|
||||
In your project, add the Maven repository
|
||||
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
|
||||
dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle` files.
|
||||
|
||||
The repository typically goes into the `build.gradle` file in the root of your project:
|
||||
|
||||
```gradle
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dependency definitions belong in the individual module `build.gradle` files:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
// (other dependencies)
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
}
|
||||
```
|
||||
|
||||
### Build and use your own SDK artifacts/binaries
|
||||
|
||||
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
|
||||
|
||||
2. ```bash
|
||||
2. Create the SDK-release assembly, by invoking the following in the jitsi-meet
|
||||
project source:
|
||||
|
||||
```bash
|
||||
cd android/
|
||||
./gradlew :sdk:assembleRelease
|
||||
```
|
||||
When this successfully executes, artifacts/binaries are ready to be published
|
||||
into a Maven repository of your choice.
|
||||
|
||||
3. Configure the Maven repositories in which you are going to publish the
|
||||
artifacts/binaries during step 4. Modify
|
||||
artifacts/binaries during step 4.
|
||||
|
||||
In the file `android/sdk/build.gradle` modify the line that contains
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
in adroid/sdk/build.gradle for Jitsi Meet SDK for Android and/or
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
in android/build.gradle for the third-party react-native modules which Jitsi
|
||||
Meet SDK for Android depends on and are not publicly available in Maven
|
||||
repositories. Generally, if you are modifying the JavaScript code of Jitsi
|
||||
Meet SDK for Android only, you will very likely need to consider the former
|
||||
only.
|
||||
|
||||
Change this value (which represents the Maven repository location used internally
|
||||
by the Jitsi Developers) to the location of the repository that you'd like to use.
|
||||
|
||||
4. Publish the Maven artifact/binary of Jitsi Meet SDK for Android in the Maven
|
||||
repository configured in step 3:
|
||||
@@ -27,27 +61,66 @@
|
||||
./gradlew :sdk:publish
|
||||
cd ../
|
||||
```
|
||||
5. In _your_ project, add the Maven repository that you configured in step 3, as well
|
||||
as the dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle`
|
||||
file. Note that it's needed to pull in the transitive dependencies:
|
||||
|
||||
If you would like to publish a third-party react-native module which Jitsi
|
||||
Meet SDK for Android depends on and is not publicly available in Maven
|
||||
repositories, replace `sdk` with the name of the react-native module. For
|
||||
example, to publish react-native-webrtc:
|
||||
```gradle
|
||||
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
|
||||
```
|
||||
|
||||
Generally, if you are modifying the JavaScript code of Jitsi Meet SDK for Android only,
|
||||
the above will suffice. If you would like to publish a third-party react-native module
|
||||
which Jitsi Meet SDK for Android depends on (and is not publicly available in Maven
|
||||
repositories) continue below.
|
||||
|
||||
6. Create the release assembly for _each_ third-party react-native module that you
|
||||
need, replacing it's name in the example below.
|
||||
|
||||
```bash
|
||||
./gradlew :react-native-webrtc:assembleRelease
|
||||
```
|
||||
|
||||
7. Configure the Maven repositories in which you are going to publish the
|
||||
artifacts/binaries during step 8.
|
||||
|
||||
In the file `android/build.gradle` (note that this is a different file than the file
|
||||
that was modified in step 3) modify the line that contains
|
||||
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
|
||||
|
||||
Change this value (which represents the Maven repository location used internally
|
||||
by the Jitsi Developers) to the location of the repository that you'd like to use.
|
||||
You can use the same repository as the one you configured in step 3 if you want.
|
||||
|
||||
8. Publish the Maven artifact/binary of _each_ third-party react-native module that
|
||||
you need, replacing it's name in the example below. For example, to publish
|
||||
react-native-webrtc:
|
||||
|
||||
```bash
|
||||
./gradlew :react-native-webrtc:publish
|
||||
```
|
||||
|
||||
## Install
|
||||
Note that there should not be a need to explicitly add these dependencies in
|
||||
_your_ project, as they will be pulled in as transitive dependencies of
|
||||
`jitsi-meet-sdk`.
|
||||
|
||||
Add the Maven repository
|
||||
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
|
||||
dependency `org.jitsi.react:jitsi-meet-sdk:1.9.0` into your `build.gradle`.
|
||||
|
||||
## API
|
||||
## Using the API
|
||||
=======
|
||||
|
||||
Jitsi Meet SDK is an Android library which embodies the whole Jitsi Meet
|
||||
experience and makes it reusable by third-party apps.
|
||||
|
||||
First, add Java 1.8 compatibility support to your project by adding the
|
||||
following lines into your `build.gradle` file:
|
||||
|
||||
```
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
```
|
||||
|
||||
To get started, extends your `android.app.Activity` from
|
||||
`org.jitsi.meet.sdk.JitsiMeetActivity`:
|
||||
|
||||
@@ -132,13 +205,13 @@ which displays a single `JitsiMeetView`.
|
||||
|
||||
See JitsiMeetView.getDefaultURL.
|
||||
|
||||
#### getPictureInPictureEnabled()
|
||||
#### isPictureInPictureEnabled()
|
||||
|
||||
See JitsiMeetView.getPictureInPictureEnabled.
|
||||
See JitsiMeetView.isPictureInPictureEnabled.
|
||||
|
||||
#### getWelcomePageEnabled()
|
||||
#### isWelcomePageEnabled()
|
||||
|
||||
See JitsiMeetView.getWelcomePageEnabled.
|
||||
See JitsiMeetView.isWelcomePageEnabled.
|
||||
|
||||
#### loadURL(URL)
|
||||
|
||||
@@ -177,13 +250,13 @@ if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
|
||||
|
||||
Returns the `JitsiMeetViewListener` instance attached to the view.
|
||||
|
||||
#### getPictureInPictureEnabled()
|
||||
#### isPictureInPictureEnabled()
|
||||
|
||||
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
|
||||
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
|
||||
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
|
||||
|
||||
#### getWelcomePageEnabled()
|
||||
#### isWelcomePageEnabled()
|
||||
|
||||
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
|
||||
empty view will be rendered when not in a conference. Defaults to false.
|
||||
@@ -243,7 +316,7 @@ effect.
|
||||
|
||||
#### setWelcomePageEnabled(boolean)
|
||||
|
||||
Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
|
||||
Sets whether the Welcome page is enabled. See `isWelcomePageEnabled` for more
|
||||
information.
|
||||
|
||||
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
|
||||
@@ -418,3 +491,29 @@ Picture-in-Picture style scenario, in a rectangle too small to accommodate its
|
||||
Jitsi Meet SDK automatically enables (unless explicitly disabled by a
|
||||
`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
|
||||
mode iff the platform is supported i.e. Android >= Oreo.
|
||||
|
||||
## Dropbox integration
|
||||
|
||||
To setup the Dropbox integration, follow these steps:
|
||||
|
||||
1. Add the following to the app's AndroidManifest.xml and change `<APP_KEY>` to
|
||||
your Dropbox app key:
|
||||
```
|
||||
<activity
|
||||
android:configChanges="keyboard|orientation"
|
||||
android:launchMode="singleTask"
|
||||
android:name="com.dropbox.core.android.AuthActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="db-<APP_KEY>" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
```
|
||||
|
||||
2. Add the following to the app's strings.xml and change `<APP_KEY>` to your
|
||||
Dropbox app key:
|
||||
```
|
||||
<string name="dropbox_app_key"><APP_KEY></string>
|
||||
```
|
||||
|
||||
@@ -2,6 +2,7 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.jitsi.meet'
|
||||
@@ -25,16 +26,88 @@ android {
|
||||
exclude '/lib/x86_64/**'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
debug {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation 'com.google.android.gms:play-services-auth:15.0.0'
|
||||
|
||||
implementation project(':sdk')
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
|
||||
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
// Dropbox integration
|
||||
//
|
||||
|
||||
def plistParser = new XmlSlurper(
|
||||
/* validating */ false,
|
||||
/* namespaceAware */ false,
|
||||
/* allowDocTypeDeclaration */ true)
|
||||
plistParser.setFeature(
|
||||
'http://apache.org/xml/features/nonvalidating/load-external-dtd',
|
||||
false)
|
||||
def plist = plistParser.parse('../ios/app/src/Info.plist')
|
||||
def dropboxScheme = plist.dict.array.dict.array.string.find { string ->
|
||||
string.text().startsWith('db-')
|
||||
}
|
||||
def dropboxAppKey = dropboxScheme?.text() - 'db-'
|
||||
|
||||
if (dropboxAppKey) {
|
||||
android.defaultConfig.resValue('string', 'dropbox_app_key', "${dropboxAppKey}")
|
||||
|
||||
def dropboxActivity = """
|
||||
<activity
|
||||
android:configChanges="keyboard|orientation"
|
||||
android:launchMode="singleTask"
|
||||
android:name="com.dropbox.core.android.AuthActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="db-${dropboxAppKey}" />
|
||||
</intent-filter>
|
||||
</activity>""";
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.processManifest.doLast {
|
||||
def f = new File(manifestOutputDirectory, 'AndroidManifest.xml')
|
||||
if (!f.isFile()) {
|
||||
f = new File(new File(manifestOutputDirectory, output.dirName), 'AndroidManifest.xml')
|
||||
}
|
||||
if (f.exists()) {
|
||||
def charset = 'UTF-8'
|
||||
def s = f.getText(charset)
|
||||
s = s.replace('</application>', "${dropboxActivity}</application>")
|
||||
f.write(s, charset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.file('google-services.json').exists()) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
|
||||
5
android/app/proguard-rules-debug.pro
Normal file
5
android/app/proguard-rules-debug.pro
Normal file
@@ -0,0 +1,5 @@
|
||||
-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
|
||||
6
android/app/proguard-rules-release.pro
Normal file
6
android/app/proguard-rules-release.pro
Normal file
@@ -0,0 +1,6 @@
|
||||
-include proguard-rules.pro
|
||||
|
||||
# Crashlytics
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep public class * extends java.lang.Exception
|
||||
27
android/app/proguard-rules.pro
vendored
27
android/app/proguard-rules.pro
vendored
@@ -16,10 +16,6 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# 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
|
||||
|
||||
# React Native
|
||||
|
||||
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||
@@ -68,3 +64,26 @@
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
||||
|
||||
# FastImage
|
||||
|
||||
-keep public class com.dylanvann.fastimage.** {*;}
|
||||
|
||||
# We added the following when we switched minifyEnabled on. Probably because we
|
||||
# ran the app and hit problems...
|
||||
|
||||
-keep class com.facebook.react.bridge.CatalystInstanceImpl { *; }
|
||||
-keep class com.facebook.react.bridge.ExecutorToken { *; }
|
||||
-keep class com.facebook.react.bridge.JavaScriptExecutor { *; }
|
||||
-keep class com.facebook.react.bridge.ModuleRegistryHolder { *; }
|
||||
-keep class com.facebook.react.bridge.ReadableType { *; }
|
||||
-keep class com.facebook.react.bridge.queue.NativeRunnable { *; }
|
||||
-keep class com.facebook.react.devsupport.** { *; }
|
||||
-keep class org.webrtc.** { *; }
|
||||
|
||||
-dontwarn com.facebook.react.devsupport.**
|
||||
-dontwarn com.google.appengine.**
|
||||
-dontwarn com.squareup.okhttp.**
|
||||
-dontwarn javax.servlet.**
|
||||
|
||||
# ^^^ We added the above when we switched minifyEnabled on.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:name=".MainApplication"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
|
||||
@@ -27,10 +27,9 @@ import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
import org.jitsi.meet.sdk.invite.InviteControllerListener;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -65,6 +64,8 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
if (BuildConfig.DEBUG && view != null) {
|
||||
view.setListener(new JitsiMeetViewListener() {
|
||||
private void on(String name, Map<String, Object> data) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Log with the tag "ReactNative" in order to have the log
|
||||
// visible in react-native log-android as well.
|
||||
Log.d(
|
||||
@@ -129,6 +130,8 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
private void onAddPeopleControllerInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
|
||||
// it is going to be memory-leaked in the associated InviteController
|
||||
// and no subsequent InviteButton clicks/taps will be delivered.
|
||||
@@ -142,6 +145,8 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
int size = results.size();
|
||||
|
||||
if (size > 0) {
|
||||
@@ -182,6 +187,8 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
private void onInviteControllerBeginAddPeople(
|
||||
InviteController inviteController,
|
||||
AddPeopleController addPeopleController) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// Log with the tag "ReactNative" in order to have the log visible in
|
||||
// react-native log-android as well.
|
||||
Log.d(
|
||||
@@ -220,20 +227,4 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode,
|
||||
String[] permissions,
|
||||
int[] grantResults) {
|
||||
CalendarEventsPackage.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
|
||||
super.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
|
||||
/**
|
||||
* Simple {@link Application} for hooking up LeakCanary:
|
||||
* https://github.com/square/leakcanary
|
||||
*/
|
||||
public class MainApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (!LeakCanary.isInAnalyzerProcess(this)) {
|
||||
LeakCanary.install(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath 'com.google.gms:google-services:3.2.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files.
|
||||
@@ -16,8 +17,10 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "https://maven.google.com" }
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "$rootDir/../node_modules/jsc-android/dist" }
|
||||
// React Native (JS, Obj-C sources, Android binaries) is installed from
|
||||
// npm.
|
||||
maven { url "$rootDir/../node_modules/react-native/android" }
|
||||
@@ -34,6 +37,12 @@ allprojects {
|
||||
def version = new groovy.json.JsonSlurper().parseText(file.text).version
|
||||
details.useVersion version
|
||||
}
|
||||
if (details.requested.group == 'org.webkit'
|
||||
&& details.requested.name == 'android-jsc') {
|
||||
def file = new File("$rootDir/../node_modules/jsc-android/package.json")
|
||||
def version = new groovy.json.JsonSlurper().parseText(file.text).version
|
||||
details.useVersion "r${version.tokenize('.')[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,10 +153,11 @@ allprojects {
|
||||
}
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "26.0.2"
|
||||
compileSdkVersion = 26
|
||||
minSdkVersion = 16
|
||||
buildToolsVersion = "27.0.3"
|
||||
compileSdkVersion = 27
|
||||
minSdkVersion = 21
|
||||
targetSdkVersion = 26
|
||||
supportLibVersion = "27.1.1"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
@@ -170,3 +180,8 @@ subprojects { subproject ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.4'
|
||||
distributionUrl = distributionUrl.replace("bin", "all")
|
||||
}
|
||||
|
||||
@@ -17,5 +17,4 @@
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
||||
version=1
|
||||
version=1
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
|
||||
110
android/gradlew
vendored
110
android/gradlew
vendored
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
14
android/gradlew.bat
vendored
14
android/gradlew.bat
vendored
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -19,128 +19,144 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
compile 'com.android.support:appcompat-v7:27.0.2'
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
|
||||
compile project(':react-native-background-timer')
|
||||
compile project(':react-native-fetch-blob')
|
||||
compile project(':react-native-immersive')
|
||||
compile project(':react-native-keep-awake')
|
||||
compile project(':react-native-locale-detector')
|
||||
compile project(':react-native-sound')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-webrtc')
|
||||
compile project(':react-native-calendar-events')
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
|
||||
api 'com.facebook.react:react-native:+'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
// Build process helpers
|
||||
//
|
||||
|
||||
void runBefore(String dependentTaskName, Task task) {
|
||||
Task dependentTask = tasks.findByPath(dependentTaskName);
|
||||
if (dependentTask != null) {
|
||||
dependentTask.dependsOn task
|
||||
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-google-signin")) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-linear-gradient')
|
||||
implementation project(':react-native-locale-detector')
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':react-native-webrtc')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
android.buildTypes.all { buildType ->
|
||||
def buildNameCapitalized = "${buildType.name.capitalize()}"
|
||||
def bundlePath = "${buildDir}/intermediates/bundles/${buildType.name}"
|
||||
|
||||
// Bundle fonts in react-native-vector-icons.
|
||||
// Here we bundle all assets, resources and React files. We cannot use the
|
||||
// react.gradle file provided by react-native because it's designed to be used
|
||||
// in an application (it taps into applicationVariants, but the SDK is a library
|
||||
// so we need libraryVariants instead).
|
||||
android.libraryVariants.all { def variant ->
|
||||
// Create variant and target names
|
||||
def targetName = variant.name.capitalize()
|
||||
def targetPath = variant.dirName
|
||||
|
||||
// React js bundle directories
|
||||
def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
|
||||
def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
|
||||
|
||||
def jsBundleFile = file("$jsBundleDir/index.android.bundle")
|
||||
|
||||
def currentBundleTask = tasks.create(
|
||||
name: "bundle${targetName}JsAndAssets",
|
||||
type: Exec) {
|
||||
group = "react"
|
||||
description = "bundle JS and assets for ${targetName}."
|
||||
|
||||
// Create dirs if they are not there (e.g. the "clean" task just ran)
|
||||
doFirst {
|
||||
jsBundleDir.deleteDir()
|
||||
jsBundleDir.mkdirs()
|
||||
resourcesDir.deleteDir()
|
||||
resourcesDir.mkdirs()
|
||||
}
|
||||
|
||||
// Set up inputs and outputs so gradle can cache the result
|
||||
def reactRoot = file("${projectDir}/../../")
|
||||
inputs.files fileTree(dir: reactRoot, excludes: ["android/**", "ios/**"])
|
||||
outputs.dir jsBundleDir
|
||||
outputs.dir resourcesDir
|
||||
|
||||
// Set up the call to the react-native cli
|
||||
workingDir reactRoot
|
||||
|
||||
// Set up dev mode
|
||||
def devEnabled = !targetName.toLowerCase().contains("release")
|
||||
|
||||
// Run the bundler
|
||||
commandLine(
|
||||
"node",
|
||||
"node_modules/react-native/local-cli/cli.js",
|
||||
"bundle",
|
||||
"--platform", "android",
|
||||
"--dev", "${devEnabled}",
|
||||
"--reset-cache",
|
||||
"--entry-file", "index.android.js",
|
||||
"--bundle-output", jsBundleFile,
|
||||
"--assets-dest", resourcesDir)
|
||||
|
||||
// Disable bundling on dev builds
|
||||
enabled !devEnabled
|
||||
}
|
||||
|
||||
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
|
||||
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
|
||||
|
||||
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
|
||||
variant.mergeResources.dependsOn(currentBundleTask)
|
||||
|
||||
def assetsDir = variant.mergeAssets.outputDir
|
||||
|
||||
variant.mergeAssets.doLast {
|
||||
// Bundle fonts
|
||||
//
|
||||
|
||||
def currentFontTask = tasks.create(
|
||||
name: "copy${buildNameCapitalized}Fonts",
|
||||
type: Copy) {
|
||||
copy {
|
||||
from("${projectDir}/../../fonts/jitsi.ttf")
|
||||
from("${projectDir}/../../node_modules/react-native-vector-icons/Fonts/")
|
||||
into("${bundlePath}/assets/fonts")
|
||||
into("${assetsDir}/fonts")
|
||||
}
|
||||
|
||||
currentFontTask.dependsOn("merge${buildNameCapitalized}Resources")
|
||||
currentFontTask.dependsOn("merge${buildNameCapitalized}Assets")
|
||||
|
||||
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentFontTask)
|
||||
runBefore("processX86${buildNameCapitalized}Resources", currentFontTask)
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentFontTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentFontTask)
|
||||
|
||||
def currentSoundsTask = tasks.create(
|
||||
name: "copy${buildNameCapitalized}Sounds",
|
||||
type: Copy) {
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
into("${bundlePath}/assets/sounds")
|
||||
}
|
||||
|
||||
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Resources")
|
||||
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Assets")
|
||||
|
||||
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("processX86${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentSoundsTask)
|
||||
|
||||
// Bundle JavaScript and React resources.
|
||||
// (adapted from react-native/react.gradle)
|
||||
// Bundle sounds
|
||||
//
|
||||
|
||||
// React JS bundle directories
|
||||
def jsBundleDir = file("${bundlePath}/assets")
|
||||
def resourcesDir = file("${bundlePath}/res/merged")
|
||||
def jsBundleFile = file("${jsBundleDir}/index.android.bundle")
|
||||
|
||||
// Bundle task name for variant.
|
||||
def bundleJsAndAssetsTaskName = "bundle${buildNameCapitalized}JsAndAssets"
|
||||
|
||||
def currentBundleTask = tasks.create(
|
||||
name: bundleJsAndAssetsTaskName,
|
||||
type: Exec) {
|
||||
// Set up inputs and outputs so gradle can cache the result.
|
||||
def reactRoot = file("${projectDir}/../../")
|
||||
inputs.files fileTree(dir: reactRoot, excludes: ['android/**', 'ios/**'])
|
||||
outputs.dir jsBundleDir
|
||||
outputs.dir resourcesDir
|
||||
|
||||
// Set up the call to the react-native cli.
|
||||
workingDir reactRoot
|
||||
|
||||
// Create JS bundle
|
||||
def devEnabled = !buildNameCapitalized.toLowerCase().contains('release')
|
||||
commandLine(
|
||||
'node',
|
||||
'node_modules/react-native/local-cli/cli.js',
|
||||
'bundle',
|
||||
'--assets-dest', resourcesDir,
|
||||
'--bundle-output', jsBundleFile,
|
||||
'--dev', "${devEnabled}",
|
||||
'--entry-file', 'index.android.js',
|
||||
'--platform', 'android',
|
||||
'--reset-cache')
|
||||
|
||||
// Disable bundling on dev builds
|
||||
enabled !devEnabled
|
||||
copy {
|
||||
from("${projectDir}/../../sounds/joined.wav")
|
||||
from("${projectDir}/../../sounds/left.wav")
|
||||
from("${projectDir}/../../sounds/outgoingRinging.wav")
|
||||
from("${projectDir}/../../sounds/outgoingStart.wav")
|
||||
from("${projectDir}/../../sounds/recordingOn.mp3")
|
||||
from("${projectDir}/../../sounds/recordingOff.mp3")
|
||||
from("${projectDir}/../../sounds/rejected.wav")
|
||||
into("${assetsDir}/sounds")
|
||||
}
|
||||
|
||||
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
|
||||
currentBundleTask.dependsOn("merge${buildNameCapitalized}Resources")
|
||||
currentBundleTask.dependsOn("merge${buildNameCapitalized}Assets")
|
||||
// Copy React assets
|
||||
//
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(jsBundleDir)
|
||||
into(assetsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentBundleTask)
|
||||
runBefore("processX86${buildNameCapitalized}Resources", currentBundleTask)
|
||||
runBefore("processUniversal${buildNameCapitalized}Resources", currentBundleTask)
|
||||
runBefore("process${buildNameCapitalized}Resources", currentBundleTask)
|
||||
variant.mergeResources.doLast {
|
||||
// Copy React resources
|
||||
//
|
||||
if (currentBundleTask.enabled) {
|
||||
copy {
|
||||
from(resourcesDir)
|
||||
into(variant.mergeResources.outputDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
aarArchive(MavenPublication) {
|
||||
|
||||
@@ -16,7 +16,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
class AndroidSettingsModule extends ReactContextBaseJavaModule {
|
||||
class AndroidSettingsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public AndroidSettingsModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -11,7 +27,9 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class AppInfoModule extends ReactContextBaseJavaModule {
|
||||
class AppInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public AppInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ import android.content.pm.PackageManager;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
@@ -41,6 +39,8 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Module implementing a simple API to select the appropriate audio device for a
|
||||
@@ -57,7 +57,10 @@ import java.util.Set;
|
||||
* Before a call has started and after it has ended the
|
||||
* {@code AudioModeModule.DEFAULT} mode should be used.
|
||||
*/
|
||||
class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
class AudioModeModule
|
||||
extends ReactContextBaseJavaModule
|
||||
implements AudioManager.OnAudioFocusChangeListener {
|
||||
|
||||
/**
|
||||
* Constants representing the audio mode.
|
||||
* - DEFAULT: Used before and after every call. It represents the default
|
||||
@@ -72,13 +75,20 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
private static final int VIDEO_CALL = 2;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constant defining the action for plugging in a headset. This is used on
|
||||
* our device detection system for API < 23.
|
||||
*/
|
||||
private static final String ACTION_HEADSET_PLUG
|
||||
= (Build.VERSION.SDK_INT >= 21)
|
||||
? AudioManager.ACTION_HEADSET_PLUG
|
||||
: Intent.ACTION_HEADSET_PLUG;
|
||||
|
||||
/**
|
||||
* Constant defining a USB headset. Only available on API level >= 26.
|
||||
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
|
||||
*/
|
||||
private static final int TYPE_USB_HEADSET = 22;
|
||||
|
||||
/**
|
||||
* The name of {@code AudioModeModule} to be used in the React Native
|
||||
* bridge.
|
||||
@@ -90,6 +100,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
static final String TAG = MODULE_NAME;
|
||||
|
||||
/**
|
||||
* Indicator that we have lost audio focus.
|
||||
*/
|
||||
private boolean audioFocusLost = false;
|
||||
|
||||
/**
|
||||
* {@link AudioManager} instance used to interact with the Android audio
|
||||
* subsystem.
|
||||
@@ -103,10 +118,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
|
||||
|
||||
/**
|
||||
* {@link Handler} for running all operations on the main thread.
|
||||
* {@link ExecutorService} for running all audio operations on a dedicated
|
||||
* thread.
|
||||
*/
|
||||
private final Handler mainThreadHandler
|
||||
= new Handler(Looper.getMainLooper());
|
||||
private static final ExecutorService executor
|
||||
= Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* {@link Runnable} for running audio device detection the main thread.
|
||||
@@ -133,6 +149,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
|
||||
case TYPE_USB_HEADSET:
|
||||
devices.add(DEVICE_HEADPHONES);
|
||||
break;
|
||||
}
|
||||
@@ -212,7 +229,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
|
||||
// Do an initial detection on Android >= M.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mainThreadHandler.post(onAudioDeviceChangeRunner);
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
} else {
|
||||
// On Android < M, detect if we have an earpiece.
|
||||
PackageManager pm = reactContext.getPackageManager();
|
||||
@@ -252,7 +269,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getAudioDevices(final Promise promise) {
|
||||
mainThreadHandler.post(new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
WritableMap map = Arguments.createMap();
|
||||
@@ -289,7 +306,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* Only used on Android >= M.
|
||||
*/
|
||||
void onAudioDeviceChange() {
|
||||
mainThreadHandler.post(onAudioDeviceChangeRunner);
|
||||
runInAudioThread(onAudioDeviceChangeRunner);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,7 +334,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
* Only used on Android < M.
|
||||
*/
|
||||
void onHeadsetDeviceChange() {
|
||||
mainThreadHandler.post(new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// XXX: isWiredHeadsetOn is not deprecated when used just for
|
||||
@@ -337,6 +354,44 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
|
||||
* when the audio focus of the system is updated.
|
||||
*
|
||||
* @param focusChange - The type of focus change.
|
||||
*/
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN: {
|
||||
Log.d(TAG, "Audio focus gained");
|
||||
// Some other application potentially stole our audio focus
|
||||
// temporarily. Restore our mode.
|
||||
if (audioFocusLost) {
|
||||
updateAudioRoute(mode);
|
||||
}
|
||||
audioFocusLost = false;
|
||||
break;
|
||||
}
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
|
||||
Log.d(TAG, "Audio focus lost");
|
||||
audioFocusLost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to run operations on a dedicated thread.
|
||||
* @param runnable
|
||||
*/
|
||||
public void runInAudioThread(Runnable runnable) {
|
||||
executor.execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user selected audio device as the active audio device.
|
||||
*
|
||||
@@ -344,7 +399,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setAudioDevice(final String device) {
|
||||
mainThreadHandler.post(new Runnable() {
|
||||
runInAudioThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!availableDevices.contains(device)) {
|
||||
@@ -415,7 +470,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
};
|
||||
mainThreadHandler.post(r);
|
||||
runInAudioThread(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,8 +542,9 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
Log.d(TAG, "Update audio route for mode: " + mode);
|
||||
|
||||
if (mode == DEFAULT) {
|
||||
audioFocusLost = false;
|
||||
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.abandonAudioFocus(null);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
audioManager.setSpeakerphoneOn(false);
|
||||
setBluetoothAudioRoute(false);
|
||||
selectedDevice = null;
|
||||
@@ -501,7 +557,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
audioManager.setMicrophoneMute(false);
|
||||
|
||||
if (audioManager.requestAudioFocus(
|
||||
null,
|
||||
this,
|
||||
AudioManager.STREAM_VOICE_CALL,
|
||||
AudioManager.AUDIOFOCUS_GAIN)
|
||||
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
|
||||
231
android/sdk/src/main/java/org/jitsi/meet/sdk/BaseReactView.java
Normal file
231
android/sdk/src/main/java/org/jitsi/meet/sdk/BaseReactView.java
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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.util.Log;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Base class for all views which are backed by a React Native view.
|
||||
*/
|
||||
public abstract class BaseReactView<ListenerT>
|
||||
extends FrameLayout {
|
||||
|
||||
/**
|
||||
* Background color used by {@code BaseReactView} and the React Native root
|
||||
* view.
|
||||
*/
|
||||
protected static int BACKGROUND_COLOR = 0xFF111111;
|
||||
|
||||
/**
|
||||
* The collection of all existing {@code BaseReactView}s. Used to find the
|
||||
* {@code BaseReactView} when delivering events coming from
|
||||
* {@link ExternalAPIModule}.
|
||||
*/
|
||||
static final Set<BaseReactView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<BaseReactView, Boolean>());
|
||||
|
||||
/**
|
||||
* Finds a {@code BaseReactView} which matches a specific external API
|
||||
* scope.
|
||||
*
|
||||
* @param externalAPIScope - The external API scope associated with the
|
||||
* {@code BaseReactView} to find.
|
||||
* @return The {@code BaseReactView}, if any, associated with the specified
|
||||
* {@code externalAPIScope}; otherwise, {@code null}.
|
||||
*/
|
||||
public static BaseReactView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
synchronized (views) {
|
||||
for (BaseReactView view : views) {
|
||||
if (view.externalAPIScope.equals(externalAPIScope)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code BaseReactView} within the process
|
||||
* for the purposes of {@link ExternalAPIModule}. The name scope was
|
||||
* inspired by postis which we use on Web for the similar purposes of the
|
||||
* iframe-based external API.
|
||||
*/
|
||||
protected final String externalAPIScope;
|
||||
|
||||
/**
|
||||
* The listener (e.g. {@link JitsiMeetViewListener}) instance for reporting
|
||||
* events occurring in Jitsi Meet.
|
||||
*/
|
||||
private ListenerT listener;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
public BaseReactView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||
((Activity) context).getApplication());
|
||||
|
||||
// Hook this BaseReactView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
synchronized (views) {
|
||||
views.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@code ReactRootView} for the given app name with the given
|
||||
* props. Once created it's set as the view of this {@code FrameLayout}.
|
||||
*
|
||||
* @param appName - The name of the "app" (in React Native terms) to load.
|
||||
* @param props - The React Component props to pass to the app.
|
||||
*/
|
||||
public void createReactRootView(String appName, @Nullable Bundle props) {
|
||||
if (props == null) {
|
||||
props = new Bundle();
|
||||
}
|
||||
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
appName,
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
reactRootView.setAppProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the React resources (specifically the {@link ReactRootView})
|
||||
* associated with this view.
|
||||
*
|
||||
* MUST be called when the {@link Activity} holding this view is destroyed,
|
||||
* typically in the {@code onDestroy} method.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView.unmountReactApplication();
|
||||
reactRootView = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the listener set on this {@code BaseReactView}.
|
||||
*
|
||||
* @return The listener set on this {@code BaseReactView}.
|
||||
*/
|
||||
public ListenerT getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method called by {@link ExternalAPIModule} when an event is
|
||||
* received for this view.
|
||||
*
|
||||
* @param name - The name of the event.
|
||||
* @param data - The details of the event associated with/specific to the
|
||||
* specified {@code name}.
|
||||
*/
|
||||
public abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
|
||||
protected void onExternalAPIEvent(
|
||||
Map<String, Method> listenerMethods,
|
||||
String name, ReadableMap data) {
|
||||
ListenerT listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
ListenerUtils.runListenerMethod(
|
||||
listener, listenerMethods, name, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the window containing this view gains or loses focus.
|
||||
*
|
||||
* @param hasFocus If the window of this view now has focus, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
|
||||
// FIXME The singleton pattern employed by RNImmersiveModule is not
|
||||
// advisable because a react-native mobule is consumable only after its
|
||||
// BaseJavaModule#initialize() has completed and here we have no
|
||||
// knowledge of whether the precondition is really met.
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
try {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
} catch (RuntimeException re) {
|
||||
// FIXME I don't know how to check myself whether
|
||||
// BaseJavaModule#initialize() has been invoked and thus
|
||||
// RNImmersiveModule is consumable. A safe workaround is to
|
||||
// swallow the failure because the whole full-screen/immersive
|
||||
// functionality is brittle anyway, akin to the icing on the
|
||||
// cake, and has been working without onWindowFocusChanged for a
|
||||
// very long time.
|
||||
Log.e(
|
||||
"RNImmersiveModule",
|
||||
"emitImmersiveStateChangeEvent() failed!",
|
||||
re);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific listener on this {@code BaseReactView}.
|
||||
*
|
||||
* @param listener The listener to set on this {@code BaseReactView}.
|
||||
*/
|
||||
public void setListener(ListenerT listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
@@ -55,12 +53,6 @@ class BluetoothHeadsetMonitor {
|
||||
*/
|
||||
private boolean headsetAvailable = false;
|
||||
|
||||
/**
|
||||
* {@link Handler} for running all operations on the main thread.
|
||||
*/
|
||||
private final Handler mainThreadHandler
|
||||
= new Handler(Looper.getMainLooper());
|
||||
|
||||
/**
|
||||
* Helper for running Bluetooth operations on the main thread.
|
||||
*/
|
||||
@@ -200,6 +192,6 @@ class BluetoothHeadsetMonitor {
|
||||
* {@link AudioModeModule#onAudioDeviceChange()} callback.
|
||||
*/
|
||||
private void updateDevices() {
|
||||
mainThreadHandler.post(updateDevicesRunnable);
|
||||
audioModeModule.runInAudioThread(updateDevicesRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,83 +16,24 @@
|
||||
|
||||
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.bridge.ReadableMapKeySetIterator;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Module implementing a simple API to enable a proximity sensor-controlled
|
||||
* wake lock. When the lock is held, if the proximity sensor detects a nearby
|
||||
* object it will dim the screen and disable touch controls. The functionality
|
||||
* is used with the conference audio-only mode.
|
||||
* Module implementing an API for sending events from JavaScript to native code.
|
||||
*/
|
||||
class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
* redux action types.
|
||||
*/
|
||||
private static final Map<String, Method> JITSI_MEET_VIEW_LISTENER_METHODS
|
||||
= new HashMap<>();
|
||||
class ExternalAPIModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
static {
|
||||
// Figure out the mapping between the JitsiMeetViewListener methods
|
||||
// and the events i.e. redux action types.
|
||||
Pattern onPattern = Pattern.compile("^on[A-Z]+");
|
||||
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
|
||||
|
||||
for (Method method : JitsiMeetViewListener.class.getDeclaredMethods()) {
|
||||
// * The method must be public (because it is declared by an
|
||||
// interface).
|
||||
// * The method must be/return void.
|
||||
if (!Modifier.isPublic(method.getModifiers())
|
||||
|| !Void.TYPE.equals(method.getReturnType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method name must start with "on" followed by a
|
||||
// capital/uppercase letter (in agreement with the camelcase
|
||||
// coding style customary to Java in general and the projects of
|
||||
// the Jitsi community in particular).
|
||||
String name = method.getName();
|
||||
|
||||
if (!onPattern.matcher(name).find()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method must accept/have exactly 1 parameter of a type
|
||||
// assignable from HashMap.
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
if (parameterTypes.length != 1
|
||||
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert the method name to an event name.
|
||||
name
|
||||
= camelcasePattern.matcher(name.substring(2))
|
||||
.replaceAll("$1_$2")
|
||||
.toUpperCase(Locale.ROOT);
|
||||
JITSI_MEET_VIEW_LISTENER_METHODS.put(name, method);
|
||||
}
|
||||
}
|
||||
private static final String TAG = ExternalAPIModule.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the application.
|
||||
* this module throughout the lifetime of the app.
|
||||
*
|
||||
* @param reactContext the {@link ReactApplicationContext} where this module
|
||||
* is created.
|
||||
@@ -112,37 +53,8 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* @param view the {@link JitsiMeetView} instance.
|
||||
*/
|
||||
private void maybeSetViewURL(
|
||||
String eventName,
|
||||
ReadableMap eventData,
|
||||
JitsiMeetView view) {
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
view.setURL(eventData.getString("url"));
|
||||
break;
|
||||
|
||||
case "CONFERENCE_FAILED":
|
||||
case "CONFERENCE_WILL_LEAVE":
|
||||
case "LOAD_CONFIG_ERROR":
|
||||
String url = eventData.getString("url");
|
||||
|
||||
if (url != null && url.equals(view.getURL())) {
|
||||
view.setURL(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on JavaScript to the view's listener.
|
||||
* Dispatches an event that occurred on the JavaScript side of the SDK to
|
||||
* the specified {@link BaseReactView}'s listener.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
@@ -153,62 +65,15 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
public void sendEvent(String name, ReadableMap data, String scope) {
|
||||
// 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 JitsiMeetView which hosts it.
|
||||
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
|
||||
// former to the native BaseReactView which hosts it.
|
||||
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
maybeSetViewURL(name, data, view);
|
||||
|
||||
JitsiMeetViewListener listener = view.getListener();
|
||||
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name);
|
||||
|
||||
if (method != null) {
|
||||
if (view != null) {
|
||||
try {
|
||||
method.invoke(listener, toHashMap(data));
|
||||
} catch (IllegalAccessException e) {
|
||||
// FIXME There was a multicatch for IllegalAccessException and
|
||||
// InvocationTargetException, but Android Studio complained
|
||||
// with:
|
||||
// "Multi-catch with these reflection exceptions requires
|
||||
// API level 19 (current min is 16) because they get compiled to
|
||||
// the common but new super type ReflectiveOperationException.
|
||||
// As a workaround either create individual catch statements, or
|
||||
// catch Exception."
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
view.onExternalAPIEvent(name, data);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "onExternalAPIEvent: error sending event", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code HashMap} instance with the key-value
|
||||
* associations of a specific {@code ReadableMap}.
|
||||
*
|
||||
* @param readableMap the {@code ReadableMap} specifying the key-value
|
||||
* associations with which the new {@code HashMap} instance is to be
|
||||
* initialized.
|
||||
* @return a new {@code HashMap} instance initialized with the key-value
|
||||
* associations of the specified {@code readableMap}.
|
||||
*/
|
||||
private HashMap<String, Object> toHashMap(ReadableMap readableMap) {
|
||||
HashMap<String, Object> hashMap = new HashMap<>();
|
||||
|
||||
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
|
||||
i.hasNextKey();) {
|
||||
String key = i.nextKey();
|
||||
|
||||
hashMap.put(key, readableMap.getString(key));
|
||||
}
|
||||
|
||||
return hashMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.view.KeyEvent;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
@@ -41,7 +42,9 @@ import java.net.URL;
|
||||
* hooked to the React Native subsystem via proxy calls through the
|
||||
* {@code JitsiMeetView} static methods.
|
||||
*/
|
||||
public class JitsiMeetActivity extends AppCompatActivity {
|
||||
public class JitsiMeetActivity
|
||||
extends AppCompatActivity implements JitsiMeetActivityInterface {
|
||||
|
||||
/**
|
||||
* The request code identifying requests for the permission to draw on top
|
||||
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
|
||||
@@ -95,25 +98,6 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
return view == null ? defaultURL : view.getDefaultURL();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getPictureInPictureEnabled()
|
||||
*/
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.getPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#getWelcomePageEnabled()
|
||||
*/
|
||||
public boolean getWelcomePageEnabled() {
|
||||
return view == null ? welcomePageEnabled : view.getWelcomePageEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link #view} of this {@code JitsiMeetActivity} with a
|
||||
* new {@link JitsiMeetView} instance.
|
||||
@@ -152,6 +136,25 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isPictureInPictureEnabled()
|
||||
*/
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
view == null
|
||||
? pictureInPictureEnabled
|
||||
: view.isPictureInPictureEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#isWelcomePageEnabled()
|
||||
*/
|
||||
public boolean isWelcomePageEnabled() {
|
||||
return view == null ? welcomePageEnabled : view.isWelcomePageEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given URL and displays the conference. If the specified URL is
|
||||
* null, the welcome page is displayed instead.
|
||||
@@ -172,12 +175,17 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
initializeContentView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onActivityResult(
|
||||
this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!JitsiMeetView.onBackPressed()) {
|
||||
if (!ReactActivityLifecycleCallbacks.onBackPressed()) {
|
||||
// JitsiMeetView didn't handle the invocation of the back button.
|
||||
// Generally, an Activity extender would very likely want to invoke
|
||||
// Activity#onBackPressed(). For the sake of consistency with
|
||||
@@ -220,7 +228,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
view = null;
|
||||
}
|
||||
|
||||
JitsiMeetView.onHostDestroy(this);
|
||||
ReactActivityLifecycleCallbacks.onHostDestroy(this);
|
||||
}
|
||||
|
||||
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
|
||||
@@ -242,7 +250,29 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
JitsiMeetView.onNewIntent(intent);
|
||||
// XXX At least twice we received bug reports about malfunctioning
|
||||
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
|
||||
// functioning as expected in our testing. But that was to be expected
|
||||
// because the app does not exercise loadURL. In order to increase the
|
||||
// test coverage of loadURL, channel deep linking through loadURL.
|
||||
Uri uri;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null
|
||||
&& JitsiMeetView.loadURLStringInViews(uri.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactActivityLifecycleCallbacks.onNewIntent(intent);
|
||||
}
|
||||
|
||||
// https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
ReactActivityLifecycleCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,24 +280,32 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||
super.onResume();
|
||||
|
||||
defaultBackButtonImpl = new DefaultHardwareBackBtnHandlerImpl(this);
|
||||
JitsiMeetView.onHostResume(this, defaultBackButtonImpl);
|
||||
ReactActivityLifecycleCallbacks.onHostResume(this, defaultBackButtonImpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetView.onHostPause(this);
|
||||
ReactActivityLifecycleCallbacks.onHostPause(this);
|
||||
defaultBackButtonImpl = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (view != null) {
|
||||
view.onUserLeaveHint();
|
||||
view.enterPictureInPicture();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the {@code PermissionAwareActivity} interface.
|
||||
*/
|
||||
@Override
|
||||
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
|
||||
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setDefaultURL(URL)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
|
||||
/**
|
||||
* This interface serves as the umbrella interface that applications not using
|
||||
* {@code JitsiMeetActivity} must implement in order to ensure full
|
||||
* functionality.
|
||||
*/
|
||||
public interface JitsiMeetActivityInterface
|
||||
extends ActivityCompat.OnRequestPermissionsResultCallback,
|
||||
PermissionAwareActivity {
|
||||
}
|
||||
@@ -16,57 +16,35 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JitsiMeetView
|
||||
extends BaseReactView<JitsiMeetViewListener> {
|
||||
|
||||
public class JitsiMeetView extends FrameLayout {
|
||||
/**
|
||||
* Background color used by {@code JitsiMeetView} and the React Native root
|
||||
* view.
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
* redux action types.
|
||||
*/
|
||||
private static final int BACKGROUND_COLOR = 0xFF111111;
|
||||
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 final static String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
private static final Set<JitsiMeetView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
|
||||
|
||||
public static JitsiMeetView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
synchronized (views) {
|
||||
for (JitsiMeetView view : views) {
|
||||
if (view.externalAPIScope.equals(externalAPIScope)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private static final String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Loads a specific URL {@code String} in all existing
|
||||
@@ -78,130 +56,19 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* at least one {@code JitsiMeetView}, then {@code true}; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
private static boolean loadURLStringInViews(String urlString) {
|
||||
synchronized (views) {
|
||||
if (!views.isEmpty()) {
|
||||
for (JitsiMeetView view : views) {
|
||||
view.loadURLString(urlString);
|
||||
}
|
||||
public static boolean loadURLStringInViews(String urlString) {
|
||||
boolean loaded = false;
|
||||
|
||||
return true;
|
||||
synchronized (views) {
|
||||
for (BaseReactView view : views) {
|
||||
if (view instanceof JitsiMeetView) {
|
||||
((JitsiMeetView)view).loadURLString(urlString);
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onBackPressed} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @return {@code true} if the back-press was processed; {@code false},
|
||||
* otherwise. If {@code false}, the application should call the parent's
|
||||
* implementation.
|
||||
*/
|
||||
public static boolean onBackPressed() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
return false;
|
||||
} else {
|
||||
reactInstanceManager.onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onDestroy} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @param activity {@code Activity} being destroyed.
|
||||
*/
|
||||
public static void onHostDestroy(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostDestroy(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onPause} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being paused.
|
||||
*/
|
||||
public static void onHostPause(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
*/
|
||||
public static void onHostResume(Activity activity) {
|
||||
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
* @param defaultBackButtonImpl a {@code DefaultHardwareBackBtnHandler} to
|
||||
* handle invoking the back button if no {@code JitsiMeetView} handles it.
|
||||
*/
|
||||
public static void onHostResume(
|
||||
Activity activity,
|
||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onNewIntent} so we can do the required internal
|
||||
* processing. Note that this is only needed if the activity's "launchMode"
|
||||
* was set to "singleTask". This is required for deep linking to work once
|
||||
* the application is already running.
|
||||
*
|
||||
* @param intent {@code Intent} instance which was received.
|
||||
*/
|
||||
public static void onNewIntent(Intent intent) {
|
||||
// XXX At least twice we received bug reports about malfunctioning
|
||||
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
|
||||
// functioning as expected in our testing. But that was to be expected
|
||||
// because the app does not exercise loadURL. In order to increase the
|
||||
// test coverage of loadURL, channel deep linking through loadURL.
|
||||
Uri uri;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())
|
||||
&& (uri = intent.getData()) != null
|
||||
&& loadURLStringInViews(uri.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,26 +78,12 @@ public class JitsiMeetView extends FrameLayout {
|
||||
*/
|
||||
private URL defaultURL;
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code JitsiMeetView} within the process
|
||||
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
|
||||
* postis which we use on Web for the similar purposes of the iframe-based
|
||||
* external API.
|
||||
*/
|
||||
private final String externalAPIScope;
|
||||
|
||||
/**
|
||||
* The entry point into the invite feature of Jitsi Meet. The Java
|
||||
* counterpart of the JavaScript {@code InviteButton}.
|
||||
*/
|
||||
private final InviteController inviteController;
|
||||
|
||||
/**
|
||||
* {@link JitsiMeetViewListener} instance for reporting events occurring in
|
||||
* Jitsi Meet.
|
||||
*/
|
||||
private JitsiMeetViewListener listener;
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
|
||||
* {@code true} iff the Android platform supports Picture-in-Picture
|
||||
@@ -238,11 +91,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
*/
|
||||
private Boolean pictureInPictureEnabled;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
/**
|
||||
* The URL of the current conference.
|
||||
*/
|
||||
@@ -258,34 +106,33 @@ public class JitsiMeetView extends FrameLayout {
|
||||
public JitsiMeetView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||
((Activity) context).getApplication());
|
||||
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
synchronized (views) {
|
||||
views.add(this);
|
||||
}
|
||||
|
||||
// The entry point into the invite feature of Jitsi Meet. The Java
|
||||
// counterpart of the JavaScript InviteButton.
|
||||
inviteController = new InviteController(externalAPIScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the React resources (specifically the {@link ReactRootView})
|
||||
* associated with this view.
|
||||
* Enters Picture-In-Picture mode, if possible. This method is designed to
|
||||
* be called from the {@code Activity.onUserLeaveHint} method.
|
||||
*
|
||||
* This method MUST be called when the Activity holding this view is
|
||||
* destroyed, typically in the {@code onDestroy} method.
|
||||
* This is currently not mandatory, but if used will provide automatic
|
||||
* handling of the picture in picture mode when user minimizes the app. It
|
||||
* will be probably the most useful in case the app is using the welcome
|
||||
* page.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView.unmountReactApplication();
|
||||
reactRootView = null;
|
||||
public void enterPictureInPicture() {
|
||||
if (isPictureInPictureEnabled() && getURL() != null) {
|
||||
PictureInPictureModule pipModule
|
||||
= ReactInstanceManagerHolder.getNativeModule(
|
||||
PictureInPictureModule.class);
|
||||
|
||||
if (pipModule != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +141,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. If not
|
||||
* set or if set to {@code null}, the default built in JavaScript is used:
|
||||
* {@link https://meet.jit.si}
|
||||
* https://meet.jit.si
|
||||
*
|
||||
* @return The default base {@code URL} or {@code null}.
|
||||
*/
|
||||
@@ -315,31 +162,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return inviteController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||
*
|
||||
* @return The {@code JitsiMeetViewListener} set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public JitsiMeetViewListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
|
||||
* natively supported on Android API >= 26 (Oreo), so it should not be
|
||||
* enabled on older platform versions.
|
||||
*
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean getPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled.booleanValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of the current conference.
|
||||
*
|
||||
@@ -353,6 +175,21 @@ public class JitsiMeetView extends FrameLayout {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
|
||||
* natively supported on Android API >= 26 (Oreo), so it should not be
|
||||
* enabled on older platform versions.
|
||||
*
|
||||
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isPictureInPictureEnabled() {
|
||||
return
|
||||
PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& (pictureInPictureEnabled == null
|
||||
|| pictureInPictureEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
|
||||
* page is rendered when this {@code JitsiMeetView} is not at a URL
|
||||
@@ -361,7 +198,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* @return {@code true} if the Welcome page is enabled; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
public boolean getWelcomePageEnabled() {
|
||||
public boolean isWelcomePageEnabled() {
|
||||
return welcomePageEnabled;
|
||||
}
|
||||
|
||||
@@ -395,9 +232,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
props.putString("defaultURL", defaultURL.toString());
|
||||
}
|
||||
|
||||
// externalAPIScope
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
// inviteController
|
||||
InviteController inviteController = getInviteController();
|
||||
|
||||
@@ -413,7 +247,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
getPictureInPictureEnabled());
|
||||
isPictureInPictureEnabled());
|
||||
|
||||
// url
|
||||
if (urlObject != null) {
|
||||
@@ -434,17 +268,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
// per loadURLObject: invocation.
|
||||
props.putLong("timestamp", System.currentTimeMillis());
|
||||
|
||||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
"App",
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
reactRootView.setAppProperties(props);
|
||||
}
|
||||
createReactRootView("App", props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,66 +292,48 @@ public class JitsiMeetView extends FrameLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity lifecycle method which should be called from
|
||||
* {@code Activity.onUserLeaveHint} so we can do the required internal
|
||||
* processing.
|
||||
* The internal processing for the URL of the current conference set on the
|
||||
* associated {@link JitsiMeetView}.
|
||||
*
|
||||
* This is currently not mandatory, but if used will provide automatic
|
||||
* handling of the picture in picture mode when user minimizes the app. It
|
||||
* will be probably the most useful in case the app is using the welcome
|
||||
* page.
|
||||
* @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}.
|
||||
*/
|
||||
public void onUserLeaveHint() {
|
||||
if (getPictureInPictureEnabled() && getURL() != null) {
|
||||
PictureInPictureModule pipModule
|
||||
= ReactInstanceManagerHolder.getNativeModule(
|
||||
PictureInPictureModule.class);
|
||||
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
|
||||
switch(eventName) {
|
||||
case "CONFERENCE_WILL_JOIN":
|
||||
setURL(eventData.getString("url"));
|
||||
break;
|
||||
|
||||
if (pipModule != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
|
||||
}
|
||||
case "CONFERENCE_FAILED":
|
||||
case "CONFERENCE_WILL_LEAVE":
|
||||
case "LOAD_CONFIG_ERROR":
|
||||
String url = eventData.getString("url");
|
||||
|
||||
if (url != null && url.equals(getURL())) {
|
||||
setURL(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the window containing this view gains or loses focus.
|
||||
* Handler for {@link ExternalAPIModule} events.
|
||||
*
|
||||
* @param hasFocus If the window of this view now has focus, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
public 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);
|
||||
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
|
||||
// FIXME The singleton pattern employed by RNImmersiveModule is not
|
||||
// advisable because a react-native mobule is consumable only after its
|
||||
// BaseJavaModule#initialize() has completed and here we have no
|
||||
// knowledge of whether the precondition is really met.
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
try {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
} catch (RuntimeException re) {
|
||||
// FIXME I don't know how to check myself whether
|
||||
// BaseJavaModule#initialize() has been invoked and thus
|
||||
// RNImmersiveModule is consumable. A safe workaround is to
|
||||
// swallow the failure because the whole full-screen/immersive
|
||||
// functionality is brittle anyway, akin to the icing on the
|
||||
// cake, and has been working without onWindowFocusChanged for a
|
||||
// very long time.
|
||||
Log.e(
|
||||
TAG,
|
||||
"RNImmersiveModule#emitImmersiveStateChangeEvent() failed!",
|
||||
re);
|
||||
}
|
||||
}
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,17 +349,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||
this.defaultURL = defaultURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific {@link JitsiMeetViewListener} on this
|
||||
* {@code JitsiMeetView}.
|
||||
*
|
||||
* @param listener The {@code JitsiMeetViewListener} to set on this
|
||||
* {@code JitsiMeetView}.
|
||||
*/
|
||||
public void setListener(JitsiMeetViewListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
|
||||
* natively supported only since certain platform versions, specifying
|
||||
@@ -563,7 +358,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
|
||||
this.pictureInPictureEnabled = Boolean.valueOf(pictureInPictureEnabled);
|
||||
this.pictureInPictureEnabled = pictureInPictureEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,9 @@ import java.util.Map;
|
||||
* Implements {@link JitsiMeetViewListener} so apps don't have to add stubs for
|
||||
* all methods in the interface if they are only interested in some.
|
||||
*/
|
||||
public abstract class JitsiMeetViewAdapter implements JitsiMeetViewListener {
|
||||
public abstract class JitsiMeetViewAdapter
|
||||
implements JitsiMeetViewListener {
|
||||
|
||||
@Override
|
||||
public void onConferenceFailed(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
166
android/sdk/src/main/java/org/jitsi/meet/sdk/ListenerUtils.java
Normal file
166
android/sdk/src/main/java/org/jitsi/meet/sdk/ListenerUtils.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility methods for helping with transforming {@link ExternalAPIModule}
|
||||
* events into listener methods. Used with descendants of {@link BaseReactView}.
|
||||
*/
|
||||
public final class ListenerUtils {
|
||||
/**
|
||||
* Extracts the methods defined in a listener and creates a mapping of this
|
||||
* form: event name -> method.
|
||||
*
|
||||
* @param listener - The listener whose methods we want to slurp.
|
||||
* @return A mapping with event names - methods.
|
||||
*/
|
||||
public static Map<String, Method> mapListenerMethods(Class listener) {
|
||||
Map<String, Method> methods = new HashMap<>();
|
||||
|
||||
// Figure out the mapping between the listener methods
|
||||
// and the events i.e. redux action types.
|
||||
Pattern onPattern = Pattern.compile("^on[A-Z]+");
|
||||
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
|
||||
|
||||
for (Method method : listener.getDeclaredMethods()) {
|
||||
// * The method must be public (because it is declared by an
|
||||
// interface).
|
||||
// * The method must be/return void.
|
||||
if (!Modifier.isPublic(method.getModifiers())
|
||||
|| !Void.TYPE.equals(method.getReturnType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method name must start with "on" followed by a
|
||||
// capital/uppercase letter (in agreement with the camelcase
|
||||
// coding style customary to Java in general and the projects of
|
||||
// the Jitsi community in particular).
|
||||
String name = method.getName();
|
||||
|
||||
if (!onPattern.matcher(name).find()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method must accept/have exactly 1 parameter of a type
|
||||
// assignable from HashMap.
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
if (parameterTypes.length != 1
|
||||
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert the method name to an event name.
|
||||
name
|
||||
= camelcasePattern.matcher(name.substring(2))
|
||||
.replaceAll("$1_$2")
|
||||
.toUpperCase(Locale.ROOT);
|
||||
methods.put(name, method);
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the right listener method for the given event.
|
||||
* NOTE: This function will run asynchronously on the UI thread.
|
||||
*
|
||||
* @param listener - The listener on which the method will be called.
|
||||
* @param listenerMethods - Mapping with event names and the matching
|
||||
* methods.
|
||||
* @param eventName - Name of the event.
|
||||
* @param eventData - Data associated with the event.
|
||||
*/
|
||||
public static void runListenerMethod(
|
||||
final Object listener,
|
||||
final Map<String, Method> listenerMethods,
|
||||
final String eventName,
|
||||
final ReadableMap eventData) {
|
||||
// Make sure listener methods are invoked on the UI thread. It
|
||||
// was requested by SDK consumers.
|
||||
if (UiThreadUtil.isOnUiThread()) {
|
||||
runListenerMethodOnUiThread(
|
||||
listener, listenerMethods, eventName, eventData);
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runListenerMethodOnUiThread(
|
||||
listener, listenerMethods, eventName, eventData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper companion for {@link ListenerUtils#runListenerMethod} which runs
|
||||
* in the UI thread.
|
||||
*/
|
||||
private static void runListenerMethodOnUiThread(
|
||||
Object listener,
|
||||
Map<String, Method> listenerMethods,
|
||||
String eventName,
|
||||
ReadableMap eventData) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
Method method = listenerMethods.get(eventName);
|
||||
if (method != null) {
|
||||
try {
|
||||
method.invoke(listener, toHashMap(eventData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code HashMap} instance with the key-value
|
||||
* associations of a specific {@code ReadableMap}.
|
||||
*
|
||||
* @param readableMap the {@code ReadableMap} specifying the key-value
|
||||
* associations with which the new {@code HashMap} instance is to be
|
||||
* initialized.
|
||||
* @return a new {@code HashMap} instance initialized with the key-value
|
||||
* associations of the specified {@code readableMap}.
|
||||
*/
|
||||
private static HashMap<String, Object> toHashMap(ReadableMap readableMap) {
|
||||
HashMap<String, Object> hashMap = new HashMap<>();
|
||||
|
||||
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
|
||||
i.hasNextKey();) {
|
||||
String key = i.nextKey();
|
||||
|
||||
hashMap.put(key, readableMap.getString(key));
|
||||
}
|
||||
|
||||
return hashMap;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -11,7 +27,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
public class PictureInPictureModule extends ReactContextBaseJavaModule {
|
||||
class PictureInPictureModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
private final static String TAG = "PictureInPicture";
|
||||
|
||||
static boolean isPictureInPictureSupported() {
|
||||
|
||||
@@ -31,7 +31,9 @@ import com.facebook.react.bridge.UiThreadUtil;
|
||||
* object it will dim the screen and disable touch controls. The functionality
|
||||
* is used with the conference audio-only mode.
|
||||
*/
|
||||
class ProximityModule extends ReactContextBaseJavaModule {
|
||||
class ProximityModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The name of {@code ProximityModule} to be used in the React Native
|
||||
* bridge.
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
* Helper class to encapsulate the work which needs to be done on
|
||||
* {@link Activity} lifecycle methods in order for the React side to be aware of
|
||||
* it.
|
||||
*/
|
||||
public class ReactActivityLifecycleCallbacks {
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onActivityResult} so we are notified about results of external intents
|
||||
* started/finished.
|
||||
*
|
||||
* @param activity {@code Activity} activity from where the result comes from.
|
||||
* @param requestCode {@code int} code of the request.
|
||||
* @param resultCode {@code int} code of the result.
|
||||
* @param data {@code Intent} the intent of the activity.
|
||||
*/
|
||||
public static void onActivityResult(
|
||||
Activity activity,
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onActivityResult(activity, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for making sure this class working with the "PermissionsAndroid"
|
||||
* React Native module.
|
||||
*/
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@link Activity#onBackPressed} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @return {@code true} if the back-press was processed; {@code false},
|
||||
* otherwise. If {@code false}, the application should call the
|
||||
* {@code super}'s implementation.
|
||||
*/
|
||||
public static boolean onBackPressed() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
return false;
|
||||
} else {
|
||||
reactInstanceManager.onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onDestroy} so we can do the required internal
|
||||
* processing.
|
||||
*
|
||||
* @param activity {@code Activity} being destroyed.
|
||||
*/
|
||||
public static void onHostDestroy(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostDestroy(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onPause} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being paused.
|
||||
*/
|
||||
public static void onHostPause(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
*/
|
||||
public static void onHostResume(Activity activity) {
|
||||
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onResume} so we can do the required internal processing.
|
||||
*
|
||||
* @param activity {@code Activity} being resumed.
|
||||
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} to
|
||||
* handle invoking the back button if no {@link BaseReactView} handles it.
|
||||
*/
|
||||
public static void onHostResume(
|
||||
Activity activity,
|
||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
}
|
||||
|
||||
if (permissionsCallback != null) {
|
||||
permissionsCallback.invoke();
|
||||
permissionsCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onNewIntent} so we can do the required internal
|
||||
* processing. Note that this is only needed if the activity's "launchMode"
|
||||
* was set to "singleTask". This is required for deep linking to work once
|
||||
* the application is already running.
|
||||
*
|
||||
* @param intent {@code Intent} instance which was received.
|
||||
*/
|
||||
public static void onNewIntent(Intent intent) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onRequestPermissionsResult(
|
||||
final int requestCode,
|
||||
final String[] permissions,
|
||||
final int[] grantResults) {
|
||||
CalendarEventsPackage.onRequestPermissionsResult(
|
||||
requestCode,
|
||||
permissions,
|
||||
grantResults);
|
||||
permissionsCallback = new Callback() {
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
if (permissionListener != null
|
||||
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||
permissionListener = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {
|
||||
permissionListener = listener;
|
||||
activity.requestPermissions(permissions, requestCode);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import com.facebook.react.common.LifecycleState;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ReactInstanceManagerHolder {
|
||||
class ReactInstanceManagerHolder {
|
||||
/**
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
@@ -45,6 +45,7 @@ public class ReactInstanceManagerHolder {
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.dropbox.Dropbox(reactContext),
|
||||
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
|
||||
);
|
||||
@@ -119,14 +120,16 @@ public class ReactInstanceManagerHolder {
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new co.apptailor.googlesignin.RNGoogleSigninPackage())
|
||||
.addPackage(new com.BV.LinearGradient.LinearGradientPackage())
|
||||
.addPackage(new com.calendarevents.CalendarEventsPackage())
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
.addPackage(new com.dylanvann.fastimage.FastImageViewPackage())
|
||||
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
||||
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
|
||||
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
|
||||
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
|
||||
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
||||
.addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
|
||||
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
||||
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
|
||||
.addPackage(new ReactPackageAdapter() {
|
||||
|
||||
@@ -25,7 +25,9 @@ import com.facebook.react.uimanager.ViewManager;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ReactPackageAdapter implements ReactPackage {
|
||||
class ReactPackageAdapter
|
||||
implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
|
||||
@@ -19,8 +19,6 @@ package org.jitsi.meet.sdk;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
@@ -36,14 +34,18 @@ import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Module exposing WiFi statistics.
|
||||
*
|
||||
* Gathers rssi, signal in percentage, timestamp and the addresses
|
||||
* of the wifi device.
|
||||
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
|
||||
* device.
|
||||
*/
|
||||
class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
class WiFiStatsModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The name of {@code WiFiStatsModule} to be used in the React Native
|
||||
* bridge.
|
||||
@@ -56,17 +58,16 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
static final String TAG = MODULE_NAME;
|
||||
|
||||
/**
|
||||
* The scale used for the signal value.
|
||||
* A level of the signal, given in the range
|
||||
* of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
|
||||
* The scale used for the signal value. A level of the signal, given in the
|
||||
* range of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
|
||||
*/
|
||||
public final static int SIGNAL_LEVEL_SCALE = 101;
|
||||
|
||||
/**
|
||||
* {@link Handler} for running all operations on the main thread.
|
||||
* {@link ExecutorService} for running all operations on a dedicated thread.
|
||||
*/
|
||||
private final Handler mainThreadHandler
|
||||
= new Handler(Looper.getMainLooper());
|
||||
private static final ExecutorService executor
|
||||
= Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
@@ -119,7 +120,6 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
Context context
|
||||
= getReactApplicationContext().getApplicationContext();
|
||||
WifiManager wifiManager
|
||||
@@ -203,6 +203,6 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
};
|
||||
mainThreadHandler.post(r);
|
||||
executor.execute(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package org.jitsi.meet.sdk.dropbox;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.dropbox.core.DbxException;
|
||||
import com.dropbox.core.DbxRequestConfig;
|
||||
import com.dropbox.core.v2.DbxClientV2;
|
||||
import com.dropbox.core.v2.users.FullAccount;
|
||||
import com.dropbox.core.v2.users.SpaceAllocation;
|
||||
import com.dropbox.core.v2.users.SpaceUsage;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.dropbox.core.android.Auth;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implements the react-native module for the dropbox integration.
|
||||
*/
|
||||
public class Dropbox
|
||||
extends ReactContextBaseJavaModule
|
||||
implements LifecycleEventListener {
|
||||
private String appKey;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private final boolean isEnabled;
|
||||
|
||||
private Promise promise;
|
||||
|
||||
public Dropbox(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
appKey
|
||||
= reactContext.getString(
|
||||
org.jitsi.meet.sdk.R.string.dropbox_app_key);
|
||||
isEnabled = !TextUtils.isEmpty(appKey);
|
||||
|
||||
clientId = generateClientId();
|
||||
|
||||
reactContext.addLifecycleEventListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the dropbox auth flow.
|
||||
*
|
||||
* @param promise The promise used to return the result of the auth flow.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void authorize(final Promise promise) {
|
||||
if (isEnabled) {
|
||||
Auth.startOAuth2Authentication(this.getCurrentActivity(), appKey);
|
||||
this.promise = promise;
|
||||
} else {
|
||||
promise.reject(
|
||||
new Exception("Dropbox integration isn't configured."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a client identifier for the dropbox sdk.
|
||||
*
|
||||
* @returns a client identifier for the dropbox sdk.
|
||||
* @see {https://dropbox.github.io/dropbox-sdk-java/api-docs/v3.0.x/com/dropbox/core/DbxRequestConfig.html#getClientIdentifier--}
|
||||
*/
|
||||
private String generateClientId() {
|
||||
Context context = getReactApplicationContext();
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
ApplicationInfo applicationInfo = null;
|
||||
PackageInfo packageInfo = null;
|
||||
|
||||
try {
|
||||
String packageName = context.getPackageName();
|
||||
|
||||
applicationInfo = packageManager.getApplicationInfo(packageName, 0);
|
||||
packageInfo = packageManager.getPackageInfo(packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
}
|
||||
|
||||
String applicationLabel
|
||||
= applicationInfo == null
|
||||
? "JitsiMeet"
|
||||
: packageManager.getApplicationLabel(applicationInfo).toString()
|
||||
.replaceAll("\\s", "");
|
||||
String version = packageInfo == null ? "dev" : packageInfo.versionName;
|
||||
|
||||
return applicationLabel + "/" + version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put("ENABLED", isEnabled);
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current user dropbox display name.
|
||||
*
|
||||
* @param token A dropbox access token.
|
||||
* @param promise The promise used to return the result of the auth flow.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getDisplayName(final String token, final Promise promise) {
|
||||
DbxRequestConfig config = DbxRequestConfig.newBuilder(clientId).build();
|
||||
DbxClientV2 client = new DbxClientV2(config, token);
|
||||
|
||||
// Get current account info
|
||||
try {
|
||||
FullAccount account = client.users().getCurrentAccount();
|
||||
|
||||
promise.resolve(account.getName().getDisplayName());
|
||||
} catch (DbxException e) {
|
||||
promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Dropbox";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current user space usage.
|
||||
*
|
||||
* @param token A dropbox access token.
|
||||
* @param promise The promise used to return the result of the auth flow.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getSpaceUsage(final String token, final Promise promise) {
|
||||
DbxRequestConfig config = DbxRequestConfig.newBuilder(clientId).build();
|
||||
DbxClientV2 client = new DbxClientV2(config, token);
|
||||
|
||||
try {
|
||||
SpaceUsage spaceUsage = client.users().getSpaceUsage();
|
||||
WritableMap map = Arguments.createMap();
|
||||
|
||||
map.putString("used", String.valueOf(spaceUsage.getUsed()));
|
||||
|
||||
SpaceAllocation allocation = spaceUsage.getAllocation();
|
||||
long allocated = 0;
|
||||
|
||||
if (allocation.isIndividual()) {
|
||||
allocated += allocation.getIndividualValue().getAllocated();
|
||||
}
|
||||
if (allocation.isTeam()) {
|
||||
allocated += allocation.getTeamValue().getAllocated();
|
||||
}
|
||||
map.putString("allocated", String.valueOf(allocated));
|
||||
|
||||
promise.resolve(map);
|
||||
} catch (DbxException e) {
|
||||
promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
String token = Auth.getOAuth2Token();
|
||||
|
||||
if (token != null && this.promise != null) {
|
||||
this.promise.resolve(token);
|
||||
this.promise = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class IncomingCallInfo {
|
||||
/**
|
||||
* URL for the caller avatar.
|
||||
*/
|
||||
private final String callerAvatarURL;
|
||||
|
||||
/**
|
||||
* Caller's name.
|
||||
*/
|
||||
private final String callerName;
|
||||
|
||||
/**
|
||||
* Whether this is a regular call or a video call.
|
||||
*/
|
||||
private final boolean hasVideo;
|
||||
|
||||
public IncomingCallInfo(
|
||||
@NonNull String callerName,
|
||||
@NonNull String callerAvatarURL,
|
||||
boolean hasVideo) {
|
||||
this.callerName = callerName;
|
||||
this.callerAvatarURL = callerAvatarURL;
|
||||
this.hasVideo = hasVideo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the caller's avatar URL.
|
||||
*
|
||||
* @return - The URL as a string.
|
||||
*/
|
||||
public String getCallerAvatarURL() {
|
||||
return callerAvatarURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the caller's name.
|
||||
*
|
||||
* @return - The caller's name.
|
||||
*/
|
||||
public String getCallerName() {
|
||||
return callerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the call is a video call or not.
|
||||
*
|
||||
* @return - {@code true} if this call has video; {@code false}, otherwise.
|
||||
*/
|
||||
public boolean hasVideo() {
|
||||
return hasVideo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.BaseReactView;
|
||||
import org.jitsi.meet.sdk.ListenerUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
public class IncomingCallView
|
||||
extends BaseReactView<IncomingCallViewListener> {
|
||||
|
||||
/**
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
* redux action types.
|
||||
*/
|
||||
private static final Map<String, Method> LISTENER_METHODS
|
||||
= ListenerUtils.mapListenerMethods(IncomingCallViewListener.class);
|
||||
|
||||
public IncomingCallView(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for {@link ExternalAPIModule} events.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
*/
|
||||
@Override
|
||||
public void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the information for the incoming call this {@code IncomingCallView}
|
||||
* represents.
|
||||
*
|
||||
* @param callInfo - {@link IncomingCallInfo} object representing the caller
|
||||
* information.
|
||||
*/
|
||||
public void setIncomingCallInfo(IncomingCallInfo callInfo) {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
props.putString("callerAvatarURL", callInfo.getCallerAvatarURL());
|
||||
props.putString("callerName", callInfo.getCallerName());
|
||||
props.putBoolean("hasVideo", callInfo.hasVideo());
|
||||
|
||||
createReactRootView("IncomingCallApp", props);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.incoming_call;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface for listening to events coming from Jitsi Meet, related to
|
||||
* {@link IncomingCallView}.
|
||||
*/
|
||||
public interface IncomingCallViewListener {
|
||||
/**
|
||||
* Called when the user presses the "Answer" button on the
|
||||
* {@link IncomingCallView}.
|
||||
*
|
||||
* @param data - Unused at the moment.
|
||||
*/
|
||||
void onIncomingCallAnswered(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when the user presses the "Decline" button on the
|
||||
* {@link IncomingCallView}.
|
||||
*
|
||||
* @param data - Unused at the moment.
|
||||
*/
|
||||
void onIncomingCallDeclined(Map<String, Object> data);
|
||||
}
|
||||
@@ -33,7 +33,8 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller object used by native code to query and submit user selections for the user invitation flow.
|
||||
* Controller object used by native code to query and submit user selections for
|
||||
* the user invitation flow.
|
||||
*/
|
||||
public class AddPeopleController {
|
||||
|
||||
@@ -44,17 +45,18 @@ public class AddPeopleController {
|
||||
private AddPeopleControllerListener listener;
|
||||
|
||||
/**
|
||||
* Local cache of search query results. Used to re-hydrate the list
|
||||
* of selected items based on their ids passed to inviteById
|
||||
* in order to pass the full item maps back to the JitsiMeetView during submission.
|
||||
* Local cache of search query results. Used to re-hydrate the list of
|
||||
* selected items based on their ids passed to inviteById in order to pass
|
||||
* the full item maps back to the JitsiMeetView during submission.
|
||||
*/
|
||||
private final Map<String, ReadableMap> items = new HashMap<>();
|
||||
|
||||
private final WeakReference<InviteController> owner;
|
||||
|
||||
private final WeakReference<ReactApplicationContext> reactContext;
|
||||
|
||||
/**
|
||||
* Randomly generated UUID, used for identification in the InviteModule
|
||||
* Randomly generated UUID, used for identification in the InviteModule.
|
||||
*/
|
||||
private final String uuid = UUID.randomUUID().toString();
|
||||
|
||||
@@ -158,10 +160,10 @@ public class AddPeopleController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches results received by the search into a local map for use
|
||||
* later when the items are submitted. Submission requires the full
|
||||
* map of information, but only the IDs are returned back to the delegate.
|
||||
* Using this map means we don't have to send the whole map back to the delegate.
|
||||
* Caches results received by the search into a local map for use later when
|
||||
* the items are submitted. Submission requires the full map of
|
||||
* information, but only the IDs are returned back to the delegate. Using
|
||||
* this map means we don't have to send the whole map back to the delegate.
|
||||
*
|
||||
* @param results
|
||||
* @param query
|
||||
@@ -179,10 +181,15 @@ public class AddPeopleController {
|
||||
|
||||
if(map.hasKey("id")) {
|
||||
items.put(map.getString("id"), map);
|
||||
} else if(map.hasKey("type") && map.getString("type").equals("phone") && map.hasKey("number")) {
|
||||
} else if(map.hasKey("type")
|
||||
&& map.getString("type").equals("phone")
|
||||
&& map.hasKey("number")) {
|
||||
items.put(map.getString("number"), map);
|
||||
} else {
|
||||
Log.w("AddPeopleController", "Received result without id and that was not a phone number, so not adding it to suggestions: " + map);
|
||||
Log.w(
|
||||
"AddPeopleController",
|
||||
"Received result without id and that was not a phone number, so not adding it to suggestions: "
|
||||
+ map);
|
||||
}
|
||||
|
||||
jvmResults.add(map.toHashMap());
|
||||
|
||||
@@ -202,9 +202,9 @@ public class InviteController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through the {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}
|
||||
* method.
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through
|
||||
* {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
|
||||
@@ -20,10 +20,10 @@ public interface InviteControllerListener {
|
||||
/**
|
||||
* Called when the add user button is tapped.
|
||||
*
|
||||
* @param addPeopleController {@code AddPeopleController} scoped
|
||||
* for this user invite flow. The {@code AddPeopleController} is used
|
||||
* to start user queries and accepts an {@code AddPeopleControllerListener}
|
||||
* for receiving user query responses.
|
||||
* @param addPeopleController {@code AddPeopleController} scoped for this
|
||||
* user invite flow. The {@code AddPeopleController} is used to start user
|
||||
* queries and accepts an {@code AddPeopleControllerListener} for receiving
|
||||
* user query responses.
|
||||
*/
|
||||
void beginAddPeople(AddPeopleController addPeopleController);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,17 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import org.jitsi.meet.sdk.BaseReactView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
|
||||
/**
|
||||
* Implements the react-native module of the feature invite.
|
||||
*/
|
||||
public class InviteModule extends ReactContextBaseJavaModule {
|
||||
public class InviteModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
public InviteModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
@@ -42,7 +46,19 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void beginAddPeople(String externalAPIScope) {
|
||||
public void beginAddPeople(final String externalAPIScope) {
|
||||
// Make sure InviteControllerListener (like all other listeners of the
|
||||
// SDK) is invoked on the UI thread. It was requested by SDK consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
beginAddPeople(externalAPIScope);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
@@ -54,7 +70,8 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
private InviteController findInviteControllerByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
JitsiMeetView view
|
||||
= JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
|
||||
= (JitsiMeetView)
|
||||
BaseReactView.findViewByExternalAPIScope(externalAPIScope);
|
||||
|
||||
return view == null ? null : view.getInviteController();
|
||||
}
|
||||
@@ -68,13 +85,30 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
* Callback for invitation failures
|
||||
*
|
||||
* @param failedInvitees the items for which the invitation failed
|
||||
* @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
|
||||
* @param addPeopleControllerScope a string that represents a connection to
|
||||
* a specific AddPeopleController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void inviteSettled(
|
||||
String externalAPIScope,
|
||||
String addPeopleControllerScope,
|
||||
ReadableArray failedInvitees) {
|
||||
final String externalAPIScope,
|
||||
final String addPeopleControllerScope,
|
||||
final ReadableArray failedInvitees) {
|
||||
// Make sure AddPeopleControllerListener (like all other listeners of
|
||||
// the SDK) is invoked on the UI thread. It was requested by SDK
|
||||
// consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
inviteSettled(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
failedInvitees);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
@@ -94,14 +128,32 @@ public class InviteModule extends ReactContextBaseJavaModule {
|
||||
*
|
||||
* @param results the results in a ReadableArray of ReadableMap objects
|
||||
* @param query the query associated with the search
|
||||
* @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
|
||||
* @param addPeopleControllerScope a string that represents a connection to
|
||||
* a specific AddPeopleController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void receivedResults(
|
||||
String externalAPIScope,
|
||||
String addPeopleControllerScope,
|
||||
String query,
|
||||
ReadableArray results) {
|
||||
final String externalAPIScope,
|
||||
final String addPeopleControllerScope,
|
||||
final String query,
|
||||
final ReadableArray results) {
|
||||
// Make sure AddPeopleControllerListener (like all other listeners of
|
||||
// the SDK) is invoked on the UI thread. It was requested by SDK
|
||||
// consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
receivedResults(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
query,
|
||||
results);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ import java.net.UnknownHostException;
|
||||
* [1]: https://tools.ietf.org/html/rfc6146
|
||||
* [2]: https://tools.ietf.org/html/rfc6052
|
||||
*/
|
||||
public class NAT64AddrInfoModule extends ReactContextBaseJavaModule {
|
||||
public class NAT64AddrInfoModule
|
||||
extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* The host for which the module wil try to resolve both IPv4 and IPv6
|
||||
* addresses in order to figure out the NAT64 prefix.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">Jitsi Meet SDK</string>
|
||||
<string name="dropbox_app_key"></string>
|
||||
</resources>
|
||||
|
||||
@@ -3,12 +3,16 @@ 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-fetch-blob'
|
||||
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/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-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android')
|
||||
include ':react-native-immersive'
|
||||
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
|
||||
include ':react-native-keep-awake'
|
||||
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
|
||||
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-locale-detector'
|
||||
project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
|
||||
include ':react-native-sound'
|
||||
|
||||
530
conference.js
530
conference.js
@@ -7,18 +7,16 @@ import Recorder from './modules/recorder/Recorder';
|
||||
|
||||
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
|
||||
|
||||
import { reportError } from './modules/util/helpers';
|
||||
|
||||
import * as RemoteControlEvents
|
||||
from './service/remotecontrol/RemoteControlEvents';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
import UIUtil from './modules/UI/util/UIUtil';
|
||||
import { createTaskQueue } from './modules/util/helpers';
|
||||
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||
|
||||
import {
|
||||
createDeviceChangedEvent,
|
||||
createScreenSharingEvent,
|
||||
createSelectParticipantFailedEvent,
|
||||
createStreamSwitchDelayEvent,
|
||||
createTrackMutedEvent,
|
||||
sendAnalytics
|
||||
@@ -27,19 +25,22 @@ import {
|
||||
redirectWithStoredParams,
|
||||
reloadWithStoredParams
|
||||
} from './react/features/app';
|
||||
import { updateRecordingSessionData } from './react/features/recording';
|
||||
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
EMAIL_COMMAND,
|
||||
authStatusChanged,
|
||||
commonUserJoinedHandling,
|
||||
commonUserLeftHandling,
|
||||
conferenceFailed,
|
||||
conferenceJoined,
|
||||
conferenceLeft,
|
||||
conferenceWillJoin,
|
||||
conferenceWillLeave,
|
||||
dataChannelOpened,
|
||||
EMAIL_COMMAND,
|
||||
lockStateChanged,
|
||||
onStartMutedPolicyChanged,
|
||||
p2pStatusChanged,
|
||||
@@ -47,6 +48,7 @@ import {
|
||||
setDesktopSharingEnabled
|
||||
} from './react/features/base/conference';
|
||||
import {
|
||||
getAvailableDevices,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
@@ -79,8 +81,6 @@ import {
|
||||
localParticipantRoleChanged,
|
||||
MAX_DISPLAY_NAME_LENGTH,
|
||||
participantConnectionStatusChanged,
|
||||
participantJoined,
|
||||
participantLeft,
|
||||
participantPresenceChanged,
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
@@ -97,6 +97,7 @@ import {
|
||||
getLocationContextRoot,
|
||||
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';
|
||||
import {
|
||||
@@ -109,6 +110,7 @@ import {
|
||||
} from './react/features/overlay';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||
import { isButtonEnabled } from './react/features/toolbox';
|
||||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -272,6 +274,27 @@ function redirectToStaticPage(pathname) {
|
||||
windowLocation.pathname = newPathname;
|
||||
}
|
||||
|
||||
/**
|
||||
* A queue for the async replaceLocalTrack action so that multiple audio
|
||||
* replacements cannot happen simultaneously. This solves the issue where
|
||||
* replaceLocalTrack is called multiple times with an oldTrack of null, causing
|
||||
* multiple local tracks of the same type to be used.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
const _replaceLocalAudioTrackQueue = createTaskQueue();
|
||||
|
||||
/**
|
||||
* A task queue for replacement local video tracks. This separate queue exists
|
||||
* so video replacement is not blocked by audio replacement tasks in the queue
|
||||
* {@link _replaceLocalAudioTrackQueue}.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
const _replaceLocalVideoTrackQueue = createTaskQueue();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -372,6 +395,8 @@ class ConferenceConnector {
|
||||
|
||||
case JitsiConferenceErrors.FOCUS_LEFT:
|
||||
case JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
// FIXME the conference should be stopped by the library and not by
|
||||
// the app. Both the errors above are unrecoverable from the library
|
||||
// perspective.
|
||||
@@ -400,10 +425,16 @@ class ConferenceConnector {
|
||||
switch (err) {
|
||||
case JitsiConferenceErrors.CHAT_ERROR:
|
||||
logger.error('Chat error.', err);
|
||||
if (isButtonEnabled('chat')) {
|
||||
if (isButtonEnabled('chat') && !interfaceConfig.filmStripOnly) {
|
||||
const [ code, msg ] = params;
|
||||
|
||||
APP.UI.showChatError(code, msg);
|
||||
APP.store.dispatch(addMessage({
|
||||
hasRead: true,
|
||||
error: code,
|
||||
message: msg,
|
||||
timestamp: Date.now(),
|
||||
type: 'error'
|
||||
}));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -468,6 +499,7 @@ function _connectionFailedHandler(error) {
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_connectionFailedHandler);
|
||||
if (room) {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
room.leave();
|
||||
}
|
||||
}
|
||||
@@ -485,8 +517,7 @@ export default {
|
||||
|
||||
/**
|
||||
* Indicates if the desktop sharing functionality has been enabled.
|
||||
* It takes into consideration {@link isDesktopSharingDisabledByConfig}
|
||||
* as well as the status returned by
|
||||
* It takes into consideration the status returned by
|
||||
* {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
|
||||
* either if the desktop sharing is not supported by the current browser
|
||||
* or if it was disabled through lib-jitsi-meet specific options (check
|
||||
@@ -494,19 +525,6 @@ export default {
|
||||
*/
|
||||
isDesktopSharingEnabled: false,
|
||||
|
||||
/**
|
||||
* Set to <tt>true</tt> if the desktop sharing functionality has been
|
||||
* explicitly disabled in the config.
|
||||
*/
|
||||
isDesktopSharingDisabledByConfig: false,
|
||||
|
||||
/**
|
||||
* The text displayed when the desktop sharing button is disabled through
|
||||
* the config. The value is set through
|
||||
* {@link interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP}.
|
||||
*/
|
||||
desktopSharingDisabledTooltip: null,
|
||||
|
||||
/**
|
||||
* The local audio track (if any).
|
||||
* FIXME tracks from redux store should be the single source of truth
|
||||
@@ -713,7 +731,7 @@ export default {
|
||||
track.mute();
|
||||
}
|
||||
});
|
||||
logger.log('initialized with %s local tracks', tracks.length);
|
||||
logger.log(`initialized with ${tracks.length} local tracks`);
|
||||
this._localTracksInitialized = true;
|
||||
con.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
@@ -721,13 +739,8 @@ export default {
|
||||
APP.connection = connection = con;
|
||||
|
||||
// Desktop sharing related stuff:
|
||||
this.isDesktopSharingDisabledByConfig
|
||||
= config.disableDesktopSharing;
|
||||
this.isDesktopSharingEnabled
|
||||
= !this.isDesktopSharingDisabledByConfig
|
||||
&& JitsiMeetJS.isDesktopSharingEnabled();
|
||||
this.desktopSharingDisabledTooltip
|
||||
= interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
|
||||
= JitsiMeetJS.isDesktopSharingEnabled();
|
||||
eventEmitter.emit(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
this.isDesktopSharingEnabled);
|
||||
@@ -870,9 +883,6 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME it is possible to queue this task twice, but it's not causing
|
||||
// any issues. Specifically this can happen when the previous
|
||||
// get user media call is blocked on "ask user for permissions" dialog.
|
||||
if (!this.localVideo && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showCameraErrorNotification(error);
|
||||
@@ -1256,13 +1266,6 @@ export default {
|
||||
_getConferenceOptions() {
|
||||
const options = config;
|
||||
|
||||
if (config.enableRecording && !config.recordingType) {
|
||||
options.recordingType
|
||||
= config.hosts && (typeof config.hosts.jirecon !== 'undefined')
|
||||
? 'jirecon'
|
||||
: 'colibri';
|
||||
}
|
||||
|
||||
const nick = APP.store.getState()['features/base/settings'].displayName;
|
||||
|
||||
if (nick) {
|
||||
@@ -1282,16 +1285,23 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useVideoStream(newStream) {
|
||||
return APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newStream, room))
|
||||
.then(() => {
|
||||
this.localVideo = newStream;
|
||||
this._setSharingScreen(newStream);
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newStream, room))
|
||||
.then(() => {
|
||||
this.localVideo = newStream;
|
||||
this._setSharingScreen(newStream);
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(onFinish);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1321,15 +1331,22 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useAudioStream(newStream) {
|
||||
return APP.store.dispatch(
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
this.localAudio = newStream;
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localAudio, newStream, room))
|
||||
.then(() => {
|
||||
this.localAudio = newStream;
|
||||
if (newStream) {
|
||||
APP.UI.addLocalStream(newStream);
|
||||
}
|
||||
this.setAudioMuteStatus(this.isLocalAudioMuted());
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.then(onFinish);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1677,23 +1694,21 @@ export default {
|
||||
room.on(
|
||||
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
|
||||
(authEnabled, authLogin) =>
|
||||
APP.UI.updateAuthInfo(authEnabled, authLogin));
|
||||
APP.store.dispatch(authStatusChanged(authEnabled, authLogin)));
|
||||
|
||||
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
|
||||
user => APP.UI.onUserFeaturesChanged(user));
|
||||
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
|
||||
// The logic shared between RN and web.
|
||||
commonUserJoinedHandling(APP.store, room, user);
|
||||
|
||||
if (user.isHidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const displayName = user.getDisplayName();
|
||||
|
||||
APP.store.dispatch(participantJoined({
|
||||
id,
|
||||
name: displayName,
|
||||
role: user.getRole()
|
||||
}));
|
||||
|
||||
logger.log('USER %s connnected', id, user);
|
||||
logger.log(`USER ${id} connnected:`, user);
|
||||
APP.API.notifyUserJoined(id, {
|
||||
displayName,
|
||||
formattedDisplayName: appendSuffix(
|
||||
@@ -1706,13 +1721,20 @@ export default {
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
|
||||
// The logic shared between RN and web.
|
||||
commonUserLeftHandling(APP.store, room, user);
|
||||
|
||||
if (user.isHidden()) {
|
||||
return;
|
||||
}
|
||||
APP.store.dispatch(participantLeft(id, user));
|
||||
logger.log('USER %s LEFT', id, user);
|
||||
|
||||
logger.log(`USER ${id} LEFT:`, user);
|
||||
APP.API.notifyUserLeft(id);
|
||||
APP.UI.removeUser(id, user.getDisplayName());
|
||||
APP.UI.messageHandler.participantNotification(
|
||||
user.getDisplayName(),
|
||||
'notify.somebody',
|
||||
'disconnected',
|
||||
'notify.disconnected');
|
||||
APP.UI.onSharedVideoStop(id);
|
||||
});
|
||||
|
||||
@@ -1797,82 +1819,21 @@ export default {
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
|
||||
(id, connectionStatus) => {
|
||||
APP.store.dispatch(participantConnectionStatusChanged(
|
||||
id, connectionStatus));
|
||||
(id, connectionStatus) => APP.store.dispatch(
|
||||
participantConnectionStatusChanged(id, connectionStatus)));
|
||||
|
||||
APP.UI.participantConnectionStatusChanged(id);
|
||||
});
|
||||
room.on(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => {
|
||||
APP.store.dispatch(dominantSpeakerChanged(id));
|
||||
});
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
APP.UI.markVideoInterrupted(true);
|
||||
});
|
||||
room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
|
||||
APP.UI.markVideoInterrupted(false);
|
||||
});
|
||||
|
||||
if (isButtonEnabled('chat')) {
|
||||
room.on(
|
||||
JitsiConferenceEvents.MESSAGE_RECEIVED,
|
||||
(id, body, ts) => {
|
||||
let nick = getDisplayName(id);
|
||||
|
||||
if (!nick) {
|
||||
nick = `${
|
||||
interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} (${
|
||||
id})`;
|
||||
}
|
||||
|
||||
APP.API.notifyReceivedChatMessage({
|
||||
id,
|
||||
nick,
|
||||
body,
|
||||
ts
|
||||
});
|
||||
APP.UI.addMessage(id, nick, body, ts);
|
||||
}
|
||||
);
|
||||
APP.UI.addListener(UIEvents.MESSAGE_CREATED, message => {
|
||||
APP.API.notifySendingChatMessage(message);
|
||||
room.sendTextMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, id => {
|
||||
APP.API.notifyOnStageParticipantChanged(id);
|
||||
try {
|
||||
// do not try to select participant if there is none (we
|
||||
// are alone in the room), otherwise an error will be
|
||||
// thrown cause reporting mechanism is not available
|
||||
// (datachannels currently)
|
||||
if (room.getParticipants().length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
room.selectParticipant(id);
|
||||
} catch (e) {
|
||||
sendAnalytics(createSelectParticipantFailedEvent(e));
|
||||
reportError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
room.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
|
||||
|
||||
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
APP.store.dispatch(localParticipantConnectionStatusChanged(
|
||||
JitsiParticipantConnectionStatus.INTERRUPTED));
|
||||
|
||||
APP.UI.showLocalConnectionInterrupted(true);
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
|
||||
APP.store.dispatch(localParticipantConnectionStatusChanged(
|
||||
JitsiParticipantConnectionStatus.ACTIVE));
|
||||
|
||||
APP.UI.showLocalConnectionInterrupted(false);
|
||||
});
|
||||
|
||||
room.on(
|
||||
@@ -1882,6 +1843,7 @@ export default {
|
||||
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id,
|
||||
name: formattedDisplayName
|
||||
}));
|
||||
@@ -1895,6 +1857,21 @@ export default {
|
||||
APP.UI.changeDisplayName(id, formattedDisplayName);
|
||||
}
|
||||
);
|
||||
room.on(
|
||||
JitsiConferenceEvents.BOT_TYPE_CHANGED,
|
||||
(id, botType) => {
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id,
|
||||
botType
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||
(...args) => APP.store.dispatch(endpointMessageReceived(...args)));
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
||||
@@ -1913,8 +1890,17 @@ export default {
|
||||
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, name, oldValue, newValue) => {
|
||||
switch (name) {
|
||||
case 'features_screen-sharing': {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
features: { 'screen-sharing': true }
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case 'raisedHand':
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
raisedHand: newValue === 'true'
|
||||
}));
|
||||
@@ -1930,38 +1916,6 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-enable max-params */
|
||||
room.on(
|
||||
JitsiConferenceEvents.RECORDER_STATE_CHANGED,
|
||||
recorderSession => {
|
||||
if (!recorderSession) {
|
||||
logger.error(
|
||||
'Received invalid recorder status update',
|
||||
recorderSession);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (recorderSession.getID()) {
|
||||
APP.store.dispatch(
|
||||
updateRecordingSessionData(recorderSession));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// These errors fire when the local participant has requested a
|
||||
// recording but the request itself failed, hence the missing
|
||||
// session ID because the recorder never started.
|
||||
if (recorderSession.getError()) {
|
||||
this._showRecordingErrorNotification(recorderSession);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error(
|
||||
'Received a recorder status update with no ID or error');
|
||||
});
|
||||
|
||||
room.on(JitsiConferenceEvents.KICKED, () => {
|
||||
APP.UI.hideStats();
|
||||
APP.UI.notifyKicked();
|
||||
@@ -2011,13 +1965,14 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
|
||||
APP.UI.addListener(UIEvents.EMAIL_CHANGED,
|
||||
this.changeLocalEmail.bind(this));
|
||||
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
email: data.value
|
||||
}));
|
||||
APP.UI.setUserEmail(from, data.value);
|
||||
});
|
||||
|
||||
room.addCommandListener(
|
||||
@@ -2025,6 +1980,7 @@ export default {
|
||||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
avatarURL: data.value
|
||||
}));
|
||||
@@ -2034,6 +1990,7 @@ export default {
|
||||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
participantUpdated({
|
||||
conference: room,
|
||||
id: from,
|
||||
avatarID: data.value
|
||||
}));
|
||||
@@ -2054,13 +2011,6 @@ export default {
|
||||
&& APP.UI.notifyInitiallyMuted();
|
||||
});
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.AVAILABLE_DEVICES_CHANGED,
|
||||
(id, devices) => {
|
||||
APP.UI.updateDevicesAvailability(id, devices);
|
||||
}
|
||||
);
|
||||
|
||||
room.on(
|
||||
JitsiConferenceEvents.DATA_CHANNEL_OPENED, () => {
|
||||
APP.store.dispatch(dataChannelOpened());
|
||||
@@ -2174,20 +2124,6 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
||||
audioOutputDeviceId => {
|
||||
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
|
||||
setAudioOutputDeviceId(audioOutputDeviceId)
|
||||
.then(() => logger.log('changed audio output device'))
|
||||
.catch(err => {
|
||||
logger.warn('Failed to change audio output device. '
|
||||
+ 'Default or previously set audio output device '
|
||||
+ 'will be used instead.', err);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => {
|
||||
|
||||
// FIXME On web video track is stored both in redux and in
|
||||
@@ -2319,10 +2255,10 @@ export default {
|
||||
|
||||
APP.store.dispatch(conferenceJoined(room));
|
||||
|
||||
APP.UI.mucJoined();
|
||||
const displayName
|
||||
= APP.store.getState()['features/base/settings'].displayName;
|
||||
|
||||
APP.UI.changeDisplayName('localVideoContainer', displayName);
|
||||
APP.API.notifyConferenceJoined(
|
||||
this.roomName,
|
||||
this._room.myUserId(),
|
||||
@@ -2335,7 +2271,6 @@ export default {
|
||||
APP.store.getState(), this._room.myUserId())
|
||||
}
|
||||
);
|
||||
APP.UI.markVideoInterrupted(false);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2360,47 +2295,43 @@ export default {
|
||||
/**
|
||||
* Inits list of current devices and event listener for device change.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initDeviceList() {
|
||||
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
||||
.then(isDeviceListAvailable => {
|
||||
if (isDeviceListAvailable
|
||||
&& JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||
JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
|
||||
// Ugly way to synchronize real device IDs with local
|
||||
// storage and settings menu. This is a workaround until
|
||||
// getConstraints() method will be implemented
|
||||
// in browsers.
|
||||
const { dispatch } = APP.store;
|
||||
const { mediaDevices } = JitsiMeetJS;
|
||||
|
||||
if (this.localAudio) {
|
||||
dispatch(updateSettings({
|
||||
micDeviceId: this.localAudio.getDeviceId()
|
||||
}));
|
||||
}
|
||||
if (mediaDevices.isDeviceListAvailable()
|
||||
&& mediaDevices.isDeviceChangeAvailable()) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
|
||||
if (this.localVideo) {
|
||||
dispatch(updateSettings({
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
const { dispatch } = APP.store;
|
||||
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
});
|
||||
return dispatch(getAvailableDevices())
|
||||
.then(devices => {
|
||||
// Ugly way to synchronize real device IDs with local
|
||||
// storage and settings menu. This is a workaround until
|
||||
// getConstraints() method will be implemented in browsers.
|
||||
if (this.localAudio) {
|
||||
dispatch(updateSettings({
|
||||
micDeviceId: this.localAudio.getDeviceId()
|
||||
}));
|
||||
}
|
||||
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(
|
||||
() => this._onDeviceListChanged(devices), 0);
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logger.warn(`Error getting device list: ${error}`);
|
||||
});
|
||||
if (this.localVideo) {
|
||||
dispatch(updateSettings({
|
||||
cameraDeviceId: this.localVideo.getDeviceId()
|
||||
}));
|
||||
}
|
||||
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2411,16 +2342,7 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onDeviceListChanged(devices) {
|
||||
let currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
|
||||
|
||||
// Event handler can be fired before direct
|
||||
// enumerateDevices() call, so handle this situation here.
|
||||
if (!currentDevices.audioinput
|
||||
&& !currentDevices.videoinput
|
||||
&& !currentDevices.audiooutput) {
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
|
||||
}
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
|
||||
const newDevices
|
||||
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
|
||||
@@ -2433,9 +2355,13 @@ export default {
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
|
||||
if (typeof newDevices.audiooutput !== 'undefined') {
|
||||
// Just ignore any errors in catch block.
|
||||
promises.push(setAudioOutputDeviceId(newDevices.audiooutput)
|
||||
.catch());
|
||||
const { dispatch } = APP.store;
|
||||
const setAudioOutputPromise
|
||||
= setAudioOutputDeviceId(newDevices.audiooutput, dispatch)
|
||||
.catch(); // Just ignore any errors in catch block.
|
||||
|
||||
|
||||
promises.push(setAudioOutputPromise);
|
||||
}
|
||||
|
||||
promises.push(
|
||||
@@ -2443,11 +2369,24 @@ export default {
|
||||
createLocalTracksF,
|
||||
newDevices.videoinput,
|
||||
newDevices.audioinput)
|
||||
.then(tracks =>
|
||||
Promise.all(this._setLocalAudioVideoStreams(tracks)))
|
||||
.then(tracks => {
|
||||
// If audio or video muted before, or we unplugged current
|
||||
// device and selected new one, then mute new track.
|
||||
const muteSyncPromises = tracks.map(track => {
|
||||
if ((track.isVideoTrack() && videoWasMuted)
|
||||
|| (track.isAudioTrack() && audioWasMuted)) {
|
||||
return track.mute();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
return Promise.all(muteSyncPromises)
|
||||
.then(() => Promise.all(
|
||||
this._setLocalAudioVideoStreams(tracks)));
|
||||
})
|
||||
.then(() => {
|
||||
// If audio was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new audio track.
|
||||
// Log and sync known mute state.
|
||||
if (audioWasMuted) {
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
'audio',
|
||||
@@ -2456,8 +2395,6 @@ export default {
|
||||
muteLocalAudio(true);
|
||||
}
|
||||
|
||||
// If video was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new video track.
|
||||
if (!this.isSharingScreen && videoWasMuted) {
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
'video',
|
||||
@@ -2469,7 +2406,6 @@ export default {
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => {
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
});
|
||||
},
|
||||
@@ -2479,7 +2415,7 @@ export default {
|
||||
*/
|
||||
updateAudioIconEnabled() {
|
||||
const audioMediaDevices
|
||||
= mediaDeviceHelper.getCurrentMediaDevices().audioinput;
|
||||
= APP.store.getState()['features/base/devices'].audioInput;
|
||||
const audioDeviceCount
|
||||
= audioMediaDevices ? audioMediaDevices.length : 0;
|
||||
|
||||
@@ -2502,7 +2438,7 @@ export default {
|
||||
*/
|
||||
updateVideoIconEnabled() {
|
||||
const videoMediaDevices
|
||||
= mediaDeviceHelper.getCurrentMediaDevices().videoinput;
|
||||
= APP.store.getState()['features/base/devices'].videoInput;
|
||||
const videoDeviceCount
|
||||
= videoMediaDevices ? videoMediaDevices.length : 0;
|
||||
|
||||
@@ -2533,9 +2469,11 @@ export default {
|
||||
APP.UI.removeLocalMedia();
|
||||
|
||||
// Remove unnecessary event listeners from firing callbacks.
|
||||
JitsiMeetJS.mediaDevices.removeEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
if (this.deviceChangeListener) {
|
||||
JitsiMeetJS.mediaDevices.removeEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
let requestFeedbackPromise;
|
||||
|
||||
@@ -2555,13 +2493,24 @@ export default {
|
||||
// before all operations are done.
|
||||
Promise.all([
|
||||
requestFeedbackPromise,
|
||||
room.leave().then(disconnect, disconnect)
|
||||
this.leaveRoomAndDisconnect()
|
||||
]).then(values => {
|
||||
APP.API.notifyReadyToClose();
|
||||
maybeRedirectToWelcomePage(values[0]);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Leaves the room and calls JitsiConnection.disconnect.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
leaveRoomAndDisconnect() {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
return room.leave().then(disconnect, disconnect);
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the email for the local user
|
||||
* @param email {string} the new email
|
||||
@@ -2578,6 +2527,12 @@ export default {
|
||||
const localId = localParticipant.id;
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id: localId,
|
||||
local: true,
|
||||
email: formattedEmail
|
||||
@@ -2586,8 +2541,9 @@ export default {
|
||||
APP.store.dispatch(updateSettings({
|
||||
email: formattedEmail
|
||||
}));
|
||||
|
||||
APP.UI.setUserEmail(localId, formattedEmail);
|
||||
APP.API.notifyEmailChanged(localId, {
|
||||
email: formattedEmail
|
||||
});
|
||||
sendData(commands.EMAIL, formattedEmail);
|
||||
},
|
||||
|
||||
@@ -2605,6 +2561,12 @@ export default {
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
avatarURL: formattedUrl
|
||||
@@ -2661,6 +2623,12 @@ export default {
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
name: formattedNickname
|
||||
@@ -2741,57 +2709,5 @@ export default {
|
||||
if (score === -1 || (score >= 1 && score <= 5)) {
|
||||
APP.store.dispatch(submitFeedback(score, message, room));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a notification about an error in the recording session. A
|
||||
* default notification will display if no error is specified in the passed
|
||||
* in recording session.
|
||||
*
|
||||
* @param {Object} recorderSession - The recorder session model from the
|
||||
* lib.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_showRecordingErrorNotification(recorderSession) {
|
||||
const isStreamMode
|
||||
= recorderSession.getMode()
|
||||
=== JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
|
||||
switch (recorderSession.getError()) {
|
||||
case JitsiMeetJS.constants.recording.error.SERVICE_UNAVAILABLE:
|
||||
APP.UI.messageHandler.showError({
|
||||
descriptionKey: 'recording.unavailable',
|
||||
descriptionArguments: {
|
||||
serviceName: isStreamMode
|
||||
? 'Live Streaming service'
|
||||
: 'Recording service'
|
||||
},
|
||||
titleKey: isStreamMode
|
||||
? 'liveStreaming.unavailableTitle'
|
||||
: 'recording.unavailableTitle'
|
||||
});
|
||||
break;
|
||||
case JitsiMeetJS.constants.recording.error.RESOURCE_CONSTRAINT:
|
||||
APP.UI.messageHandler.showError({
|
||||
descriptionKey: isStreamMode
|
||||
? 'liveStreaming.busy'
|
||||
: 'recording.busy',
|
||||
titleKey: isStreamMode
|
||||
? 'liveStreaming.busyTitle'
|
||||
: 'recording.busyTitle'
|
||||
});
|
||||
break;
|
||||
default:
|
||||
APP.UI.messageHandler.showError({
|
||||
descriptionKey: isStreamMode
|
||||
? 'liveStreaming.error'
|
||||
: 'recording.error',
|
||||
titleKey: isStreamMode
|
||||
? 'liveStreaming.failedToStart'
|
||||
: 'recording.failedToStart'
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
81
config.js
81
config.js
@@ -18,9 +18,6 @@ var config = {
|
||||
// XMPP domain.
|
||||
domain: 'jitsi-meet.example.com',
|
||||
|
||||
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
|
||||
muc: 'conference.jitsi-meet.example.com'
|
||||
|
||||
// When using authentication, domain for guest users.
|
||||
// anonymousdomain: 'guest.example.com',
|
||||
|
||||
@@ -35,6 +32,9 @@ var config = {
|
||||
|
||||
// Focus component domain. Defaults to focus.<domain>.
|
||||
// focus: 'focus.jitsi-meet.example.com',
|
||||
|
||||
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
|
||||
muc: 'conference.jitsi-meet.example.com'
|
||||
},
|
||||
|
||||
// BOSH URL. FIXME: use XEP-0156 to discover it.
|
||||
@@ -99,13 +99,13 @@ var config = {
|
||||
// used by browsers that return true from lib-jitsi-meet's
|
||||
// util#browser#usesNewGumFlow. The constraints are independency from
|
||||
// this config's resolution value. Defaults to requesting an ideal aspect
|
||||
// ratio of 16:9 with an ideal resolution of 1080p.
|
||||
// ratio of 16:9 with an ideal resolution of 720.
|
||||
// constraints: {
|
||||
// video: {
|
||||
// aspectRatio: 16 / 9,
|
||||
// height: {
|
||||
// ideal: 1080,
|
||||
// max: 1080,
|
||||
// ideal: 720,
|
||||
// max: 720,
|
||||
// min: 240
|
||||
// }
|
||||
// }
|
||||
@@ -114,6 +114,11 @@ var config = {
|
||||
// Enable / disable simulcast support.
|
||||
// disableSimulcast: false,
|
||||
|
||||
// Enable / disable layer suspension. If enabled, endpoints whose HD
|
||||
// layers are not in use will be suspended (no longer sent) until they
|
||||
// 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,
|
||||
@@ -137,9 +142,6 @@ var config = {
|
||||
|
||||
// Desktop sharing
|
||||
|
||||
// Enable / disable desktop sharing
|
||||
// disableDesktopSharing: false,
|
||||
|
||||
// The ID of the jidesha extension for Chrome.
|
||||
desktopSharingChromeExtId: null,
|
||||
|
||||
@@ -167,11 +169,19 @@ var config = {
|
||||
|
||||
// Recording
|
||||
|
||||
// Whether to enable recording or not.
|
||||
// enableRecording: false,
|
||||
// Whether to enable file recording or not.
|
||||
// fileRecordingsEnabled: false,
|
||||
// Enable the dropbox integration.
|
||||
// dropbox: {
|
||||
// appKey: '<APP_KEY>' // Specify your app key here.
|
||||
// },
|
||||
|
||||
// Type for recording: one of jibri or jirecon.
|
||||
// recordingType: 'jibri',
|
||||
// Whether to enable live streaming or not.
|
||||
// liveStreamingEnabled: false,
|
||||
|
||||
// Transcription (in interface_config,
|
||||
// subtitles and buttons can be configured)
|
||||
// transcribingEnabled: false,
|
||||
|
||||
// Misc
|
||||
|
||||
@@ -231,10 +241,6 @@ var config = {
|
||||
// Disable hiding of remote thumbnails when in a 1-on-1 conference call.
|
||||
// disable1On1Mode: false,
|
||||
|
||||
// The minimum value a video's height (or width, whichever is smaller) needs
|
||||
// to be in order to be considered high-definition.
|
||||
minHDHeight: 540,
|
||||
|
||||
// Default language for the user interface.
|
||||
// defaultLanguage: 'en',
|
||||
|
||||
@@ -243,10 +249,16 @@ var config = {
|
||||
// edit their profile.
|
||||
enableUserRolesBasedOnToken: false,
|
||||
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
|
||||
// Enables calendar integration, depends on googleApiApplicationClientID
|
||||
// and microsoftApiApplicationClientID
|
||||
// enableCalendarIntegration: false,
|
||||
|
||||
// Stats
|
||||
//
|
||||
@@ -339,9 +351,39 @@ var config = {
|
||||
// userRegion: "asia"
|
||||
}
|
||||
|
||||
// Local Recording
|
||||
//
|
||||
|
||||
// localRecording: {
|
||||
// Enables local recording.
|
||||
// Additionally, 'localrecording' (all lowercase) needs to be added to
|
||||
// TOOLBAR_BUTTONS in interface_config.js for the Local Recording
|
||||
// button to show up on the toolbar.
|
||||
//
|
||||
// enabled: true,
|
||||
//
|
||||
|
||||
// The recording format, can be one of 'ogg', 'flac' or 'wav'.
|
||||
// format: 'flac'
|
||||
//
|
||||
|
||||
// }
|
||||
|
||||
// Options related to end-to-end (participant to participant) ping.
|
||||
// e2eping: {
|
||||
// // The interval in milliseconds at which pings will be sent.
|
||||
// // Defaults to 10000, set to <= 0 to disable.
|
||||
// pingInterval: 10000,
|
||||
//
|
||||
// // The interval in milliseconds at which analytics events
|
||||
// // with the measured RTT will be sent. Defaults to 60000, set
|
||||
// // to <= 0 to disable.
|
||||
// analyticsInterval: 60000,
|
||||
// }
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
alwaysVisibleToolbar
|
||||
_immediateReloadThreshold
|
||||
autoRecord
|
||||
autoRecordToken
|
||||
debug
|
||||
@@ -358,8 +400,10 @@ var config = {
|
||||
externalConnectUrl
|
||||
firefox_fake_device
|
||||
googleApiApplicationClientID
|
||||
googleApiIOSClientID
|
||||
iAmRecorder
|
||||
iAmSipGateway
|
||||
microsoftApiApplicationClientID
|
||||
peopleSearchQueryTypes
|
||||
peopleSearchUrl
|
||||
requireDisplayName
|
||||
@@ -388,6 +432,7 @@ var config = {
|
||||
nick
|
||||
startBitrate
|
||||
*/
|
||||
|
||||
};
|
||||
|
||||
/* eslint-enable no-unused-vars, no-var */
|
||||
|
||||
@@ -132,7 +132,7 @@ function connect(id, password, roomName) {
|
||||
*
|
||||
*/
|
||||
function handleConnectionEstablished() {
|
||||
APP.store.dispatch(connectionEstablished(connection));
|
||||
APP.store.dispatch(connectionEstablished(connection, Date.now()));
|
||||
unsubscribe();
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Project animations
|
||||
**/
|
||||
|
||||
/**
|
||||
* Slide in animation for extended toolbar (inner) panel.
|
||||
*/
|
||||
|
||||
// FIX: Can't use percentage because of breaking animation when width is changed
|
||||
// (100% of 0 is also zero) Extracted this to config variable.
|
||||
@include keyframes(slideInExt) {
|
||||
from { left: -$sidebarWidth; }
|
||||
to { left: 0; }
|
||||
}
|
||||
|
||||
@include keyframes(slideOutExt) {
|
||||
from { left: 0; }
|
||||
to { left: -$sidebarWidth; }
|
||||
}
|
||||
57
css/_atlaskit_overrides.scss
Normal file
57
css/_atlaskit_overrides.scss
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Move Atlaskit Flag up a little bit so it does not cover the toolbar with the
|
||||
* first notification.
|
||||
*/
|
||||
.cxGWJB{
|
||||
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
|
||||
}
|
||||
.gXSEsl:nth-child(n+2) {
|
||||
transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
-ms-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
-webkit-transform: translateX(0) translateY(100%) translateY(16px) !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome page tab color adjustments.
|
||||
*/
|
||||
.welcome {
|
||||
/**
|
||||
* The text color of the selected tab and hovered tabs.
|
||||
*/
|
||||
.bVobOt,
|
||||
.bVobOt:hover,
|
||||
.ebveIl:hover {
|
||||
color: #172B4D;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of the inactive tab text.
|
||||
*/
|
||||
.ebveIl {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of the underline of a selected tab.
|
||||
*/
|
||||
.kByArU {
|
||||
background-color: #172B4D;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
/**
|
||||
* Update the dropdown trigger wrapper to make sure it looks click-able.
|
||||
*/
|
||||
.gwEjuO {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override Atlaskit dropdown styling when in a modal because the dropdown
|
||||
* backgrounds clash with the modal backgrounds.
|
||||
*/
|
||||
.gBLqhw[data-role=droplistContent] {
|
||||
border: 1px solid #455166;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,11 @@ textarea {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
@@ -103,14 +108,15 @@ form {
|
||||
}
|
||||
|
||||
.leftwatermark {
|
||||
left: $defaultToolbarSize;
|
||||
margin-left: 10px;
|
||||
left: 32px;
|
||||
top: 32px;
|
||||
background-image: url($defaultWatermarkLink);
|
||||
background-position: center left;
|
||||
}
|
||||
|
||||
.rightwatermark {
|
||||
right: 15;
|
||||
right: 32px;
|
||||
top: 32px;
|
||||
background-position: center right;
|
||||
}
|
||||
|
||||
|
||||
246
css/_chat.scss
246
css/_chat.scss
@@ -1,21 +1,58 @@
|
||||
#sideToolbarContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
display: flex;
|
||||
/**
|
||||
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||
* the toolbar and subtract from 100%.
|
||||
*/
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
left: -$sidebarWidth;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: left 0.5s;
|
||||
width: $sidebarWidth;
|
||||
z-index: $sideToolbarContainerZ;
|
||||
|
||||
/**
|
||||
* The sidebar (chat) is off-screen when hidden. Move it flush to the left
|
||||
* side of the window when it should be visible.
|
||||
*/
|
||||
&.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 {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
font-size: 10pt;
|
||||
line-height: 20px;
|
||||
margin-top: 15px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
line-height: 20px;
|
||||
font-size: 10pt;
|
||||
width: 100%;
|
||||
height: 90%;
|
||||
overflow: auto;
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: rgb(184, 184, 184);
|
||||
}
|
||||
@@ -55,71 +92,52 @@
|
||||
}
|
||||
}
|
||||
|
||||
#chat_container.is-conversation-mode #chatconversation {
|
||||
visibility: visible;
|
||||
.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: 5px;
|
||||
width: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.localuser {
|
||||
color: #087dba;
|
||||
}
|
||||
.use-new-toolbox {
|
||||
.chatmessage {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.localuser {
|
||||
color: #4C9AFF;
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chatmessage,
|
||||
#smileysarea,
|
||||
#smileysContainer,
|
||||
#usermsg {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
}
|
||||
|
||||
.smileyContainer:hover {
|
||||
background-color: $newToolbarButtonToggleColor;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
color: red;
|
||||
#chat-input {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: white;
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
.usrmsg-form {
|
||||
flex: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
visibility:hidden;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 83%;
|
||||
height: 30px;
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 10pt;
|
||||
line-height: 30px;
|
||||
padding: 5px 5px 5px 0px;
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
border: 0px none;
|
||||
background: #3a3a3a;
|
||||
color: #a7a7a7;
|
||||
box-shadow: none;
|
||||
border-radius:0;
|
||||
font-size: 10pt;
|
||||
line-height: 30px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#usermsg:hover {
|
||||
@@ -127,10 +145,6 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#chat_container.is-conversation-mode #usermsg {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#nickname {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
@@ -142,20 +156,7 @@
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
#chat_container.is-conversation-mode #nickname {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#nickinput {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
background: #3a3a3a;
|
||||
box-shadow: inset 0 0 3px 2px #a7a7a7;
|
||||
border: 1px solid #a7a7a7;
|
||||
color: #a7a7a7;
|
||||
}
|
||||
|
||||
#chat_container .username {
|
||||
#chat_container .display-name {
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
@@ -171,29 +172,45 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#chat_container .usermessage {
|
||||
.usermessage {
|
||||
padding-top: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.chatArrow {
|
||||
position: absolute;
|
||||
height: 15px;
|
||||
left: 5px;
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background: #3a3a3a;
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
width: 93%;
|
||||
margin-left: 9px;
|
||||
margin-right: auto;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0px;
|
||||
margin-top: 3px;
|
||||
left: 5px;
|
||||
color: #a7a7a7;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
padding-bottom: 3px;
|
||||
position: relative;
|
||||
|
||||
&.localuser .display-name {
|
||||
color: #4C9AFF
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatArrow,
|
||||
.timestamp,
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
color: red;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.smiley {
|
||||
@@ -201,11 +218,9 @@
|
||||
}
|
||||
|
||||
#smileys {
|
||||
position: absolute;
|
||||
bottom: 7px;
|
||||
right: 5px;
|
||||
background: white;
|
||||
border-radius: 50px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
@@ -217,33 +232,40 @@
|
||||
}
|
||||
|
||||
#smileysarea {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 17%;
|
||||
min-width: 31px;
|
||||
height: 40px;
|
||||
padding: 0px;
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 0px none;
|
||||
background: #3a3a3a;
|
||||
display: flex;
|
||||
height: 70px;
|
||||
max-height: 150px;
|
||||
min-height: 35px;
|
||||
min-width: 31px;
|
||||
padding: 0px;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 17%;
|
||||
}
|
||||
|
||||
#chat_container.is-conversation-mode #smileysarea {
|
||||
visibility: visible;
|
||||
.smiley-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#smileysContainer {
|
||||
display: none;
|
||||
.smileys-panel {
|
||||
bottom: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
background: #3a3a3a;
|
||||
border-bottom: 1px solid;
|
||||
border-top: 1px solid;
|
||||
width: 100%;
|
||||
bottom: 10%;
|
||||
transition: height 0.3s;
|
||||
width: $sidebarWidth;
|
||||
|
||||
&.show-smileys {
|
||||
height: 202px;
|
||||
}
|
||||
|
||||
#smileysContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-bottom: 1px solid;
|
||||
border-top: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
#smileysContainer .smiley {
|
||||
@@ -257,7 +279,9 @@
|
||||
}
|
||||
|
||||
.smileyContainer:hover {
|
||||
background: #3e3e3e;
|
||||
background-color: $newToolbarButtonToggleColor;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#usermsg::-webkit-input-placeholder {
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
%align-right {
|
||||
@include flex();
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.use-new-toolbox {
|
||||
.filmstrip.reduce-height {
|
||||
bottom: $newToolbarSizeWithPadding;
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
transition: bottom .3s;
|
||||
}
|
||||
|
||||
.filmstrip__videos.hidden {
|
||||
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 10px 5px;
|
||||
@extend %align-right;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&__toolbar {
|
||||
@include flex();
|
||||
flex-direction: column-reverse;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
width: $filmstripToggleButtonWidth;
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
opacity: 0.7;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
-webkit-appearance: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__videos {
|
||||
@extend %align-right;
|
||||
position:relative;
|
||||
padding: 0;
|
||||
/* The filmstrip should not be covered by the left toolbar. */
|
||||
bottom: 0;
|
||||
width:auto;
|
||||
overflow: visible !important;
|
||||
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: $defaultToolbarSize + 5;
|
||||
transition: bottom 2s;
|
||||
}
|
||||
|
||||
/**
|
||||
* The local video identifier.
|
||||
*/
|
||||
&#filmstripLocalVideo {
|
||||
align-self: flex-end;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
bottom: -196px;
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
display: none;
|
||||
position: relative;
|
||||
background-size: contain;
|
||||
border: $thumbnailVideoBorder solid transparent;
|
||||
border-radius: $borderRadius;
|
||||
margin: 0 $thumbnailVideoMargin;
|
||||
|
||||
&.videoContainerFocused, &:hover {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focused video thumbnail.
|
||||
*/
|
||||
&.videoContainerFocused {
|
||||
transition-duration: 0.5s;
|
||||
-webkit-transition-duration: 0.5s;
|
||||
-webkit-animation-name: greyPulse;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailSelected !important;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailSelected,
|
||||
0 0 3px $videoThumbnailSelected !important;
|
||||
}
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.presence-label {
|
||||
color: $participantNameColor;
|
||||
font-size: 12px;
|
||||
font-weight: 100;
|
||||
left: 0;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
top: calc(50% + 30px);
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
z-index: $zindex3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovered video thumbnail.
|
||||
*/
|
||||
&:hover {
|
||||
cursor: hand;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailHovered,
|
||||
0 0 3px $videoThumbnailHovered;
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
/* With the TemasysWebRTC plugin <object/> element is used
|
||||
instead of <video/> */
|
||||
& > video,
|
||||
& > object {
|
||||
cursor: hand;
|
||||
border-radius: $borderRadius;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the filmstrip videos in filmstrip-only mode.
|
||||
*/
|
||||
&__videos-filmstripOnly {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
.filmstrip__videos {
|
||||
&#filmstripLocalVideo {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
&.hide-videos {
|
||||
.remote-videos-container {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,13 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-arrow_back:before {
|
||||
content: "\e5c4";
|
||||
}
|
||||
.icon-close:before {
|
||||
content: "\e5cd";
|
||||
}
|
||||
.icon-event_note:before {
|
||||
content: "\e616";
|
||||
}
|
||||
@@ -204,3 +208,15 @@
|
||||
.icon-live:before {
|
||||
content: "\e92c";
|
||||
}
|
||||
.icon-speaker:before {
|
||||
content: "\e92d";
|
||||
}
|
||||
.icon-tiles-many:before {
|
||||
content: "\e92e";
|
||||
}
|
||||
.icon-tiles-one:before {
|
||||
content: "\e92f";
|
||||
}
|
||||
.icon-closed_caption:before {
|
||||
content: "\e930";
|
||||
}
|
||||
|
||||
117
css/_meetings_list.scss
Normal file
117
css/_meetings_list.scss
Normal file
@@ -0,0 +1,117 @@
|
||||
.meetings-list {
|
||||
font-size: 14px;
|
||||
color: #253858;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.meetings-list-empty {
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: rgba(255,255,255,0.50);
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
margin-top: 5px;
|
||||
min-height: 92px;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-align: left;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 140px;
|
||||
flex-grow: 0;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.date {
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding-left: 30px;
|
||||
padding-top: 25px;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
&.with-click-handler {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
background-color: #75A7E7;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.join-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .join-button {
|
||||
display: block
|
||||
}
|
||||
}
|
||||
}
|
||||
80
css/_navigate_section_list.scss
Normal file
80
css/_navigate_section_list.scss
Normal file
@@ -0,0 +1,80 @@
|
||||
%navigate-section-list-text {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: $welcomePageTitleColor;
|
||||
text-align: left;
|
||||
font-family: 'open_sanslight', Helvetica, sans-serif;
|
||||
}
|
||||
%navigate-section-list-tile-text {
|
||||
@extend %navigate-section-list-text;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
float: left;
|
||||
}
|
||||
.navigate-section-list-tile {
|
||||
background-color: #1754A9;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 8px;
|
||||
min-height: 100px;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
|
||||
&.with-click-handler {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
background-color: #1a5dbb;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.element-after {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.join-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .join-button {
|
||||
display: block
|
||||
}
|
||||
}
|
||||
.navigate-section-tile-body {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: normal;
|
||||
line-height: 24px;
|
||||
}
|
||||
.navigate-section-list-tile-info {
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
.navigate-section-tile-title {
|
||||
@extend %navigate-section-list-tile-text;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
.navigate-section-section-header {
|
||||
@extend %navigate-section-list-text;
|
||||
font-weight: bold;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
.navigate-section-list {
|
||||
position: relative;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 36px;
|
||||
width: 100%;
|
||||
}
|
||||
.navigate-section-list-empty {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -10,14 +10,24 @@
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.popover-mousemove-padding-right {
|
||||
|
||||
%vertical-popover-padding {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: -20;
|
||||
top: 0;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-left {
|
||||
@extend %vertical-popover-padding;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
.popover-mousemove-padding-right {
|
||||
@extend %vertical-popover-padding;
|
||||
right: -20px;
|
||||
}
|
||||
|
||||
/**
|
||||
* An invisible element is added to the top of the popover to ensure the mouse
|
||||
* stays over the popover when the popover's height is shrunk, which would then
|
||||
|
||||
@@ -2,6 +2,57 @@
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.recording-dialog {
|
||||
flex: 0;
|
||||
flex-direction: column;
|
||||
|
||||
.recording-header {
|
||||
display: flex;
|
||||
flex: 0;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.recording-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.authorization-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.dropbox-sign-in {
|
||||
align-items: center;
|
||||
border: 1px solid #4285f4;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 10px 0px;
|
||||
color: #4285f4;
|
||||
|
||||
.dropbox-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding-right: 5px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.logged-in-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.live-stream-dialog {
|
||||
/**
|
||||
* Set font-size to be consistent with Atlaskit FieldText.
|
||||
@@ -30,37 +81,8 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Google sign in button must follow Google's design guidelines.
|
||||
* See: https://developers.google.com/identity/branding-guidelines
|
||||
*/
|
||||
.google-sign-in {
|
||||
background-color: #4285f4;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: Roboto, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 1px;
|
||||
|
||||
.google-cta {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
/**
|
||||
* Hack the line height for vertical centering of text.
|
||||
*/
|
||||
line-height: 32px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.google-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
.google-error {
|
||||
color: $errorColor;
|
||||
}
|
||||
|
||||
.google-panel {
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/**
|
||||
* Toolbar side panel main container element.
|
||||
*/
|
||||
.use-new-toolbox #sideToolbarContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
|
||||
/**
|
||||
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||
* the toolbar and subtract from 100%.
|
||||
*/
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
left: 0;
|
||||
|
||||
.side-toolbar-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: 5px;
|
||||
width: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
top: 15px;
|
||||
}
|
||||
}
|
||||
#sideToolbarContainer {
|
||||
background-color: $sideToolbarContainerBg;
|
||||
height: 100%;
|
||||
left: $defaultToolbarSize;
|
||||
max-width: $sidebarWidth;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0;
|
||||
z-index: $sideToolbarContainerZ;
|
||||
|
||||
/**
|
||||
* Labels inside the side panel.
|
||||
*/
|
||||
label {
|
||||
color: $baseLight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form elements and blocks.
|
||||
*/
|
||||
input,
|
||||
a,
|
||||
.sideToolbarBlock,
|
||||
.form-control {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
margin-left: 10%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify styling of elements inside a block.
|
||||
*/
|
||||
.sideToolbarBlock {
|
||||
input, a {
|
||||
margin-left: 0;
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner container, for example settings or profile.
|
||||
*/
|
||||
.sideToolbarContainer__inner {
|
||||
display: none;
|
||||
height: 100%;
|
||||
width: $sidebarWidth;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
|
||||
.input-control {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Titles and subtitles of inner containers.
|
||||
*/
|
||||
div.title, div.subTitle {
|
||||
margin: 24px 0 11px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main title size.
|
||||
*/
|
||||
div.title {
|
||||
color: $toolbarTitleColor;
|
||||
text-align: center;
|
||||
font-size: $toolbarTitleFontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* First element after a title.
|
||||
*/
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
|
||||
.moderator-checkbox {
|
||||
display: inline-block;
|
||||
margin: 0 5px 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.moderator-option {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
color: $defaultSideBarFontColor;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile
|
||||
*/
|
||||
.auth_container {
|
||||
ul {
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
|
||||
a.authButton {
|
||||
width: 160px;
|
||||
margin: 10px 20px;
|
||||
padding: 3px 29px;
|
||||
box-sizing: border-box;
|
||||
background-color: #06a5df;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: $defaultColor;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,17 +19,6 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.use-new-toolbox {
|
||||
.cxGWJB{
|
||||
bottom: calc(#{$newToolbarSizeWithPadding});
|
||||
}
|
||||
.gXSEsl:nth-child(n+2) {
|
||||
transform: translateX(0) translateY(100%) translateY(16px);
|
||||
-ms-transform: translateX(0) translateY(100%) translateY(16px);
|
||||
-webkit-transform: translateX(0) translateY(100%) translateY(16px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
|
||||
*/
|
||||
@@ -39,8 +28,12 @@
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
left: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 12px 8px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: bottom .3s ease-in;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ;
|
||||
@@ -91,7 +84,13 @@
|
||||
}
|
||||
|
||||
i.disabled {
|
||||
cursor: initial
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
.disabled i {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
|
||||
i.disabled:hover {
|
||||
@@ -121,6 +120,12 @@
|
||||
height: 22px;
|
||||
padding: 5px 12px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #313D52;
|
||||
}
|
||||
@@ -131,6 +136,20 @@
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #B8C7E0;
|
||||
border-radius: 2px;
|
||||
color: $newToolbarBackgroundColor;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
@@ -284,27 +303,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* START of slide in animation for extended toolbar panel.
|
||||
*/
|
||||
@include keyframes(slideInExt) {
|
||||
from { width: 0px; }
|
||||
to { width: $sidebarWidth; } // TO FIX: Make this value a percentage.
|
||||
}
|
||||
|
||||
.slideInExt {
|
||||
@include animation("slideInExt .5s forwards");
|
||||
}
|
||||
|
||||
@include keyframes(slideOutExt) {
|
||||
from { width: $sidebarWidth; } // TO FIX: Make this value a percentage.
|
||||
to { width: 0px; }
|
||||
}
|
||||
|
||||
.slideOutExt {
|
||||
@include animation("slideOutExt .5s forwards");
|
||||
}
|
||||
|
||||
/**
|
||||
* START of fade in animation for main toolbar
|
||||
*/
|
||||
|
||||
20
css/_transcription-subtitles.scss
Normal file
20
css/_transcription-subtitles.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
.transcription-subtitles{
|
||||
bottom: 10%;
|
||||
font-size: 16px;
|
||||
font-weight: 1000;
|
||||
left: 50%;
|
||||
max-width: 50vw;
|
||||
opacity: 0.80;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 1px 1px rgba(0,0,0,0.3),
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
transform: translateX(-50%);
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
|
||||
span {
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Style variables
|
||||
*/
|
||||
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$baseFontFamily: -apple-system, BlinkMacSystemFont, 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$hangupColor: #bf2117;
|
||||
$hangupFontSize: 2em;
|
||||
|
||||
@@ -146,5 +146,5 @@ $watermarkHeight: 74px;
|
||||
*/
|
||||
$welcomePageDescriptionColor: #fff;
|
||||
$welcomePageFontFamily: inherit;
|
||||
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
|
||||
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
|
||||
$welcomePageTitleColor: #fff;
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
/**
|
||||
* Override other styles to support vertical filmstrip mode.
|
||||
*/
|
||||
.vertical-filmstrip {
|
||||
/*
|
||||
* Firefox sets flex items to min-height: auto and min-width: auto,
|
||||
* preventing flex children from shrinking like they do on other browsers.
|
||||
* Setting min-height and min-width 0 is a workaround for the issue so
|
||||
* Firefox behaves like other browsers.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
|
||||
*/
|
||||
@mixin minHWAutoFix() {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#etherpad,
|
||||
#sharedvideo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&.use-new-toolbox {
|
||||
/**
|
||||
* Adjust the height of the filmstrip as the toolbar is displayed.
|
||||
*/
|
||||
.filmstrip {
|
||||
top: 0;
|
||||
transition: height .3s ease-in;
|
||||
|
||||
&.reduce-height {
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
align-items: flex-end;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
|
||||
* a library called popper which will position its elements fixed if
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
/**
|
||||
* Hide videos by making them slight to the right.
|
||||
*/
|
||||
.filmstrip__videos {
|
||||
right: 0;
|
||||
|
||||
&.hidden {
|
||||
bottom: auto;
|
||||
right: -196px;
|
||||
}
|
||||
|
||||
/**
|
||||
* An id selector is used to match id specificity with existing
|
||||
* filmstrip styles.
|
||||
*/
|
||||
&#remoteVideos {
|
||||
/**
|
||||
* Remove horizontal filmstrip padding used to prevent videos
|
||||
* from overlapping getting near the left-side of the screen.
|
||||
* An id selector is used to match id specificity with filmstrip
|
||||
* styles.
|
||||
*/
|
||||
padding-left: 0;
|
||||
transition: right 2s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video to better fit vertical filmstrip layout.
|
||||
*/
|
||||
#filmstripLocalVideo {
|
||||
align-self: initial;
|
||||
bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecssary padding that is normally used to prevent horizontal
|
||||
* filmstrip from overlapping the left edge of the screen.
|
||||
*/
|
||||
#filmstripLocalVideo,
|
||||
#filmstripRemoteVideos {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
justify-content: flex-end;
|
||||
|
||||
#filmstripRemoteVideosContainer {
|
||||
flex-direction: column-reverse;
|
||||
/**
|
||||
* Add padding as a hack for Firefox not to show scrollbars when
|
||||
* unnecessary.
|
||||
*/
|
||||
padding: 1px 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the hide filmstrip icon so it points towards the right edge
|
||||
* of the screen.
|
||||
*/
|
||||
&__toolbar {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the remote video menu trigger to the bottom left of the
|
||||
* video thumbnail.
|
||||
*/
|
||||
.remotevideomenu,
|
||||
.remote-video-menu-trigger {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: auto;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.remote-video-menu-trigger {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.videocontainer {
|
||||
/**
|
||||
* Move status icons to the bottom right of the thumbnail.
|
||||
*/
|
||||
&__toolbar {
|
||||
text-align: right;
|
||||
|
||||
.right {
|
||||
float: none;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.filmstrip-only {
|
||||
.filmstrip {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.filmstrip__videos-filmstripOnly {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These styles are for the video labels that display on the top right. The
|
||||
* styles adjust the labels' positioning as the filmstrip itself or
|
||||
* filmstrip's remote videos appear and disappear.
|
||||
*
|
||||
* The class with-filmstrip is for when the filmstrip is visible.
|
||||
* The class without-filmstrip is for when the filmstrip has been toggled to
|
||||
* be hidden.
|
||||
* The class opening is for when the filmstrip is transitioning from hidden
|
||||
* to visible.
|
||||
*/
|
||||
.large-video-labels {
|
||||
&.with-filmstrip {
|
||||
right: 150px;
|
||||
}
|
||||
|
||||
&.with-filmstrip.opening {
|
||||
transition: 0.9s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
&.without-filmstrip {
|
||||
transition: 1.2s ease-in-out;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply hardware acceleration to prevent flickering on scroll. The
|
||||
* selectors are specific to icon wrappers to prevent fixed position dialogs
|
||||
* and tooltips from getting a new location context due to translate3d.
|
||||
*/
|
||||
.connection-indicator,
|
||||
.remote-video-menu-trigger,
|
||||
.indicator-icon-container {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
float: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: disable pointer to allow any elements moved below to still be
|
||||
* clickable. The real fix would to make sure those moved elements are
|
||||
* actually part of the toolbar instead of positioning being faked.
|
||||
*/
|
||||
.videocontainer__toolbar {
|
||||
pointer-events: none;
|
||||
|
||||
> div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toolbar-icon {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workarounds for Edge, IE11, and Firefox not handling scrolling properly
|
||||
* with flex-direction: column-reverse. The remove videos in filmstrip should
|
||||
* start scrolling from the bottom of the filmstrip, but in those browsers the
|
||||
* scrolling won't happen. Per W3C spec, scrolling should happen from the
|
||||
* bottom. As such, use css hacks to get around the css issue, with the intent
|
||||
* being to remove the hacks as the spec is supported.
|
||||
*/
|
||||
@mixin undoColumnReverseVideos() {
|
||||
.vertical-filmstrip {
|
||||
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Firefox detection hack **/
|
||||
@-moz-document url-prefix() {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
|
||||
/** IE11 detection hack **/
|
||||
@media screen and (-ms-high-contrast: active),
|
||||
screen and (-ms-high-contrast: none) {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
|
||||
/** Edge detection hack **/
|
||||
@supports (-ms-ime-align:auto) {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#videospace {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
@@ -84,7 +86,7 @@
|
||||
* wrapper needed before we're able to move all top toolbar indicators
|
||||
* creation to react.
|
||||
*/
|
||||
.ckAJgx {
|
||||
.sc-ifAKCX {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
@@ -224,6 +226,11 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#largeVideoContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#largeVideoWrapper {
|
||||
box-shadow: 0 0 20px -2px #444;
|
||||
}
|
||||
@@ -550,30 +557,6 @@
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.noMic {
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
z-index: $zindex1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("../images/noMic.png");
|
||||
background-color: #000;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.noVideo {
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
z-index: $zindex1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("../images/noVideo.png");
|
||||
background-color: #000;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.videoMessageFilter {
|
||||
-webkit-filter: grayscale(.5) opacity(0.8);
|
||||
filter: grayscale(.5) opacity(0.8);
|
||||
@@ -620,7 +603,6 @@
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#remotePresenceMessage .no-presence,
|
||||
#remoteConnectionMessage {
|
||||
display: none;
|
||||
}
|
||||
@@ -642,3 +624,110 @@
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.display-avatar-with-name {
|
||||
.avatar-container {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name-on-black {
|
||||
.avatar-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
opacity: 0.2;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.display-video {
|
||||
.avatar-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name-on-video {
|
||||
.avatar-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.display-avatar-only {
|
||||
.avatar-container {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.displayNameContainer {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.videocontainer__hoverOverlay {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
video {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.presence-label {
|
||||
color: $participantNameColor;
|
||||
font-size: 12px;
|
||||
font-weight: 100;
|
||||
left: 0;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
top: calc(50% + 30px);
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,19 @@ body.welcome-page {
|
||||
}
|
||||
|
||||
.welcome {
|
||||
background-image: $welcomePageHeaderBackground;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $welcomePageFontFamily;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
background: $welcomePageHeaderBackground;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: fit-content;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
@@ -20,74 +24,148 @@ body.welcome-page {
|
||||
.header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
margin-top: 120px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: $watermarkHeight + 35;
|
||||
margin-bottom: 35px;
|
||||
max-width: calc(100% - 40px);
|
||||
min-height: 286px;
|
||||
width: 645px;
|
||||
width: 650px;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
.header-text-title {
|
||||
color: $welcomePageTitleColor;
|
||||
font-size: 48px;
|
||||
letter-spacing: -1px;
|
||||
line-height: 58px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.18;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.header-text-description {
|
||||
color: $welcomePageDescriptionColor;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.header-image {
|
||||
background-image: url(../images/welcome_page/curves.png);
|
||||
background-size: contain;
|
||||
height: 209px;
|
||||
position: absolute;
|
||||
width: 1070px;
|
||||
}
|
||||
|
||||
#new_enter_room {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
margin-bottom: 20px;
|
||||
max-width: calc(100% - 40px);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.enter-room-input {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
width: 350px;
|
||||
#enter_room {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: calc(100% - 40px);
|
||||
width: 680px;
|
||||
z-index: $zindex2;
|
||||
background-color: #fff;
|
||||
padding: 25px 30px;
|
||||
|
||||
.enter-room-input-container {
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
padding-bottom: 5px;
|
||||
text-align: left;
|
||||
color: #253858;
|
||||
height: fit-content;
|
||||
border-width: 0px 0px 2px 0px;
|
||||
border-style: solid;
|
||||
border-image: linear-gradient(to right, #dee1e6, #fff) 1;
|
||||
|
||||
.enter-room-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.enter-room-input {
|
||||
border: none;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: #253858;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
min-height: 354px;
|
||||
width: 710px;
|
||||
background: #75A7E7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tab-content{
|
||||
margin: 5px 0px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-direction: row;
|
||||
min-height: 54px;
|
||||
width: 100%;
|
||||
|
||||
.tab {
|
||||
text-align: center;
|
||||
background: rgba(9,30,66,0.37);
|
||||
height: 55px;
|
||||
line-height: 54px;
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected, &:hover {
|
||||
background: rgba(9,30,66,0.71);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-page-button {
|
||||
font-size: 16px;
|
||||
width: 51px;
|
||||
height: 35px;
|
||||
font-size: 14px;
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome.with-content {
|
||||
.header {
|
||||
min-height: 552px;
|
||||
}
|
||||
.header-image {
|
||||
left: -61px;
|
||||
top: 401px;
|
||||
}
|
||||
}
|
||||
.welcome-page-settings {
|
||||
color: $welcomePageDescriptionColor;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
right: 32px;
|
||||
z-index: $zindex2;
|
||||
|
||||
.welcome.without-content {
|
||||
.header {
|
||||
* {
|
||||
cursor: pointer;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-watermark {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.header-image {
|
||||
bottom: -20px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
31
css/filmstrip/_filmstrip_toolbar.scss
Normal file
31
css/filmstrip/_filmstrip_toolbar.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
.filmstrip__toolbar {
|
||||
@include flex();
|
||||
flex-direction: column-reverse;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
width: $filmstripToggleButtonWidth;
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
opacity: 0.7;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
-webkit-appearance: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
css/filmstrip/_horizontal_filmstrip.scss
Normal file
77
css/filmstrip/_horizontal_filmstrip.scss
Normal file
@@ -0,0 +1,77 @@
|
||||
%align-right {
|
||||
@include flex();
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.horizontal-filmstrip .filmstrip {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 10px 5px;
|
||||
@extend %align-right;
|
||||
transition: bottom .3s;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&.reduce-height {
|
||||
bottom: $newToolbarSizeWithPadding;
|
||||
}
|
||||
|
||||
&__videos {
|
||||
@extend %align-right;
|
||||
position:relative;
|
||||
padding: 0;
|
||||
/* The filmstrip should not be covered by the left toolbar. */
|
||||
bottom: 0;
|
||||
width:auto;
|
||||
overflow: visible !important;
|
||||
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: $defaultToolbarSize + 5;
|
||||
transition: bottom 2s;
|
||||
}
|
||||
|
||||
/**
|
||||
* The local video identifier.
|
||||
*/
|
||||
&#filmstripLocalVideo {
|
||||
align-self: flex-end;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the filmstrip videos in filmstrip-only mode.
|
||||
*/
|
||||
&__videos-filmstripOnly {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
.filmstrip__videos {
|
||||
&#filmstripLocalVideo {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
&.hide-videos {
|
||||
.remote-videos-container {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
css/filmstrip/_small_video.scss
Normal file
51
css/filmstrip/_small_video.scss
Normal file
@@ -0,0 +1,51 @@
|
||||
.filmstrip__videos .videocontainer {
|
||||
display: none;
|
||||
position: relative;
|
||||
background-size: contain;
|
||||
border: $thumbnailVideoBorder solid transparent;
|
||||
border-radius: $borderRadius;
|
||||
margin: 0 $thumbnailVideoMargin;
|
||||
|
||||
&.videoContainerFocused, &:hover {
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focused video thumbnail.
|
||||
*/
|
||||
&.videoContainerFocused {
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailSelected;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailSelected,
|
||||
0 0 3px $videoThumbnailSelected;
|
||||
}
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovered video thumbnail.
|
||||
*/
|
||||
&:hover:not(.videoContainerFocused):not(.active-speaker) {
|
||||
cursor: hand;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
|
||||
box-shadow: inset 0 0 3px $videoThumbnailHovered,
|
||||
0 0 3px $videoThumbnailHovered;
|
||||
|
||||
.remotevideomenu > .icon-menu {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
& > video {
|
||||
cursor: hand;
|
||||
border-radius: $borderRadius;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.presence-label {
|
||||
position: absolute;
|
||||
z-index: $zindex3;
|
||||
}
|
||||
}
|
||||
113
css/filmstrip/_tile_view.scss
Normal file
113
css/filmstrip/_tile_view.scss
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* CSS styles that are specific to the filmstrip that shows the thumbnail tiles.
|
||||
*/
|
||||
.tile-view {
|
||||
/**
|
||||
* Add a border around the active speaker to make the thumbnail easier to
|
||||
* see.
|
||||
*/
|
||||
.active-speaker {
|
||||
box-shadow: 0 0 5px 3px $videoThumbnailSelected
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.filmstrip__videos .videocontainer {
|
||||
&:not(.active-speaker),
|
||||
&:hover:not(.active-speaker) {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
/**
|
||||
* Height is modified with an inline style in horizontal filmstrip mode
|
||||
* so !important is used to override that.
|
||||
*/
|
||||
height: 100% !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filmstrip {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: $filmstripVideosZ
|
||||
}
|
||||
|
||||
/**
|
||||
* Regardless of the user setting, do not let the filmstrip be in a hidden
|
||||
* state.
|
||||
*/
|
||||
.filmstrip__videos.hidden {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
box-sizing: border-box;
|
||||
|
||||
/**
|
||||
* Allow vertical scrolling of the thumbnails.
|
||||
*/
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of the thumbnails should be set with javascript, based on
|
||||
* desired column count and window width. The rows are created using flex
|
||||
* and allowing the thumbnails to wrap.
|
||||
*/
|
||||
#filmstripRemoteVideosContainer {
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 100vh;
|
||||
justify-content: center;
|
||||
padding: 100px 0;
|
||||
|
||||
.videocontainer {
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
video {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.has-overflow#filmstripRemoteVideosContainer {
|
||||
align-content: baseline;
|
||||
}
|
||||
|
||||
.has-overflow .videocontainer {
|
||||
align-self: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Firefox flex acts a little differently. To make sure the bottom row of
|
||||
* thumbnails is not overlapped by the horizontal toolbar, margin is added
|
||||
* to the local thumbnail to keep it from the bottom of the screen. It is
|
||||
* assumed the local thumbnail will always be on the bottom row.
|
||||
*/
|
||||
.has-overflow #localVideoContainer {
|
||||
margin-bottom: 100px !important;
|
||||
}
|
||||
}
|
||||
47
css/filmstrip/_tile_view_overrides.scss
Normal file
47
css/filmstrip/_tile_view_overrides.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Various overrides outside of the filmstrip to style the app to support a
|
||||
* tiled thumbnail experience.
|
||||
*/
|
||||
.tile-view {
|
||||
/**
|
||||
* Let the avatar grow with the tile.
|
||||
*/
|
||||
.userAvatar {
|
||||
max-height: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide various features that should not be displayed while in tile view.
|
||||
*/
|
||||
#dominantSpeaker,
|
||||
#filmstripLocalVideoThumbnail,
|
||||
#largeVideoElementsContainer,
|
||||
#sharedVideo,
|
||||
.filmstrip__toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#localConnectionMessage,
|
||||
#remoteConnectionMessage,
|
||||
.watermark {
|
||||
z-index: $filmstripVideosZ + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The follow styling uses !important to override inline styles set with
|
||||
* javascript.
|
||||
*
|
||||
* TODO: These overrides should be more easy to remove and should be removed
|
||||
* when the components are in react so their rendering done declaratively,
|
||||
* making conditional styling easier to apply.
|
||||
*/
|
||||
#largeVideoElementsContainer,
|
||||
#remoteConnectionMessage,
|
||||
#remotePresenceMessage {
|
||||
display: none !important;
|
||||
}
|
||||
#largeVideoContainer {
|
||||
background-color: $defaultBackground !important;
|
||||
}
|
||||
}
|
||||
171
css/filmstrip/_vertical_filmstrip.scss
Normal file
171
css/filmstrip/_vertical_filmstrip.scss
Normal file
@@ -0,0 +1,171 @@
|
||||
.vertical-filmstrip .filmstrip {
|
||||
&.hide-videos {
|
||||
.remote-videos-container {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Firefox sets flex items to min-height: auto and min-width: auto,
|
||||
* preventing flex children from shrinking like they do on other browsers.
|
||||
* Setting min-height and min-width 0 is a workaround for the issue so
|
||||
* Firefox behaves like other browsers.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
|
||||
*/
|
||||
@mixin minHWAutoFix() {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@extend %align-right;
|
||||
align-items: flex-end;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 100%;
|
||||
padding: 10px 5px;
|
||||
/**
|
||||
* fixed positioning is necessary for remote menus and tooltips to pop
|
||||
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
|
||||
* a library called popper which will position its elements fixed if
|
||||
* any parent is also fixed.
|
||||
*/
|
||||
position: fixed;
|
||||
top: 0;
|
||||
transition: height .3s ease-in;
|
||||
right: 0;
|
||||
z-index: $filmstripVideosZ;
|
||||
|
||||
&.reduce-height {
|
||||
height: calc(100% - #{$newToolbarSizeWithPadding});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide videos by making them slight to the right.
|
||||
*/
|
||||
.filmstrip__videos {
|
||||
@extend %align-right;
|
||||
bottom: 0;
|
||||
overflow: visible !important;
|
||||
padding: 0;
|
||||
position:relative;
|
||||
right: 0;
|
||||
width: auto;
|
||||
|
||||
&.hidden {
|
||||
bottom: auto;
|
||||
right: -196px;
|
||||
}
|
||||
|
||||
/**
|
||||
* An id selector is used to match id specificity with existing
|
||||
* filmstrip styles.
|
||||
*/
|
||||
&#remoteVideos {
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
padding-left: 0;
|
||||
transition: right 2s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-styles the local Video to better fit vertical filmstrip layout.
|
||||
*/
|
||||
#filmstripLocalVideo {
|
||||
align-self: initial;
|
||||
bottom: 5px;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
height: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecssary padding that is normally used to prevent horizontal
|
||||
* filmstrip from overlapping the left edge of the screen.
|
||||
*/
|
||||
#filmstripLocalVideo,
|
||||
#filmstripRemoteVideos {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
justify-content: flex-end;
|
||||
|
||||
#filmstripRemoteVideosContainer {
|
||||
flex-direction: column-reverse;
|
||||
/**
|
||||
* Add padding as a hack for Firefox not to show scrollbars when
|
||||
* unnecessary.
|
||||
*/
|
||||
padding: 1px 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
#remoteVideos {
|
||||
@include minHWAutoFix();
|
||||
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.remote-videos-container {
|
||||
display: flex;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override other styles to support vertical filmstrip mode.
|
||||
*/
|
||||
.filmstrip-only .vertical-filmstrip {
|
||||
.filmstrip {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.filmstrip__videos-filmstripOnly {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.filmstrip__videos {
|
||||
&#filmstripLocalVideo {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workarounds for Edge and Firefox not handling scrolling properly with
|
||||
* flex-direction: column-reverse. The remove videos in filmstrip should
|
||||
* start scrolling from the bottom of the filmstrip, but in those browsers the
|
||||
* scrolling won't happen. Per W3C spec, scrolling should happen from the
|
||||
* bottom. As such, use css hacks to get around the css issue, with the intent
|
||||
* being to remove the hacks as the spec is supported.
|
||||
*/
|
||||
@mixin undoColumnReverseVideos() {
|
||||
.vertical-filmstrip {
|
||||
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Firefox detection hack **/
|
||||
@-moz-document url-prefix() {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
|
||||
/** Edge detection hack **/
|
||||
@supports (-ms-ime-align:auto) {
|
||||
@include undoColumnReverseVideos();
|
||||
}
|
||||
105
css/filmstrip/_vertical_filmstrip_overrides.scss
Normal file
105
css/filmstrip/_vertical_filmstrip_overrides.scss
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Rotate the hide filmstrip icon so it points towards the right edge
|
||||
* of the screen.
|
||||
*/
|
||||
.vertical-filmstrip .filmstrip__toolbar {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for video containers that should not be centered aligned to avoid=
|
||||
* clashing with the filmstrip.
|
||||
*/
|
||||
.vertical-filmstrip #etherpad,
|
||||
.vertical-filmstrip #sharedvideo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for small videos in vertical filmstrip mode.
|
||||
*/
|
||||
.vertical-filmstrip .filmstrip__videos .videocontainer {
|
||||
/**
|
||||
* Move status icons to the bottom right of the thumbnail.
|
||||
*/
|
||||
.videocontainer__toolbar {
|
||||
/**
|
||||
* FIXME: disable pointer to allow any elements moved below to still
|
||||
* be clickable. The real fix would to make sure those moved elements
|
||||
* are actually part of the toolbar instead of positioning being faked.
|
||||
*/
|
||||
pointer-events: none;
|
||||
text-align: right;
|
||||
|
||||
> div {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: none;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.toolbar-icon {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply hardware acceleration to prevent flickering on scroll. The
|
||||
* selectors are specific to icon wrappers to prevent fixed position dialogs
|
||||
* and tooltips from getting a new location context due to translate3d.
|
||||
*/
|
||||
.connection-indicator,
|
||||
.remote-video-menu-trigger,
|
||||
.indicator-icon-container {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
float: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the remote video menu trigger to the bottom left of the video
|
||||
* thumbnail.
|
||||
*/
|
||||
.remotevideomenu,
|
||||
.remote-video-menu-trigger {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: auto;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.remote-video-menu-trigger {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for quality labels in filmstrip only mode. The styles adjust the
|
||||
* labels' positioning as the filmstrip itself or filmstrip's remote videos
|
||||
* appear and disappear.
|
||||
*
|
||||
* The class with-filmstrip is for when the filmstrip is visible.
|
||||
* The class without-filmstrip is for when the filmstrip has been toggled to
|
||||
* be hidden.
|
||||
* The class opening is for when the filmstrip is transitioning from hidden
|
||||
* to visible.
|
||||
*/
|
||||
.vertical-filmstrip .large-video-labels {
|
||||
&.with-filmstrip {
|
||||
right: 150px;
|
||||
}
|
||||
|
||||
&.with-filmstrip.opening {
|
||||
transition: 0.9s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
&.without-filmstrip {
|
||||
transition: 1.2s ease-in-out;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,6 @@
|
||||
|
||||
/* Mixins END */
|
||||
|
||||
/* Animations BEGIN */
|
||||
|
||||
@import "animations";
|
||||
|
||||
/* Animations END */
|
||||
|
||||
/* Fonts BEGIN */
|
||||
@@ -31,6 +27,7 @@
|
||||
/* Modules BEGIN */
|
||||
|
||||
@import 'aui_reset';
|
||||
@import 'atlaskit_overrides';
|
||||
@import 'base';
|
||||
@import 'utils';
|
||||
@import 'overlay/overlay';
|
||||
@@ -41,8 +38,10 @@
|
||||
@import 'modals/dialog';
|
||||
@import 'modals/feedback/feedback';
|
||||
@import 'modals/invite/info';
|
||||
@import 'modals/settings/settings';
|
||||
@import 'modals/speaker_stats/speaker_stats';
|
||||
@import 'modals/video-quality/video-quality';
|
||||
@import 'modals/local-recording/local-recording';
|
||||
@import 'videolayout_default';
|
||||
@import 'notice';
|
||||
@import 'popup_menu';
|
||||
@@ -53,7 +52,6 @@
|
||||
@import 'welcome_page';
|
||||
@import 'welcome_page_content';
|
||||
@import 'toolbars';
|
||||
@import 'side_toolbar_container';
|
||||
@import 'jquery.contextMenu';
|
||||
@import 'keyboard-shortcuts';
|
||||
@import 'redirect_page';
|
||||
@@ -67,10 +65,20 @@
|
||||
@import '404';
|
||||
@import 'policy';
|
||||
@import 'popover';
|
||||
@import 'filmstrip';
|
||||
@import 'filmstrip/filmstrip_toolbar';
|
||||
@import 'filmstrip/horizontal_filmstrip';
|
||||
@import 'filmstrip/small_video';
|
||||
@import 'filmstrip/tile_view';
|
||||
@import 'filmstrip/tile_view_overrides';
|
||||
@import 'filmstrip/vertical_filmstrip';
|
||||
@import 'filmstrip/vertical_filmstrip_overrides';
|
||||
@import 'unsupported-browser/main';
|
||||
@import 'modals/invite/add-people';
|
||||
@import 'vertical_filmstrip_overrides';
|
||||
@import 'deep-linking/main';
|
||||
@import 'transcription-subtitles';
|
||||
@import '_meetings_list.scss';
|
||||
@import 'navigate_section_list';
|
||||
@import 'third-party-branding/google';
|
||||
@import 'third-party-branding/microsoft';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
@@ -154,14 +154,6 @@
|
||||
&-error {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override Atlaskit dropdown styling when in a modal because the dropdown
|
||||
* backgrounds clash with the modal backgrounds.
|
||||
*/
|
||||
.htclLc[data-role=droplistContent] {
|
||||
border: 1px solid #455166;
|
||||
}
|
||||
}
|
||||
.modal-dialog-footer {
|
||||
font-size: $modalButtonFontSize;
|
||||
|
||||
@@ -103,10 +103,14 @@
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: 4C9AFF;
|
||||
color: #2684FF;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #B3D4FF;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-input-preview {
|
||||
|
||||
@@ -23,6 +23,21 @@
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-text-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.footer-telephone-icon {
|
||||
display: flex;
|
||||
transform: scaleX(-1);
|
||||
padding-left: 10px;
|
||||
|
||||
i {
|
||||
line-height: 20px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
.use-new-toolbox {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-dialog {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
|
||||
.info-dialog-action-link {
|
||||
display: inline-block;
|
||||
@@ -129,11 +126,16 @@
|
||||
|
||||
.dial-in-page {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
padding: 25px;
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
|
||||
.dial-in-numbers-list {
|
||||
@@ -143,6 +145,7 @@
|
||||
|
||||
.dial-in-conference-id {
|
||||
text-align: center;
|
||||
min-width: 200px;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
92
css/modals/local-recording/_local-recording.scss
Normal file
92
css/modals/local-recording/_local-recording.scss
Normal file
@@ -0,0 +1,92 @@
|
||||
.localrec-participant-stats {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
|
||||
.localrec-participant-stats-item__status-dot {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
|
||||
&.status-on {
|
||||
background: green;
|
||||
}
|
||||
|
||||
&.status-off {
|
||||
background: gray;
|
||||
}
|
||||
|
||||
&.status-unknown {
|
||||
background: darkgoldenrod;
|
||||
}
|
||||
|
||||
&.status-error {
|
||||
background: darkred;
|
||||
}
|
||||
}
|
||||
|
||||
.localrec-participant-stats-item__status,
|
||||
.localrec-participant-stats-item__name,
|
||||
.localrec-participant-stats-item__sessionid {
|
||||
display: inline-block;
|
||||
margin: 5px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.localrec-participant-stats-item__status {
|
||||
width: 5%;
|
||||
}
|
||||
.localrec-participant-stats-item__name {
|
||||
width: 40%;
|
||||
}
|
||||
.localrec-participant-stats-item__sessionid {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.localrec-participant-stats-item__name,
|
||||
.localrec-participant-stats-item__sessionid {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.localrec-control-info-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.localrec-control-info-label:after {
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.localrec-control-action-link {
|
||||
display: inline-block;
|
||||
line-height: 1.5em;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.localrec-control-action-link:before {
|
||||
color: $linkFontColor;
|
||||
content: '\2022';
|
||||
font-size: 1.5em;
|
||||
padding: 0 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.localrec-control-action-link:first-child:before {
|
||||
content: '';
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.localrec-control-action-links {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
61
css/modals/settings/_settings.scss
Normal file
61
css/modals/settings/_settings.scss
Normal file
@@ -0,0 +1,61 @@
|
||||
.settings-pane {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
&.profile-pane {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.auth-name {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.calendar-tab,
|
||||
.device-selection {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.mock-atlaskit-label {
|
||||
color: #56637A;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.33;
|
||||
padding: 20px 0px 4px 0px;
|
||||
}
|
||||
|
||||
.calendar-tab,
|
||||
.more-tab,
|
||||
.profile-edit {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-edit-field,
|
||||
.settings-sub-pane {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.profile-edit-field {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.language-settings {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.calendar-tab {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
min-height: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar-tab-sign-in {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sign-out-cta {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,6 @@
|
||||
|
||||
.circular-label {
|
||||
color: white;
|
||||
font-family: -apple-system, BlinkMacSystemFont, $baseFontFamily;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
opacity: 0.8;
|
||||
@@ -169,6 +168,10 @@
|
||||
background: #FF5630;
|
||||
}
|
||||
|
||||
.circular-label.local-rec {
|
||||
background: #FF5630;
|
||||
}
|
||||
|
||||
.circular-label.stream {
|
||||
background: #0065FF;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
z-index: $ringingZ;
|
||||
background: linear-gradient(transparent, #000);
|
||||
opacity: 0.8;
|
||||
@include transparentBg(#283447, 0.95);
|
||||
|
||||
&.solidBG {
|
||||
background: $defaultBackground;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&__content {
|
||||
@@ -22,20 +20,26 @@
|
||||
top: 50%;
|
||||
margin-left: -200px;
|
||||
margin-top: -125px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #1B2638;
|
||||
}
|
||||
|
||||
&__caller-info {
|
||||
.mention {
|
||||
color: #333;
|
||||
}
|
||||
&__status{
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
32
css/third-party-branding/google.scss
Normal file
32
css/third-party-branding/google.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* The Google sign in button must follow Google's design guidelines.
|
||||
* See: https://developers.google.com/identity/branding-guidelines
|
||||
*/
|
||||
.google-sign-in {
|
||||
background-color: #4285f4;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: Roboto, arial, sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 1px;
|
||||
|
||||
.google-cta {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
/**
|
||||
* Hack the line height for vertical centering of text.
|
||||
*/
|
||||
line-height: 32px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.google-logo {
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
28
css/third-party-branding/microsoft.scss
Normal file
28
css/third-party-branding/microsoft.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* The Microsoft sign in button must follow Microsoft's brand guidelines.
|
||||
* See: https://docs.microsoft.com/en-us/azure/active-directory/
|
||||
* develop/active-directory-branding-guidelines
|
||||
*/
|
||||
.microsoft-sign-in {
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #8C8C8C;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-family: Segoe UI, Roboto, arial, sans-serif;
|
||||
height: 41px;
|
||||
padding: 12px;
|
||||
|
||||
.microsoft-cta {
|
||||
display: inline-block;
|
||||
color: #5E5E5E;
|
||||
font-size: 15px;
|
||||
line-height: 41px;
|
||||
}
|
||||
|
||||
.microsoft-logo {
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
14
doc/api.md
14
doc/api.md
@@ -194,6 +194,15 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **emailChange** - event notifications about email
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his email
|
||||
"email": email // the new email
|
||||
}
|
||||
```
|
||||
|
||||
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
@@ -290,6 +299,11 @@ You can get the display name of a participant in the conference with the followi
|
||||
var displayName = api.getDisplayName(participantId);
|
||||
```
|
||||
|
||||
You can get the email of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var email = api.getEmail(participantId);
|
||||
```
|
||||
|
||||
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
|
||||
```javascript
|
||||
var iframe = api.getIFrame();
|
||||
|
||||
@@ -13,7 +13,7 @@ There are also some complete [example config files](https://github.com/jitsi/jit
|
||||
|
||||
## Network description
|
||||
|
||||
This how the network look like:
|
||||
This is how the network looks:
|
||||
```
|
||||
+ +
|
||||
| |
|
||||
@@ -135,7 +135,8 @@ server {
|
||||
location / {
|
||||
ssi on;
|
||||
}
|
||||
# BOSH
|
||||
# BOSH, Bidirectional-streams Over Synchronous HTTP
|
||||
# https://en.wikipedia.org/wiki/BOSH_(protocol)
|
||||
location /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
28
doc/mobile-dropbox.md
Normal file
28
doc/mobile-dropbox.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Setting up Dropbox integration
|
||||
1. Create a Dropbox app.
|
||||
2. Add the following to ```ios/app/src/Info.plist``` by replacing `<APP_KEY>`
|
||||
with your own Dropbox app key (which can be found in the
|
||||
[App Console](https://www.dropbox.com/developers/apps)):
|
||||
```
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>db-<APP_KEY></string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>dbapi-2</string>
|
||||
<string>dbapi-8-emm</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
**NOTE:** Both Android and iOS builds of the apps will parse the Dropbox app key
|
||||
from ```ios/app/src/Info.plist```.
|
||||
|
||||
**NOTE:** See [Dropbox developer guide](https://www.dropbox.com/developers/reference/developer-guide) for more information
|
||||
22
doc/mobile-google-auth.md
Normal file
22
doc/mobile-google-auth.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Setting up Google Authentication
|
||||
|
||||
- Create a Firebase project here: https://firebase.google.com/. You'll need a
|
||||
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
|
||||
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
|
||||
the ```.gitignore``` of the application itself!).
|
||||
- Your web client ID is auto generated during the Firebase project
|
||||
creation. Find them in the Google Developer console
|
||||
(https://console.developers.google.com/)
|
||||
- Make sure your config reflects this ID by setting
|
||||
```googleApiApplicationClientID``` in config.js.
|
||||
- Add your iOS client ID (the REVERSED_CLIENT_ID in the plist file) as an
|
||||
application URL schema into ```ios/app/src/Info.plist```
|
||||
(replacing placeholder).
|
||||
- Enable YouTube API access on the developer console (see above) to enable live
|
||||
streaming.
|
||||
@@ -4,7 +4,13 @@ This document describes the required steps for a quick Jitsi Meet installation o
|
||||
|
||||
Debian Wheezy and other older systems may require additional things to be done. Specifically for Wheezy, [libc needs to be updated](http://lists.jitsi.org/pipermail/users/2015-September/010064.html).
|
||||
|
||||
N.B.: All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
|
||||
Also note that a recent default Ubuntu installation has only the `main` repository enabled, and Jitsi Meet needs packages from `universe`. Check your `/etc/apt/sources.list` file, and if `universe` is not present refer to [Ubuntu's documentation](https://help.ubuntu.com/community/Repositories/Ubuntu) on how to enable it. (Usually it amounts to copying the `main` lines and changing to `universe`.)
|
||||
|
||||
N.B.:
|
||||
|
||||
a.) All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
|
||||
|
||||
b.) You only need to do this if you want to ___host your own Jitsi server___. If you just want to have a video conference with someone, use https://meet.jit.si instead.
|
||||
|
||||
## Basic Jitsi Meet install
|
||||
|
||||
@@ -59,6 +65,17 @@ org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>
|
||||
See [the documenation of ice4j](https://github.com/jitsi/ice4j/blob/master/doc/configuration.md)
|
||||
for details.
|
||||
|
||||
Default deployments on systems using systemd will have low default values for maximum processes and open files. If the used bridge will expect higher number of participants the default values need to be adjusted (the default values are good for less than 100 participants).
|
||||
To update the values edit `/etc/systemd/system.conf` and make sure you have the following values:
|
||||
```
|
||||
DefaultLimitNOFILE=65000
|
||||
DefaultLimitNPROC=65000
|
||||
DefaultTasksMax=65000
|
||||
```
|
||||
To load the values and check them look [here](#systemd-details) for details.
|
||||
|
||||
By default, anyone who has access to your jitsi instance will be able to start a conferencee: if your server is open to the world, anyone can have a chat with anyone else. If you want to limit the ability to start a conference to registered users, set up a "secure domain". Follow the instructions at https://github.com/jitsi/jicofo#secure-domain.
|
||||
|
||||
### Open a conference
|
||||
|
||||
Launch a web browser (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
|
||||
@@ -103,3 +120,12 @@ Sometimes the following packages will fail to uninstall properly:
|
||||
When this happens, just run the uninstall command a second time and it should be ok.
|
||||
|
||||
The reason for failure is that sometimes, the uninstall script is faster than the process that stops the daemons. The second run of the uninstall command fixes this, as by then the jigasi or jvb daemons are already stopped.
|
||||
|
||||
#### Systemd details
|
||||
To reload the systemd changes on a running system execute `systemctl daemon-reload` and `service jitsi-videobridge restart`.
|
||||
To check the tasks part execute `service jitsi-videobridge status` and you should see `Tasks: XX (limit: 65000)`.
|
||||
To check the files and process part execute ```cat /proc/`cat /var/run/jitsi-videobridge.pid`/limits``` and you should see:
|
||||
```
|
||||
Max processes 65000 65000 processes
|
||||
Max open files 65000 65000 files
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
One-to-one calls should avoid going throught the JVB for optimal performance and for optimal resource usage. This is why we've added the peer-to-peer mode where the two participants connect directly to each other. Unfortunately, a direct connection is not always possible between the participants. In those cases you can use a TURN server to relay the traffic (n.b. the JVB does much more than just relay the traffic, so this is not the same as using the JVB to "relay" the traffic).
|
||||
|
||||
This document describes how to enable TURN server support in one-to-one calls in Jitsi Meet. Even tho it gives some hints how to configure [prosody](prosody.im) and [coTURN](https://github.com/coturn/coturn), it assumes a properly configured TURN server and a proprely configured XMPP server.
|
||||
This document describes how to enable TURN server support in one-to-one calls in Jitsi Meet, even though it gives some hints how to configure [prosody](prosody.im) and [coTURN](https://github.com/coturn/coturn), it assumes a properly configured TURN server and a proprely configured XMPP server.
|
||||
|
||||
One way to configure TURN support in meet with a static configuration. You can simply fill out the `p2p.stunServers` option with appropriate values, e.g.:
|
||||
|
||||
|
||||
3686
flow-typed/npm/jquery_v3.x.x.js
vendored
Normal file
3686
flow-typed/npm/jquery_v3.x.x.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2568
flow-typed/npm/lodash_v4.x.x.js
vendored
2568
flow-typed/npm/lodash_v4.x.x.js
vendored
File diff suppressed because it is too large
Load Diff
329
flow-typed/npm/moment_v2.3.x.js
vendored
Normal file
329
flow-typed/npm/moment_v2.3.x.js
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
// flow-typed signature: c30aa20539f52183d4d30dd36d8ab9c2
|
||||
// flow-typed version: 886cf7c002/moment_v2.3.x/flow_>=v0.63.x
|
||||
|
||||
type moment$MomentOptions = {
|
||||
y?: number | string,
|
||||
year?: number | string,
|
||||
years?: number | string,
|
||||
M?: number | string,
|
||||
month?: number | string,
|
||||
months?: number | string,
|
||||
d?: number | string,
|
||||
day?: number | string,
|
||||
days?: number | string,
|
||||
date?: number | string,
|
||||
h?: number | string,
|
||||
hour?: number | string,
|
||||
hours?: number | string,
|
||||
m?: number | string,
|
||||
minute?: number | string,
|
||||
minutes?: number | string,
|
||||
s?: number | string,
|
||||
second?: number | string,
|
||||
seconds?: number | string,
|
||||
ms?: number | string,
|
||||
millisecond?: number | string,
|
||||
milliseconds?: number | string
|
||||
};
|
||||
|
||||
type moment$MomentObject = {
|
||||
years: number,
|
||||
months: number,
|
||||
date: number,
|
||||
hours: number,
|
||||
minutes: number,
|
||||
seconds: number,
|
||||
milliseconds: number
|
||||
};
|
||||
|
||||
type moment$MomentCreationData = {
|
||||
input: string,
|
||||
format: string,
|
||||
locale: Object,
|
||||
isUTC: boolean,
|
||||
strict: boolean
|
||||
};
|
||||
|
||||
type moment$CalendarFormat = string | ((moment: moment$Moment) => string);
|
||||
|
||||
type moment$CalendarFormats = {
|
||||
sameDay?: moment$CalendarFormat,
|
||||
nextDay?: moment$CalendarFormat,
|
||||
nextWeek?: moment$CalendarFormat,
|
||||
lastDay?: moment$CalendarFormat,
|
||||
lastWeek?: moment$CalendarFormat,
|
||||
sameElse?: moment$CalendarFormat
|
||||
};
|
||||
|
||||
declare class moment$LocaleData {
|
||||
months(moment: moment$Moment): string,
|
||||
monthsShort(moment: moment$Moment): string,
|
||||
monthsParse(month: string): number,
|
||||
weekdays(moment: moment$Moment): string,
|
||||
weekdaysShort(moment: moment$Moment): string,
|
||||
weekdaysMin(moment: moment$Moment): string,
|
||||
weekdaysParse(weekDay: string): number,
|
||||
longDateFormat(dateFormat: string): string,
|
||||
isPM(date: string): boolean,
|
||||
meridiem(hours: number, minutes: number, isLower: boolean): string,
|
||||
calendar(
|
||||
key:
|
||||
| "sameDay"
|
||||
| "nextDay"
|
||||
| "lastDay"
|
||||
| "nextWeek"
|
||||
| "prevWeek"
|
||||
| "sameElse",
|
||||
moment: moment$Moment
|
||||
): string,
|
||||
relativeTime(
|
||||
number: number,
|
||||
withoutSuffix: boolean,
|
||||
key: "s" | "m" | "mm" | "h" | "hh" | "d" | "dd" | "M" | "MM" | "y" | "yy",
|
||||
isFuture: boolean
|
||||
): string,
|
||||
pastFuture(diff: any, relTime: string): string,
|
||||
ordinal(number: number): string,
|
||||
preparse(str: string): any,
|
||||
postformat(str: string): any,
|
||||
week(moment: moment$Moment): string,
|
||||
invalidDate(): string,
|
||||
firstDayOfWeek(): number,
|
||||
firstDayOfYear(): number
|
||||
}
|
||||
declare class moment$MomentDuration {
|
||||
humanize(suffix?: boolean): string,
|
||||
milliseconds(): number,
|
||||
asMilliseconds(): number,
|
||||
seconds(): number,
|
||||
asSeconds(): number,
|
||||
minutes(): number,
|
||||
asMinutes(): number,
|
||||
hours(): number,
|
||||
asHours(): number,
|
||||
days(): number,
|
||||
asDays(): number,
|
||||
months(): number,
|
||||
asMonths(): number,
|
||||
years(): number,
|
||||
asYears(): number,
|
||||
add(value: number | moment$MomentDuration | Object, unit?: string): this,
|
||||
subtract(value: number | moment$MomentDuration | Object, unit?: string): this,
|
||||
as(unit: string): number,
|
||||
get(unit: string): number,
|
||||
toJSON(): string,
|
||||
toISOString(): string,
|
||||
isValid(): boolean
|
||||
}
|
||||
declare class moment$Moment {
|
||||
static ISO_8601: string,
|
||||
static (
|
||||
string?: string,
|
||||
format?: string | Array<string>,
|
||||
strict?: boolean
|
||||
): moment$Moment,
|
||||
static (
|
||||
string?: string,
|
||||
format?: string | Array<string>,
|
||||
locale?: string,
|
||||
strict?: boolean
|
||||
): moment$Moment,
|
||||
static (
|
||||
initDate: ?Object | number | Date | Array<number> | moment$Moment | string
|
||||
): moment$Moment,
|
||||
static unix(seconds: number): moment$Moment,
|
||||
static utc(): moment$Moment,
|
||||
static utc(number: number | Array<number>): moment$Moment,
|
||||
static utc(
|
||||
str: string,
|
||||
str2?: string | Array<string>,
|
||||
str3?: string
|
||||
): moment$Moment,
|
||||
static utc(moment: moment$Moment): moment$Moment,
|
||||
static utc(date: Date): moment$Moment,
|
||||
static parseZone(): moment$Moment,
|
||||
static parseZone(rawDate: string): moment$Moment,
|
||||
static parseZone(
|
||||
rawDate: string,
|
||||
format: string | Array<string>
|
||||
): moment$Moment,
|
||||
static parseZone(
|
||||
rawDate: string,
|
||||
format: string,
|
||||
strict: boolean
|
||||
): moment$Moment,
|
||||
static parseZone(
|
||||
rawDate: string,
|
||||
format: string,
|
||||
locale: string,
|
||||
strict: boolean
|
||||
): moment$Moment,
|
||||
isValid(): boolean,
|
||||
invalidAt(): 0 | 1 | 2 | 3 | 4 | 5 | 6,
|
||||
creationData(): moment$MomentCreationData,
|
||||
millisecond(number: number): this,
|
||||
milliseconds(number: number): this,
|
||||
millisecond(): number,
|
||||
milliseconds(): number,
|
||||
second(number: number): this,
|
||||
seconds(number: number): this,
|
||||
second(): number,
|
||||
seconds(): number,
|
||||
minute(number: number): this,
|
||||
minutes(number: number): this,
|
||||
minute(): number,
|
||||
minutes(): number,
|
||||
hour(number: number): this,
|
||||
hours(number: number): this,
|
||||
hour(): number,
|
||||
hours(): number,
|
||||
date(number: number): this,
|
||||
dates(number: number): this,
|
||||
date(): number,
|
||||
dates(): number,
|
||||
day(day: number | string): this,
|
||||
days(day: number | string): this,
|
||||
day(): number,
|
||||
days(): number,
|
||||
weekday(number: number): this,
|
||||
weekday(): number,
|
||||
isoWeekday(number: number): this,
|
||||
isoWeekday(): number,
|
||||
dayOfYear(number: number): this,
|
||||
dayOfYear(): number,
|
||||
week(number: number): this,
|
||||
weeks(number: number): this,
|
||||
week(): number,
|
||||
weeks(): number,
|
||||
isoWeek(number: number): this,
|
||||
isoWeeks(number: number): this,
|
||||
isoWeek(): number,
|
||||
isoWeeks(): number,
|
||||
month(number: number): this,
|
||||
months(number: number): this,
|
||||
month(): number,
|
||||
months(): number,
|
||||
quarter(number: number): this,
|
||||
quarter(): number,
|
||||
year(number: number): this,
|
||||
years(number: number): this,
|
||||
year(): number,
|
||||
years(): number,
|
||||
weekYear(number: number): this,
|
||||
weekYear(): number,
|
||||
isoWeekYear(number: number): this,
|
||||
isoWeekYear(): number,
|
||||
weeksInYear(): number,
|
||||
isoWeeksInYear(): number,
|
||||
get(string: string): number,
|
||||
set(unit: string, value: number): this,
|
||||
set(options: { [unit: string]: number }): this,
|
||||
static max(...dates: Array<moment$Moment>): moment$Moment,
|
||||
static max(dates: Array<moment$Moment>): moment$Moment,
|
||||
static min(...dates: Array<moment$Moment>): moment$Moment,
|
||||
static min(dates: Array<moment$Moment>): moment$Moment,
|
||||
add(
|
||||
value: number | moment$MomentDuration | moment$Moment | Object,
|
||||
unit?: string
|
||||
): this,
|
||||
subtract(
|
||||
value: number | moment$MomentDuration | moment$Moment | string | Object,
|
||||
unit?: string
|
||||
): this,
|
||||
startOf(unit: string): this,
|
||||
endOf(unit: string): this,
|
||||
local(): this,
|
||||
utc(): this,
|
||||
utcOffset(
|
||||
offset: number | string,
|
||||
keepLocalTime?: boolean,
|
||||
keepMinutes?: boolean
|
||||
): this,
|
||||
utcOffset(): number,
|
||||
format(format?: string): string,
|
||||
fromNow(removeSuffix?: boolean): string,
|
||||
from(
|
||||
value: moment$Moment | string | number | Date | Array<number>,
|
||||
removePrefix?: boolean
|
||||
): string,
|
||||
toNow(removePrefix?: boolean): string,
|
||||
to(
|
||||
value: moment$Moment | string | number | Date | Array<number>,
|
||||
removePrefix?: boolean
|
||||
): string,
|
||||
calendar(refTime?: any, formats?: moment$CalendarFormats): string,
|
||||
diff(
|
||||
date: moment$Moment | string | number | Date | Array<number>,
|
||||
format?: string,
|
||||
floating?: boolean
|
||||
): number,
|
||||
valueOf(): number,
|
||||
unix(): number,
|
||||
daysInMonth(): number,
|
||||
toDate(): Date,
|
||||
toArray(): Array<number>,
|
||||
toJSON(): string,
|
||||
toISOString(
|
||||
keepOffset?: boolean
|
||||
): string,
|
||||
toObject(): moment$MomentObject,
|
||||
isBefore(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isSame(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isAfter(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isSameOrBefore(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isSameOrAfter(
|
||||
date?: moment$Moment | string | number | Date | Array<number>,
|
||||
units?: ?string
|
||||
): boolean,
|
||||
isBetween(
|
||||
fromDate: moment$Moment | string | number | Date | Array<number>,
|
||||
toDate?: ?moment$Moment | string | number | Date | Array<number>,
|
||||
granularity?: ?string,
|
||||
inclusion?: ?string
|
||||
): boolean,
|
||||
isDST(): boolean,
|
||||
isDSTShifted(): boolean,
|
||||
isLeapYear(): boolean,
|
||||
clone(): moment$Moment,
|
||||
static isMoment(obj: any): boolean,
|
||||
static isDate(obj: any): boolean,
|
||||
static locale(locale: string, localeData?: Object): string,
|
||||
static updateLocale(locale: string, localeData?: ?Object): void,
|
||||
static locale(locales: Array<string>): string,
|
||||
locale(locale: string, customization?: Object | null): moment$Moment,
|
||||
locale(): string,
|
||||
static months(): Array<string>,
|
||||
static monthsShort(): Array<string>,
|
||||
static weekdays(): Array<string>,
|
||||
static weekdaysShort(): Array<string>,
|
||||
static weekdaysMin(): Array<string>,
|
||||
static months(): string,
|
||||
static monthsShort(): string,
|
||||
static weekdays(): string,
|
||||
static weekdaysShort(): string,
|
||||
static weekdaysMin(): string,
|
||||
static localeData(key?: string): moment$LocaleData,
|
||||
static duration(
|
||||
value: number | Object | string,
|
||||
unit?: string
|
||||
): moment$MomentDuration,
|
||||
static isDuration(obj: any): boolean,
|
||||
static normalizeUnits(unit: string): string,
|
||||
static invalid(object: any): moment$Moment
|
||||
}
|
||||
|
||||
declare module "moment" {
|
||||
declare module.exports: Class<moment$Moment>;
|
||||
}
|
||||
5
flow-typed/npm/prop-types_v15.x.x.js
vendored
5
flow-typed/npm/prop-types_v15.x.x.js
vendored
@@ -1,5 +1,5 @@
|
||||
// flow-typed signature: 3eaa1f24c7397b78a7481992d2cddcb2
|
||||
// flow-typed version: a1a20d4928/prop-types_v15.x.x/flow_>=v0.41.x
|
||||
// flow-typed signature: d9a983bb1ac458a256c31c139047bdbb
|
||||
// flow-typed version: 927687984d/prop-types_v15.x.x/flow_>=v0.41.x
|
||||
|
||||
type $npm$propTypes$ReactPropsCheckType = (
|
||||
props: any,
|
||||
@@ -14,6 +14,7 @@ declare module 'prop-types' {
|
||||
declare var number: React$PropType$Primitive<number>;
|
||||
declare var object: React$PropType$Primitive<Object>;
|
||||
declare var string: React$PropType$Primitive<string>;
|
||||
declare var symbol: React$PropType$Primitive<Symbol>;
|
||||
declare var any: React$PropType$Primitive<any>;
|
||||
declare var arrayOf: React$PropType$ArrayOf;
|
||||
declare var element: React$PropType$Primitive<any>; /* TODO */
|
||||
|
||||
292
flow-typed/npm/react-redux_v5.x.x.js
vendored
292
flow-typed/npm/react-redux_v5.x.x.js
vendored
@@ -1,132 +1,192 @@
|
||||
// flow-typed signature: 59b0c4be0e1408f21e2446be96c79804
|
||||
// flow-typed version: 9092387fd2/react-redux_v5.x.x/flow_>=v0.54.x
|
||||
// flow-typed signature: d4e793bc07ef1dc9906a244b12960f7b
|
||||
// flow-typed version: cf33ff8762/react-redux_v5.x.x/flow_>=v0.63.0
|
||||
|
||||
import type { Dispatch, Store } from "redux";
|
||||
|
||||
declare module "react-redux" {
|
||||
/*
|
||||
import type { ComponentType, ElementConfig } from 'react';
|
||||
|
||||
S = State
|
||||
A = Action
|
||||
OP = OwnProps
|
||||
SP = StateProps
|
||||
DP = DispatchProps
|
||||
|
||||
*/
|
||||
|
||||
declare type MapStateToProps<S, OP: Object, SP: Object> = (
|
||||
state: S,
|
||||
ownProps: OP
|
||||
) => ((state: S, ownProps: OP) => SP) | SP;
|
||||
|
||||
declare type MapDispatchToProps<A, OP: Object, DP: Object> =
|
||||
| ((dispatch: Dispatch<A>, ownProps: OP) => DP)
|
||||
| DP;
|
||||
|
||||
declare type MergeProps<SP, DP: Object, OP: Object, P: Object> = (
|
||||
stateProps: SP,
|
||||
dispatchProps: DP,
|
||||
ownProps: OP
|
||||
) => P;
|
||||
|
||||
declare type Context = { store: Store<*, *> };
|
||||
|
||||
declare type ComponentWithDefaultProps<DP: {}, P: {}, CP: P> = Class<
|
||||
React$Component<CP>
|
||||
> & { defaultProps: DP };
|
||||
|
||||
declare class ConnectedComponentWithDefaultProps<
|
||||
OP,
|
||||
DP,
|
||||
CP
|
||||
> extends React$Component<OP> {
|
||||
static defaultProps: DP, // <= workaround for https://github.com/facebook/flow/issues/4644
|
||||
static WrappedComponent: Class<React$Component<CP>>,
|
||||
getWrappedInstance(): React$Component<CP>,
|
||||
props: OP,
|
||||
state: void
|
||||
}
|
||||
|
||||
declare class ConnectedComponent<OP, P> extends React$Component<OP> {
|
||||
static WrappedComponent: Class<React$Component<P>>,
|
||||
getWrappedInstance(): React$Component<P>,
|
||||
props: OP,
|
||||
state: void
|
||||
}
|
||||
|
||||
declare type ConnectedComponentWithDefaultPropsClass<OP, DP, CP> = Class<
|
||||
ConnectedComponentWithDefaultProps<OP, DP, CP>
|
||||
>;
|
||||
|
||||
declare type ConnectedComponentClass<OP, P> = Class<
|
||||
ConnectedComponent<OP, P>
|
||||
>;
|
||||
|
||||
declare type Connector<OP, P> = (<DP: {}, CP: {}>(
|
||||
component: ComponentWithDefaultProps<DP, P, CP>
|
||||
) => ConnectedComponentWithDefaultPropsClass<OP, DP, CP>) &
|
||||
((component: React$ComponentType<P>) => ConnectedComponentClass<OP, P>);
|
||||
|
||||
declare class Provider<S, A> extends React$Component<{
|
||||
declare export class Provider<S, A> extends React$Component<{
|
||||
store: Store<S, A>,
|
||||
children?: any
|
||||
}> {}
|
||||
|
||||
declare function createProvider(
|
||||
declare export function createProvider(
|
||||
storeKey?: string,
|
||||
subKey?: string
|
||||
): Provider<*, *>;
|
||||
|
||||
declare type ConnectOptions = {
|
||||
/*
|
||||
|
||||
S = State
|
||||
A = Action
|
||||
OP = OwnProps
|
||||
SP = StateProps
|
||||
DP = DispatchProps
|
||||
MP = Merge props
|
||||
MDP = Map dispatch to props object
|
||||
RSP = Returned state props
|
||||
RDP = Returned dispatch props
|
||||
RMP = Returned merge props
|
||||
CP = Props for returned component
|
||||
Com = React Component
|
||||
*/
|
||||
|
||||
declare type MapStateToProps<S: Object, SP: Object, RSP: Object> = (state: S, props: SP) => RSP;
|
||||
|
||||
declare type MapDispatchToProps<A, OP: Object, RDP: Object> = (dispatch: Dispatch<A>, ownProps: OP) => RDP;
|
||||
|
||||
declare type MergeProps<SP: Object, DP: Object, MP: Object, RMP: Object> = (
|
||||
stateProps: SP,
|
||||
dispatchProps: DP,
|
||||
ownProps: MP
|
||||
) => RMP;
|
||||
|
||||
declare type ConnectOptions<S: Object, OP: Object, RSP: Object, RMP: Object> = {|
|
||||
pure?: boolean,
|
||||
withRef?: boolean
|
||||
withRef?: boolean,
|
||||
areStatesEqual?: (next: S, prev: S) => boolean,
|
||||
areOwnPropsEqual?: (next: OP, prev: OP) => boolean,
|
||||
areStatePropsEqual?: (next: RSP, prev: RSP) => boolean,
|
||||
areMergedPropsEqual?: (next: RMP, prev: RMP) => boolean,
|
||||
storeKey?: string
|
||||
|};
|
||||
|
||||
declare type OmitDispatch<Component> = $Diff<Component, {dispatch: Dispatch<*>}>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
RSP: Object,
|
||||
CP: $Diff<OmitDispatch<ElementConfig<Com>>, RSP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, DP, RSP>,
|
||||
mapDispatchToProps?: null
|
||||
): (component: Com) => ComponentType<CP & DP>;
|
||||
|
||||
declare export function connect<Com: ComponentType<*>>(
|
||||
mapStateToProps?: null,
|
||||
mapDispatchToProps?: null
|
||||
): (component: Com) => ComponentType<OmitDispatch<ElementConfig<Com>>>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
CP: $Diff<$Diff<ElementConfig<Com>, RSP>, RDP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, DP, RDP>
|
||||
): (component: Com) => ComponentType<CP & SP & DP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
OP: Object,
|
||||
DP: Object,
|
||||
PR: Object,
|
||||
CP: $Diff<ElementConfig<Com>, DP>
|
||||
>(
|
||||
mapStateToProps?: null,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP>
|
||||
): (Com) => ComponentType<CP & OP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
MDP: Object
|
||||
>(
|
||||
mapStateToProps?: null,
|
||||
mapDispatchToProps: MDP
|
||||
): (component: Com) => ComponentType<$Diff<ElementConfig<Com>, MDP>>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
S: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
MDP: Object,
|
||||
CP: $Diff<ElementConfig<Com>, RSP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToPRops: MDP
|
||||
): (component: Com) => ComponentType<$Diff<CP, MDP> & SP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object,
|
||||
CP: $Diff<ElementConfig<Com>, RMP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: ?MapDispatchToProps<A, DP, RDP>,
|
||||
mergeProps: MergeProps<RSP, RDP, MP, RMP>
|
||||
): (component: Com) => ComponentType<CP & SP & DP & MP>;
|
||||
|
||||
declare export function connect<
|
||||
Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object,
|
||||
CP: $Diff<ElementConfig<Com>, RMP>
|
||||
>(
|
||||
mapStateToProps: MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: MDP,
|
||||
mergeProps: MergeProps<RSP, RDP, MP, RMP>
|
||||
): (component: Com) => ComponentType<CP & SP & DP & MP>;
|
||||
|
||||
declare export function connect<Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object
|
||||
>(
|
||||
mapStateToProps: ?MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: ?MapDispatchToProps<A, DP, RDP>,
|
||||
mergeProps: ?MergeProps<RSP, RDP, MP, RMP>,
|
||||
options: ConnectOptions<S, SP & DP & MP, RSP, RMP>
|
||||
): (component: Com) => ComponentType<$Diff<ElementConfig<Com>, RMP> & SP & DP & MP>;
|
||||
|
||||
declare export function connect<Com: ComponentType<*>,
|
||||
A,
|
||||
S: Object,
|
||||
DP: Object,
|
||||
SP: Object,
|
||||
RSP: Object,
|
||||
RDP: Object,
|
||||
MDP: Object,
|
||||
MP: Object,
|
||||
RMP: Object
|
||||
>(
|
||||
mapStateToProps: ?MapStateToProps<S, SP, RSP>,
|
||||
mapDispatchToProps: ?MapDispatchToProps<A, DP, RDP>,
|
||||
mergeProps: MDP,
|
||||
options: ConnectOptions<S, SP & DP & MP, RSP, RMP>
|
||||
): (component: Com) => ComponentType<$Diff<ElementConfig<Com>, RMP> & SP & DP & MP>;
|
||||
|
||||
declare export default {
|
||||
Provider: typeof Provider,
|
||||
createProvider: typeof createProvider,
|
||||
connect: typeof connect,
|
||||
};
|
||||
|
||||
declare type Null = null | void;
|
||||
|
||||
declare function connect<A, OP>(
|
||||
...rest: Array<void> // <= workaround for https://github.com/facebook/flow/issues/2360
|
||||
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
|
||||
|
||||
declare function connect<A, OP>(
|
||||
mapStateToProps: Null,
|
||||
mapDispatchToProps: Null,
|
||||
mergeProps: Null,
|
||||
options: ConnectOptions
|
||||
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>;
|
||||
|
||||
// declare function connect<S, A, OP, SP>(
|
||||
// mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
// mapDispatchToProps: Null,
|
||||
// mergeProps: Null,
|
||||
// options?: ConnectOptions
|
||||
// ): Connector<OP, $Supertype<SP & { dispatch: Dispatch<A> } & OP>>;
|
||||
|
||||
declare function connect<A, OP, DP>(
|
||||
mapStateToProps: Null,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
|
||||
mergeProps: Null,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, $Supertype<DP & OP>>;
|
||||
|
||||
declare function connect<S, A, OP, SP, DP>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP> | Null,
|
||||
mergeProps: Null,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, $Supertype<SP & DP & OP>>;
|
||||
|
||||
declare function connect<S, A, OP, SP, DP, P>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: Null,
|
||||
mergeProps: MergeProps<SP, DP, OP, P>,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, P>;
|
||||
|
||||
declare function connect<S, A, OP, SP, DP, P>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
|
||||
mergeProps: MergeProps<SP, DP, OP, P>,
|
||||
options?: ConnectOptions
|
||||
): Connector<OP, P>;
|
||||
}
|
||||
|
||||
9
flow-typed/npm/redux_v3.x.x.js
vendored
9
flow-typed/npm/redux_v3.x.x.js
vendored
@@ -1,6 +1,3 @@
|
||||
// flow-typed signature: ec7daead5cb4fec5ab25fedbedef29e8
|
||||
// flow-typed version: 2c04631d20/redux_v3.x.x/flow_>=v0.55.x
|
||||
|
||||
declare module 'redux' {
|
||||
|
||||
/*
|
||||
@@ -27,7 +24,7 @@ declare module 'redux' {
|
||||
replaceReducer(nextReducer: Reducer<S, A>): void
|
||||
};
|
||||
|
||||
declare export type Reducer<S, A> = (state: S, action: A) => S;
|
||||
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;
|
||||
|
||||
@@ -43,7 +40,7 @@ declare module 'redux' {
|
||||
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>, 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>;
|
||||
|
||||
@@ -56,4 +53,4 @@ declare module 'redux' {
|
||||
declare export function combineReducers<O: Object, A>(reducers: O): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
|
||||
|
||||
declare export var compose: $Compose;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user