Compare commits
1142 Commits
text-form-
...
developmen
| Author | SHA1 | Date |
|---|---|---|
|
|
bc7f3e9f35 | 4 years ago |
|
|
3a9c64779e | 4 years ago |
|
|
4fe2a4444d | 4 years ago |
|
|
bcb1e90ae6 | 4 years ago |
|
|
6bdb2a1600 | 4 years ago |
|
|
092cfbc891 | 4 years ago |
|
|
f2bb245c25 | 4 years ago |
|
|
8b79b0041c | 4 years ago |
|
|
5f7b52a529 | 4 years ago |
|
|
17f32c4dc8 | 4 years ago |
|
|
986af6ba9c | 4 years ago |
|
|
d717d9e0cf | 4 years ago |
|
|
10798f73de | 4 years ago |
|
|
d4f01c19f7 | 4 years ago |
|
|
6c1425ad5a | 4 years ago |
|
|
46760ffc77 | 4 years ago |
|
|
8069e89a61 | 4 years ago |
|
|
c30d6bebd8 | 4 years ago |
|
|
5422472ccc | 4 years ago |
|
|
33b8d6d78d | 4 years ago |
|
|
1a3ad32aa5 | 4 years ago |
|
|
70573d190a | 4 years ago |
|
|
379bfd5bb3 | 4 years ago |
|
|
041c652b86 | 4 years ago |
|
|
89d4a7aacb | 4 years ago |
|
|
9ecfe896f9 | 4 years ago |
|
|
f84ae8b4e7 | 4 years ago |
|
|
447ca6d4dc | 4 years ago |
|
|
4b7b4fda7f | 4 years ago |
|
|
e242aa19bc | 4 years ago |
|
|
9f7cdba8b7 | 4 years ago |
|
|
8bdde45d81 | 4 years ago |
|
|
8c93d5c450 | 4 years ago |
|
|
28defc2582 | 4 years ago |
|
|
f1dd50d7bf | 4 years ago |
|
|
31082249e5 | 4 years ago |
|
|
5ec07bb931 | 4 years ago |
|
|
36e245dd7d | 4 years ago |
|
|
077cad7df5 | 4 years ago |
|
|
a019319589 | 4 years ago |
|
|
323e3f3538 | 4 years ago |
|
|
e73271cb29 | 4 years ago |
|
|
1ac24bee90 | 4 years ago |
|
|
e85a32274a | 4 years ago |
|
|
010310eb48 | 4 years ago |
|
|
47352ae77d | 4 years ago |
|
|
4e6c940cdf | 4 years ago |
|
|
1ca7b28590 | 4 years ago |
|
|
e935014776 | 4 years ago |
|
|
e3a71945de | 4 years ago |
|
|
d743c2c99c | 4 years ago |
|
|
e1c635a911 | 4 years ago |
|
|
7fc52ecad4 | 4 years ago |
|
|
31cf229763 | 4 years ago |
|
|
286dbe0a3f | 4 years ago |
|
|
dfc792ce64 | 4 years ago |
|
|
fa4e4b0dfd | 4 years ago |
|
|
b7892fc18e | 4 years ago |
|
|
6107639ecd | 4 years ago |
|
|
3f41ff267a | 4 years ago |
|
|
c96939e6b0 | 4 years ago |
|
|
fcdec6dbf7 | 4 years ago |
|
|
9a1a7ed247 | 4 years ago |
|
|
7dc71d01de | 4 years ago |
|
|
4eaa62a48c | 4 years ago |
|
|
1dd66803fa | 4 years ago |
|
|
4d820728e8 | 4 years ago |
|
|
eaea16e480 | 4 years ago |
|
|
65ae00cec2 | 4 years ago |
|
|
41e79d5e00 | 4 years ago |
|
|
7962d9b1be | 4 years ago |
|
|
8980f6f440 | 4 years ago |
|
|
39c64779aa | 4 years ago |
|
|
95321a9305 | 4 years ago |
|
|
ed6af7f26e | 4 years ago |
|
|
f7770c0315 | 4 years ago |
|
|
505c3f470f | 4 years ago |
|
|
dc37e2e559 | 4 years ago |
|
|
47dd2ecf56 | 4 years ago |
|
|
d32731451c | 4 years ago |
|
|
208aa9dd6e | 4 years ago |
|
|
4c7db58489 | 4 years ago |
|
|
63b6a957b6 | 4 years ago |
|
|
e61d1e78f6 | 4 years ago |
|
|
c93ea2f285 | 4 years ago |
|
|
240c9aa411 | 4 years ago |
|
|
c083f9ffa3 | 4 years ago |
|
|
e732f9e61c | 4 years ago |
|
|
81d3aa5ad1 | 4 years ago |
|
|
177a2e8bc4 | 4 years ago |
|
|
01cd91ef93 | 4 years ago |
|
|
c4f2543471 | 4 years ago |
|
|
40faef136d | 4 years ago |
|
|
70fa365377 | 4 years ago |
|
|
e5fe12f26b | 4 years ago |
|
|
9f7dbf5ec7 | 4 years ago |
|
|
d22ea2e2fa | 4 years ago |
|
|
17a6ec5795 | 4 years ago |
|
|
d4cef7ba5f | 4 years ago |
|
|
7812d5081e | 4 years ago |
|
|
dceefadc06 | 4 years ago |
|
|
f3b39906f5 | 4 years ago |
|
|
22c26c02de | 4 years ago |
|
|
4505ef9d02 | 4 years ago |
|
|
40c1502a9b | 4 years ago |
|
|
f4c7be6cc2 | 4 years ago |
|
|
b1ddd9935c | 4 years ago |
|
|
ada10f595a | 4 years ago |
|
|
ba62056c4d | 4 years ago |
|
|
954402fe52 | 4 years ago |
|
|
9461d3d4a1 | 4 years ago |
|
|
1f770793fd | 4 years ago |
|
|
85a6f4965f | 4 years ago |
|
|
cdc0d2f4a9 | 4 years ago |
|
|
34f658a166 | 4 years ago |
|
|
f266f54be0 | 4 years ago |
|
|
c76900a868 | 4 years ago |
|
|
8cb5171bc9 | 4 years ago |
|
|
0147481c27 | 4 years ago |
|
|
5f3f509b8c | 4 years ago |
|
|
e10c07f6a4 | 4 years ago |
|
|
ce978db2dd | 4 years ago |
|
|
d5e3a79f85 | 4 years ago |
|
|
5b8211eec4 | 4 years ago |
|
|
c1956aee9b | 4 years ago |
|
|
2b38293d61 | 4 years ago |
|
|
113fb42d68 | 4 years ago |
|
|
3f25acda8f | 4 years ago |
|
|
f769189bee | 4 years ago |
|
|
7afe24c2bc | 4 years ago |
|
|
11652e448d | 4 years ago |
|
|
65e9840418 | 4 years ago |
|
|
228237bf96 | 4 years ago |
|
|
182f890e92 | 4 years ago |
|
|
d0bdf8c1e8 | 4 years ago |
|
|
cb19a5fcfe | 4 years ago |
|
|
f8371f71a1 | 4 years ago |
|
|
0257bb1f16 | 4 years ago |
|
|
2a0148467e | 4 years ago |
|
|
1789eb8c5b | 4 years ago |
|
|
994b43a590 | 4 years ago |
|
|
b22023cdbc | 4 years ago |
|
|
6905ed702e | 4 years ago |
|
|
25f902487c | 4 years ago |
|
|
17998cbbf3 | 4 years ago |
|
|
83ea30303b | 4 years ago |
|
|
df3ba10a54 | 4 years ago |
|
|
e8b5165953 | 4 years ago |
|
|
b8076f177b | 4 years ago |
|
|
db83bec292 | 4 years ago |
|
|
1ae6c4682b | 4 years ago |
|
|
5ed3084130 | 4 years ago |
|
|
45f30a8d61 | 4 years ago |
|
|
bbdc243f0f | 4 years ago |
|
|
06b2e7f194 | 4 years ago |
|
|
8f1d4f80bd | 4 years ago |
|
|
47a077bd52 | 4 years ago |
|
|
b1a65425c5 | 4 years ago |
|
|
e6e5d8e03b | 4 years ago |
|
|
af72810b26 | 4 years ago |
|
|
2ffa745e73 | 4 years ago |
|
|
c6a091ae32 | 4 years ago |
|
|
90c74c2f06 | 4 years ago |
|
|
f7e509d967 | 4 years ago |
|
|
8464c70f35 | 4 years ago |
|
|
4ef41e0c5a | 4 years ago |
|
|
fdc39f28e3 | 4 years ago |
|
|
19d981d9e0 | 4 years ago |
|
|
63e94e0d7b | 4 years ago |
|
|
a829532717 | 4 years ago |
|
|
211990518f | 4 years ago |
|
|
9f77d080c0 | 4 years ago |
|
|
74501dbf07 | 4 years ago |
|
|
de9dc0cb8c | 4 years ago |
|
|
b38a153e22 | 4 years ago |
|
|
694be7315b | 4 years ago |
|
|
fec899ebb7 | 4 years ago |
|
|
d3f0f1a141 | 4 years ago |
|
|
0895a80d16 | 4 years ago |
|
|
fde96fbce7 | 4 years ago |
|
|
ad8d0f32b6 | 4 years ago |
|
|
37928c1757 | 4 years ago |
|
|
44bc3f1c3c | 4 years ago |
|
|
23e1fed349 | 4 years ago |
|
|
be40076def | 4 years ago |
|
|
65d04b2d0f | 4 years ago |
|
|
a5974ad584 | 4 years ago |
|
|
b79eff7f6b | 4 years ago |
|
|
e165ec5b1d | 4 years ago |
|
|
af9e8dd095 | 4 years ago |
|
|
0d542a862e | 4 years ago |
|
|
6d0ff69386 | 4 years ago |
|
|
d9b2a618f5 | 4 years ago |
|
|
f300dbf315 | 4 years ago |
|
|
05b656416d | 4 years ago |
|
|
278bd5e23f | 4 years ago |
|
|
ed447f6b17 | 4 years ago |
|
|
0d61d0febc | 4 years ago |
|
|
ed9c77465f | 4 years ago |
|
|
1b12354581 | 4 years ago |
|
|
26fa1aec62 | 4 years ago |
|
|
79372b86c1 | 4 years ago |
|
|
0a0aa6ba77 | 4 years ago |
|
|
fec10319bf | 4 years ago |
|
|
af1217dd67 | 4 years ago |
|
|
42feae9a2e | 4 years ago |
|
|
406165c348 | 4 years ago |
|
|
5ed5a266e9 | 4 years ago |
|
|
da63af6db3 | 4 years ago |
|
|
810cda1f9b | 4 years ago |
|
|
c8fde6e869 | 4 years ago |
|
|
956afab2b4 | 4 years ago |
|
|
afbd389925 | 4 years ago |
|
|
0f71025e31 | 4 years ago |
|
|
294d979255 | 4 years ago |
|
|
1755650304 | 4 years ago |
|
|
268c637316 | 4 years ago |
|
|
94b86ee4b9 | 4 years ago |
|
|
5ff270858d | 4 years ago |
|
|
6e976c4597 | 4 years ago |
|
|
66637345e9 | 4 years ago |
|
|
71ce8d22e3 | 4 years ago |
|
|
edbb99e445 | 4 years ago |
|
|
5526380da7 | 4 years ago |
|
|
1088a0ef27 | 4 years ago |
|
|
eb5d2f3def | 4 years ago |
|
|
bc4b271d0c | 4 years ago |
|
|
b70ccf66be | 4 years ago |
|
|
88ade7546a | 4 years ago |
|
|
fb610d21b0 | 4 years ago |
|
|
d37d10639a | 4 years ago |
|
|
b9f3eefcbd | 4 years ago |
|
|
17eeaf2cc6 | 4 years ago |
|
|
06855665b5 | 4 years ago |
|
|
d23e725794 | 4 years ago |
|
|
0901a2e39c | 4 years ago |
|
|
e360ff5596 | 4 years ago |
|
|
746c2fdddb | 4 years ago |
|
|
bfeb1ac047 | 4 years ago |
|
|
d9d2e40927 | 4 years ago |
|
|
f02a6f3f30 | 4 years ago |
|
|
d66bc3ea2a | 4 years ago |
|
|
f8051cda0a | 4 years ago |
|
|
c54015c5c1 | 4 years ago |
|
|
bce6dc6f63 | 4 years ago |
|
|
558de8ec75 | 4 years ago |
|
|
4dc93f7c31 | 4 years ago |
|
|
73cc5e4b00 | 4 years ago |
|
|
0c4d6da3f4 | 4 years ago |
|
|
26e80356e1 | 4 years ago |
|
|
480017f887 | 4 years ago |
|
|
a4aa695f25 | 4 years ago |
|
|
0116de2454 | 4 years ago |
|
|
a578d92cb7 | 4 years ago |
|
|
74540f054a | 4 years ago |
|
|
5d20480e9b | 4 years ago |
|
|
3431c353e9 | 4 years ago |
|
|
4dcc20b681 | 4 years ago |
|
|
3e1ac71a48 | 4 years ago |
|
|
56fbc47835 | 4 years ago |
|
|
34b4484972 | 4 years ago |
|
|
ae5c9170a7 | 4 years ago |
|
|
fca6990938 | 4 years ago |
|
|
6f1790d71e | 4 years ago |
|
|
b8b943e3d9 | 4 years ago |
|
|
bd133f8f31 | 4 years ago |
|
|
f68c0fe426 | 4 years ago |
|
|
35279405af | 4 years ago |
|
|
933ecea67c | 4 years ago |
|
|
ad1ae99707 | 4 years ago |
|
|
75a10939fb | 4 years ago |
|
|
63af889e2f | 4 years ago |
|
|
4c1ed7f931 | 4 years ago |
|
|
46ec7d4c99 | 4 years ago |
|
|
10bbf51c52 | 4 years ago |
|
|
c2fbd99ced | 4 years ago |
|
|
af10c60a7e | 4 years ago |
|
|
3200a7a351 | 4 years ago |
|
|
70b3776aa7 | 4 years ago |
|
|
6375d9fb2d | 4 years ago |
|
|
c6635397bb | 4 years ago |
|
|
bff995ecfa | 4 years ago |
|
|
4049b0e066 | 4 years ago |
|
|
fb51325188 | 4 years ago |
|
|
25e09fa105 | 4 years ago |
|
|
c6a77563cc | 4 years ago |
|
|
85e6f63094 | 4 years ago |
|
|
efe25b898d | 4 years ago |
|
|
fc81370b0a | 4 years ago |
|
|
9e4a9c4b6a | 4 years ago |
|
|
658a28e558 | 4 years ago |
|
|
f97b0bee14 | 4 years ago |
|
|
6b0e790cd2 | 4 years ago |
|
|
753a72b85e | 4 years ago |
|
|
fc126caddf | 4 years ago |
|
|
98bc33042f | 4 years ago |
|
|
9a1b9eb50b | 4 years ago |
|
|
b54b4d96e9 | 4 years ago |
|
|
d3e90ed984 | 4 years ago |
|
|
279004b58b | 4 years ago |
|
|
f256527302 | 4 years ago |
|
|
a02e89febb | 4 years ago |
|
|
6c78fda392 | 4 years ago |
|
|
47357fe84e | 4 years ago |
|
|
adc9f8b4dc | 4 years ago |
|
|
2b763e954a | 4 years ago |
|
|
b265210565 | 4 years ago |
|
|
67a5bcff91 | 4 years ago |
|
|
3c8d1fdf39 | 4 years ago |
|
|
ecffe99f19 | 4 years ago |
|
|
fedb4533c7 | 4 years ago |
|
|
5f2e0be78c | 4 years ago |
|
|
9e1aeb857f | 4 years ago |
|
|
5d710054ea | 4 years ago |
|
|
98f6d63fcd | 4 years ago |
|
|
52bd1511e8 | 4 years ago |
|
|
4760d4ae50 | 4 years ago |
|
|
2ff5fbad7c | 4 years ago |
|
|
3d379b3b84 | 4 years ago |
|
|
3bfee08b9d | 4 years ago |
|
|
bd7e683900 | 4 years ago |
|
|
fbaeb1b361 | 4 years ago |
|
|
118d367b4c | 4 years ago |
|
|
1f893042db | 4 years ago |
|
|
72df25d949 | 4 years ago |
|
|
00e0680c95 | 4 years ago |
|
|
e2cde9a39f | 4 years ago |
|
|
e59220d1b7 | 4 years ago |
|
|
b0de660325 | 4 years ago |
|
|
f16f54fee2 | 4 years ago |
|
|
e31fadc9be | 4 years ago |
|
|
8e795535b8 | 4 years ago |
|
|
83bea13b8d | 4 years ago |
|
|
92ceb99212 | 4 years ago |
|
|
f3462385fd | 4 years ago |
|
|
14a43ab53a | 4 years ago |
|
|
cce0a8377c | 4 years ago |
|
|
999c2a920d | 4 years ago |
|
|
b3bfda9128 | 4 years ago |
|
|
5d8b521941 | 4 years ago |
|
|
c17397bf5e | 4 years ago |
|
|
1c8d25c95c | 4 years ago |
|
|
13c05e87dc | 4 years ago |
|
|
6ae4d3a7cd | 4 years ago |
|
|
eda9aeb26b | 4 years ago |
|
|
d1060ba27a | 4 years ago |
|
|
bfd86f533a | 4 years ago |
|
|
189864f4ba | 4 years ago |
|
|
81ae0b04d3 | 4 years ago |
|
|
8a1d88c1f6 | 4 years ago |
|
|
24618e5232 | 4 years ago |
|
|
1146f613df | 4 years ago |
|
|
a8adacfc5c | 4 years ago |
|
|
43c670e5b1 | 4 years ago |
|
|
948b8c877d | 4 years ago |
|
|
d808f8b2ac | 4 years ago |
|
|
1eb2221a00 | 4 years ago |
|
|
f4a9ab14c8 | 4 years ago |
|
|
1f28a09043 | 4 years ago |
|
|
ec83d72038 | 4 years ago |
|
|
cb29b1021e | 4 years ago |
|
|
dc0f3e7235 | 4 years ago |
|
|
ced1c9b392 | 4 years ago |
|
|
ede9574084 | 4 years ago |
|
|
7f9969b6cf | 4 years ago |
|
|
47437c8637 | 4 years ago |
|
|
f99ce10edf | 4 years ago |
|
|
3974586bc9 | 4 years ago |
|
|
dfce0d20e2 | 4 years ago |
|
|
eceb3976ad | 4 years ago |
|
|
37d4dfffa2 | 4 years ago |
|
|
a6153db2f9 | 4 years ago |
|
|
8f0e69df3a | 4 years ago |
|
|
ff1cffc24d | 4 years ago |
|
|
bc92f9b9f5 | 4 years ago |
|
|
49205f797d | 4 years ago |
|
|
6ffbf92d43 | 4 years ago |
|
|
f23607b6e4 | 4 years ago |
|
|
f98c34fcc6 | 4 years ago |
|
|
2e884c4a35 | 4 years ago |
|
|
101bc9f1b6 | 4 years ago |
|
|
ca854638e5 | 4 years ago |
|
|
0bdb15b14e | 4 years ago |
|
|
c4617250ae | 4 years ago |
|
|
8d2b6e9b08 | 4 years ago |
|
|
132d781eb5 | 4 years ago |
|
|
aaabae5900 | 4 years ago |
|
|
92d81700e1 | 4 years ago |
|
|
8e85bb239e | 4 years ago |
|
|
25f65802a5 | 4 years ago |
|
|
61d2c6cdad | 4 years ago |
|
|
eb89dc3395 | 4 years ago |
|
|
52ec3fc0e3 | 4 years ago |
|
|
ffba0ae352 | 4 years ago |
|
|
03acd233cf | 4 years ago |
|
|
26ffd7cb7a | 4 years ago |
|
|
515297ebf6 | 4 years ago |
|
|
cf498de70f | 4 years ago |
|
|
5567b31daf | 4 years ago |
|
|
ad845b882c | 4 years ago |
|
|
4fe53ef71e | 4 years ago |
|
|
3eddf2eab5 | 4 years ago |
|
|
13c42b297a | 4 years ago |
|
|
9eadc0f0b1 | 4 years ago |
|
|
fd9e8ddb28 | 4 years ago |
|
|
482feece26 | 4 years ago |
|
|
75e944427c | 4 years ago |
|
|
514fb71c51 | 4 years ago |
|
|
08df479b37 | 4 years ago |
|
|
d4927873d5 | 4 years ago |
|
|
6f29a70991 | 4 years ago |
|
|
324f515253 | 4 years ago |
|
|
f4311b4106 | 4 years ago |
|
|
4f123b0af2 | 4 years ago |
|
|
1a35201f27 | 4 years ago |
|
|
3229a1ff99 | 4 years ago |
|
|
4d01c74bf8 | 4 years ago |
|
|
cb223be8b3 | 4 years ago |
|
|
5e4b2d8202 | 4 years ago |
|
|
9de1085b49 | 4 years ago |
|
|
d0e6532814 | 4 years ago |
|
|
45822d16d1 | 4 years ago |
|
|
a9d097da54 | 4 years ago |
|
|
e371d105c4 | 4 years ago |
|
|
6b09276c93 | 4 years ago |
|
|
ac1b4d9865 | 4 years ago |
|
|
5e976367f0 | 4 years ago |
|
|
04583d8c42 | 4 years ago |
|
|
22b424515c | 4 years ago |
|
|
97a15bfb35 | 4 years ago |
|
|
2fe475d98a | 4 years ago |
|
|
9270f58615 | 4 years ago |
|
|
6e61bd8c1d | 4 years ago |
|
|
230ebaf445 | 4 years ago |
|
|
93bcfff7a0 | 4 years ago |
|
|
2a677cd5d5 | 4 years ago |
|
|
331b27c3ec | 4 years ago |
|
|
5ad9976c40 | 4 years ago |
|
|
9689044be7 | 4 years ago |
|
|
7723ee2ec1 | 4 years ago |
|
|
faa7c08266 | 4 years ago |
|
|
7b8312fe26 | 4 years ago |
|
|
9e2b72a7cb | 4 years ago |
|
|
514e6c81c7 | 4 years ago |
|
|
60a15d1a60 | 4 years ago |
|
|
056b0bbd78 | 4 years ago |
|
|
9b87ea8c22 | 4 years ago |
|
|
3f7a839950 | 4 years ago |
|
|
ae0c588d32 | 4 years ago |
|
|
dd971ffddb | 4 years ago |
|
|
b355545549 | 4 years ago |
|
|
45f4aa9a22 | 4 years ago |
|
|
b21acfd2c6 | 4 years ago |
|
|
52e5b86f6c | 4 years ago |
|
|
7aa2c9f3ad | 4 years ago |
|
|
64b5652c8d | 4 years ago |
|
|
35acd1f21f | 4 years ago |
|
|
1a3567f917 | 4 years ago |
|
|
72c948371a | 4 years ago |
|
|
4ebecc4277 | 4 years ago |
|
|
a44a207116 | 4 years ago |
|
|
d7b1212b54 | 4 years ago |
|
|
26663b9666 | 4 years ago |
|
|
ee3d207795 | 4 years ago |
|
|
231614e914 | 4 years ago |
|
|
fbf1cd29ee | 4 years ago |
|
|
d035f1ba37 | 4 years ago |
|
|
7684b65e8a | 4 years ago |
|
|
469556758b | 4 years ago |
|
|
d4363aeefb | 4 years ago |
|
|
318560b1d7 | 4 years ago |
|
|
efbe6ae76b | 4 years ago |
|
|
6003df7f63 | 4 years ago |
|
|
93da7b8118 | 4 years ago |
|
|
4e52155342 | 4 years ago |
|
|
54f1bbf184 | 4 years ago |
|
|
e15be3f09d | 4 years ago |
|
|
bb8ffbf63b | 4 years ago |
|
|
96cfc3d659 | 4 years ago |
|
|
2f65546ff6 | 4 years ago |
|
|
9c6cebcffd | 4 years ago |
|
|
a4654e9bce | 4 years ago |
|
|
7436cdb5be | 5 years ago |
|
|
7d4f6d5264 | 5 years ago |
|
|
737f9f6389 | 5 years ago |
|
|
96ee21b15f | 5 years ago |
|
|
35bfa85a1a | 5 years ago |
|
|
6dd8f62b2a | 5 years ago |
|
|
b9ebde0ec8 | 5 years ago |
|
|
17ee942de1 | 5 years ago |
|
|
65f8f061db | 5 years ago |
|
|
8ae51c3580 | 5 years ago |
|
|
d1ccb3c433 | 5 years ago |
|
|
c231ae378a | 5 years ago |
|
|
8be6bd257d | 5 years ago |
|
|
b23fe5ee1b | 5 years ago |
|
|
87b70ff905 | 5 years ago |
|
|
dd4eb2f76d | 5 years ago |
|
|
9a5eff8c8b | 5 years ago |
|
|
495c8fb3b0 | 5 years ago |
|
|
4711b6264c | 5 years ago |
|
|
8a0e94c854 | 5 years ago |
|
|
f05455fddf | 5 years ago |
|
|
ff07686bf7 | 5 years ago |
|
|
2274affd9a | 5 years ago |
|
|
29336a9ef3 | 5 years ago |
|
|
cec3a78d94 | 5 years ago |
|
|
b86bc33266 | 5 years ago |
|
|
fe5456db2a | 5 years ago |
|
|
640dc239b0 | 5 years ago |
|
|
71463dc459 | 5 years ago |
|
|
aaca94e93c | 5 years ago |
|
|
3cd4973eab | 5 years ago |
|
|
62ee6dff3a | 5 years ago |
|
|
f095da8943 | 5 years ago |
|
|
771d94ff4c | 5 years ago |
|
|
0ab0570bc6 | 5 years ago |
|
|
dac8f90a8a | 5 years ago |
|
|
faf8c8ede7 | 5 years ago |
|
|
b27cc6ae9a | 5 years ago |
|
|
6002e22d6d | 5 years ago |
|
|
1a7fd766d5 | 5 years ago |
|
|
062194ba01 | 5 years ago |
|
|
136313ac72 | 5 years ago |
|
|
4663a65fd6 | 5 years ago |
|
|
47df7e5919 | 5 years ago |
|
|
7be6ef6ed6 | 5 years ago |
|
|
288bef5a5f | 5 years ago |
|
|
f56a23aad9 | 5 years ago |
|
|
b2ce970195 | 5 years ago |
|
|
4923b66065 | 5 years ago |
|
|
57d34425ba | 5 years ago |
|
|
b119590e39 | 5 years ago |
|
|
6b7fc1bf56 | 5 years ago |
|
|
8a07dfda15 | 5 years ago |
|
|
8d8200ab9b | 5 years ago |
|
|
28140b6ca4 | 5 years ago |
|
|
942bcd1bd3 | 5 years ago |
|
|
777bdb8a0d | 5 years ago |
|
|
5a821e3d64 | 5 years ago |
|
|
8383ce17bd | 5 years ago |
|
|
66bdf11b1f | 5 years ago |
|
|
a731851a92 | 5 years ago |
|
|
702bb5b8da | 5 years ago |
|
|
d70ad7f2c3 | 5 years ago |
|
|
6ca3baf7cb | 5 years ago |
|
|
b62d4d1dad | 5 years ago |
|
|
c74dca1404 | 5 years ago |
|
|
5e90f14a89 | 5 years ago |
|
|
99868a058c | 5 years ago |
|
|
ca3dddd581 | 5 years ago |
|
|
c1103f26fa | 5 years ago |
|
|
02109a40e1 | 5 years ago |
|
|
6eecb4a614 | 5 years ago |
|
|
f2dbfaf2be | 5 years ago |
|
|
de5621a629 | 5 years ago |
|
|
cdc3b36174 | 5 years ago |
|
|
ce199f7d98 | 5 years ago |
|
|
3d7f8b5997 | 5 years ago |
|
|
9598c09ea2 | 5 years ago |
|
|
696b8b89ec | 5 years ago |
|
|
ba275eaed4 | 5 years ago |
|
|
37cd34f568 | 5 years ago |
|
|
6a96a9cb28 | 5 years ago |
|
|
a1f9729091 | 5 years ago |
|
|
0ff08801f1 | 5 years ago |
|
|
763686fc76 | 5 years ago |
|
|
9317173c05 | 5 years ago |
|
|
09c8d9c09e | 5 years ago |
|
|
fe193f4084 | 5 years ago |
|
|
c5eac79522 | 5 years ago |
|
|
8fc4b1596b | 5 years ago |
|
|
e9e86e4d8c | 5 years ago |
|
|
680ba27bbc | 5 years ago |
|
|
96ad55b552 | 5 years ago |
|
|
1fd192e4bc | 5 years ago |
|
|
aae1cba405 | 5 years ago |
|
|
3762e60552 | 5 years ago |
|
|
353743b1b6 | 5 years ago |
|
|
a014e0140d | 5 years ago |
|
|
a855dfca23 | 5 years ago |
|
|
92c791dd72 | 5 years ago |
|
|
00b7aaf32a | 5 years ago |
|
|
9297ea037d | 5 years ago |
|
|
c608b5601d | 5 years ago |
|
|
e334dbd221 | 5 years ago |
|
|
ba2d97f96e | 5 years ago |
|
|
7ef7c8225c | 5 years ago |
|
|
8fc25538b0 | 5 years ago |
|
|
1ba47515c6 | 5 years ago |
|
|
c9fe9ccb63 | 5 years ago |
|
|
ee41f2f494 | 5 years ago |
|
|
95a694a85f | 5 years ago |
|
|
b461af1be2 | 5 years ago |
|
|
449206c6e8 | 5 years ago |
|
|
ae190d8802 | 5 years ago |
|
|
382d6935ba | 5 years ago |
|
|
26f37893a1 | 5 years ago |
|
|
5bbb2049ce | 5 years ago |
|
|
4c2d55015f | 5 years ago |
|
|
cc26053bc1 | 5 years ago |
|
|
46698a4382 | 5 years ago |
|
|
b707857526 | 5 years ago |
|
|
441d319a9e | 5 years ago |
|
|
835c8cd77d | 5 years ago |
|
|
a4c8178695 | 5 years ago |
|
|
4ce6ff8d7d | 5 years ago |
|
|
478900d4fd | 5 years ago |
|
|
cdbd905a62 | 5 years ago |
|
|
68e37ddf86 | 5 years ago |
|
|
41ed593707 | 5 years ago |
|
|
fdc4e52980 | 5 years ago |
|
|
99a4838044 | 5 years ago |
|
|
8eb9401941 | 5 years ago |
|
|
b1294a863c | 5 years ago |
|
|
6b146ed00b | 5 years ago |
|
|
1f3d1f68fb | 5 years ago |
|
|
766b563550 | 5 years ago |
|
|
2acad4e5dc | 5 years ago |
|
|
0deca4f624 | 5 years ago |
|
|
a0bc5b73de | 5 years ago |
|
|
e5e7452dad | 5 years ago |
|
|
632e930c6b | 5 years ago |
|
|
2a570b98be | 5 years ago |
|
|
19487ba435 | 5 years ago |
|
|
df40a2dc09 | 5 years ago |
|
|
5241273303 | 5 years ago |
|
|
a4ba2de459 | 5 years ago |
|
|
dba43fb7bd | 5 years ago |
|
|
ce6ec576dd | 5 years ago |
|
|
5a91f6a25b | 5 years ago |
|
|
e4f350fad3 | 5 years ago |
|
|
680ac7a4e9 | 5 years ago |
|
|
50e8e7b0d6 | 5 years ago |
|
|
7e552cccd4 | 5 years ago |
|
|
ae70fe1bdf | 5 years ago |
|
|
d4fec5080c | 5 years ago |
|
|
829d7b9be7 | 5 years ago |
|
|
547eb430db | 5 years ago |
|
|
792c7aea9b | 5 years ago |
|
|
844629c8bd | 5 years ago |
|
|
d5cff77743 | 5 years ago |
|
|
a33c0312b7 | 5 years ago |
|
|
f03a6d0982 | 5 years ago |
|
|
ff979c725e | 5 years ago |
|
|
393943a19b | 5 years ago |
|
|
a845f5b48d | 5 years ago |
|
|
2210a0445c | 5 years ago |
|
|
5ba26692c1 | 5 years ago |
|
|
0625de2b15 | 5 years ago |
|
|
ac4a68600c | 5 years ago |
|
|
b3e350ecaa | 5 years ago |
|
|
076301e91a | 5 years ago |
|
|
bf12203e81 | 5 years ago |
|
|
66e33b7085 | 5 years ago |
|
|
047acd7116 | 5 years ago |
|
|
64c98ba128 | 5 years ago |
|
|
943463b1d9 | 5 years ago |
|
|
b0a92d560d | 5 years ago |
|
|
1c2ccd9149 | 5 years ago |
|
|
c17f9fb91d | 5 years ago |
|
|
68e1a2b399 | 5 years ago |
|
|
a5f3731b9a | 5 years ago |
|
|
de6936efb8 | 5 years ago |
|
|
0535566232 | 5 years ago |
|
|
8d493f40fa | 5 years ago |
|
|
528e16c3dc | 5 years ago |
|
|
794c935d05 | 5 years ago |
|
|
6882a327c0 | 5 years ago |
|
|
0fb4b4a890 | 5 years ago |
|
|
1712874c87 | 5 years ago |
|
|
fa9942ac24 | 5 years ago |
|
|
0489e9584f | 5 years ago |
|
|
1d6026f9fd | 5 years ago |
|
|
800d6a1011 | 5 years ago |
|
|
94f7d7773f | 5 years ago |
|
|
e8c0281913 | 5 years ago |
|
|
f25c93f7a2 | 5 years ago |
|
|
abfba6e292 | 5 years ago |
|
|
2c0cdff0dc | 5 years ago |
|
|
9bcfd0587a | 5 years ago |
|
|
e2caebde8b | 5 years ago |
|
|
8b7d61ba03 | 5 years ago |
|
|
d362793fb4 | 5 years ago |
|
|
947fc7f8f8 | 5 years ago |
|
|
4bfcca230e | 5 years ago |
|
|
3d7cd70df6 | 5 years ago |
|
|
79a0e0917f | 5 years ago |
|
|
53d7d68012 | 5 years ago |
|
|
539d8500f1 | 5 years ago |
|
|
4abd005499 | 5 years ago |
|
|
74a6ebebdc | 5 years ago |
|
|
26a1758af9 | 5 years ago |
|
|
42ecf9a0fb | 5 years ago |
|
|
80b976dff3 | 5 years ago |
|
|
13be1c6a00 | 5 years ago |
|
|
562fd1b515 | 5 years ago |
|
|
c49827dec5 | 5 years ago |
|
|
64697b82a6 | 5 years ago |
|
|
c31db4890b | 5 years ago |
|
|
a1aa1d5689 | 5 years ago |
|
|
09ca6d2454 | 5 years ago |
|
|
7b65f1bade | 5 years ago |
|
|
3626857317 | 5 years ago |
|
|
21f3ab9ac8 | 5 years ago |
|
|
0ae0650acb | 5 years ago |
|
|
300ad103d3 | 5 years ago |
|
|
41190bc501 | 5 years ago |
|
|
6db5191e07 | 5 years ago |
|
|
8147d20fc6 | 5 years ago |
|
|
7830c41d02 | 5 years ago |
|
|
8c3fc3a6f7 | 5 years ago |
|
|
ab584c792a | 5 years ago |
|
|
1c45fcea22 | 5 years ago |
|
|
2f89eebffe | 5 years ago |
|
|
ebea54f2cc | 5 years ago |
|
|
8a4e0db113 | 5 years ago |
|
|
369eecb0a3 | 5 years ago |
|
|
854f42ce96 | 5 years ago |
|
|
e046928e3e | 5 years ago |
|
|
0d3b64af7d | 5 years ago |
|
|
a020c9865b | 5 years ago |
|
|
ae30e23d4d | 5 years ago |
|
|
a502308a8a | 5 years ago |
|
|
36786a3079 | 5 years ago |
|
|
9f1631ba4c | 5 years ago |
|
|
e58ce34aa4 | 5 years ago |
|
|
b341bd5f47 | 5 years ago |
|
|
e06bd6a4f8 | 5 years ago |
|
|
bf8ac21122 | 5 years ago |
|
|
243108e81a | 5 years ago |
|
|
0780cafcca | 5 years ago |
|
|
6b65de77e9 | 5 years ago |
|
|
7227679081 | 5 years ago |
|
|
14a0911aec | 5 years ago |
|
|
36f34226bd | 5 years ago |
|
|
529711d2ce | 5 years ago |
|
|
88b04a8f5d | 5 years ago |
|
|
8a69becab3 | 5 years ago |
|
|
d29d3e2666 | 5 years ago |
|
|
cac949f402 | 5 years ago |
|
|
64a7aca228 | 5 years ago |
|
|
3b1a493d76 | 5 years ago |
|
|
7e57882349 | 5 years ago |
|
|
25d0a08105 | 5 years ago |
|
|
d301c2a3f4 | 5 years ago |
|
|
804c88b11f | 5 years ago |
|
|
efd3d4942e | 5 years ago |
|
|
d0d6874159 | 5 years ago |
|
|
46683df8a9 | 5 years ago |
|
|
4553570547 | 5 years ago |
|
|
659e7f5886 | 5 years ago |
|
|
b015cb06f7 | 5 years ago |
|
|
87c906ae2b | 5 years ago |
|
|
7a696e4767 | 5 years ago |
|
|
3b6fcb4240 | 5 years ago |
|
|
1429a59138 | 5 years ago |
|
|
eabe16a1d3 | 5 years ago |
|
|
5a61a3eaba | 5 years ago |
|
|
c3ed212e6a | 5 years ago |
|
|
8a4dcf2334 | 5 years ago |
|
|
a84027ad6f | 5 years ago |
|
|
469169023a | 5 years ago |
|
|
f52917f219 | 5 years ago |
|
|
9b40638f86 | 5 years ago |
|
|
254f324ca6 | 5 years ago |
|
|
3ee0acbf6e | 5 years ago |
|
|
c7c6314a8d | 5 years ago |
|
|
ac6a6e7855 | 5 years ago |
|
|
adab2d47d3 | 5 years ago |
|
|
c69f122713 | 5 years ago |
|
|
993a266d4f | 5 years ago |
|
|
638ae40035 | 5 years ago |
|
|
f3d675cdf9 | 5 years ago |
|
|
a1ab1e4f43 | 5 years ago |
|
|
d0f1f9e17f | 5 years ago |
|
|
ef8db07875 | 5 years ago |
|
|
d63fa42178 | 5 years ago |
|
|
057a06fffb | 5 years ago |
|
|
d19a881801 | 5 years ago |
|
|
3871e3c465 | 5 years ago |
|
|
3be4fb2986 | 5 years ago |
|
|
abb1e52515 | 5 years ago |
|
|
73cadb1c00 | 5 years ago |
|
|
23ccbbe806 | 5 years ago |
|
|
7cc25f6101 | 5 years ago |
|
|
c0a38b8097 | 5 years ago |
|
|
eb40cc819d | 5 years ago |
|
|
300c868340 | 5 years ago |
|
|
4c8f79c87e | 5 years ago |
|
|
8d8d7d8697 | 5 years ago |
|
|
405a363e28 | 5 years ago |
|
|
b41c0036d3 | 5 years ago |
|
|
158bea519e | 5 years ago |
|
|
8cbefffb3d | 5 years ago |
|
|
e8f9c958b1 | 5 years ago |
|
|
8bfb37d4c0 | 5 years ago |
|
|
0006a4de57 | 5 years ago |
|
|
93c7797ed4 | 5 years ago |
|
|
0c730e8270 | 5 years ago |
|
|
74294bfaf5 | 5 years ago |
|
|
7d6123f9cb | 5 years ago |
|
|
33f032b6c4 | 5 years ago |
|
|
1af52dc5b7 | 5 years ago |
|
|
1a735dca62 | 5 years ago |
|
|
b1f3cf3ece | 5 years ago |
|
|
f0d90c0e9a | 5 years ago |
|
|
d839fd3545 | 5 years ago |
|
|
1e35b70b01 | 5 years ago |
|
|
1bb40ddeb9 | 5 years ago |
|
|
783c74108b | 5 years ago |
|
|
ec3ac18a15 | 5 years ago |
|
|
9303091f20 | 5 years ago |
|
|
eb62fdfe21 | 5 years ago |
|
|
a7c5e1afde | 5 years ago |
|
|
994757b662 | 5 years ago |
|
|
9890d6a063 | 5 years ago |
|
|
fc9a2a277a | 5 years ago |
|
|
e80523749b | 5 years ago |
|
|
1918c2219e | 5 years ago |
|
|
29eeb21e76 | 5 years ago |
|
|
19548ccb1e | 5 years ago |
|
|
80ef1a241d | 5 years ago |
|
|
bec5eb82bc | 5 years ago |
|
|
c652f40a89 | 5 years ago |
|
|
a5988c2885 | 5 years ago |
|
|
74b17c5fc9 | 5 years ago |
|
|
b4e702f41d | 5 years ago |
|
|
f5b7829e7d | 5 years ago |
|
|
ac526e8913 | 5 years ago |
|
|
1fd9c4f2f2 | 5 years ago |
|
|
e1c4721ac1 | 5 years ago |
|
|
0bc3b84cf8 | 5 years ago |
|
|
a52124eb5d | 5 years ago |
|
|
385820fdce | 5 years ago |
|
|
07835aaa1b | 5 years ago |
|
|
c722c9b05a | 5 years ago |
|
|
c9d7df01e3 | 5 years ago |
|
|
c9a95188ce | 5 years ago |
|
|
6c6b043571 | 5 years ago |
|
|
db4501bd14 | 5 years ago |
|
|
d879f1da77 | 5 years ago |
|
|
e70eabe68d | 5 years ago |
|
|
b85d507781 | 5 years ago |
|
|
b8c7ce2920 | 5 years ago |
|
|
d4ff42ad88 | 5 years ago |
|
|
02307e8fac | 5 years ago |
|
|
e5056c5d93 | 5 years ago |
|
|
57178d3633 | 5 years ago |
|
|
9a2ce62216 | 5 years ago |
|
|
e4a7f4e105 | 5 years ago |
|
|
cfd1912f7d | 5 years ago |
|
|
550d54c034 | 5 years ago |
|
|
8d5f86e4f2 | 5 years ago |
|
|
321bf7825e | 5 years ago |
|
|
b3933c1568 | 5 years ago |
|
|
9da1bff723 | 5 years ago |
|
|
eb2b614f74 | 5 years ago |
|
|
95c7161987 | 5 years ago |
|
|
0e2bcd7916 | 5 years ago |
|
|
7fed1c58f3 | 5 years ago |
|
|
a1ef722954 | 5 years ago |
|
|
329711a002 | 5 years ago |
|
|
ca6306950d | 5 years ago |
|
|
ac5ec049da | 5 years ago |
|
|
7f6128b458 | 5 years ago |
|
|
4d3371e882 | 5 years ago |
|
|
78338ebc64 | 5 years ago |
|
|
327d392f6b | 5 years ago |
|
|
47dfde6e78 | 5 years ago |
|
|
737d0e9bec | 5 years ago |
|
|
3990ba6ff7 | 5 years ago |
|
|
387f75b36c | 5 years ago |
|
|
0cb82586bd | 5 years ago |
|
|
352d3263ac | 5 years ago |
|
|
f9a989b6fd | 5 years ago |
|
|
61c94f85b2 | 5 years ago |
|
|
c585cbb5be | 5 years ago |
|
|
bf54139647 | 5 years ago |
|
|
02f43d6e61 | 5 years ago |
|
|
8940de50d6 | 5 years ago |
|
|
764e2e29f5 | 5 years ago |
|
|
d17316f7b2 | 5 years ago |
|
|
e4ef47213d | 5 years ago |
|
|
73f2abbea2 | 5 years ago |
|
|
e4fb83826a | 5 years ago |
|
|
c67f3d10ad | 5 years ago |
|
|
2140f0a229 | 5 years ago |
|
|
a08e801918 | 5 years ago |
|
|
654a86a9b6 | 5 years ago |
|
|
3c2abb66c6 | 5 years ago |
|
|
b66b624f14 | 5 years ago |
|
|
bed7dee27c | 5 years ago |
|
|
b8de9c9a14 | 5 years ago |
|
|
46d09ee55d | 5 years ago |
|
|
daf125b995 | 5 years ago |
|
|
52eef31e2e | 5 years ago |
|
|
edca552f78 | 5 years ago |
|
|
4e6237f2ce | 5 years ago |
|
|
b9711fce65 | 5 years ago |
|
|
78903043d9 | 5 years ago |
|
|
7614b4aa5c | 5 years ago |
|
|
ee7f8e9e42 | 5 years ago |
|
|
746f44157e | 5 years ago |
|
|
0868de7961 | 5 years ago |
|
|
bb20b4d582 | 5 years ago |
|
|
c1de908777 | 5 years ago |
|
|
5312c221d1 | 5 years ago |
|
|
c1ed1dc737 | 5 years ago |
|
|
7c7aa781aa | 5 years ago |
|
|
1ed22e68e3 | 5 years ago |
|
|
259a63384e | 5 years ago |
|
|
550b4a848d | 5 years ago |
|
|
bc661a6f38 | 5 years ago |
|
|
890a356d8c | 5 years ago |
|
|
b4305a00ae | 5 years ago |
|
|
86a3dcaeb4 | 5 years ago |
|
|
f76bac898b | 5 years ago |
|
|
09634716e5 | 5 years ago |
|
|
3cf4ef4348 | 5 years ago |
|
|
fee36336d8 | 5 years ago |
|
|
6b44a1c3aa | 5 years ago |
|
|
883778d278 | 5 years ago |
|
|
37e6853976 | 5 years ago |
|
|
1c3391baed | 5 years ago |
|
|
7f3ae19fd2 | 5 years ago |
|
|
7b71bd752f | 5 years ago |
|
|
363b55069f | 5 years ago |
|
|
48874c0efd | 5 years ago |
|
|
84561a7363 | 5 years ago |
|
|
3913f8936c | 5 years ago |
|
|
c46c339933 | 5 years ago |
|
|
1edd375fd7 | 5 years ago |
|
|
13ee7f6491 | 5 years ago |
|
|
148657e06b | 5 years ago |
|
|
61b0f6c22c | 5 years ago |
|
|
6fca263109 | 5 years ago |
|
|
9328724167 | 5 years ago |
|
|
6fd9af9b26 | 5 years ago |
|
|
07857b28cd | 5 years ago |
|
|
f60ff85e31 | 5 years ago |
|
|
01a49ae200 | 5 years ago |
|
|
0bfc63bddc | 5 years ago |
|
|
712a742aa2 | 5 years ago |
|
|
161cb1e872 | 5 years ago |
|
|
142fbb93fd | 5 years ago |
|
|
411746da98 | 5 years ago |
|
|
1b4988e5f0 | 5 years ago |
|
|
bc53ca7b8a | 5 years ago |
|
|
ea2a635925 | 5 years ago |
|
|
96f2de2c35 | 5 years ago |
|
|
3fefcf60d7 | 5 years ago |
|
|
110a1983c7 | 5 years ago |
|
|
e77f5773dc | 5 years ago |
|
|
132565c298 | 5 years ago |
|
|
63526113f4 | 5 years ago |
|
|
d2e0555144 | 5 years ago |
|
|
b0938562fa | 5 years ago |
|
|
fb28b7e910 | 5 years ago |
|
|
c44b3d74bb | 5 years ago |
|
|
726d3ab885 | 5 years ago |
|
|
30bf59458c | 5 years ago |
|
|
a74f4a3d5a | 5 years ago |
|
|
1834d9f776 | 5 years ago |
|
|
e530dc0168 | 5 years ago |
|
|
717468b637 | 5 years ago |
|
|
57399c3dcb | 5 years ago |
|
|
95425d9568 | 5 years ago |
|
|
6aef8b5878 | 5 years ago |
|
|
42a4f9330a | 5 years ago |
|
|
1c80533e67 | 5 years ago |
|
|
017669c406 | 5 years ago |
|
|
354a1f2dec | 5 years ago |
|
|
ca0cc745d5 | 5 years ago |
|
|
ad773c5e56 | 5 years ago |
|
|
0e513ecca8 | 5 years ago |
|
|
23feb2c354 | 5 years ago |
|
|
1b47b60155 | 5 years ago |
|
|
10441e4af0 | 5 years ago |
|
|
228df1e762 | 5 years ago |
|
|
4cdc654348 | 5 years ago |
|
|
418918558f | 5 years ago |
|
|
a0c7f7321d | 5 years ago |
|
|
1a158f49bb | 5 years ago |
|
|
cb5abfb2ec | 5 years ago |
|
|
36caca0e46 | 5 years ago |
|
|
0b52b4009d | 5 years ago |
|
|
e76ecf172f | 5 years ago |
|
|
176c0ab97c | 5 years ago |
|
|
693f57a13e | 5 years ago |
|
|
f1d1cc6243 | 5 years ago |
|
|
b1062dd537 | 5 years ago |
|
|
2e0f6aad5c | 5 years ago |
|
|
b779450b62 | 5 years ago |
|
|
9d6a23c1a3 | 5 years ago |
|
|
3df6a55dad | 5 years ago |
|
|
72966c3269 | 5 years ago |
|
|
663c8d1104 | 5 years ago |
|
|
95b00dd67d | 5 years ago |
|
|
f6922ee41a | 5 years ago |
|
|
77be588614 | 5 years ago |
|
|
ecb600c4fc | 5 years ago |
|
|
f81e0b58cb | 5 years ago |
|
|
8a8b55d2de | 5 years ago |
|
|
792c85e28a | 5 years ago |
|
|
a1abcaa7c0 | 5 years ago |
|
|
ec9f628b67 | 5 years ago |
|
|
f80215b6bb | 5 years ago |
|
|
9b3c0af555 | 5 years ago |
|
|
6e6a7deba5 | 5 years ago |
|
|
d57b331df2 | 5 years ago |
|
|
39887f3ead | 5 years ago |
|
|
400420bd4d | 5 years ago |
|
|
64001e6b19 | 5 years ago |
|
|
6dfcbcca12 | 5 years ago |
|
|
d6d58c5545 | 5 years ago |
|
|
5a477848db | 5 years ago |
|
|
0fe6d880fd | 5 years ago |
|
|
3edb4f68cf | 5 years ago |
|
|
0afe3b3afd | 5 years ago |
|
|
931e21ed5e | 5 years ago |
|
|
d784310c82 | 5 years ago |
|
|
1f8a90e47b | 5 years ago |
|
|
5d0a7a4045 | 5 years ago |
|
|
c800219428 | 5 years ago |
|
|
60b5d474c1 | 5 years ago |
|
|
0747dfb1e0 | 5 years ago |
|
|
c671b60097 | 5 years ago |
|
|
25583d7bee | 5 years ago |
|
|
6989235334 | 5 years ago |
|
|
f535bf1eaa | 5 years ago |
|
|
d0173bd061 | 5 years ago |
|
|
66d12f8fdd | 5 years ago |
|
|
259ea53319 | 5 years ago |
|
|
b36ddf9317 | 5 years ago |
|
|
3d5bb08adf | 5 years ago |
|
|
a5ea23c38e | 5 years ago |
|
|
cc7fedc8f5 | 5 years ago |
|
|
08e104ca56 | 5 years ago |
|
|
ea0658c05c | 5 years ago |
|
|
91cd7f2b88 | 5 years ago |
|
|
01abd02d19 | 5 years ago |
|
|
6439bb9a90 | 5 years ago |
|
|
4af94e6b71 | 5 years ago |
|
|
7483f662f8 | 5 years ago |
|
|
d9358bce75 | 5 years ago |
|
|
4a9748739e | 5 years ago |
|
|
4dc72d6f35 | 5 years ago |
|
|
9b857c47be | 5 years ago |
|
|
7f2f065806 | 5 years ago |
|
|
cf69d34829 | 5 years ago |
|
|
a9a7c8a73c | 5 years ago |
|
|
ff18b8c0ae | 5 years ago |
|
|
8dbfb92f88 | 5 years ago |
|
|
9692e54b97 | 5 years ago |
|
|
fb6ef8fca1 | 5 years ago |
|
|
3107950f5c | 5 years ago |
|
|
151b69ed17 | 5 years ago |
|
|
a6a8f616e1 | 5 years ago |
|
|
df6a9a7822 | 5 years ago |
|
|
61ea94faeb | 5 years ago |
|
|
93796d18c7 | 5 years ago |
|
|
ad74ff00dc | 5 years ago |
|
|
bc48d915c6 | 5 years ago |
|
|
00aa2c3752 | 5 years ago |
|
|
519e6f2fec | 5 years ago |
|
|
19bac417c4 | 5 years ago |
|
|
49afc338d6 | 5 years ago |
|
|
9454e9d8af | 5 years ago |
|
|
dc6cbdd188 | 5 years ago |
|
|
8d2252ecdf | 5 years ago |
|
|
70020f35c9 | 5 years ago |
|
|
8e656dbc0d | 5 years ago |
|
|
97f7426aee | 5 years ago |
|
|
c82a4a05d5 | 5 years ago |
|
|
acffc72f56 | 5 years ago |
|
|
dc22e3f46e | 5 years ago |
|
|
2bff3a594c | 5 years ago |
|
|
13fb50f9be | 5 years ago |
|
|
0c0062091a | 5 years ago |
|
|
2fa8fa6177 | 5 years ago |
|
|
7595629c8b | 5 years ago |
|
|
4b6263ca1a | 5 years ago |
|
|
c7ce4676f6 | 5 years ago |
|
|
59e06f53af | 5 years ago |
|
|
8cdcd51086 | 5 years ago |
|
|
6e660883f6 | 5 years ago |
|
|
cbf7ccebe8 | 5 years ago |
|
|
1b2e158a24 | 5 years ago |
|
|
54fbe76c7b | 5 years ago |
|
|
d2dac36bd2 | 5 years ago |
|
|
e336821f65 | 5 years ago |
|
|
733c697a65 | 5 years ago |
|
|
3c325ef199 | 5 years ago |
|
|
80a751ae4d | 5 years ago |
|
|
982fc9a5c0 | 5 years ago |
|
|
eedaef4fa3 | 5 years ago |
|
|
b16dc2092b | 5 years ago |
|
|
2b6736ed6a | 5 years ago |
|
|
725c87fe8b | 5 years ago |
|
|
b06d286c81 | 5 years ago |
|
|
6119deb7a9 | 5 years ago |
|
|
390a34dd81 | 5 years ago |
|
|
c6e54d63f1 | 5 years ago |
|
|
f75ac64cad | 5 years ago |
|
|
e4fe0b38a0 | 5 years ago |
|
|
6620c79478 | 5 years ago |
|
|
38d0c6ddbd | 5 years ago |
|
|
cf10eba134 | 5 years ago |
|
|
a4b6d82023 | 5 years ago |
|
|
280f87e514 | 5 years ago |
|
|
e068009527 | 5 years ago |
|
|
8f6de2e02b | 5 years ago |
|
|
da45f8c4b6 | 5 years ago |
|
|
65ed8f1842 | 5 years ago |
|
|
ad036f7133 | 5 years ago |
|
|
819418e9f8 | 5 years ago |
|
|
281a807ec8 | 5 years ago |
|
|
362d9425cd | 5 years ago |
|
|
2b8caab71d | 5 years ago |
|
|
bf0ec6a035 | 5 years ago |
|
|
1391799101 | 5 years ago |
|
|
e89d5b13ab | 5 years ago |
|
|
169e5301a0 | 5 years ago |
|
|
7d423d9457 | 5 years ago |
|
|
ac701c9806 | 5 years ago |
|
|
837ff69c82 | 5 years ago |
|
|
764578f28e | 5 years ago |
|
|
ccdd6696be | 5 years ago |
|
|
69eca869b4 | 5 years ago |
|
|
fdc23ce932 | 5 years ago |
|
|
401516403f | 5 years ago |
|
|
2b429e6679 | 5 years ago |
|
|
1051a704bc | 5 years ago |
|
|
368d94168b | 5 years ago |
|
|
361596a973 | 5 years ago |
|
|
e5de65bc82 | 5 years ago |
|
|
947423a285 | 5 years ago |
|
|
4136546550 | 5 years ago |
|
|
a12ae0e0d1 | 5 years ago |
|
|
e341a6eb4d | 5 years ago |
|
|
8e5316a0b0 | 5 years ago |
@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.hmg.hmgDr">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@ -1,15 +0,0 @@
|
||||
package com.example.doctor_app_flutter.Service;
|
||||
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel;
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel;
|
||||
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface SessionStatusAPI {
|
||||
|
||||
@POST("LiveCareApi/DoctorApp/GetSessionStatus")
|
||||
Call<SessionStatusModel> getSessionStatusModelData(@Body GetSessionStatusModel getSessionStatusModel);
|
||||
}
|
||||
@ -1,430 +0,0 @@
|
||||
package com.example.doctor_app_flutter.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel;
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel;
|
||||
import com.hmg.hmgDr.R;
|
||||
import com.opentok.android.Session;
|
||||
import com.opentok.android.Stream;
|
||||
import com.opentok.android.Publisher;
|
||||
import com.opentok.android.PublisherKit;
|
||||
import com.opentok.android.Subscriber;
|
||||
import com.opentok.android.BaseVideoRenderer;
|
||||
import com.opentok.android.OpentokError;
|
||||
import com.opentok.android.SubscriberKit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import pub.devrel.easypermissions.AfterPermissionGranted;
|
||||
import pub.devrel.easypermissions.AppSettingsDialog;
|
||||
import pub.devrel.easypermissions.EasyPermissions;
|
||||
|
||||
public class VideoCallActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,
|
||||
Session.SessionListener,
|
||||
Publisher.PublisherListener,
|
||||
Subscriber.VideoListener, VideoCallContract.VideoCallView {
|
||||
|
||||
private static final String TAG = VideoCallActivity.class.getSimpleName();
|
||||
|
||||
VideoCallContract.VideoCallPresenter videoCallPresenter;
|
||||
|
||||
private static final int RC_SETTINGS_SCREEN_PERM = 123;
|
||||
private static final int RC_VIDEO_APP_PERM = 124;
|
||||
|
||||
|
||||
private Session mSession;
|
||||
private Publisher mPublisher;
|
||||
private Subscriber mSubscriber;
|
||||
|
||||
private Handler mVolHandler, mConnectedHandler;
|
||||
private Runnable mVolRunnable, mConnectedRunnable;
|
||||
|
||||
private FrameLayout mPublisherViewContainer;
|
||||
private RelativeLayout mSubscriberViewContainer;
|
||||
private RelativeLayout controlPanel;
|
||||
|
||||
private String apiKey;
|
||||
private String sessionId;
|
||||
private String token;
|
||||
private String appLang;
|
||||
private String baseUrl;
|
||||
|
||||
private boolean isSwitchCameraClicked;
|
||||
private boolean isCameraClicked;
|
||||
private boolean isSpeckerClicked;
|
||||
private boolean isMicClicked;
|
||||
|
||||
private ImageView mCallBtn;
|
||||
private ImageView mCameraBtn;
|
||||
private ImageView mSwitchCameraBtn;
|
||||
private ImageView mspeckerBtn;
|
||||
private ImageView mMicBtn;
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private CountDownTimer countDownTimer;
|
||||
private TextView progressBarTextView;
|
||||
private RelativeLayout progressBarLayout;
|
||||
|
||||
private boolean isConnected = false;
|
||||
|
||||
private GetSessionStatusModel sessionStatusModel;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(R.style.AppTheme);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_video_call);
|
||||
Objects.requireNonNull(getSupportActionBar()).hide();
|
||||
initUI();
|
||||
requestPermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (mSession == null) {
|
||||
return;
|
||||
}
|
||||
mSession.onPause();
|
||||
|
||||
if (isFinishing()) {
|
||||
disconnectSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mSession == null) {
|
||||
return;
|
||||
}
|
||||
mSession.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
disconnectSession();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initUI() {
|
||||
mPublisherViewContainer = (FrameLayout) findViewById(R.id.local_video_view_container);
|
||||
mSubscriberViewContainer = (RelativeLayout) findViewById(R.id.remote_video_view_container);
|
||||
|
||||
apiKey = getIntent().getStringExtra("apiKey");
|
||||
sessionId = getIntent().getStringExtra("sessionId");
|
||||
token = getIntent().getStringExtra("token");
|
||||
appLang = getIntent().getStringExtra("appLang");
|
||||
baseUrl = getIntent().getStringExtra("baseUrl");
|
||||
sessionStatusModel = getIntent().getParcelableExtra("sessionStatusModel");
|
||||
|
||||
controlPanel = findViewById(R.id.control_panel);
|
||||
|
||||
videoCallPresenter = new VideoCallPresenterImpl(this, baseUrl);
|
||||
|
||||
|
||||
mCallBtn = findViewById(R.id.btn_call);
|
||||
mCameraBtn = findViewById(R.id.btn_camera);
|
||||
mSwitchCameraBtn = findViewById(R.id.btn_switch_camera);
|
||||
mspeckerBtn = findViewById(R.id.btn_specker);
|
||||
mMicBtn = findViewById(R.id.btn_mic);
|
||||
|
||||
// progressBarLayout=findViewById(R.id.progressBar);
|
||||
// progressBar=findViewById(R.id.progress_bar);
|
||||
// progressBarTextView=findViewById(R.id.progress_bar_text);
|
||||
// progressBar.setVisibility(View.GONE);
|
||||
|
||||
hiddenButtons();
|
||||
|
||||
checkClientConnected();
|
||||
|
||||
mSubscriberViewContainer.setOnTouchListener((v, event) -> {
|
||||
controlPanel.setVisibility(View.VISIBLE);
|
||||
mVolHandler.removeCallbacks(mVolRunnable);
|
||||
mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (appLang.equals("ar")) {
|
||||
progressBarLayout.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void checkClientConnected() {
|
||||
mConnectedHandler = new Handler();
|
||||
mConnectedRunnable = () -> {
|
||||
if (!isConnected) {
|
||||
videoCallPresenter.callClintConnected(sessionStatusModel);
|
||||
}
|
||||
};
|
||||
mConnectedHandler.postDelayed(mConnectedRunnable, 30 * 1000);
|
||||
|
||||
}
|
||||
|
||||
private void hiddenButtons() {
|
||||
mVolHandler = new Handler();
|
||||
mVolRunnable = new Runnable() {
|
||||
public void run() {
|
||||
controlPanel.setVisibility(View.GONE);
|
||||
}
|
||||
};
|
||||
mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsGranted(int requestCode, List<String> perms) {
|
||||
Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsDenied(int requestCode, List<String> perms) {
|
||||
Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
|
||||
|
||||
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
|
||||
new AppSettingsDialog.Builder(this)
|
||||
.setTitle(getString(R.string.title_settings_dialog))
|
||||
.setRationale(getString(R.string.rationale_ask_again))
|
||||
.setPositiveButton(getString(R.string.setting))
|
||||
.setNegativeButton(getString(R.string.cancel))
|
||||
.setRequestCode(RC_SETTINGS_SCREEN_PERM)
|
||||
.build()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterPermissionGranted(RC_VIDEO_APP_PERM)
|
||||
private void requestPermissions() {
|
||||
String[] perms = {Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
|
||||
if (EasyPermissions.hasPermissions(this, perms)) {
|
||||
mSession = new Session.Builder(VideoCallActivity.this, apiKey, sessionId).build();
|
||||
mSession.setSessionListener(this);
|
||||
mSession.connect(token);
|
||||
} else {
|
||||
EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, perms);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(Session session) {
|
||||
Log.i(TAG, "Session Connected");
|
||||
|
||||
mPublisher = new Publisher.Builder(this).build();
|
||||
mPublisher.setPublisherListener(this);
|
||||
|
||||
mPublisherViewContainer.addView(mPublisher.getView());
|
||||
|
||||
if (mPublisher.getView() instanceof GLSurfaceView) {
|
||||
((GLSurfaceView) mPublisher.getView()).setZOrderOnTop(true);
|
||||
}
|
||||
|
||||
mSession.publish(mPublisher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(Session session) {
|
||||
Log.d(TAG, "onDisconnected: disconnected from session " + session.getSessionId());
|
||||
|
||||
mSession = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Session session, OpentokError opentokError) {
|
||||
Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in session " + session.getSessionId());
|
||||
|
||||
Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamReceived(Session session, Stream stream) {
|
||||
Log.d(TAG, "onStreamReceived: New stream " + stream.getStreamId() + " in session " + session.getSessionId());
|
||||
if (mSubscriber != null) {
|
||||
isConnected = true;
|
||||
return;
|
||||
}
|
||||
isConnected = true;
|
||||
subscribeToStream(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamDropped(Session session, Stream stream) {
|
||||
Log.d(TAG, "onStreamDropped: Stream " + stream.getStreamId() + " dropped from session " + session.getSessionId());
|
||||
|
||||
if (mSubscriber == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSubscriber.getStream().equals(stream)) {
|
||||
mSubscriberViewContainer.removeView(mSubscriber.getView());
|
||||
mSubscriber.destroy();
|
||||
mSubscriber = null;
|
||||
}
|
||||
disconnectSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamCreated(PublisherKit publisherKit, Stream stream) {
|
||||
Log.d(TAG, "onStreamCreated: Own stream " + stream.getStreamId() + " created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) {
|
||||
Log.d(TAG, "onStreamDestroyed: Own stream " + stream.getStreamId() + " destroyed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(PublisherKit publisherKit, OpentokError opentokError) {
|
||||
Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in publisher");
|
||||
|
||||
Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDataReceived(SubscriberKit subscriberKit) {
|
||||
mSubscriber.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL);
|
||||
mSubscriberViewContainer.addView(mSubscriber.getView());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDisabled(SubscriberKit subscriberKit, String s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoEnabled(SubscriberKit subscriberKit, String s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDisableWarning(SubscriberKit subscriberKit) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDisableWarningLifted(SubscriberKit subscriberKit) {
|
||||
|
||||
}
|
||||
|
||||
private void subscribeToStream(Stream stream) {
|
||||
mSubscriber = new Subscriber.Builder(VideoCallActivity.this, stream).build();
|
||||
mSubscriber.setVideoListener(this);
|
||||
mSession.subscribe(mSubscriber);
|
||||
}
|
||||
|
||||
private void disconnectSession() {
|
||||
if (mSession == null) {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSubscriber != null) {
|
||||
mSubscriberViewContainer.removeView(mSubscriber.getView());
|
||||
mSession.unsubscribe(mSubscriber);
|
||||
mSubscriber.destroy();
|
||||
mSubscriber = null;
|
||||
}
|
||||
|
||||
if (mPublisher != null) {
|
||||
mPublisherViewContainer.removeView(mPublisher.getView());
|
||||
mSession.unpublish(mPublisher);
|
||||
mPublisher.destroy();
|
||||
mPublisher = null;
|
||||
}
|
||||
mSession.disconnect();
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer.cancel();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
public void onSwitchCameraClicked(View view) {
|
||||
if (mPublisher != null) {
|
||||
isSwitchCameraClicked = !isSwitchCameraClicked;
|
||||
mPublisher.cycleCamera();
|
||||
int res = isSwitchCameraClicked ? R.drawable.flip_disapled : R.drawable.flip_enabled;
|
||||
mSwitchCameraBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCameraClicked(View view) {
|
||||
if (mPublisher != null) {
|
||||
isCameraClicked = !isCameraClicked;
|
||||
mPublisher.setPublishVideo(!isCameraClicked);
|
||||
int res = isCameraClicked ? R.drawable.video_disanabled : R.drawable.video_enabled;
|
||||
mCameraBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSpeckerClicked(View view) {
|
||||
if (mSubscriber != null) {
|
||||
isSpeckerClicked = !isSpeckerClicked;
|
||||
mSubscriber.setSubscribeToAudio(!isSpeckerClicked);
|
||||
int res = isSpeckerClicked ? R.drawable.audio_disabled : R.drawable.audio_enabled;
|
||||
mspeckerBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMicClicked(View view) {
|
||||
|
||||
if (mPublisher != null) {
|
||||
isMicClicked = !isMicClicked;
|
||||
mPublisher.setPublishAudio(!isMicClicked);
|
||||
int res = isMicClicked ? R.drawable.mic_disabled : R.drawable.mic_enabled;
|
||||
mMicBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCallClicked(View view) {
|
||||
disconnectSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallSuccessful(SessionStatusModel sessionStatusModel) {
|
||||
if (sessionStatusModel.getSessionStatus() == 2 || sessionStatusModel.getSessionStatus() == 3) {
|
||||
Intent returnIntent = new Intent();
|
||||
returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel);
|
||||
setResult(Activity.RESULT_OK, returnIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package com.example.doctor_app_flutter.ui;
|
||||
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel;
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel;
|
||||
|
||||
public interface VideoCallContract {
|
||||
|
||||
interface VideoCallView{
|
||||
|
||||
void onCallSuccessful(SessionStatusModel sessionStatusModel);
|
||||
void onFailure();
|
||||
}
|
||||
|
||||
interface VideoCallPresenter {
|
||||
|
||||
void callClintConnected(GetSessionStatusModel statusModel);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.hmg.hmgDr
|
||||
|
||||
import com.hmg.hmgDr.globalErrorHandler.LoggingExceptionHandler
|
||||
import io.flutter.app.FlutterApplication
|
||||
|
||||
class AppApplication : FlutterApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// LoggingExceptionHandler(this, "ErrorFile")
|
||||
}
|
||||
}
|
||||
@ -1,101 +1,284 @@
|
||||
package com.hmg.hmgDr
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.NonNull
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel
|
||||
import com.example.doctor_app_flutter.ui.VideoCallActivity
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.hmg.hmgDr.Service.VideoStreamFloatingWidgetService
|
||||
import com.hmg.hmgDr.model.GetSessionStatusModel
|
||||
import com.hmg.hmgDr.model.SessionStatusModel
|
||||
import com.hmg.hmgDr.ui.VideoCallResponseListener
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||
import pub.devrel.easypermissions.AfterPermissionGranted
|
||||
import pub.devrel.easypermissions.AppSettingsDialog
|
||||
import pub.devrel.easypermissions.EasyPermissions
|
||||
|
||||
class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler {
|
||||
|
||||
class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
|
||||
VideoCallResponseListener {
|
||||
|
||||
/* Permission request code to draw over other apps */
|
||||
private val DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE = 1222
|
||||
|
||||
private val CHANNEL = "Dr.cloudSolution/videoCall"
|
||||
private lateinit var methodChannel: MethodChannel
|
||||
private var result: MethodChannel.Result? = null
|
||||
private var call: MethodCall? = null
|
||||
private val LAUNCH_VIDEO: Int = 1
|
||||
|
||||
private var serviceIntent: Intent? = null
|
||||
private var videoStreamService: VideoStreamFloatingWidgetService? = null
|
||||
private var bound = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine)
|
||||
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler(this)
|
||||
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
methodChannel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
|
||||
this.result = result
|
||||
this.call = call
|
||||
|
||||
if (call.method == "openVideoCall") {
|
||||
val apiKey = call.argument<String>("kApiKey")
|
||||
val sessionId = call.argument<String>("kSessionId")
|
||||
val token = call.argument<String>("kToken")
|
||||
val appLang = call.argument<String>("appLang")
|
||||
val baseUrl = call.argument<String>("baseUrl")
|
||||
when (call.method) {
|
||||
"openVideoCall" -> {
|
||||
val apiKey = call.argument<String>("kApiKey")
|
||||
val sessionId = call.argument<String>("kSessionId")
|
||||
val token = call.argument<String>("kToken")
|
||||
val appLang = call.argument<String>("appLang")
|
||||
val baseUrl = call.argument<String>("baseUrl")
|
||||
|
||||
// Session Status model
|
||||
val VC_ID = call.argument<Int>("VC_ID")
|
||||
val tokenID = call.argument<String>("TokenID")
|
||||
val generalId = call.argument<String>("generalId")
|
||||
val doctorId = call.argument<Int>("DoctorId")
|
||||
// Session Status model
|
||||
val VC_ID = call.argument<Int>("VC_ID")
|
||||
val tokenID = call.argument<String>("TokenID")
|
||||
val generalId = call.argument<String>("generalId")
|
||||
val doctorId = call.argument<Int>("DoctorId")
|
||||
val patientName = call.argument<String>("patientName")
|
||||
val isRecording = call.argument<Boolean>("isRecording")
|
||||
|
||||
val sessionStatusModel = GetSessionStatusModel(VC_ID, tokenID, generalId, doctorId)
|
||||
val sessionStatusModel =
|
||||
GetSessionStatusModel(
|
||||
VC_ID,
|
||||
tokenID,
|
||||
generalId,
|
||||
doctorId,
|
||||
patientName,
|
||||
isRecording!!
|
||||
)
|
||||
|
||||
|
||||
openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel)
|
||||
openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel)
|
||||
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
"closeVideoCall" -> {
|
||||
videoStreamService?.closeVideoCall()
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?, appLang: String?, baseUrl: String?, sessionStatusModel: GetSessionStatusModel) {
|
||||
// val videoCallActivity = VideoCallActivity()
|
||||
private fun openVideoCall(
|
||||
apiKey: String?,
|
||||
sessionId: String?,
|
||||
token: String?,
|
||||
appLang: String?,
|
||||
baseUrl: String?,
|
||||
sessionStatusModel: GetSessionStatusModel
|
||||
) {
|
||||
|
||||
val arguments = Bundle()
|
||||
arguments.putString("apiKey", apiKey)
|
||||
arguments.putString("sessionId", sessionId)
|
||||
arguments.putString("token", token)
|
||||
arguments.putString("appLang", appLang)
|
||||
arguments.putString("baseUrl", baseUrl)
|
||||
arguments.putParcelable("sessionStatusModel", sessionStatusModel)
|
||||
|
||||
// start service
|
||||
// serviceIntent = Intent(this@MainActivity, VideoStreamContainerService::class.java)
|
||||
if (videoStreamService == null || videoStreamService?.serviceRunning == false) {
|
||||
serviceIntent = Intent(this@MainActivity, VideoStreamFloatingWidgetService::class.java)
|
||||
serviceIntent?.run {
|
||||
putExtras(arguments)
|
||||
action = VideoStreamFloatingWidgetService.ACTION_START_CALL
|
||||
}
|
||||
checkFloatingWidgetPermission()
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(this, VideoCallActivity::class.java)
|
||||
intent.putExtra("apiKey", apiKey)
|
||||
intent.putExtra("sessionId", sessionId)
|
||||
intent.putExtra("token", token)
|
||||
intent.putExtra("appLang", appLang)
|
||||
intent.putExtra("baseUrl", baseUrl)
|
||||
intent.putExtra("sessionStatusModel", sessionStatusModel)
|
||||
startActivityForResult(intent, LAUNCH_VIDEO)
|
||||
private fun checkFloatingWidgetPermission() {
|
||||
// Check if the application has draw over other apps permission or not?
|
||||
// This permission is by default available for API<23. But for API > 23
|
||||
// you have to ask for the permission in runtime.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
|
||||
//If the draw over permission is not available open the settings screen
|
||||
//to grant the permission.
|
||||
val intent = Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:$packageName")
|
||||
)
|
||||
startActivityForResult(intent, DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE)
|
||||
} else { //If permission is granted start floating widget service
|
||||
startFloatingWidgetService()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startFloatingWidgetService() {
|
||||
startService(serviceIntent)
|
||||
bindService()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (bound) {
|
||||
unbindService()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
var asd = "";
|
||||
if (requestCode == LAUNCH_VIDEO) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val result : SessionStatusModel? = data?.getParcelableExtra("sessionStatusNotRespond")
|
||||
val callResponse : HashMap<String, String> = HashMap()
|
||||
if (requestCode == DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE) {
|
||||
//Check if the permission is granted or not.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
startFloatingWidgetService()
|
||||
} else {
|
||||
//Permission is not available then display toast
|
||||
Toast.makeText(
|
||||
this,
|
||||
"Draw over other app permission not available. App won\\'t work without permission. Please try again.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
startFloatingWidgetService()
|
||||
}
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCallFinished(resultCode: Int, intent: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val result: SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond")
|
||||
val callResponse: HashMap<String, String> = HashMap()
|
||||
|
||||
val sessionStatus : HashMap<String, String> = HashMap()
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
val sessionStatus: HashMap<String, String> = HashMap()
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
|
||||
callResponse["callResponse"] = "CallNotRespond"
|
||||
val jsonRes = gson.toJson(result)
|
||||
callResponse["sessionStatus"] = jsonRes
|
||||
callResponse["callResponse"] = "CallNotRespond"
|
||||
val jsonRes = gson.toJson(result)
|
||||
callResponse["sessionStatus"] = jsonRes
|
||||
|
||||
try {
|
||||
this.result?.success(callResponse)
|
||||
} catch (e: Exception) {
|
||||
Log.e("onVideoCallFinished", "${e.message}.")
|
||||
}
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
val callResponse : HashMap<String, String> = HashMap()
|
||||
callResponse["callResponse"] = "CallEnd"
|
||||
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
val callResponse: HashMap<String, String> = HashMap()
|
||||
callResponse["callResponse"] = "CallEnd"
|
||||
try {
|
||||
result?.success(callResponse)
|
||||
} catch (e: Exception) {
|
||||
Log.e("onVideoCallFinished", "${e.message}.")
|
||||
}
|
||||
}
|
||||
|
||||
stopService(serviceIntent)
|
||||
}
|
||||
|
||||
override fun minimizeVideoEvent(isMinimize: Boolean) {
|
||||
if (isMinimize)
|
||||
methodChannel.invokeMethod("onCallConnected", null)
|
||||
else {
|
||||
methodChannel.invokeMethod("onCallDisconnected", null)
|
||||
unbindService()
|
||||
videoStreamService?.serviceRunning = false
|
||||
videoStreamService = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (videoStreamService != null && videoStreamService?.serviceRunning == true && videoStreamService?.isFullScreen!!) {
|
||||
videoStreamService!!.onMinimizedClicked()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (videoStreamService != null && videoStreamService?.serviceRunning == true && videoStreamService?.isFullScreen!!) {
|
||||
videoStreamService!!.onMinimizedClicked()
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
// override fun onStart() {
|
||||
// super.onStart()
|
||||
// bindService()
|
||||
// }
|
||||
//
|
||||
// override fun onStop() {
|
||||
// super.onStop()
|
||||
// unbindService()
|
||||
// }
|
||||
|
||||
private fun bindService() {
|
||||
serviceIntent?.run {
|
||||
if (videoStreamService != null && !videoStreamService!!.serviceRunning) {
|
||||
startService(this)
|
||||
}
|
||||
bindService(this, serviceConnection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unbindService() {
|
||||
if (bound) {
|
||||
videoStreamService?.videoCallResponseListener = null // unregister
|
||||
unbindService(serviceConnection)
|
||||
bound = false
|
||||
}
|
||||
}
|
||||
|
||||
private val serviceConnection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
// val binder: VideoStreamContainerService.VideoStreamBinder =
|
||||
// service as VideoStreamContainerService.VideoStreamBinder
|
||||
val binder: VideoStreamFloatingWidgetService.VideoStreamBinder =
|
||||
service as VideoStreamFloatingWidgetService.VideoStreamBinder
|
||||
videoStreamService = binder.service
|
||||
bound = true
|
||||
videoStreamService!!.videoCallResponseListener = this@MainActivity // register
|
||||
videoStreamService?.serviceRunning = true
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
bound = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.example.doctor_app_flutter.Service;
|
||||
package com.hmg.hmgDr.Service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
@ -0,0 +1,234 @@
|
||||
package com.hmg.hmgDr.Service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Point
|
||||
import android.os.Build
|
||||
import android.os.CountDownTimer
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.core.view.GestureDetectorCompat
|
||||
import com.hmg.hmgDr.R
|
||||
import com.hmg.hmgDr.util.ViewsUtil
|
||||
|
||||
abstract class BaseMovingFloatingWidget : Service() {
|
||||
|
||||
val szWindow = Point()
|
||||
lateinit var windowManagerParams: WindowManager.LayoutParams
|
||||
var mWindowManager: WindowManager? = null
|
||||
var floatingWidgetView: View? = null
|
||||
lateinit var floatingViewContainer: View
|
||||
|
||||
lateinit var mDetector: GestureDetectorCompat
|
||||
|
||||
private var xInitCord = 0
|
||||
private var yInitCord: Int = 0
|
||||
private var xInitMargin: Int = 0
|
||||
private var yInitMargin: Int = 0
|
||||
|
||||
/* Add Floating Widget View to Window Manager */
|
||||
open fun addFloatingWidgetView() {
|
||||
mWindowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
|
||||
//Init LayoutInflater
|
||||
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
//Inflate the removing view layout we created
|
||||
floatingWidgetView = inflater.inflate(R.layout.activity_video_call, null)
|
||||
|
||||
//Add the view to the window.
|
||||
windowManagerParams =
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_PHONE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
)
|
||||
} else {
|
||||
WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
)
|
||||
}
|
||||
|
||||
//Specify the view position
|
||||
windowManagerParams.gravity = Gravity.TOP or Gravity.START
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
val dragListener: View.OnTouchListener = View.OnTouchListener { _, event ->
|
||||
mDetector.onTouchEvent(event)
|
||||
|
||||
//Get Floating widget view params
|
||||
val layoutParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
//get the touch location coordinates
|
||||
val x_cord = event.rawX.toInt()
|
||||
val y_cord = event.rawY.toInt()
|
||||
val x_cord_Destination: Int
|
||||
var y_cord_Destination: Int
|
||||
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
xInitCord = x_cord
|
||||
yInitCord = y_cord
|
||||
|
||||
//remember the initial position.
|
||||
xInitMargin = layoutParams.x
|
||||
yInitMargin = layoutParams.y
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
//Get the difference between initial coordinate and current coordinate
|
||||
val x_diff: Int = x_cord - xInitCord
|
||||
val y_diff: Int = y_cord - yInitCord
|
||||
|
||||
y_cord_Destination = yInitMargin + y_diff
|
||||
val barHeight: Int = ViewsUtil.getStatusBarHeight(applicationContext)
|
||||
if (y_cord_Destination < 0) {
|
||||
y_cord_Destination = 0
|
||||
// y_cord_Destination =
|
||||
// -(szWindow.y - (videoCallContainer.height /*+ barHeight*/))
|
||||
// y_cord_Destination = -(szWindow.y / 2)
|
||||
} else if (y_cord_Destination + (floatingViewContainer.height + barHeight) > szWindow.y) {
|
||||
y_cord_Destination = szWindow.y - (floatingViewContainer.height + barHeight)
|
||||
// y_cord_Destination = (szWindow.y / 2)
|
||||
}
|
||||
layoutParams.y = y_cord_Destination
|
||||
|
||||
//reset position if user drags the floating view
|
||||
resetPosition(x_cord)
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val x_diff_move: Int = x_cord - xInitCord
|
||||
val y_diff_move: Int = y_cord - yInitCord
|
||||
x_cord_Destination = xInitMargin + x_diff_move
|
||||
y_cord_Destination = yInitMargin + y_diff_move
|
||||
|
||||
layoutParams.x = x_cord_Destination
|
||||
layoutParams.y = y_cord_Destination
|
||||
|
||||
//Update the layout with new X & Y coordinate
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, layoutParams)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/**
|
||||
* OnTouch actions
|
||||
*/
|
||||
class MyGestureListener(
|
||||
val onTabCall: () -> Unit,
|
||||
val miniCircleDoubleTap: () -> Unit
|
||||
) : GestureDetector.SimpleOnGestureListener() {
|
||||
|
||||
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
|
||||
// onTabCall()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||
miniCircleDoubleTap()
|
||||
return super.onDoubleTap(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Reset position of Floating Widget view on dragging */
|
||||
fun resetPosition(x_cord_now: Int) {
|
||||
if (x_cord_now <= szWindow.x / 2) {
|
||||
moveToLeft(x_cord_now)
|
||||
} else {
|
||||
moveToRight(x_cord_now)
|
||||
}
|
||||
}
|
||||
|
||||
/* Method to move the Floating widget view to Left */
|
||||
private fun moveToLeft(current_x_cord: Int) {
|
||||
|
||||
val mParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
|
||||
mParams.x =
|
||||
(szWindow.x - current_x_cord * current_x_cord - floatingViewContainer.width).toInt()
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
val x = szWindow.x - current_x_cord
|
||||
object : CountDownTimer(500, 5) {
|
||||
//get params of Floating Widget view
|
||||
val mParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
|
||||
override fun onTick(t: Long) {
|
||||
val step = (500 - t) / 5
|
||||
// mParams.x = 0 - (current_x_cord * current_x_cord * step).toInt()
|
||||
mParams.x =
|
||||
(szWindow.x - current_x_cord * current_x_cord * step - floatingViewContainer.width).toInt()
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
mParams.x = -(szWindow.x - floatingViewContainer.width)
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
/* Method to move the Floating widget view to Right */
|
||||
private fun moveToRight(current_x_cord: Int) {
|
||||
object : CountDownTimer(500, 5) {
|
||||
//get params of Floating Widget view
|
||||
val mParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
|
||||
override fun onTick(t: Long) {
|
||||
val step = (500 - t) / 5
|
||||
mParams.x =
|
||||
(szWindow.x + current_x_cord * current_x_cord * step - floatingViewContainer.width).toInt()
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
mParams.x = szWindow.x - floatingViewContainer.width
|
||||
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
/***
|
||||
* Utils
|
||||
*/
|
||||
|
||||
fun getWindowManagerDefaultDisplay() {
|
||||
val w = mWindowManager!!.defaultDisplay.width
|
||||
val h = mWindowManager!!.defaultDisplay.height
|
||||
szWindow[w] = h
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.hmg.hmgDr.Service;
|
||||
|
||||
import com.hmg.hmgDr.model.ChangeCallStatusRequestModel;
|
||||
import com.hmg.hmgDr.model.GetSessionStatusModel;
|
||||
import com.hmg.hmgDr.model.SessionStatusModel;
|
||||
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface SessionStatusAPI {
|
||||
|
||||
@POST("LiveCareApi/DoctorApp/GetSessionStatus")
|
||||
Call<SessionStatusModel> getSessionStatusModelData(@Body GetSessionStatusModel getSessionStatusModel);
|
||||
|
||||
@POST("LiveCareApi/DoctorApp/ChangeCallStatus")
|
||||
Call<SessionStatusModel> changeCallStatus(@Body ChangeCallStatusRequestModel changeCallStatusRequestModel);
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package com.hmg.hmgDr.Service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import com.hmg.hmgDr.MainActivity
|
||||
import com.hmg.hmgDr.ui.VideoCallResponseListener
|
||||
import com.hmg.hmgDr.ui.fragment.VideoCallFragment
|
||||
|
||||
class VideoStreamContainerService : Service(), VideoCallResponseListener {
|
||||
|
||||
var videoCallResponseListener: VideoCallResponseListener? = null
|
||||
var mActivity: MainActivity? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (field != null) {
|
||||
setDialogFragment()
|
||||
}
|
||||
}
|
||||
var arguments: Bundle? = null
|
||||
var serviceRunning: Boolean = false
|
||||
|
||||
|
||||
private val serviceBinder: IBinder = VideoStreamBinder()
|
||||
private var dialogFragment: VideoCallFragment? = null
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return serviceBinder
|
||||
}
|
||||
|
||||
private fun setDialogFragment() {
|
||||
mActivity?.run {
|
||||
if (dialogFragment == null) {
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
dialogFragment = VideoCallFragment.newInstance(arguments!!)
|
||||
dialogFragment?.let {
|
||||
it.setCallListener(this@VideoStreamContainerService)
|
||||
it.isCancelable = true
|
||||
if (it.isAdded) {
|
||||
it.dismiss()
|
||||
} else {
|
||||
it.show(transaction, "dialog")
|
||||
}
|
||||
}
|
||||
} else if (!dialogFragment!!.isVisible) {
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
dialogFragment!!.show(transaction, "dialog")
|
||||
} else {
|
||||
// don't do anything
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeVideoCall(){
|
||||
dialogFragment?.onCallClicked()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent != null && intent.extras != null) {
|
||||
arguments = intent.extras
|
||||
}
|
||||
// Toast.makeText(this, "Service started by user.", Toast.LENGTH_LONG).show()
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// Toast.makeText(this, "Service destroyed by user.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
inner class VideoStreamBinder : Binder() {
|
||||
val service: VideoStreamContainerService
|
||||
get() = this@VideoStreamContainerService
|
||||
}
|
||||
|
||||
override fun onCallFinished(resultCode: Int, intent: Intent?) {
|
||||
dialogFragment = null
|
||||
videoCallResponseListener?.onCallFinished(resultCode, intent)
|
||||
}
|
||||
|
||||
override fun errorHandle(message: String) {
|
||||
dialogFragment = null
|
||||
// Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun minimizeVideoEvent(isMinimize: Boolean) {
|
||||
videoCallResponseListener?.minimizeVideoEvent(isMinimize)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import android.os.Environment
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
object FileUtil {
|
||||
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
|
||||
|
||||
fun pushLog(body: String?) {
|
||||
try {
|
||||
val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
|
||||
val time = SimpleDateFormat("HH:MM:SS", Locale.getDefault()).format(Date())
|
||||
val root =
|
||||
File(Environment.getExternalStorageDirectory(),"error_log_dir")
|
||||
// if external memory exists and folder with name Notes
|
||||
if (!root.exists()) {
|
||||
root.mkdirs() // this will create folder.
|
||||
}
|
||||
val oldFile = File(root, "error" + sdf.format(Date()).toString() + ".txt") // old file
|
||||
if (oldFile.exists()) oldFile.delete()
|
||||
val filepath = File(root, "error$date.txt") // file path to save
|
||||
val bufferedWriter = BufferedWriter(FileWriter(filepath, true))
|
||||
bufferedWriter.append("\r\n")
|
||||
bufferedWriter.append("\r\n").append(body).append(" Time : ").append(time)
|
||||
bufferedWriter.flush()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.hmg.hmgDr.MainActivity
|
||||
import com.hmg.hmgDr.globalErrorHandler.FileUtil.pushLog
|
||||
|
||||
|
||||
class LoggingExceptionHandler(private val context: Context, ErrorFile: String) :
|
||||
Thread.UncaughtExceptionHandler {
|
||||
private val rootHandler: Thread.UncaughtExceptionHandler
|
||||
override fun uncaughtException(t: Thread, e: Throwable) {
|
||||
object : Thread() {
|
||||
override fun run() {
|
||||
pushLog("UnCaught Exception is thrown in $error$e")
|
||||
try {
|
||||
sleep(500)
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
context.startActivity(intent)
|
||||
} catch (e1: Exception) {
|
||||
e1.printStackTrace()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
rootHandler.uncaughtException(t, e)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = LoggingExceptionHandler::class.java.simpleName
|
||||
lateinit var error: String
|
||||
}
|
||||
|
||||
init {
|
||||
error = "$ErrorFile.error "
|
||||
rootHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler(this)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class UCEDefaultActivity : AppCompatActivity() {
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import androidx.core.content.FileProvider
|
||||
|
||||
class UCEFileProvider : FileProvider() {
|
||||
}
|
||||
@ -0,0 +1,280 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Date;
|
||||
import java.util.Deque;
|
||||
import java.util.Locale;
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class UCEHandler(val builder: Builder) {
|
||||
|
||||
val EXTRA_STACK_TRACE = "EXTRA_STACK_TRACE"
|
||||
val EXTRA_ACTIVITY_LOG = "EXTRA_ACTIVITY_LOG"
|
||||
private val TAG = "UCEHandler"
|
||||
private val UCE_HANDLER_PACKAGE_NAME = "com.rohitss.uceh"
|
||||
private val DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os"
|
||||
private val MAX_STACK_TRACE_SIZE = 131071 //128 KB - 1
|
||||
|
||||
private val MAX_ACTIVITIES_IN_LOG = 50
|
||||
private val SHARED_PREFERENCES_FILE = "uceh_preferences"
|
||||
private val SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp"
|
||||
private val activityLog: Deque<String> = ArrayDeque(MAX_ACTIVITIES_IN_LOG)
|
||||
var COMMA_SEPARATED_EMAIL_ADDRESSES: String? = null
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private var application: Application? = null
|
||||
private var isInBackground = true
|
||||
private var isBackgroundMode = false
|
||||
private var isUCEHEnabled = false
|
||||
private var isTrackActivitiesEnabled = false
|
||||
private var lastActivityCreated: WeakReference<Activity?> = WeakReference(null)
|
||||
|
||||
fun UCEHandler(builder: Builder) {
|
||||
isUCEHEnabled = builder.isUCEHEnabled
|
||||
isTrackActivitiesEnabled = builder.isTrackActivitiesEnabled
|
||||
isBackgroundMode = builder.isBackgroundModeEnabled
|
||||
COMMA_SEPARATED_EMAIL_ADDRESSES = builder.commaSeparatedEmailAddresses
|
||||
setUCEHandler(builder.context)
|
||||
}
|
||||
|
||||
private fun setUCEHandler(context: Context?) {
|
||||
try {
|
||||
if (context != null) {
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
if (oldHandler != null && oldHandler.javaClass.name.startsWith(
|
||||
UCE_HANDLER_PACKAGE_NAME
|
||||
)
|
||||
) {
|
||||
Log.e(TAG, "UCEHandler was already installed, doing nothing!")
|
||||
} else {
|
||||
if (oldHandler != null && !oldHandler.javaClass.name.startsWith(
|
||||
DEFAULT_HANDLER_PACKAGE_NAME
|
||||
)
|
||||
) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"You already have an UncaughtExceptionHandler. If you use a custom UncaughtExceptionHandler, it should be initialized after UCEHandler! Installing anyway, but your original handler will not be called."
|
||||
)
|
||||
}
|
||||
application = context.getApplicationContext() as Application
|
||||
//Setup UCE Handler.
|
||||
Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler { thread, throwable ->
|
||||
if (isUCEHEnabled) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"App crashed, executing UCEHandler's UncaughtExceptionHandler",
|
||||
throwable
|
||||
)
|
||||
if (hasCrashedInTheLastSeconds(application!!)) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?",
|
||||
throwable
|
||||
)
|
||||
if (oldHandler != null) {
|
||||
oldHandler.uncaughtException(thread, throwable)
|
||||
return@UncaughtExceptionHandler
|
||||
}
|
||||
} else {
|
||||
setLastCrashTimestamp(application!!, Date().getTime())
|
||||
if (!isInBackground || isBackgroundMode) {
|
||||
val intent = Intent(application, UCEDefaultActivity::class.java)
|
||||
val sw = StringWriter()
|
||||
val pw = PrintWriter(sw)
|
||||
throwable.printStackTrace(pw)
|
||||
var stackTraceString: String = sw.toString()
|
||||
if (stackTraceString.length > MAX_STACK_TRACE_SIZE) {
|
||||
val disclaimer = " [stack trace too large]"
|
||||
stackTraceString = stackTraceString.substring(
|
||||
0,
|
||||
MAX_STACK_TRACE_SIZE - disclaimer.length
|
||||
) + disclaimer
|
||||
}
|
||||
intent.putExtra(EXTRA_STACK_TRACE, stackTraceString)
|
||||
if (isTrackActivitiesEnabled) {
|
||||
val activityLogStringBuilder = StringBuilder()
|
||||
while (!activityLog.isEmpty()) {
|
||||
activityLogStringBuilder.append(activityLog.poll())
|
||||
}
|
||||
intent.putExtra(
|
||||
EXTRA_ACTIVITY_LOG,
|
||||
activityLogStringBuilder.toString()
|
||||
)
|
||||
}
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
application!!.startActivity(intent)
|
||||
} else {
|
||||
if (oldHandler != null) {
|
||||
oldHandler.uncaughtException(thread, throwable)
|
||||
return@UncaughtExceptionHandler
|
||||
}
|
||||
//If it is null (should not be), we let it continue and kill the process or it will be stuck
|
||||
}
|
||||
}
|
||||
val lastActivity: Activity? = lastActivityCreated.get()
|
||||
if (lastActivity != null) {
|
||||
lastActivity.finish()
|
||||
lastActivityCreated.clear()
|
||||
}
|
||||
killCurrentProcess()
|
||||
} else oldHandler?.uncaughtException(thread, throwable)
|
||||
})
|
||||
application!!.registerActivityLifecycleCallbacks(object :
|
||||
Application.ActivityLifecycleCallbacks {
|
||||
val dateFormat: DateFormat =
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
var currentlyStartedActivities = 0
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
if (activity.javaClass !== UCEDefaultActivity::class.java) {
|
||||
lastActivityCreated = WeakReference(activity)
|
||||
}
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.getSimpleName() + " created\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
currentlyStartedActivities++
|
||||
isInBackground = currentlyStartedActivities == 0
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.simpleName + " resumed\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.simpleName + " paused\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
currentlyStartedActivities--
|
||||
isInBackground = currentlyStartedActivities == 0
|
||||
}
|
||||
|
||||
override fun onActivitySaveInstanceState(
|
||||
activity: Activity,
|
||||
outState: Bundle
|
||||
) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.simpleName + " destroyed\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Log.i(TAG, "UCEHandler has been installed.")
|
||||
} else {
|
||||
Log.e(TAG, "Context can not be null")
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"UCEHandler can not be initialized. Help making it better by reporting this as a bug.",
|
||||
throwable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL method that tells if the app has crashed in the last seconds.
|
||||
* This is used to avoid restart loops.
|
||||
*
|
||||
* @return true if the app has crashed in the last seconds, false otherwise.
|
||||
*/
|
||||
private fun hasCrashedInTheLastSeconds(context: Context): Boolean {
|
||||
val lastTimestamp = getLastCrashTimestamp(context)
|
||||
val currentTimestamp: Long = Date().getTime()
|
||||
return lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < 3000
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun setLastCrashTimestamp(context: Context, timestamp: Long) {
|
||||
context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).edit()
|
||||
.putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp).commit()
|
||||
}
|
||||
|
||||
private fun killCurrentProcess() {
|
||||
// Process.killProcess(Process.myPid())
|
||||
exitProcess(10)
|
||||
}
|
||||
|
||||
private fun getLastCrashTimestamp(context: Context): Long {
|
||||
return context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
|
||||
.getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1)
|
||||
}
|
||||
|
||||
fun closeApplication(activity: Activity) {
|
||||
activity.finish()
|
||||
killCurrentProcess()
|
||||
}
|
||||
|
||||
inner class Builder(context: Context) {
|
||||
val context: Context
|
||||
var isUCEHEnabled = true
|
||||
var commaSeparatedEmailAddresses: String? = null
|
||||
var isTrackActivitiesEnabled = false
|
||||
var isBackgroundModeEnabled = true
|
||||
fun setUCEHEnabled(isUCEHEnabled: Boolean): Builder {
|
||||
this.isUCEHEnabled = isUCEHEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTrackActivitiesEnabled(isTrackActivitiesEnabled: Boolean): Builder {
|
||||
this.isTrackActivitiesEnabled = isTrackActivitiesEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBackgroundModeEnabled(isBackgroundModeEnabled: Boolean): Builder {
|
||||
this.isBackgroundModeEnabled = isBackgroundModeEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun addCommaSeparatedEmailAddresses(commaSeparatedEmailAddresses: String?): Builder {
|
||||
this.commaSeparatedEmailAddresses = commaSeparatedEmailAddresses ?: ""
|
||||
return this
|
||||
}
|
||||
|
||||
fun build() {
|
||||
return UCEHandler(this)
|
||||
}
|
||||
|
||||
init {
|
||||
this.context = context
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
package com.hmg.hmgDr.model;
|
||||
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
|
||||
public class ChangeCallStatusRequestModel implements Parcelable {
|
||||
|
||||
@SerializedName("CallStatus")
|
||||
@Expose
|
||||
private Integer callStatus;
|
||||
@SerializedName("DoctorId")
|
||||
@Expose
|
||||
private Integer doctorId;
|
||||
@SerializedName("generalid")
|
||||
@Expose
|
||||
private String generalid;
|
||||
@SerializedName("TokenID")
|
||||
@Expose
|
||||
private String tokenID;
|
||||
@SerializedName("VC_ID")
|
||||
@Expose
|
||||
private Integer vcId;
|
||||
|
||||
public ChangeCallStatusRequestModel(Integer callStatus, Integer doctorId, String generalid, String tokenID, Integer vcId) {
|
||||
this.callStatus = callStatus;
|
||||
this.doctorId = doctorId;
|
||||
this.generalid = generalid;
|
||||
this.tokenID = tokenID;
|
||||
this.vcId = vcId;
|
||||
}
|
||||
|
||||
protected ChangeCallStatusRequestModel(Parcel in) {
|
||||
if (in.readByte() == 0) {
|
||||
callStatus = null;
|
||||
} else {
|
||||
callStatus = in.readInt();
|
||||
}
|
||||
if (in.readByte() == 0) {
|
||||
doctorId = null;
|
||||
} else {
|
||||
doctorId = in.readInt();
|
||||
}
|
||||
generalid = in.readString();
|
||||
tokenID = in.readString();
|
||||
if (in.readByte() == 0) {
|
||||
vcId = null;
|
||||
} else {
|
||||
vcId = in.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<ChangeCallStatusRequestModel> CREATOR = new Creator<ChangeCallStatusRequestModel>() {
|
||||
@Override
|
||||
public ChangeCallStatusRequestModel createFromParcel(Parcel in) {
|
||||
return new ChangeCallStatusRequestModel(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeCallStatusRequestModel[] newArray(int size) {
|
||||
return new ChangeCallStatusRequestModel[size];
|
||||
}
|
||||
};
|
||||
|
||||
public Integer getCallStatus() {
|
||||
return callStatus;
|
||||
}
|
||||
|
||||
public void setCallStatus(Integer callStatus) {
|
||||
this.callStatus = callStatus;
|
||||
}
|
||||
|
||||
public Integer getDoctorId() {
|
||||
return doctorId;
|
||||
}
|
||||
|
||||
public void setDoctorId(Integer doctorId) {
|
||||
this.doctorId = doctorId;
|
||||
}
|
||||
|
||||
public String getGeneralid() {
|
||||
return generalid;
|
||||
}
|
||||
|
||||
public void setGeneralid(String generalid) {
|
||||
this.generalid = generalid;
|
||||
}
|
||||
|
||||
public String getTokenID() {
|
||||
return tokenID;
|
||||
}
|
||||
|
||||
public void setTokenID(String tokenID) {
|
||||
this.tokenID = tokenID;
|
||||
}
|
||||
|
||||
public Integer getVcId() {
|
||||
return vcId;
|
||||
}
|
||||
|
||||
public void setVcId(Integer vcId) {
|
||||
this.vcId = vcId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
if (callStatus == null) {
|
||||
dest.writeByte((byte) 0);
|
||||
} else {
|
||||
dest.writeByte((byte) 1);
|
||||
dest.writeInt(callStatus);
|
||||
}
|
||||
if (doctorId == null) {
|
||||
dest.writeByte((byte) 0);
|
||||
} else {
|
||||
dest.writeByte((byte) 1);
|
||||
dest.writeInt(doctorId);
|
||||
}
|
||||
dest.writeString(generalid);
|
||||
dest.writeString(tokenID);
|
||||
if (vcId == null) {
|
||||
dest.writeByte((byte) 0);
|
||||
} else {
|
||||
dest.writeByte((byte) 1);
|
||||
dest.writeInt(vcId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.hmg.hmgDr.model
|
||||
|
||||
/** Represents standard data needed for a Notification. */
|
||||
open class NotificationDataModel(
|
||||
// Standard notification values:
|
||||
var mContentTitle: String,
|
||||
var mContentText: String,
|
||||
var mPriority: Int ,
|
||||
// Notification channel values (O and above):
|
||||
var mChannelId: String,
|
||||
var mChannelName: CharSequence,
|
||||
var mChannelDescription: String,
|
||||
var mChannelImportance: Int ,
|
||||
var mChannelEnableVibrate: Boolean ,
|
||||
var mChannelLockscreenVisibility: Int
|
||||
)
|
||||
@ -0,0 +1,35 @@
|
||||
package com.hmg.hmgDr.model
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
class NotificationVideoModel constructor(
|
||||
mContentTitle: String,
|
||||
mContentText: String,
|
||||
mChannelId: String,
|
||||
mChannelName: CharSequence,
|
||||
mChannelDescription: String,
|
||||
mPriority: Int = Notification.PRIORITY_MAX,
|
||||
mChannelImportance: Int = NotificationManager.IMPORTANCE_LOW,
|
||||
mChannelEnableVibrate: Boolean = true,
|
||||
mChannelLockscreenVisibility: Int = NotificationCompat.VISIBILITY_PUBLIC,
|
||||
// Unique data for this Notification.Style:
|
||||
var mBigContentTitle: String = mContentTitle,
|
||||
val mBigText: String = mContentText,
|
||||
var mSummaryText: String
|
||||
) : NotificationDataModel(
|
||||
mContentTitle,
|
||||
mContentText,
|
||||
mPriority,
|
||||
mChannelId,
|
||||
mChannelName,
|
||||
mChannelDescription,
|
||||
mChannelImportance,
|
||||
mChannelEnableVibrate,
|
||||
mChannelLockscreenVisibility
|
||||
) {
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.example.doctor_app_flutter.Model;
|
||||
package com.hmg.hmgDr.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@ -0,0 +1,25 @@
|
||||
package com.hmg.hmgDr.ui;
|
||||
|
||||
import com.hmg.hmgDr.model.ChangeCallStatusRequestModel;
|
||||
import com.hmg.hmgDr.model.GetSessionStatusModel;
|
||||
import com.hmg.hmgDr.model.SessionStatusModel;
|
||||
|
||||
public interface VideoCallContract {
|
||||
|
||||
interface VideoCallView {
|
||||
|
||||
void onCallSuccessful(SessionStatusModel sessionStatusModel);
|
||||
|
||||
void onCallChangeCallStatusSuccessful(SessionStatusModel sessionStatusModel);
|
||||
|
||||
void onFailure();
|
||||
|
||||
}
|
||||
|
||||
interface VideoCallPresenter {
|
||||
|
||||
void callClintConnected(GetSessionStatusModel statusModel);
|
||||
|
||||
void callChangeCallStatus(ChangeCallStatusRequestModel statusModel);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.hmg.hmgDr.ui
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
interface VideoCallResponseListener {
|
||||
|
||||
fun onCallFinished(resultCode : Int, intent: Intent? = null)
|
||||
|
||||
fun errorHandle(message: String){}
|
||||
|
||||
fun minimizeVideoEvent(isMinimize : Boolean)
|
||||
|
||||
fun onBackHandle(){}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
object DateUtils {
|
||||
|
||||
var simpleDateFormat: SimpleDateFormat = SimpleDateFormat("hh:mm:ss", Locale.ENGLISH)
|
||||
|
||||
fun differentDateTimeBetweenDateAndNow(firstDate: Date): String {
|
||||
val now: Date = Calendar.getInstance().time
|
||||
//1 minute = 60 seconds
|
||||
//1 hour = 60 x 60 = 3600
|
||||
//1 day = 3600 x 24 = 86400
|
||||
|
||||
var different: Long = now.time - firstDate.time
|
||||
|
||||
val secondsInMilli: Long = 1000
|
||||
val minutesInMilli = secondsInMilli * 60
|
||||
val hoursInMilli = minutesInMilli * 60
|
||||
val daysInMilli = hoursInMilli * 24
|
||||
|
||||
val elapsedDays = different / daysInMilli
|
||||
different %= daysInMilli
|
||||
|
||||
val elapsedHours = different / hoursInMilli
|
||||
different %= hoursInMilli
|
||||
|
||||
val elapsedMinutes = different / minutesInMilli
|
||||
different %= minutesInMilli
|
||||
|
||||
val elapsedSeconds = different / secondsInMilli
|
||||
|
||||
val format = "%1$02d:%2$02d" // two digits
|
||||
return String.format(format, elapsedMinutes, elapsedSeconds)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.hmg.hmgDr.model.NotificationDataModel
|
||||
import com.hmg.hmgDr.model.NotificationVideoModel
|
||||
|
||||
object NotificationUtil {
|
||||
|
||||
fun createNotificationChannel(
|
||||
context: Context,
|
||||
notificationDataModel: NotificationDataModel
|
||||
): String {
|
||||
// The id of the channel.
|
||||
val channelId: String = notificationDataModel.mChannelId
|
||||
|
||||
// NotificationChannels are required for Notifications on O (API 26) and above.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// The user-visible name of the channel.
|
||||
val channelName: CharSequence = notificationDataModel.mChannelName
|
||||
// The user-visible description of the channel.
|
||||
val channelDescription: String = notificationDataModel.mChannelDescription
|
||||
val channelImportance: Int = notificationDataModel.mChannelImportance
|
||||
val channelEnableVibrate: Boolean = notificationDataModel.mChannelEnableVibrate
|
||||
val channelLockscreenVisibility: Int =
|
||||
notificationDataModel.mChannelLockscreenVisibility
|
||||
|
||||
// Initializes NotificationChannel.
|
||||
val notificationChannel = NotificationChannel(channelId, channelName, channelImportance)
|
||||
notificationChannel.description = channelDescription
|
||||
notificationChannel.lightColor = Color.BLUE
|
||||
notificationChannel.lockscreenVisibility = channelLockscreenVisibility
|
||||
// no vibration
|
||||
notificationChannel.vibrationPattern = longArrayOf(0)
|
||||
notificationChannel.enableVibration(channelEnableVibrate)
|
||||
|
||||
// Adds NotificationChannel to system. Attempting to create an existing notification
|
||||
// channel with its original values performs no operation, so it's safe to perform the
|
||||
// below sequence.
|
||||
val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
return channelId
|
||||
}
|
||||
|
||||
fun setNotificationBigStyle(notificationData : NotificationVideoModel): NotificationCompat.BigTextStyle {
|
||||
return NotificationCompat.BigTextStyle() // Overrides ContentText in the big form of the template.
|
||||
.bigText(notificationData.mBigText) // Overrides ContentTitle in the big form of the template.
|
||||
.setBigContentTitle(notificationData.mBigContentTitle) // Summary line after the detail section in the big form of the template.
|
||||
// Note: To improve readability, don't overload the user with info. If Summary Text
|
||||
// doesn't add critical information, you should skip it.
|
||||
.setSummaryText(notificationData.mSummaryText)
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT NOTE: You should not do this action unless the user takes an action to see your
|
||||
* Notifications like this sample demonstrates. Spamming users to re-enable your notifications
|
||||
* is a bad idea.
|
||||
*/
|
||||
fun openNotificationSettingsForApp(context: Context) {
|
||||
// Links to this app's notification settings.
|
||||
val intent = Intent()
|
||||
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
|
||||
intent.putExtra("app_package", context.packageName)
|
||||
intent.putExtra("app_uid", context.applicationInfo.uid)
|
||||
|
||||
// for Android 8 and above
|
||||
intent.putExtra("android.provider.extra.APP_PACKAGE", context.packageName)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,467 @@
|
||||
package com.hmg.hmgDr.util.audio
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioRecord
|
||||
import android.media.AudioTrack
|
||||
import android.media.MediaRecorder.AudioSource
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
|
||||
import com.opentok.android.BaseAudioDevice
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.locks.Condition
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
class CustomAudioDevice(context: Context) : BaseAudioDevice() {
|
||||
|
||||
private val m_context: Context = context
|
||||
private var m_audioTrack: AudioTrack? = null
|
||||
private var m_audioRecord: AudioRecord? = null
|
||||
|
||||
// Capture & render buffers
|
||||
private var m_playBuffer: ByteBuffer? = null
|
||||
private var m_recBuffer: ByteBuffer? = null
|
||||
private val m_tempBufPlay: ByteArray
|
||||
private val m_tempBufRec: ByteArray
|
||||
private val m_rendererLock: ReentrantLock = ReentrantLock(true)
|
||||
private val m_renderEvent: Condition = m_rendererLock.newCondition()
|
||||
|
||||
@Volatile
|
||||
private var m_isRendering = false
|
||||
|
||||
@Volatile
|
||||
private var m_shutdownRenderThread = false
|
||||
private val m_captureLock: ReentrantLock = ReentrantLock(true)
|
||||
private val m_captureEvent: Condition = m_captureLock.newCondition()
|
||||
|
||||
@Volatile
|
||||
private var m_isCapturing = false
|
||||
|
||||
@Volatile
|
||||
private var m_shutdownCaptureThread = false
|
||||
private val m_captureSettings: AudioSettings
|
||||
private val m_rendererSettings: AudioSettings
|
||||
|
||||
// Capturing delay estimation
|
||||
private var m_estimatedCaptureDelay = 0
|
||||
|
||||
// Rendering delay estimation
|
||||
private var m_bufferedPlaySamples = 0
|
||||
private var m_playPosition = 0
|
||||
private var m_estimatedRenderDelay = 0
|
||||
private val m_audioManager: AudioManager
|
||||
private var isRendererMuted = false
|
||||
|
||||
companion object {
|
||||
private const val LOG_TAG = "opentok-defaultaudio"
|
||||
private const val SAMPLING_RATE = 44100
|
||||
private const val NUM_CHANNELS_CAPTURING = 1
|
||||
private const val NUM_CHANNELS_RENDERING = 1
|
||||
private const val MAX_SAMPLES = 2 * 480 * 2 // Max 10 ms @ 48 kHz
|
||||
}
|
||||
|
||||
init {
|
||||
try {
|
||||
m_playBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
|
||||
m_recBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "${e.message}.")
|
||||
}
|
||||
m_tempBufPlay = ByteArray(MAX_SAMPLES)
|
||||
m_tempBufRec = ByteArray(MAX_SAMPLES)
|
||||
m_captureSettings = AudioSettings(
|
||||
SAMPLING_RATE,
|
||||
NUM_CHANNELS_CAPTURING
|
||||
)
|
||||
m_rendererSettings = AudioSettings(
|
||||
SAMPLING_RATE,
|
||||
NUM_CHANNELS_RENDERING
|
||||
)
|
||||
m_audioManager = m_context
|
||||
.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
m_audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||
}
|
||||
|
||||
override fun initCapturer(): Boolean {
|
||||
|
||||
// get the minimum buffer size that can be used
|
||||
val minRecBufSize: Int = AudioRecord.getMinBufferSize(
|
||||
m_captureSettings
|
||||
.sampleRate,
|
||||
if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
)
|
||||
|
||||
// double size to be more safe
|
||||
val recBufSize = minRecBufSize * 2
|
||||
|
||||
// release the object
|
||||
if (m_audioRecord != null) {
|
||||
m_audioRecord!!.release()
|
||||
m_audioRecord = null
|
||||
}
|
||||
try {
|
||||
m_audioRecord = AudioRecord(
|
||||
AudioSource.VOICE_COMMUNICATION,
|
||||
m_captureSettings.sampleRate,
|
||||
if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, recBufSize
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "${e.message}.")
|
||||
return false
|
||||
}
|
||||
|
||||
// check that the audioRecord is ready to be used
|
||||
if (m_audioRecord!!.state != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.i(
|
||||
LOG_TAG, "Audio capture is not initialized "
|
||||
+ m_captureSettings.sampleRate
|
||||
)
|
||||
return false
|
||||
}
|
||||
m_shutdownCaptureThread = false
|
||||
Thread(m_captureThread).start()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun destroyCapturer(): Boolean {
|
||||
m_captureLock.lock()
|
||||
// release the object
|
||||
m_audioRecord?.release()
|
||||
m_audioRecord = null
|
||||
m_shutdownCaptureThread = true
|
||||
m_captureEvent.signal()
|
||||
m_captureLock.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getEstimatedCaptureDelay(): Int {
|
||||
return m_estimatedCaptureDelay
|
||||
}
|
||||
|
||||
override fun startCapturer(): Boolean {
|
||||
// start recording
|
||||
try {
|
||||
m_audioRecord!!.startRecording()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
m_captureLock.lock()
|
||||
m_isCapturing = true
|
||||
m_captureEvent.signal()
|
||||
m_captureLock.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stopCapturer(): Boolean {
|
||||
m_captureLock.lock()
|
||||
try {
|
||||
// only stop if we are recording
|
||||
if (m_audioRecord!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
|
||||
// stop recording
|
||||
try {
|
||||
m_audioRecord!!.stop()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Ensure we always unlock
|
||||
m_isCapturing = false
|
||||
m_captureLock.unlock()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val m_captureThread = Runnable {
|
||||
val samplesToRec = SAMPLING_RATE / 100
|
||||
var samplesRead = 0
|
||||
try {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
while (!m_shutdownCaptureThread) {
|
||||
m_captureLock.lock()
|
||||
samplesRead = try {
|
||||
if (!m_isCapturing) {
|
||||
m_captureEvent.await()
|
||||
continue
|
||||
} else {
|
||||
if (m_audioRecord == null) {
|
||||
continue
|
||||
}
|
||||
val lengthInBytes = ((samplesToRec shl 1)
|
||||
* NUM_CHANNELS_CAPTURING)
|
||||
val readBytes: Int = m_audioRecord!!.read(
|
||||
m_tempBufRec, 0,
|
||||
lengthInBytes
|
||||
)
|
||||
m_recBuffer!!.rewind()
|
||||
m_recBuffer!!.put(m_tempBufRec)
|
||||
(readBytes shr 1) / NUM_CHANNELS_CAPTURING
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "RecordAudio try failed: " + e.message)
|
||||
continue
|
||||
} finally {
|
||||
// Ensure we always unlock
|
||||
m_captureLock.unlock()
|
||||
}
|
||||
audioBus.writeCaptureData(m_recBuffer, samplesRead)
|
||||
m_estimatedCaptureDelay = samplesRead * 1000 / SAMPLING_RATE
|
||||
}
|
||||
}
|
||||
|
||||
override fun initRenderer(): Boolean {
|
||||
|
||||
// get the minimum buffer size that can be used
|
||||
val minPlayBufSize: Int = AudioTrack.getMinBufferSize(
|
||||
m_rendererSettings
|
||||
.sampleRate,
|
||||
if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
)
|
||||
var playBufSize = minPlayBufSize
|
||||
if (playBufSize < 6000) {
|
||||
playBufSize *= 2
|
||||
}
|
||||
|
||||
// release the object
|
||||
if (m_audioTrack != null) {
|
||||
m_audioTrack!!.release()
|
||||
m_audioTrack = null
|
||||
}
|
||||
try {
|
||||
m_audioTrack = AudioTrack(
|
||||
AudioManager.STREAM_VOICE_CALL,
|
||||
m_rendererSettings.sampleRate,
|
||||
if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, playBufSize,
|
||||
AudioTrack.MODE_STREAM
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "${e.message}.")
|
||||
return false
|
||||
}
|
||||
|
||||
// check that the audioRecord is ready to be used
|
||||
if (m_audioTrack!!.state != AudioTrack.STATE_INITIALIZED) {
|
||||
Log.i(
|
||||
LOG_TAG, "Audio renderer not initialized "
|
||||
+ m_rendererSettings.sampleRate
|
||||
)
|
||||
return false
|
||||
}
|
||||
m_bufferedPlaySamples = 0
|
||||
outputMode = OutputMode.SpeakerPhone
|
||||
m_shutdownRenderThread = false
|
||||
Thread(m_renderThread).start()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun destroyRenderer(): Boolean {
|
||||
m_rendererLock.lock()
|
||||
// release the object
|
||||
m_audioTrack!!.release()
|
||||
m_audioTrack = null
|
||||
m_shutdownRenderThread = true
|
||||
m_renderEvent.signal()
|
||||
m_rendererLock.unlock()
|
||||
unregisterHeadsetReceiver()
|
||||
m_audioManager.isSpeakerphoneOn = false
|
||||
m_audioManager.mode = AudioManager.MODE_NORMAL
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getEstimatedRenderDelay(): Int {
|
||||
return m_estimatedRenderDelay
|
||||
}
|
||||
|
||||
override fun startRenderer(): Boolean {
|
||||
// start playout
|
||||
try {
|
||||
m_audioTrack!!.play()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
m_rendererLock.lock()
|
||||
m_isRendering = true
|
||||
m_renderEvent.signal()
|
||||
m_rendererLock.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stopRenderer(): Boolean {
|
||||
m_rendererLock.lock()
|
||||
try {
|
||||
// only stop if we are playing
|
||||
if (m_audioTrack!!.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
// stop playout
|
||||
try {
|
||||
m_audioTrack!!.stop()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
|
||||
// flush the buffers
|
||||
m_audioTrack!!.flush()
|
||||
}
|
||||
} finally {
|
||||
// Ensure we always unlock, both for success, exception or error
|
||||
// return.
|
||||
m_isRendering = false
|
||||
m_rendererLock.unlock()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val m_renderThread = Runnable {
|
||||
val samplesToPlay = SAMPLING_RATE / 100
|
||||
try {
|
||||
Process
|
||||
.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
while (!m_shutdownRenderThread) {
|
||||
m_rendererLock.lock()
|
||||
try {
|
||||
if (!m_isRendering) {
|
||||
m_renderEvent.await()
|
||||
continue
|
||||
} else {
|
||||
m_rendererLock.unlock()
|
||||
|
||||
// Don't lock on audioBus calls
|
||||
m_playBuffer!!.clear()
|
||||
val samplesRead: Int = audioBus.readRenderData(
|
||||
m_playBuffer, samplesToPlay
|
||||
)
|
||||
|
||||
// Log.d(LOG_TAG, "Samples read: " + samplesRead);
|
||||
m_rendererLock.lock()
|
||||
if (!isRendererMuted) {
|
||||
// After acquiring the lock again
|
||||
// we must check if we are still playing
|
||||
if (m_audioTrack == null
|
||||
|| !m_isRendering
|
||||
) {
|
||||
continue
|
||||
}
|
||||
val bytesRead = ((samplesRead shl 1)
|
||||
* NUM_CHANNELS_RENDERING)
|
||||
m_playBuffer!!.get(m_tempBufPlay, 0, bytesRead)
|
||||
val bytesWritten: Int = m_audioTrack!!.write(
|
||||
m_tempBufPlay, 0,
|
||||
bytesRead
|
||||
)
|
||||
|
||||
// increase by number of written samples
|
||||
m_bufferedPlaySamples += ((bytesWritten shr 1)
|
||||
/ NUM_CHANNELS_RENDERING)
|
||||
|
||||
// decrease by number of played samples
|
||||
val pos: Int = m_audioTrack!!.getPlaybackHeadPosition()
|
||||
if (pos < m_playPosition) {
|
||||
// wrap or reset by driver
|
||||
m_playPosition = 0
|
||||
}
|
||||
m_bufferedPlaySamples -= pos - m_playPosition
|
||||
m_playPosition = pos
|
||||
|
||||
// we calculate the estimated delay based on the
|
||||
// buffered samples
|
||||
m_estimatedRenderDelay = (m_bufferedPlaySamples * 1000
|
||||
/ SAMPLING_RATE)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "Exception: " + e.message)
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
m_rendererLock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCaptureSettings(): AudioSettings {
|
||||
return m_captureSettings
|
||||
}
|
||||
|
||||
override fun getRenderSettings(): AudioSettings {
|
||||
return m_rendererSettings
|
||||
}
|
||||
|
||||
/**
|
||||
* Communication modes handling
|
||||
*/
|
||||
override fun setOutputMode(mode: OutputMode): Boolean {
|
||||
super.setOutputMode(mode)
|
||||
if (mode == OutputMode.Handset) {
|
||||
unregisterHeadsetReceiver()
|
||||
m_audioManager.isSpeakerphoneOn = false
|
||||
} else {
|
||||
m_audioManager.isSpeakerphoneOn = true
|
||||
registerHeadsetReceiver()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val m_headsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
if (intent.action!!.compareTo(Intent.ACTION_HEADSET_PLUG) == 0) {
|
||||
val state: Int = intent.getIntExtra("state", 0)
|
||||
m_audioManager.isSpeakerphoneOn = state == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
private var m_receiverRegistered = false
|
||||
private fun registerHeadsetReceiver() {
|
||||
if (!m_receiverRegistered) {
|
||||
val receiverFilter = IntentFilter(
|
||||
Intent.ACTION_HEADSET_PLUG
|
||||
)
|
||||
m_context.registerReceiver(m_headsetReceiver, receiverFilter)
|
||||
m_receiverRegistered = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun unregisterHeadsetReceiver() {
|
||||
if (m_receiverRegistered) {
|
||||
try {
|
||||
m_context.unregisterReceiver(m_headsetReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
m_receiverRegistered = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (outputMode == OutputMode.SpeakerPhone) {
|
||||
unregisterHeadsetReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (outputMode == OutputMode.SpeakerPhone) {
|
||||
registerHeadsetReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
fun setRendererMute(isRendererMuted: Boolean) {
|
||||
this.isRendererMuted = isRendererMuted
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,379 @@
|
||||
package com.hmg.hmgDr.util.opentok
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.PixelFormat
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.view.View
|
||||
import com.opentok.android.BaseVideoRenderer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import java.nio.ShortBuffer
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
/*
|
||||
* https://nhancv.medium.com/android-how-to-make-a-circular-view-as-a-thumbnail-of-opentok-27992aee15c9
|
||||
* to solve make circle video stream
|
||||
* */
|
||||
|
||||
class DynamicVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
||||
private val mRenderer: MyRenderer
|
||||
|
||||
interface DynamicVideoRendererMetadataListener {
|
||||
fun onMetadataReady(metadata: ByteArray?)
|
||||
}
|
||||
|
||||
fun setDynamicVideoRendererMetadataListener(metadataListener: DynamicVideoRendererMetadataListener?) {
|
||||
mRenderer.metadataListener = metadataListener
|
||||
}
|
||||
|
||||
fun enableThumbnailCircle(enable: Boolean) {
|
||||
mRenderer.requestEnableThumbnailCircle = enable
|
||||
}
|
||||
|
||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
||||
var mTextureIds = IntArray(3)
|
||||
var mScaleMatrix = FloatArray(16)
|
||||
private val mVertexBuffer: FloatBuffer
|
||||
private val mTextureBuffer: FloatBuffer
|
||||
private val mDrawListBuffer: ShortBuffer
|
||||
var requestEnableThumbnailCircle = false
|
||||
var mVideoFitEnabled = true
|
||||
var mVideoDisabled = false
|
||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
||||
|
||||
// vertices
|
||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
varying vec2 vTextureCoord;
|
||||
void main() {
|
||||
gl_Position = uMVPMatrix * aPosition;
|
||||
vTextureCoord = aTextureCoord;
|
||||
}
|
||||
"""
|
||||
private val fragmentShaderCode = """precision mediump float;
|
||||
uniform sampler2D Ytex;
|
||||
uniform sampler2D Utex,Vtex;
|
||||
uniform int enableCircle;
|
||||
uniform vec2 radiusDp;
|
||||
varying vec2 vTextureCoord;
|
||||
void main(void) {
|
||||
float nx,ny,r,g,b,y,u,v;
|
||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
||||
ny=vTextureCoord[1];
|
||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
||||
y=1.1643*(y-0.0625);
|
||||
u=u-0.5;
|
||||
v=v-0.5;
|
||||
r=y+1.5958*v;
|
||||
g=y-0.39173*u-0.81290*v;
|
||||
b=y+2.017*u;
|
||||
if (enableCircle > 0) {
|
||||
float radius = 0.5;
|
||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 color1 = vec4(r, g, b, 1.0);
|
||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
||||
float t = 1.0;
|
||||
if (dist < 0.0) t = 0.0;
|
||||
gl_FragColor = mix(color0, color1, t);
|
||||
}
|
||||
else {
|
||||
gl_FragColor = vec4(r, g, b, 1.0);
|
||||
}
|
||||
}
|
||||
"""
|
||||
var mFrameLock = ReentrantLock()
|
||||
var mCurrentFrame: Frame? = null
|
||||
private var mProgram = 0
|
||||
private var mTextureWidth = 0
|
||||
private var mTextureHeight = 0
|
||||
private var mViewportWidth = 0
|
||||
private var mViewportHeight = 0
|
||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
||||
vertexShaderCode)
|
||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
||||
fragmentShaderCode)
|
||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
||||
// Program
|
||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
||||
// shader to program
|
||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
||||
// shader to
|
||||
// program
|
||||
GLES20.glLinkProgram(mProgram)
|
||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aPosition")
|
||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aTextureCoord")
|
||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
||||
mVertexBuffer)
|
||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
||||
GLES20.glVertexAttribPointer(textureHandle,
|
||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
||||
GLES20.glUseProgram(mProgram)
|
||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
|
||||
fun enableThumbnailCircle(enable: Boolean) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
val enableCircleLocation = GLES20.glGetUniformLocation(mProgram, "enableCircle")
|
||||
GLES20.glUniform1i(enableCircleLocation, if (enable) 1 else 0)
|
||||
}
|
||||
|
||||
fun setupTextures(frame: Frame) {
|
||||
if (mTextureIds[0] != 0) {
|
||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
||||
}
|
||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
||||
val w = frame.width
|
||||
val h = frame.height
|
||||
val hw = w + 1 shr 1
|
||||
val hh = h + 1 shr 1
|
||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
||||
mTextureWidth = frame.width
|
||||
mTextureHeight = frame.height
|
||||
}
|
||||
|
||||
fun updateTextures(frame: Frame) {
|
||||
val width = frame.width
|
||||
val height = frame.height
|
||||
val half_width = width + 1 shr 1
|
||||
val half_height = height + 1 shr 1
|
||||
val y_size = width * height
|
||||
val uv_size = half_width * half_height
|
||||
val bb = frame.buffer
|
||||
// If we are reusing this frame, make sure we reset position and
|
||||
// limit
|
||||
bb.clear()
|
||||
if (bb.remaining() == y_size + uv_size * 2) {
|
||||
bb.position(0)
|
||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
||||
bb)
|
||||
bb.position(y_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
bb.position(y_size + uv_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
} else {
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
mViewportWidth = width
|
||||
mViewportHeight = height
|
||||
}
|
||||
|
||||
var metadataListener: DynamicVideoRendererMetadataListener? = null
|
||||
override fun onDrawFrame(gl: GL10) {
|
||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
if (mTextureWidth != mCurrentFrame!!.width
|
||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
||||
setupTextures(mCurrentFrame!!)
|
||||
}
|
||||
updateTextures(mCurrentFrame!!)
|
||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
||||
var scaleX = 1.0f
|
||||
var scaleY = 1.0f
|
||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
||||
/ mCurrentFrame!!.height)
|
||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
||||
if (mVideoFitEnabled) {
|
||||
if (ratio > vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
} else {
|
||||
if (ratio < vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
}
|
||||
Matrix.scaleM(mScaleMatrix, 0,
|
||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
||||
scaleY, 1f)
|
||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
||||
"uMVPMatrix")
|
||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
||||
mScaleMatrix, 0)
|
||||
enableThumbnailCircle(requestEnableThumbnailCircle)
|
||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
||||
} else {
|
||||
//black frame when video is disabled
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun displayFrame(frame: Frame?) {
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = frame
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun disableVideo(b: Boolean) {
|
||||
mFrameLock.lock()
|
||||
mVideoDisabled = b
|
||||
if (mVideoDisabled) {
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = null
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
||||
mVideoFitEnabled = enableVideoFit
|
||||
}
|
||||
|
||||
companion object {
|
||||
// number of coordinates per vertex in this array
|
||||
const val COORDS_PER_VERTEX = 3
|
||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
||||
var mXYZCoords = floatArrayOf(
|
||||
-1.0f, 1.0f, 0.0f, // top left
|
||||
-1.0f, -1.0f, 0.0f, // bottom left
|
||||
1.0f, -1.0f, 0.0f, // bottom right
|
||||
1.0f, 1.0f, 0.0f // top right
|
||||
)
|
||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
||||
GLES20.glActiveTexture(name)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
||||
width, height, 0, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, null)
|
||||
}
|
||||
|
||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
||||
val shader = GLES20.glCreateShader(type)
|
||||
GLES20.glShaderSource(shader, shaderCode)
|
||||
GLES20.glCompileShader(shader)
|
||||
return shader
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
||||
bb.order(ByteOrder.nativeOrder())
|
||||
mVertexBuffer = bb.asFloatBuffer()
|
||||
mVertexBuffer.put(mXYZCoords)
|
||||
mVertexBuffer.position(0)
|
||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
||||
tb.order(ByteOrder.nativeOrder())
|
||||
mTextureBuffer = tb.asFloatBuffer()
|
||||
mTextureBuffer.put(mUVCoords)
|
||||
mTextureBuffer.position(0)
|
||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
||||
dlb.order(ByteOrder.nativeOrder())
|
||||
mDrawListBuffer = dlb.asShortBuffer()
|
||||
mDrawListBuffer.put(mVertexIndex)
|
||||
mDrawListBuffer.position(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFrame(frame: Frame) {
|
||||
mRenderer.displayFrame(frame)
|
||||
mView.requestRender()
|
||||
}
|
||||
|
||||
override fun setStyle(key: String, value: String) {
|
||||
if (STYLE_VIDEO_SCALE == key) {
|
||||
if (STYLE_VIDEO_FIT == value) {
|
||||
mRenderer.enableVideoFit(true)
|
||||
} else if (STYLE_VIDEO_FILL == value) {
|
||||
mRenderer.enableVideoFit(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
||||
mRenderer.disableVideo(!videoEnabled)
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
return mView
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mView.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
mView.onResume()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
||||
}
|
||||
|
||||
init {
|
||||
mView.setEGLContextClientVersion(2)
|
||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
mRenderer = MyRenderer()
|
||||
mView.setRenderer(mRenderer)
|
||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,357 @@
|
||||
package com.hmg.hmgDr.util.opentok
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.PixelFormat
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.view.View
|
||||
import com.opentok.android.BaseVideoRenderer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import java.nio.ShortBuffer
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
|
||||
class ThumbnailCircleVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
||||
private val mRenderer: MyRenderer
|
||||
|
||||
interface ThumbnailCircleVideoRendererMetadataListener {
|
||||
fun onMetadataReady(metadata: ByteArray?)
|
||||
}
|
||||
|
||||
fun setThumbnailCircleVideoRendererMetadataListener(metadataListener: ThumbnailCircleVideoRendererMetadataListener?) {
|
||||
mRenderer.metadataListener = metadataListener
|
||||
}
|
||||
|
||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
||||
var mTextureIds = IntArray(3)
|
||||
var mScaleMatrix = FloatArray(16)
|
||||
private val mVertexBuffer: FloatBuffer
|
||||
private val mTextureBuffer: FloatBuffer
|
||||
private val mDrawListBuffer: ShortBuffer
|
||||
var mVideoFitEnabled = true
|
||||
var mVideoDisabled = false
|
||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
||||
|
||||
// vertices
|
||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
varying vec2 vTextureCoord;
|
||||
void main() {
|
||||
gl_Position = uMVPMatrix * aPosition;
|
||||
vTextureCoord = aTextureCoord;
|
||||
}
|
||||
"""
|
||||
private val fragmentShaderCode = """precision mediump float;
|
||||
uniform sampler2D Ytex;
|
||||
uniform sampler2D Utex,Vtex;
|
||||
uniform vec2 radiusDp;
|
||||
varying vec2 vTextureCoord;
|
||||
void main(void) {
|
||||
float nx,ny,r,g,b,y,u,v;
|
||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
||||
ny=vTextureCoord[1];
|
||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
||||
y=1.1643*(y-0.0625);
|
||||
u=u-0.5;
|
||||
v=v-0.5;
|
||||
r=y+1.5958*v;
|
||||
g=y-0.39173*u-0.81290*v;
|
||||
b=y+2.017*u;
|
||||
float radius = 0.5;
|
||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 color1 = vec4(r, g, b, 1.0);
|
||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
||||
float t = 1.0;
|
||||
if (dist < 0.0) t = 0.0;
|
||||
gl_FragColor = mix(color0, color1, t);
|
||||
}
|
||||
"""
|
||||
var mFrameLock = ReentrantLock()
|
||||
var mCurrentFrame: Frame? = null
|
||||
private var mProgram = 0
|
||||
private var mTextureWidth = 0
|
||||
private var mTextureHeight = 0
|
||||
private var mViewportWidth = 0
|
||||
private var mViewportHeight = 0
|
||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
||||
vertexShaderCode)
|
||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
||||
fragmentShaderCode)
|
||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
||||
// Program
|
||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
||||
// shader to program
|
||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
||||
// shader to
|
||||
// program
|
||||
GLES20.glLinkProgram(mProgram)
|
||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aPosition")
|
||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aTextureCoord")
|
||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
||||
mVertexBuffer)
|
||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
||||
GLES20.glVertexAttribPointer(textureHandle,
|
||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
||||
GLES20.glUseProgram(mProgram)
|
||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
|
||||
fun setupTextures(frame: Frame) {
|
||||
if (mTextureIds[0] != 0) {
|
||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
||||
}
|
||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
||||
val w = frame.width
|
||||
val h = frame.height
|
||||
val hw = w + 1 shr 1
|
||||
val hh = h + 1 shr 1
|
||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
||||
mTextureWidth = frame.width
|
||||
mTextureHeight = frame.height
|
||||
}
|
||||
|
||||
fun updateTextures(frame: Frame) {
|
||||
val width = frame.width
|
||||
val height = frame.height
|
||||
val half_width = width + 1 shr 1
|
||||
val half_height = height + 1 shr 1
|
||||
val y_size = width * height
|
||||
val uv_size = half_width * half_height
|
||||
val bb = frame.buffer
|
||||
// If we are reusing this frame, make sure we reset position and
|
||||
// limit
|
||||
bb.clear()
|
||||
if (bb.remaining() == y_size + uv_size * 2) {
|
||||
bb.position(0)
|
||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
||||
bb)
|
||||
bb.position(y_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
bb.position(y_size + uv_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
} else {
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
mViewportWidth = width
|
||||
mViewportHeight = height
|
||||
}
|
||||
|
||||
var metadataListener: ThumbnailCircleVideoRendererMetadataListener? = null
|
||||
override fun onDrawFrame(gl: GL10) {
|
||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
if (mTextureWidth != mCurrentFrame!!.width
|
||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
||||
setupTextures(mCurrentFrame!!)
|
||||
}
|
||||
updateTextures(mCurrentFrame!!)
|
||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
||||
var scaleX = 1.0f
|
||||
var scaleY = 1.0f
|
||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
||||
/ mCurrentFrame!!.height)
|
||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
||||
if (mVideoFitEnabled) {
|
||||
if (ratio > vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
} else {
|
||||
if (ratio < vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
}
|
||||
Matrix.scaleM(mScaleMatrix, 0,
|
||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
||||
scaleY, 1f)
|
||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
||||
"uMVPMatrix")
|
||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
||||
mScaleMatrix, 0)
|
||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
||||
} else {
|
||||
//black frame when video is disabled
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun displayFrame(frame: Frame?) {
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = frame
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun disableVideo(b: Boolean) {
|
||||
mFrameLock.lock()
|
||||
mVideoDisabled = b
|
||||
if (mVideoDisabled) {
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = null
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
||||
mVideoFitEnabled = enableVideoFit
|
||||
}
|
||||
|
||||
companion object {
|
||||
// number of coordinates per vertex in this array
|
||||
const val COORDS_PER_VERTEX = 3
|
||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
||||
var mXYZCoords = floatArrayOf(
|
||||
-1.0f, 1.0f, 0.0f, // top left
|
||||
-1.0f, -1.0f, 0.0f, // bottom left
|
||||
1.0f, -1.0f, 0.0f, // bottom right
|
||||
1.0f, 1.0f, 0.0f // top right
|
||||
)
|
||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
||||
GLES20.glActiveTexture(name)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
||||
width, height, 0, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, null)
|
||||
}
|
||||
|
||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
||||
val shader = GLES20.glCreateShader(type)
|
||||
GLES20.glShaderSource(shader, shaderCode)
|
||||
GLES20.glCompileShader(shader)
|
||||
return shader
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
||||
bb.order(ByteOrder.nativeOrder())
|
||||
mVertexBuffer = bb.asFloatBuffer()
|
||||
mVertexBuffer.put(mXYZCoords)
|
||||
mVertexBuffer.position(0)
|
||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
||||
tb.order(ByteOrder.nativeOrder())
|
||||
mTextureBuffer = tb.asFloatBuffer()
|
||||
mTextureBuffer.put(mUVCoords)
|
||||
mTextureBuffer.position(0)
|
||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
||||
dlb.order(ByteOrder.nativeOrder())
|
||||
mDrawListBuffer = dlb.asShortBuffer()
|
||||
mDrawListBuffer.put(mVertexIndex)
|
||||
mDrawListBuffer.position(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFrame(frame: Frame) {
|
||||
mRenderer.displayFrame(frame)
|
||||
mView.requestRender()
|
||||
}
|
||||
|
||||
override fun setStyle(key: String, value: String) {
|
||||
if (STYLE_VIDEO_SCALE == key) {
|
||||
if (STYLE_VIDEO_FIT == value) {
|
||||
mRenderer.enableVideoFit(true)
|
||||
} else if (STYLE_VIDEO_FILL == value) {
|
||||
mRenderer.enableVideoFit(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
||||
mRenderer.disableVideo(!videoEnabled)
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
return mView
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mView.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
mView.onResume()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
||||
}
|
||||
|
||||
init {
|
||||
mView.setEGLContextClientVersion(2)
|
||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
mRenderer = MyRenderer()
|
||||
mView.setRenderer(mRenderer)
|
||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import kotlin.math.ceil
|
||||
|
||||
object ViewsUtil {
|
||||
|
||||
/* return status bar height on basis of device display metrics */
|
||||
fun getStatusBarHeight(context: Context): Int {
|
||||
return ceil(
|
||||
(25 * context.resources.displayMetrics.density).toDouble()
|
||||
).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @return the Screen height in DP
|
||||
*/
|
||||
fun getHeightDp(context: Context, isInDp: Boolean = false): Float {
|
||||
val displayMetrics: DisplayMetrics = context.resources.displayMetrics
|
||||
return if (isInDp) {
|
||||
displayMetrics.heightPixels / displayMetrics.density
|
||||
} else {
|
||||
displayMetrics.heightPixels.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @return the screnn width in dp
|
||||
*/
|
||||
fun getWidthDp(context: Context, isInDp: Boolean = false): Float {
|
||||
val displayMetrics: DisplayMetrics = context.resources.displayMetrics
|
||||
return if (isInDp) {
|
||||
displayMetrics.widthPixels / displayMetrics.density
|
||||
} else {
|
||||
displayMetrics.widthPixels.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
// code to hide soft keyboard
|
||||
fun hideSoftKeyBoard(context: Context, editBox: EditText?) {
|
||||
val imm = context.getSystemService(FlutterFragmentActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(editBox?.windowToken, 0)
|
||||
}
|
||||
|
||||
|
||||
// code to show soft keyboard
|
||||
private fun showSoftKeyBoard(context: Context, editBox: EditText?) {
|
||||
val inputMethodManager = context.getSystemService(FlutterFragmentActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editBox?.requestFocus()
|
||||
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<solid
|
||||
android:color="@color/remoteBackground"/>
|
||||
|
||||
<stroke android:width="2dp" android:color="@color/text_color" />
|
||||
|
||||
<size
|
||||
android:width="120dp"
|
||||
android:height="120dp"/>
|
||||
</shape>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0.56,-0.5 -0.56,-0.9v-3.1C15.15,9.25 13.6,9 12,9z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,13H5v-2h14v2z"/>
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/text_color" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/text_color" />
|
||||
<corners android:radius="10dp" />
|
||||
<padding
|
||||
android:bottom="0dp"
|
||||
android:left="0dp"
|
||||
android:right="0dp"
|
||||
android:top="0dp" />
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="1000dp"/>
|
||||
|
||||
<solid android:color="@color/green_dark"/>
|
||||
|
||||
</shape>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.6 KiB |
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/ask_for_error_log"
|
||||
android:textColor="#212121"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_view_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="View Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_copy_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Copy Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_share_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Share Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_email_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Email Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_save_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Save Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_close_app"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Close App"
|
||||
android:textColor="#212121"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="88dp"
|
||||
android:background="@android:color/holo_blue_dark"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_space_medium">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:text="HMG Doctor"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small" />
|
||||
|
||||
<Chronometer
|
||||
android:id="@+id/notify_timer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:format="MM:SS"
|
||||
tools:text="25:45" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_Arrow"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_arrow_bottom" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_title"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_space_medium"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:textStyle="bold"
|
||||
tools:text="Mosa zaid mosa abuzaid" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_content"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:text="Tap to return to call" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/holo_blue_dark"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_space_medium">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:text="HMG Doctor"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small" />
|
||||
|
||||
<Chronometer
|
||||
android:id="@+id/notify_timer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:format="MM:SS"
|
||||
tools:text="25:45" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_Arrow"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_arrow_top" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_title"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_space_big"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="Mosa zaid mosa abuzaid" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_content"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="Tap to return to call" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_end"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_space_medium"
|
||||
android:layout_marginBottom="@dimen/padding_space_medium"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:text="End call" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path name="external_files" path="."/>
|
||||
</paths>
|
||||
@ -0,0 +1 @@
|
||||
include ':app'
|
||||
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 348 KiB |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 412 B |
|
Before Width: | Height: | Size: 946 B |
|
Before Width: | Height: | Size: 651 B |
|
Before Width: | Height: | Size: 655 KiB |
|
Before Width: | Height: | Size: 577 KiB |
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 73 74" style="enable-background:new 0 0 73 74;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#E3E3E3;}
|
||||
.st1{fill:none;stroke:#CCCCCC;}
|
||||
.st2{fill:#B4B4B4;}
|
||||
</style>
|
||||
<g id="Ellipse_123-2" transform="translate(2933 475)">
|
||||
<circle class="st0" cx="-2896.5" cy="-437.5" r="35"/>
|
||||
<circle class="st1" cx="-2896.5" cy="-437.5" r="34.5"/>
|
||||
</g>
|
||||
<path class="st2" d="M40.25,43.44c-0.02-0.24-0.04-0.63-0.05-1.04c3.94-0.38,6.7-1.26,6.7-2.29c-0.01,0-0.01-0.04-0.01-0.06
|
||||
c-2.94-2.48,2.55-20.09-7.68-19.73c-0.64-0.51-1.77-0.96-3.38-0.96c-13.86,0.98-7.73,17.61-10.89,20.75c0,0,0,0-0.01,0
|
||||
c0,0,0,0,0,0.01c0,0,0,0,0,0s0,0,0,0c0.01,1.01,2.68,1.87,6.51,2.26c-0.01,0.25-0.03,0.55-0.08,1.06
|
||||
c-1.59,3.99-12.3,2.87-12.8,10.56H53C52.51,46.31,41.84,47.43,40.25,43.44z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,7 +0,0 @@
|
||||
<svg id="enter" xmlns="http://www.w3.org/2000/svg" width="42" height="42" viewBox="0 0 42 42">
|
||||
<path id="Path_1146" data-name="Path 1146" d="M17.938,210.625H1.313a1.313,1.313,0,1,1,0-2.625H17.938a1.313,1.313,0,1,1,0,2.625Zm0,0" transform="translate(22.749 -190.938)"/>
|
||||
<path id="Path_1147" data-name="Path 1147" d="M123.984,143.736a1.313,1.313,0,0,1-.928-2.242l5.635-5.635-5.635-5.633a1.313,1.313,0,0,1,1.857-1.857l6.563,6.562a1.313,1.313,0,0,1,0,1.857l-6.562,6.562A1.31,1.31,0,0,1,123.984,143.736Zm0,0" transform="translate(-89.86 -117.486)"/>
|
||||
<path id="Path_1148" data-name="Path 1148" d="M312.668,42.076a3.5,3.5,0,0,0,3.5-3.5V7.076A3.522,3.522,0,0,0,313.8,3.753L303.284.248a3.537,3.537,0,0,0-4.616,3.329v31.5a3.524,3.524,0,0,0,2.368,3.321L311.553,41.9A3.662,3.662,0,0,0,312.668,42.076ZM302.168,2.7a1.03,1.03,0,0,1,.313.046l10.47,3.491a.9.9,0,0,1,.592.838v31.5a.922.922,0,0,1-1.188.83l-10.47-3.491a.905.905,0,0,1-.591-.838V3.576A.876.876,0,0,1,302.168,2.7Zm0,0" transform="translate(-298.668 -0.076)"/>
|
||||
<path id="Path_1149" data-name="Path 1149" d="M195.168,8.75a1.313,1.313,0,0,0,1.313-1.312V4.813A4.816,4.816,0,0,0,191.668,0H171.98a1.313,1.313,0,0,0,0,2.625h19.688a2.19,2.19,0,0,1,2.187,2.188V7.438A1.313,1.313,0,0,0,195.168,8.75Zm0,0" transform="translate(-168.48)"/>
|
||||
<path id="Path_1150" data-name="Path 1150" d="M171.98,350.082h7a4.816,4.816,0,0,0,4.812-4.813v-2.625a1.313,1.313,0,0,0-2.625,0v2.625a2.19,2.19,0,0,1-2.187,2.188h-7a1.313,1.313,0,0,0,0,2.625Zm0,0" transform="translate(-155.793 -313.332)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 753 B |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 246 B |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 206 B |
|
Before Width: | Height: | Size: 987 B |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 409 B |
|
Before Width: | Height: | Size: 942 B |
|
Before Width: | Height: | Size: 765 B |
|
Before Width: | Height: | Size: 232 B |