mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-20 11:20:52 +02:00
Compare commits
518 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b2a5ff5f9d | ||
![]() |
f47ef2b5ea | ||
![]() |
bd7ec3b692 | ||
![]() |
52895e7b6b | ||
![]() |
a6a82c6477 | ||
![]() |
af66ed94b2 | ||
![]() |
583f1476d6 | ||
![]() |
b42bef32fd | ||
![]() |
8bb85ccf19 | ||
![]() |
3d88c2a5fa | ||
![]() |
e350acaf08 | ||
![]() |
172f70bef9 | ||
![]() |
9d25c0bf8a | ||
![]() |
75b377aab3 | ||
![]() |
3706f30b44 | ||
![]() |
a9697a61ad | ||
![]() |
e16a2d7cb6 | ||
![]() |
f106e2945b | ||
![]() |
1ad7deddb1 | ||
![]() |
7b81e98581 | ||
![]() |
0cae58ce8e | ||
![]() |
7231150115 | ||
![]() |
071986a4c9 | ||
![]() |
39e7d43f10 | ||
![]() |
aa1b17ae66 | ||
![]() |
92bae88355 | ||
![]() |
067eaf363e | ||
![]() |
00efc266d9 | ||
![]() |
0b014185e3 | ||
![]() |
3eee7378de | ||
![]() |
c31428f6bc | ||
![]() |
163e561cf9 | ||
![]() |
a32ded2829 | ||
![]() |
a5b7517fbd | ||
![]() |
176d57b35a | ||
![]() |
927a1d58e2 | ||
![]() |
bbd0df08d3 | ||
![]() |
9e57195e14 | ||
![]() |
05ab54c30d | ||
![]() |
e3e2028153 | ||
![]() |
883bcc735d | ||
![]() |
158727e2f2 | ||
![]() |
899f69d120 | ||
![]() |
b575046c05 | ||
![]() |
b5c60d2be2 | ||
![]() |
631dfee763 | ||
![]() |
d7f610113e | ||
![]() |
e0e4f6db2b | ||
![]() |
c27a26c0aa | ||
![]() |
3dcd2468a2 | ||
![]() |
ea43b28f74 | ||
![]() |
a3e2a085b6 | ||
![]() |
635d51b60d | ||
![]() |
95eb1c0d95 | ||
![]() |
8aca43a7e6 | ||
![]() |
f6afe59788 | ||
![]() |
8e13161f64 | ||
![]() |
97437b8af3 | ||
![]() |
9a938093e2 | ||
![]() |
3edcc9f9fd | ||
![]() |
55db408720 | ||
![]() |
caef874814 | ||
![]() |
1622639eca | ||
![]() |
4df89f4217 | ||
![]() |
079b98ed3f | ||
![]() |
a0526d2c9c | ||
![]() |
169b1cbd32 | ||
![]() |
8968081e77 | ||
![]() |
93ba7510e1 | ||
![]() |
579bb743bb | ||
![]() |
9e5e9ea612 | ||
![]() |
2241a13cba | ||
![]() |
9c1fb0cb92 | ||
![]() |
4904514257 | ||
![]() |
6d829c26a1 | ||
![]() |
c05467fb92 | ||
![]() |
d47f7f3348 | ||
![]() |
5931a84651 | ||
![]() |
5771783d11 | ||
![]() |
aae07a60bd | ||
![]() |
462d418ee0 | ||
![]() |
ce63c2e1db | ||
![]() |
52baf8cbe5 | ||
![]() |
1779b9ee1a | ||
![]() |
eae169236c | ||
![]() |
ce0efba0d2 | ||
![]() |
87c7ac3970 | ||
![]() |
0f8d196a52 | ||
![]() |
6dc7dab154 | ||
![]() |
dd4cb23005 | ||
![]() |
0589017f8c | ||
![]() |
375e18bec8 | ||
![]() |
b9de74f183 | ||
![]() |
08ca69507f | ||
![]() |
65ca982342 | ||
![]() |
658281a92c | ||
![]() |
a959f61367 | ||
![]() |
8e61f744ec | ||
![]() |
73d3e52e29 | ||
![]() |
49a134845c | ||
![]() |
29807f3d39 | ||
![]() |
29b79b7725 | ||
![]() |
1cdb10a040 | ||
![]() |
5f7851df72 | ||
![]() |
3d9bc05d7a | ||
![]() |
c127428c59 | ||
![]() |
7966d8403a | ||
![]() |
80cc8a8e02 | ||
![]() |
ee4e205fef | ||
![]() |
ea443dc80c | ||
![]() |
283645513d | ||
![]() |
81b99382b8 | ||
![]() |
ab74465e6c | ||
![]() |
b3eadb557b | ||
![]() |
0abd2bcba6 | ||
![]() |
9cf76a918e | ||
![]() |
ae437b1510 | ||
![]() |
1096ec1c09 | ||
![]() |
235394d96c | ||
![]() |
d25e1d801c | ||
![]() |
2dca5ab966 | ||
![]() |
89ab57b1c1 | ||
![]() |
dc66e6a4bf | ||
![]() |
5c2f2fd882 | ||
![]() |
5c3ddefbf9 | ||
![]() |
a8d3f45ea1 | ||
![]() |
cc2c41ddc8 | ||
![]() |
b990f30a09 | ||
![]() |
5c711322d4 | ||
![]() |
b7d4a4f604 | ||
![]() |
cc8874b687 | ||
![]() |
2d0bc05488 | ||
![]() |
1429774487 | ||
![]() |
2060312dc1 | ||
![]() |
8b6728480f | ||
![]() |
cecafdee29 | ||
![]() |
05f2af25af | ||
![]() |
e3d826f6c4 | ||
![]() |
02430bed90 | ||
![]() |
3f7005ed9a | ||
![]() |
586ee75833 | ||
![]() |
1d903f11a8 | ||
![]() |
578159b95c | ||
![]() |
5c95587284 | ||
![]() |
f7e9227ad2 | ||
![]() |
c11a4d6867 | ||
![]() |
6c2b0448a4 | ||
![]() |
bd0eb8cccf | ||
![]() |
09bb043952 | ||
![]() |
9ca6cfd637 | ||
![]() |
3869a66fcc | ||
![]() |
d1c94f5120 | ||
![]() |
c55e9941ec | ||
![]() |
fa9a419d73 | ||
![]() |
ab4e0da6b4 | ||
![]() |
073572681e | ||
![]() |
b630f269c4 | ||
![]() |
40b1cd82b1 | ||
![]() |
abcbdef63b | ||
![]() |
dc8d1b0993 | ||
![]() |
56d53d8805 | ||
![]() |
7433fe049c | ||
![]() |
bb2be49d3b | ||
![]() |
cd1b578e84 | ||
![]() |
c3df9b4105 | ||
![]() |
243f3e21ec | ||
![]() |
375291380c | ||
![]() |
d221194454 | ||
![]() |
6d94a54387 | ||
![]() |
910bde88c7 | ||
![]() |
72916544ce | ||
![]() |
a01975dfce | ||
![]() |
10708801ae | ||
![]() |
ac096fb4e7 | ||
![]() |
620c1397ba | ||
![]() |
5b2f2f34f6 | ||
![]() |
ae3861a29d | ||
![]() |
93e2145254 | ||
![]() |
2b6290d275 | ||
![]() |
f81af7acb3 | ||
![]() |
f0170247a4 | ||
![]() |
3430874d11 | ||
![]() |
cb5e7532ab | ||
![]() |
5b928d679c | ||
![]() |
697b9694e5 | ||
![]() |
81c3e7e7f6 | ||
![]() |
0517bba8ca | ||
![]() |
83c7244fe6 | ||
![]() |
68fd129042 | ||
![]() |
b697e8a616 | ||
![]() |
2b281fbde9 | ||
![]() |
de8c4018c4 | ||
![]() |
9e8af96bbf | ||
![]() |
b0415a5289 | ||
![]() |
ff7344438b | ||
![]() |
f5f8e5d279 | ||
![]() |
b4ddc8f96c | ||
![]() |
e556c8ee15 | ||
![]() |
2f9a0b3376 | ||
![]() |
828f07b401 | ||
![]() |
36921b3426 | ||
![]() |
3988c6491c | ||
![]() |
2cf558ec05 | ||
![]() |
6fbadbdd94 | ||
![]() |
faa1d7effb | ||
![]() |
fe73a708d4 | ||
![]() |
a5ca262faa | ||
![]() |
8ebb1e29fa | ||
![]() |
1b44dc9522 | ||
![]() |
a66d468dc2 | ||
![]() |
ca8beafc2d | ||
![]() |
639c589a4a | ||
![]() |
cd66836218 | ||
![]() |
98662baa26 | ||
![]() |
004e5794e3 | ||
![]() |
f4f4f062cf | ||
![]() |
d8d9c7e171 | ||
![]() |
ceff82732e | ||
![]() |
d94b1708a9 | ||
![]() |
46e1f16012 | ||
![]() |
bbd014d409 | ||
![]() |
d553ee7c60 | ||
![]() |
2df6ab240d | ||
![]() |
8a2b9dfd6a | ||
![]() |
1f7892d7a9 | ||
![]() |
d11c537bea | ||
![]() |
2d4d237009 | ||
![]() |
daeee6b616 | ||
![]() |
eacb0b13b2 | ||
![]() |
dc5748059a | ||
![]() |
96d75f4bcb | ||
![]() |
19bfdf3f9f | ||
![]() |
4ea273b297 | ||
![]() |
72c9845174 | ||
![]() |
77597b329e | ||
![]() |
f62f00b4ad | ||
![]() |
00262b4a49 | ||
![]() |
de6cabe408 | ||
![]() |
d65552b59f | ||
![]() |
3755f48bce | ||
![]() |
8fe75d2015 | ||
![]() |
b0c0249ce6 | ||
![]() |
17685f3d86 | ||
![]() |
30f1c71569 | ||
![]() |
137afba1b6 | ||
![]() |
b27de5cac1 | ||
![]() |
8d43ae9805 | ||
![]() |
3cebb028f4 | ||
![]() |
28ab9d3515 | ||
![]() |
f7739309e8 | ||
![]() |
2db0d63c97 | ||
![]() |
437b86d1a7 | ||
![]() |
5ba1df52e0 | ||
![]() |
04ab753b26 | ||
![]() |
bc4a598a55 | ||
![]() |
651cdec9b5 | ||
![]() |
346f9fbacd | ||
![]() |
0f493ae808 | ||
![]() |
a07f143759 | ||
![]() |
0ec22c7a6e | ||
![]() |
73611004a0 | ||
![]() |
776ddddc83 | ||
![]() |
f60cce54ea | ||
![]() |
63087a4311 | ||
![]() |
5a193d50f6 | ||
![]() |
08a6e999b9 | ||
![]() |
e33cdca1ef | ||
![]() |
9ede7a3c42 | ||
![]() |
430d4e1ccd | ||
![]() |
de4d6037d3 | ||
![]() |
fc1fc6842b | ||
![]() |
0649b297f6 | ||
![]() |
9a470b9d41 | ||
![]() |
47d1ab356d | ||
![]() |
1ea5787486 | ||
![]() |
1d4695c109 | ||
![]() |
b673f9dd7f | ||
![]() |
a1dd03472f | ||
![]() |
497e545024 | ||
![]() |
6892fdb70b | ||
![]() |
aa1cc32d17 | ||
![]() |
b22398ae6c | ||
![]() |
e6eddaff73 | ||
![]() |
cd53518897 | ||
![]() |
8e9b1b7213 | ||
![]() |
7a88fae2e2 | ||
![]() |
f066da23c5 | ||
![]() |
34aa3d3e00 | ||
![]() |
715119fd45 | ||
![]() |
bde34fc4c4 | ||
![]() |
07b4aa89d4 | ||
![]() |
d60351114c | ||
![]() |
c932a70bef | ||
![]() |
4641d7ee8c | ||
![]() |
d4b3ee50f2 | ||
![]() |
5392daa3ff | ||
![]() |
a70e366fb4 | ||
![]() |
dff14268db | ||
![]() |
5517e157ad | ||
![]() |
bdf4ffc36b | ||
![]() |
71455c63c1 | ||
![]() |
b1ae2b1a41 | ||
![]() |
8a31732ce2 | ||
![]() |
e79aed7792 | ||
![]() |
973fc08f2d | ||
![]() |
00211e1fb2 | ||
![]() |
ce7286a72a | ||
![]() |
db335d5cec | ||
![]() |
ee5ce0c809 | ||
![]() |
b8efef7c7a | ||
![]() |
e2cbf40957 | ||
![]() |
d7d45fb8e2 | ||
![]() |
1d0c3de65f | ||
![]() |
fe1646caa0 | ||
![]() |
72710f075b | ||
![]() |
c7c01aedc2 | ||
![]() |
c2e2e76fd8 | ||
![]() |
f30a87e4e2 | ||
![]() |
7a84cfd510 | ||
![]() |
7a5a773b07 | ||
![]() |
cf1488f6ce | ||
![]() |
b02badba0c | ||
![]() |
772d84ea5a | ||
![]() |
ddaa66f080 | ||
![]() |
8fd75833f0 | ||
![]() |
1967d60813 | ||
![]() |
f38f265cf7 | ||
![]() |
79f37ffee0 | ||
![]() |
d4b2a3c696 | ||
![]() |
39ec365821 | ||
![]() |
bc423c471d | ||
![]() |
a790f43566 | ||
![]() |
9fbdc950d2 | ||
![]() |
8319963cbb | ||
![]() |
4341219497 | ||
![]() |
835504270d | ||
![]() |
daed42d208 | ||
![]() |
850f51a156 | ||
![]() |
d37b195708 | ||
![]() |
54ceb85ebe | ||
![]() |
ef7a5bc753 | ||
![]() |
b7ef60eedd | ||
![]() |
70ede70ea8 | ||
![]() |
d1d942f3fd | ||
![]() |
53b3bda909 | ||
![]() |
ac5571a363 | ||
![]() |
c42f5eca87 | ||
![]() |
9cb6816b3c | ||
![]() |
feab633e60 | ||
![]() |
a50e430cd9 | ||
![]() |
46918ee907 | ||
![]() |
fe1889653e | ||
![]() |
9487b5367d | ||
![]() |
6b47df75a7 | ||
![]() |
bd9b2d54aa | ||
![]() |
506d1dc1f2 | ||
![]() |
90f9819cbd | ||
![]() |
9bbd03c14e | ||
![]() |
2852815e1a | ||
![]() |
41a100613f | ||
![]() |
63e489f134 | ||
![]() |
914d3c4a66 | ||
![]() |
2b47a1b06a | ||
![]() |
625419a7db | ||
![]() |
2710d9de5b | ||
![]() |
e51314b104 | ||
![]() |
4c128d837c | ||
![]() |
c392804f47 | ||
![]() |
cc7a25d9ce | ||
![]() |
36b2bea25f | ||
![]() |
913796ff0f | ||
![]() |
a1b9892c77 | ||
![]() |
03de4b29ea | ||
![]() |
35a706f745 | ||
![]() |
65cd9751d8 | ||
![]() |
ff9a1ebb1b | ||
![]() |
b5df000e9d | ||
![]() |
655522a3e5 | ||
![]() |
e4a4af34c5 | ||
![]() |
b047e562ca | ||
![]() |
1600233c48 | ||
![]() |
55c8bcd0e3 | ||
![]() |
2d0dadbd34 | ||
![]() |
49879bc9db | ||
![]() |
2c453c7691 | ||
![]() |
5166c22ce9 | ||
![]() |
225b9e1b15 | ||
![]() |
7a9d2c9a74 | ||
![]() |
3855e488cb | ||
![]() |
ce75747887 | ||
![]() |
c462766cb8 | ||
![]() |
bb905b70df | ||
![]() |
5c8b9f6b4c | ||
![]() |
c726639484 | ||
![]() |
100c7b8360 | ||
![]() |
5502df89bb | ||
![]() |
4491b66872 | ||
![]() |
9bc24728b4 | ||
![]() |
a3a00ea052 | ||
![]() |
1b1534add5 | ||
![]() |
a54c8d4f55 | ||
![]() |
0cddd15203 | ||
![]() |
5653d443d9 | ||
![]() |
a5a497c4ea | ||
![]() |
d60feb466c | ||
![]() |
a435167619 | ||
![]() |
99c823c763 | ||
![]() |
aebed13a40 | ||
![]() |
1347bdd545 | ||
![]() |
562754c0b9 | ||
![]() |
ec52e144e8 | ||
![]() |
3e383a9f57 | ||
![]() |
3a2444db0d | ||
![]() |
14d01ae358 | ||
![]() |
1f4b147ddd | ||
![]() |
12405f4059 | ||
![]() |
8d6965713c | ||
![]() |
fe2858bc75 | ||
![]() |
99dd6ae6aa | ||
![]() |
cb7ed4079f | ||
![]() |
89fbc055f4 | ||
![]() |
df12b838ad | ||
![]() |
055fa19c9b | ||
![]() |
d1b661506e | ||
![]() |
180ddcceaa | ||
![]() |
512fad207b | ||
![]() |
82d514d857 | ||
![]() |
037fac7cd4 | ||
![]() |
ebad85664d | ||
![]() |
4b626c39fe | ||
![]() |
81c539f150 | ||
![]() |
0e74d82777 | ||
![]() |
5ea34a3c07 | ||
![]() |
4f8c5c3c0a | ||
![]() |
37e45e0984 | ||
![]() |
5683ad6666 | ||
![]() |
92ca1e6e09 | ||
![]() |
6571fdbaa2 | ||
![]() |
9c3f138b8e | ||
![]() |
0ac2865b74 | ||
![]() |
98fc88dec6 | ||
![]() |
8cab790030 | ||
![]() |
954399b255 | ||
![]() |
66c95f901d | ||
![]() |
8265922d68 | ||
![]() |
2403184845 | ||
![]() |
15a53d299d | ||
![]() |
24a1a5d680 | ||
![]() |
c1cfff1502 | ||
![]() |
1ed387dd54 | ||
![]() |
9c795895ba | ||
![]() |
3c193dca58 | ||
![]() |
ce2a8fbfab | ||
![]() |
4bbcf44351 | ||
![]() |
d9c6f7acb6 | ||
![]() |
41061d0289 | ||
![]() |
fed9197d23 | ||
![]() |
282d3dbf8c | ||
![]() |
b6c6dc7282 | ||
![]() |
55480c8290 | ||
![]() |
63bcc04eff | ||
![]() |
fda5405e48 | ||
![]() |
819e52cab3 | ||
![]() |
6a84f433ea | ||
![]() |
713bf58c44 | ||
![]() |
a7af21958f | ||
![]() |
8fed3df681 | ||
![]() |
edff3c35f2 | ||
![]() |
3a611adc11 | ||
![]() |
381b491845 | ||
![]() |
6aca344bf7 | ||
![]() |
512046e300 | ||
![]() |
9cb3cf250c | ||
![]() |
1cc5a67d82 | ||
![]() |
fa6823599a | ||
![]() |
6a3a72eb06 | ||
![]() |
56544802e8 | ||
![]() |
5bd0c701c7 | ||
![]() |
50a2771d87 | ||
![]() |
e6df041613 | ||
![]() |
caa1de8aff | ||
![]() |
fac13fb8cb | ||
![]() |
55d2637214 | ||
![]() |
8c9015b57b | ||
![]() |
a0cb96abff | ||
![]() |
3f51114129 | ||
![]() |
29136d633a | ||
![]() |
20bff1389e | ||
![]() |
106e538d08 | ||
![]() |
dc7ae3917e | ||
![]() |
c0fb96a911 | ||
![]() |
a1e02f7704 | ||
![]() |
436c75ca6c | ||
![]() |
7d75950624 | ||
![]() |
5f051a9766 | ||
![]() |
5716cf8cb2 | ||
![]() |
7bb5cacb0d | ||
![]() |
9801cf50e3 | ||
![]() |
b5558a8b78 | ||
![]() |
a7c31e6bcc | ||
![]() |
6e76610f30 | ||
![]() |
6da2b399e8 | ||
![]() |
79c962fc88 | ||
![]() |
28fb864ed0 | ||
![]() |
d23227d427 | ||
![]() |
eb6d26b6a4 | ||
![]() |
a8a28294d3 | ||
![]() |
7db1ba40eb | ||
![]() |
d8bd8d87ec | ||
![]() |
d29e0aa1a7 | ||
![]() |
644ad110c0 | ||
![]() |
6791de5fc0 | ||
![]() |
1bb96ef405 | ||
![]() |
7dc4ccf144 | ||
![]() |
2b39438eba | ||
![]() |
8952e2b0cd | ||
![]() |
4806ac62ee | ||
![]() |
eaa1179572 |
38
.github/CONTRIBUTING.md
vendored
38
.github/CONTRIBUTING.md
vendored
@@ -12,57 +12,53 @@ add a comment to it. You'll see exactly what is sent, the system is 100% transpa
|
|||||||
## Issue reporting/feature requests
|
## Issue reporting/feature requests
|
||||||
|
|
||||||
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
|
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
|
||||||
hasn't been reported/requested before
|
hasn't been reported/requested before.
|
||||||
* Check whether your issue/feature is already fixed/implemented
|
* Check whether your issue/feature is already fixed/implemented.
|
||||||
* Check if the issue still exists in the latest release/beta version
|
* Check if the issue still exists in the latest release/beta version.
|
||||||
* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
|
* If you are an Android/Java developer, you are always welcome to fix an issue or implement a feature yourself. PRs welcome!
|
||||||
* We use English for development. Issues in other languages will be closed and ignored.
|
* We use English for development. Issues in other languages will be closed and ignored.
|
||||||
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
|
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
|
||||||
* When reporting a bug please give us a context, and a description how to reproduce it.
|
* Follow the template! Issues or feature requests not matching the template might be closed.
|
||||||
* Issues that only contain a generated bug report, but no description might be closed.
|
|
||||||
|
|
||||||
## Bug Fixing
|
## Bug Fixing
|
||||||
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
|
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
|
||||||
tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request,
|
<a href="mailto:tnp@newpipe.schabi.org">tnp@newpipe.schabi.org</a> to let us know that you intend to help. We'll send you further instructions. You may, on request,
|
||||||
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
|
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information).
|
||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
|
* NewPipe is translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
|
||||||
with your GitHub account.
|
with your GitHub account.
|
||||||
|
* If the language you want to translate is not on Weblate, you can add it: see [How to add a new language](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-add-a-new-language-to-NewPipe) in the wiki.
|
||||||
|
|
||||||
## Code contribution
|
## Code contribution
|
||||||
|
|
||||||
* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
|
* Stick to NewPipe's style conventions: follow [checkStyle](https://github.com/checkstyle/checkstyle). It will run each time you build the project.
|
||||||
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google
|
* Do not bring non-free software (e.g. binary blobs) into the project. Also, make sure you do not introduce Google
|
||||||
libraries.
|
libraries.
|
||||||
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
|
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy).
|
||||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You
|
* Make changes on a separate branch with a meaningful name, not on the master neither dev branch. This is commonly known as *feature branch workflow*. You
|
||||||
may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might
|
may then send your changes as a pull request (PR) on GitHub.
|
||||||
not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
|
|
||||||
* When submitting changes, you confirm that your code is licensed under the terms of the
|
* When submitting changes, you confirm that your code is licensed under the terms of the
|
||||||
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
|
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
|
||||||
description. Untested code will **not** be merged!
|
description. Untested code will **not** be merged!
|
||||||
* Try to figure out yourself why builds on our CI fail.
|
* Try to figure out yourself why builds on our CI fail.
|
||||||
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
|
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
|
||||||
but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the
|
but if not, you are asked to rebase the dev branch manually and resolve the problems on your own. You can find help [on the wiki](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-merge-a-PR). That will make the
|
||||||
maintainers' jobs way easier.
|
maintainers' jobs way easier.
|
||||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
|
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
|
||||||
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
|
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
|
||||||
about submission, or clearly state that in the description of your PR.
|
about submission, or clearly state that in the description of your PR.
|
||||||
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
|
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
|
||||||
* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
|
|
||||||
* Check if your submission can be build with the current fdroid build server setup.
|
|
||||||
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
|
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
|
||||||
independent solutions.
|
independent solutions.
|
||||||
|
|
||||||
## Communication
|
## Communication
|
||||||
|
|
||||||
* WE DO NOW HAVE A MAILING LIST: [newpipe@list.schabi.org](https://list.schabi.org/cgi-bin/mailman/listinfo/newpipe).
|
|
||||||
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
|
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
|
||||||
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
||||||
* If you want to get in touch with the core team or one of our other contributors you can send an email to
|
* If you want to get in touch with the core team or one of our other contributors you can send an email to
|
||||||
tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
|
<a href="mailto:tnp@newpipe.schabi.org">tnp@newpipe.schabi.org</a>. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
|
||||||
tracker described above!
|
tracker described above!
|
||||||
* Feel free to post suggestions, changes, ideas etc. on GitHub, IRC or the mailing list!
|
* Feel free to post suggestions, changes, ideas etc. on GitHub or IRC!
|
||||||
|
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,38 +7,40 @@ assignees: ''
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe.
|
Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe. If this is your first bug report, read the following information before proceeding:
|
||||||
|
|
||||||
Use this template to notify us if you found a bug.
|
Please note, we only support the latest version of NewPipe. In order to check your app version, open the left drawer and click on "About". If you don't have the latest version, upgrade to it and reproduce the problem before opening the issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is where you can get it.
|
||||||
|
|
||||||
To make it easier for us to help you please enter detailed information below.
|
|
||||||
|
|
||||||
Please note, we only support the latest version of NewPipe and the master branch. Make sure you have that version installed. If you don't, upgrade & reproduce the problem before opening the issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is the go-to place to get this version. In order to check your app version, open the left drawer and click on "About".
|
|
||||||
|
|
||||||
P.S.: Our contribution guidelines might be a nice document to read before you fill out the report :) You can find it at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md
|
P.S.: Our contribution guidelines might be a nice document to read before you fill out the report :) You can find it at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md
|
||||||
-->
|
|
||||||
### Version
|
|
||||||
<!-- Which version are you using? -->
|
|
||||||
-
|
|
||||||
|
|
||||||
|
To make it easier for us to help you please enter detailed information in the template we have provided below. If a section isn't relevant, just delete it, though it would be helpful to still provide as much detail as possible.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the preview). -->
|
||||||
|
|
||||||
|
### Version
|
||||||
|
<!-- Which version are you using? Hopefully the latest! We just told you that above! -->
|
||||||
|
-
|
||||||
|
|
||||||
### Steps to reproduce the bug
|
### Steps to reproduce the bug
|
||||||
<!-- If you can't reproduce it, please try to give as many details as possible on how you think you got to the bug. -->
|
<!--
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Press on '....'
|
2. Press on '....'
|
||||||
3. Swipe down to '....'
|
3. Swipe down to '....'
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. -->
|
||||||
|
|
||||||
### Expected behavior
|
### Expected behavior
|
||||||
Tell us what you expected to happen.
|
<!-- Tell us what you expect to happen. -->
|
||||||
|
|
||||||
### Actual behaviour
|
### Actual behaviour
|
||||||
Tell us what happens instead.
|
<!-- Tell us what happens instead. -->
|
||||||
|
|
||||||
### Screenshots/Screen records
|
### Screenshots/Screen recordings
|
||||||
If applicable, add screenshots or a screen recording to help explain your problem. GitHub should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from an image/video hoster here instead.
|
<!-- If applicable, add screenshots or a screen recording to help explain your problem. GitHub supports uploading them directly in the issue text box. If your file is too big for Github to accept, feel free to paste a link from an image/video hoster here instead. -->
|
||||||
|
|
||||||
### Logs
|
### Logs
|
||||||
If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it here:
|
<!-- If your bug includes a crash (where you're shown the Error Report page with a bunch of info), copy it to the clipboard (there is a share button for this), head over to our bug report to markdown converter at https://teamnewpipe.github.io/CrashReportToMarkdown/ and paste it. Copy the converted text (it is MUCH easier to read this way) from there and paste it here: -->
|
||||||
|
|
||||||
<!-- That's right, here! -->
|
<!-- That's right, here! -->
|
||||||
|
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,22 +7,33 @@ assignees: ''
|
|||||||
---
|
---
|
||||||
<!-- Hey. Our contribution guidelines (https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) might be an appropriate
|
<!-- Hey. Our contribution guidelines (https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) might be an appropriate
|
||||||
document to read before you fill out the request :) -->
|
document to read before you fill out the request :) -->
|
||||||
#### Is your feature request related to a problem? Please describe it
|
|
||||||
A clear and concise description of what the problem is.
|
|
||||||
Example: *I want to do X, but there is no way to do it.*
|
|
||||||
|
|
||||||
#### Describe the solution you'd like
|
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the preview). -->
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
#### Describe the feature you want
|
||||||
|
<!-- A clear and concise description of what you want to happen. PLEASE MAKE SURE it is one feature ONLY. You should open separate issues for separate feature requests, because those issues will be used to track their status.
|
||||||
Example: *I think it would be nice if you add feature Y which makes X possible.*
|
Example: *I think it would be nice if you add feature Y which makes X possible.*
|
||||||
|
|
||||||
#### (Optional) Describe alternatives you've considered
|
Optionally, also describe alternatives you've considered.
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
Example: *Z is also a good alternative. Not as good as Y, but at least...* or *I considered Z, but that didn't turn out to be a good idea because...* -->
|
||||||
Example: *I considered Z, but that didn't turn out to be a good idea because...*
|
|
||||||
|
<!-- Write below this -->
|
||||||
|
|
||||||
|
#### Is your feature request related to a problem? Please describe it
|
||||||
|
<!-- A clear and concise description of what the problem is. Maybe the developers could brainstorm and come up with a better solution to your problem. If they exist, link to related Issues and/or PRs for developers to keep track easier.
|
||||||
|
Example: *I want to do X, but there is no way to do it.* -->
|
||||||
|
|
||||||
|
<!-- Write below this -->
|
||||||
|
|
||||||
#### Additional context
|
#### Additional context
|
||||||
Add any other context or screenshots about the feature request here.
|
<!-- Add any other context, like screenshots, about the feature request here.
|
||||||
Example: *Here's a photo of my cat!*
|
Example: *Here's a photo of my cat!* -->
|
||||||
|
|
||||||
|
<!-- Write below this -->
|
||||||
|
|
||||||
#### How will you/everyone benefit from this feature?
|
#### How will you/everyone benefit from this feature?
|
||||||
Convince us! How does it change your NewPipe experience and/or your life?
|
<!-- Convince us! How does it change your NewPipe experience and/or your life?
|
||||||
The better this paragraph is, the more likely a developer will think about working on it.
|
The better this paragraph is, the more likely a developer will think about working on it.
|
||||||
|
Example: *This feature will help us colonize the galaxy! -->
|
||||||
|
|
||||||
|
<!-- Write below this -->
|
||||||
|
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,10 +1,12 @@
|
|||||||
<!-- Hey there. Thank you so much for improving NewPipe. Please take a moment to fill out the following suggestion on how to structure this PR description. Having roughly the same layout helps everyone considerably :)-->
|
<!-- Hey there. Thank you so much for improving NewPipe. Please take a moment to fill out the following suggestion on how to structure this PR description. Having roughly the same layout helps everyone considerably :)-->
|
||||||
|
|
||||||
#### What is it?
|
#### What is it?
|
||||||
- [ ] Bug fix
|
- [ ] Bug fix (user facing)
|
||||||
- [ ] Feature
|
- [ ] Feature (user facing)
|
||||||
|
- [ ] Code base improvement (dev facing)
|
||||||
|
- [ ] Meta improvement to the project (dev facing)
|
||||||
|
|
||||||
#### Long description of the changes in your PR
|
#### Description of the changes in your PR
|
||||||
<!-- While bullet points are the norm in this section, feel free to write a text instead if you can't fit it in a list -->
|
<!-- While bullet points are the norm in this section, feel free to write a text instead if you can't fit it in a list -->
|
||||||
- record videos
|
- record videos
|
||||||
- create clones
|
- create clones
|
||||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@@ -10,3 +10,11 @@
|
|||||||
*~
|
*~
|
||||||
.weblate
|
.weblate
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
|
# vscode / eclipse files
|
||||||
|
*.classpath
|
||||||
|
*.project
|
||||||
|
*.settings
|
||||||
|
bin/
|
||||||
|
.vscode/
|
||||||
|
*.code-workspace
|
||||||
|
@@ -5,13 +5,13 @@ android:
|
|||||||
components:
|
components:
|
||||||
# The BuildTools version used by NewPipe
|
# The BuildTools version used by NewPipe
|
||||||
- tools
|
- tools
|
||||||
- build-tools-28.0.3
|
- build-tools-29.0.3
|
||||||
|
|
||||||
# The SDK version used to compile NewPipe
|
# The SDK version used to compile NewPipe
|
||||||
- android-28
|
- android-29
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- yes | sdkmanager "platforms;android-28"
|
- yes | sdkmanager "platforms;android-29"
|
||||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||||
|
|
||||||
licenses:
|
licenses:
|
||||||
|
@@ -4,10 +4,10 @@
|
|||||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
||||||
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg"></a>
|
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg"></a>
|
||||||
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg"></a>
|
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||||
</p>
|
</p>
|
||||||
|
194
app/build.gradle
194
app/build.gradle
@@ -2,18 +2,19 @@ apply plugin: 'com.android.application'
|
|||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: 'checkstyle'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 29
|
||||||
buildToolsVersion '28.0.3'
|
buildToolsVersion '29.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.schabi.newpipe"
|
applicationId "org.schabi.newpipe"
|
||||||
resValue "string", "app_name", "NewPipe"
|
resValue "string", "app_name", "NewPipe"
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 28
|
targetSdkVersion 29
|
||||||
versionCode 920
|
versionCode 940
|
||||||
versionName "0.19.2"
|
versionName "0.19.4"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -26,12 +27,6 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
|
||||||
minifyEnabled true
|
|
||||||
shrinkResources true
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
debuggable true
|
debuggable true
|
||||||
@@ -49,6 +44,16 @@ android {
|
|||||||
archivesBaseName = 'NewPipe_' + normalizedWorkingBranch
|
archivesBaseName = 'NewPipe_' + normalizedWorkingBranch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the release build type at the end of the list to override 'archivesBaseName' of
|
||||||
|
// debug build. This seems to be a Gradle bug, therefore
|
||||||
|
// TODO: update Gradle version
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
archivesBaseName = 'app'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
@@ -61,6 +66,7 @@ android {
|
|||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
encoding 'utf-8'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Required and used only by groupie
|
// Required and used only by groupie
|
||||||
@@ -74,81 +80,133 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
androidxLibVersion = '1.0.0'
|
icepickVersion = '3.2.0'
|
||||||
exoPlayerLibVersion = '2.10.8'
|
checkstyleVersion = '8.32'
|
||||||
roomDbLibVersion = '2.1.0'
|
stethoVersion = '1.5.1'
|
||||||
leakCanaryLibVersion = '1.5.4' //1.6.1
|
leakCanaryVersion = '2.2'
|
||||||
okHttpLibVersion = '3.12.6'
|
exoPlayerVersion = '2.11.4'
|
||||||
icepickLibVersion = '3.2.0'
|
androidxLifecycleVersion = '2.2.0'
|
||||||
stethoLibVersion = '1.5.0'
|
androidxRoomVersion = '2.2.5'
|
||||||
markwonVersion = '4.2.1'
|
groupieVersion = '2.8.0'
|
||||||
|
markwonVersion = '4.3.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
checkstyle {
|
||||||
|
configFile rootProject.file('checkstyle.xml')
|
||||||
|
ignoreFailures false
|
||||||
|
showViolations true
|
||||||
|
toolVersion = checkstyleVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
task runCheckstyle(type: Checkstyle) {
|
||||||
|
source 'src'
|
||||||
|
include '**/*.java'
|
||||||
|
exclude '**/gen/**'
|
||||||
|
exclude '**/R.java'
|
||||||
|
exclude '**/BuildConfig.java'
|
||||||
|
exclude 'main/java/us/shandian/giga/**'
|
||||||
|
|
||||||
|
// empty classpath
|
||||||
|
classpath = files()
|
||||||
|
|
||||||
|
showViolations true
|
||||||
|
|
||||||
|
reports {
|
||||||
|
xml.enabled true
|
||||||
|
html.enabled true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
ktlint
|
||||||
|
}
|
||||||
|
|
||||||
|
task runKtlint(type: JavaExec) {
|
||||||
|
main = "com.pinterest.ktlint.Main"
|
||||||
|
classpath = configurations.ktlint
|
||||||
|
args "src/**/*.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
task formatKtlint(type: JavaExec) {
|
||||||
|
main = "com.pinterest.ktlint.Main"
|
||||||
|
classpath = configurations.ktlint
|
||||||
|
args "-F", "src/**/*.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
preDebugBuild.dependsOn runCheckstyle, runKtlint
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||||
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
|
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
|
||||||
|
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||||
|
ktlint "com.pinterest:ktlint:0.35.0"
|
||||||
|
|
||||||
|
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||||
|
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
||||||
|
|
||||||
|
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
|
||||||
|
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
||||||
|
|
||||||
|
debugImplementation "androidx.multidex:multidex:2.0.1"
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.13'
|
||||||
|
testImplementation 'org.mockito:mockito-core:3.3.3'
|
||||||
|
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:1.1.1"
|
||||||
|
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", {
|
||||||
exclude module: 'support-annotations'
|
exclude module: 'support-annotations'
|
||||||
})
|
}
|
||||||
|
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:69e0624e3'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:98055a3c3c17f2543a63d375a44c1d1f557fa76e'
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||||
implementation "androidx.legacy:legacy-support-v4:${androidxLibVersion}"
|
implementation "org.jsoup:jsoup:1.13.1"
|
||||||
implementation "com.google.android.material:material:${androidxLibVersion}"
|
|
||||||
implementation "androidx.recyclerview:recyclerview:${androidxLibVersion}"
|
|
||||||
implementation "androidx.legacy:legacy-preference-v14:${androidxLibVersion}"
|
|
||||||
implementation "androidx.cardview:cardview:${androidxLibVersion}"
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
|
||||||
|
|
||||||
implementation 'com.xwray:groupie:2.7.0'
|
implementation "com.squareup.okhttp3:okhttp:3.12.11"
|
||||||
implementation 'com.xwray:groupie-kotlin-android-extensions:2.7.0'
|
|
||||||
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
|
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.0.0'
|
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
|
||||||
|
|
||||||
// Originally in NewPipeExtractor
|
implementation "com.google.android.material:material:1.1.0"
|
||||||
implementation 'com.grack:nanojson:1.1'
|
|
||||||
implementation 'org.jsoup:jsoup:1.9.2'
|
|
||||||
|
|
||||||
implementation 'ch.acra:acra:4.9.2' //4.11
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
|
implementation "androidx.preference:preference:1.1.1"
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
|
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||||
|
|
||||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||||
implementation 'com.nononsenseapps:filepicker:4.2.1'
|
implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
|
||||||
|
|
||||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
|
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerLibVersion}"
|
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
|
||||||
|
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||||
|
|
||||||
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
|
implementation "com.xwray:groupie:${groupieVersion}"
|
||||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
|
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
|
||||||
debugImplementation 'androidx.multidex:multidex:2.0.1'
|
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
|
implementation "de.hdodenhof:circleimageview:3.1.0"
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
|
|
||||||
implementation 'org.ocpsoft.prettytime:prettytime:4.0.3.Final'
|
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:${roomDbLibVersion}"
|
|
||||||
implementation "androidx.room:room-rxjava2:${roomDbLibVersion}"
|
|
||||||
kapt "androidx.room:room-compiler:${roomDbLibVersion}"
|
|
||||||
|
|
||||||
implementation "frankiesardo:icepick:${icepickLibVersion}"
|
|
||||||
kapt "frankiesardo:icepick-processor:${icepickLibVersion}"
|
|
||||||
|
|
||||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}"
|
|
||||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}"
|
|
||||||
|
|
||||||
implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
|
|
||||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"
|
|
||||||
|
|
||||||
implementation "io.noties.markwon:core:${markwonVersion}"
|
implementation "io.noties.markwon:core:${markwonVersion}"
|
||||||
implementation "io.noties.markwon:linkify:${markwonVersion}"
|
implementation "io.noties.markwon:linkify:${markwonVersion}"
|
||||||
|
|
||||||
|
implementation "com.nononsenseapps:filepicker:4.2.1"
|
||||||
|
|
||||||
|
implementation "ch.acra:acra-core:5.5.0"
|
||||||
|
|
||||||
|
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
|
||||||
|
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||||
|
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
|
||||||
|
|
||||||
|
implementation "org.ocpsoft.prettytime:prettytime:4.0.5.Final"
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getGitWorkingBranch() {
|
static String getGitWorkingBranch() {
|
||||||
@@ -165,4 +223,4 @@ static String getGitWorkingBranch() {
|
|||||||
// git was not found
|
// git was not found
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,8 +30,9 @@ class AppDatabaseTest {
|
|||||||
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
|
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Rule val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
|
@get:Rule
|
||||||
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory());
|
val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
|
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun migrateDatabaseFrom2to3() {
|
fun migrateDatabaseFrom2to3() {
|
||||||
@@ -72,7 +73,7 @@ class AppDatabaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
|
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
|
||||||
true, Migrations.MIGRATION_2_3);
|
true, Migrations.MIGRATION_2_3)
|
||||||
|
|
||||||
val migratedDatabaseV3 = getMigratedDatabase()
|
val migratedDatabaseV3 = getMigratedDatabase()
|
||||||
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
|
||||||
@@ -115,4 +116,4 @@ class AppDatabaseTest {
|
|||||||
testHelper.closeWhenFinished(database)
|
testHelper.closeWhenFinished(database)
|
||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.report;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -12,15 +13,16 @@ import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented tests for {@link ErrorInfo}
|
* Instrumented tests for {@link ErrorInfo}.
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public class ErrorInfoTest {
|
public class ErrorInfoTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void errorInfo_testParcelable() {
|
public void errorInfoTestParcelable() {
|
||||||
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request", R.string.general_error);
|
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
||||||
|
R.string.general_error);
|
||||||
// Obtain a Parcel object and write the parcelable object to it:
|
// Obtain a Parcel object and write the parcelable object to it:
|
||||||
Parcel parcel = Parcel.obtain();
|
Parcel parcel = Parcel.obtain();
|
||||||
info.writeToParcel(parcel, 0);
|
info.writeToParcel(parcel, 0);
|
||||||
@@ -34,4 +36,4 @@ public class ErrorInfoTest {
|
|||||||
|
|
||||||
parcel.recycle();
|
parcel.recycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,104 +0,0 @@
|
|||||||
package org.schabi.newpipe;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.multidex.MultiDex;
|
|
||||||
|
|
||||||
import com.facebook.stetho.Stetho;
|
|
||||||
import com.facebook.stetho.okhttp3.StethoInterceptor;
|
|
||||||
import com.squareup.leakcanary.AndroidHeapDumper;
|
|
||||||
import com.squareup.leakcanary.DefaultLeakDirectoryProvider;
|
|
||||||
import com.squareup.leakcanary.HeapDumper;
|
|
||||||
import com.squareup.leakcanary.LeakCanary;
|
|
||||||
import com.squareup.leakcanary.LeakDirectoryProvider;
|
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
|
|
||||||
public class DebugApp extends App {
|
|
||||||
private static final String TAG = DebugApp.class.toString();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(Context base) {
|
|
||||||
super.attachBaseContext(base);
|
|
||||||
MultiDex.install(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
initStetho();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Downloader getDownloader() {
|
|
||||||
return DownloaderImpl.init(new OkHttpClient.Builder()
|
|
||||||
.addNetworkInterceptor(new StethoInterceptor()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initStetho() {
|
|
||||||
// Create an InitializerBuilder
|
|
||||||
Stetho.InitializerBuilder initializerBuilder =
|
|
||||||
Stetho.newInitializerBuilder(this);
|
|
||||||
|
|
||||||
// Enable Chrome DevTools
|
|
||||||
initializerBuilder.enableWebKitInspector(
|
|
||||||
Stetho.defaultInspectorModulesProvider(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Enable command line interface
|
|
||||||
initializerBuilder.enableDumpapp(
|
|
||||||
Stetho.defaultDumperPluginsProvider(getApplicationContext())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use the InitializerBuilder to generate an Initializer
|
|
||||||
Stetho.Initializer initializer = initializerBuilder.build();
|
|
||||||
|
|
||||||
// Initialize Stetho with the Initializer
|
|
||||||
Stetho.initialize(initializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isDisposedRxExceptionsReported() {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RefWatcher installLeakCanary() {
|
|
||||||
return LeakCanary.refWatcher(this)
|
|
||||||
.heapDumper(new ToggleableHeapDumper(this))
|
|
||||||
// give each object 10 seconds to be gc'ed, before leak canary gets nosy on it
|
|
||||||
.watchDelay(10, TimeUnit.SECONDS)
|
|
||||||
.buildAndInstall();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ToggleableHeapDumper implements HeapDumper {
|
|
||||||
private final HeapDumper dumper;
|
|
||||||
private final SharedPreferences preferences;
|
|
||||||
private final String dumpingAllowanceKey;
|
|
||||||
|
|
||||||
ToggleableHeapDumper(@NonNull final Context context) {
|
|
||||||
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
|
|
||||||
this.dumper = new AndroidHeapDumper(context, leakDirectoryProvider);
|
|
||||||
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
this.dumpingAllowanceKey = context.getString(R.string.allow_heap_dumping_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDumpingAllowed() {
|
|
||||||
return preferences.getBoolean(dumpingAllowanceKey, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File dumpHeap() {
|
|
||||||
return isDumpingAllowed() ? dumper.dumpHeap() : HeapDumper.RETRY_LATER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
59
app/src/debug/java/org/schabi/newpipe/DebugApp.kt
Normal file
59
app/src/debug/java/org/schabi/newpipe/DebugApp.kt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package org.schabi.newpipe
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.multidex.MultiDex
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.facebook.stetho.Stetho
|
||||||
|
import com.facebook.stetho.okhttp3.StethoInterceptor
|
||||||
|
import leakcanary.AppWatcher
|
||||||
|
import leakcanary.LeakCanary
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
|
|
||||||
|
class DebugApp : App() {
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base)
|
||||||
|
MultiDex.install(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
initStetho()
|
||||||
|
|
||||||
|
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
|
||||||
|
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
|
||||||
|
LeakCanary.config = LeakCanary.config.copy(dumpHeap = PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(this).getBoolean(getString(
|
||||||
|
R.string.allow_heap_dumping_key), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDownloader(): Downloader {
|
||||||
|
val downloader = DownloaderImpl.init(OkHttpClient.Builder()
|
||||||
|
.addNetworkInterceptor(StethoInterceptor()))
|
||||||
|
setCookiesToDownloader(downloader)
|
||||||
|
return downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initStetho() {
|
||||||
|
// Create an InitializerBuilder
|
||||||
|
val initializerBuilder = Stetho.newInitializerBuilder(this)
|
||||||
|
|
||||||
|
// Enable Chrome DevTools
|
||||||
|
initializerBuilder.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
|
||||||
|
|
||||||
|
// Enable command line interface
|
||||||
|
initializerBuilder.enableDumpapp(
|
||||||
|
Stetho.defaultDumperPluginsProvider(applicationContext))
|
||||||
|
|
||||||
|
// Use the InitializerBuilder to generate an Initializer
|
||||||
|
val initializer = initializerBuilder.build()
|
||||||
|
|
||||||
|
// Initialize Stetho with the Initializer
|
||||||
|
Stetho.initialize(initializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isDisposedRxExceptionsReported(): Boolean {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
|
||||||
|
}
|
||||||
|
}
|
@@ -11,7 +11,10 @@
|
|||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:banner="@mipmap/newpipe_tv_banner"
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -26,6 +29,7 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
@@ -38,12 +38,15 @@ import java.util.ArrayList;
|
|||||||
* This is a copy from {@link androidx.fragment.app.FragmentStatePagerAdapter}.
|
* This is a copy from {@link androidx.fragment.app.FragmentStatePagerAdapter}.
|
||||||
* <p>
|
* <p>
|
||||||
* It includes a workaround to fix the menu visibility when the adapter is restored.
|
* It includes a workaround to fix the menu visibility when the adapter is restored.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* When restoring the state of this adapter, all the fragments' menu visibility were set to false,
|
* When restoring the state of this adapter, all the fragments' menu visibility were set to false,
|
||||||
* effectively disabling the menu from the user until he switched pages or another event that triggered the
|
* effectively disabling the menu from the user until he switched pages or another event
|
||||||
* menu to be visible again happened.
|
* that triggered the menu to be visible again happened.
|
||||||
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* <br><b>Check out the changes in:</b>
|
* <b>Check out the changes in:</b>
|
||||||
|
* </p>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #saveState()}</li>
|
* <li>{@link #saveState()}</li>
|
||||||
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
||||||
@@ -88,8 +91,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
private Fragment mCurrentPrimaryItem = null;
|
private Fragment mCurrentPrimaryItem = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround} that sets the fragment manager for the
|
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
|
||||||
* adapter. This is the equivalent of calling
|
* that sets the fragment manager for the adapter. This is the equivalent of calling
|
||||||
* {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in
|
* {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in
|
||||||
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
|
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
|
||||||
*
|
*
|
||||||
@@ -101,7 +104,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
|
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm) {
|
public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm) {
|
||||||
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,20 +120,21 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
* @param fm fragment manager that will interact with this adapter
|
* @param fm fragment manager that will interact with this adapter
|
||||||
* @param behavior determines if only current fragments are in a resumed state
|
* @param behavior determines if only current fragments are in a resumed state
|
||||||
*/
|
*/
|
||||||
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm,
|
public FragmentStatePagerAdapterMenuWorkaround(@NonNull final FragmentManager fm,
|
||||||
@Behavior int behavior) {
|
@Behavior final int behavior) {
|
||||||
mFragmentManager = fm;
|
mFragmentManager = fm;
|
||||||
mBehavior = behavior;
|
mBehavior = behavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the Fragment associated with a specified position.
|
* @param position the position of the item you want
|
||||||
|
* @return the {@link Fragment} associated with a specified position
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public abstract Fragment getItem(int position);
|
public abstract Fragment getItem(int position);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startUpdate(@NonNull ViewGroup container) {
|
public void startUpdate(@NonNull final ViewGroup container) {
|
||||||
if (container.getId() == View.NO_ID) {
|
if (container.getId() == View.NO_ID) {
|
||||||
throw new IllegalStateException("ViewPager with adapter " + this
|
throw new IllegalStateException("ViewPager with adapter " + this
|
||||||
+ " requires a view id");
|
+ " requires a view id");
|
||||||
@@ -140,7 +144,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
|
||||||
// If we already have this item instantiated, there is nothing
|
// If we already have this item instantiated, there is nothing
|
||||||
// to do. This can happen when we are restoring the entire pager
|
// to do. This can happen when we are restoring the entire pager
|
||||||
// from its saved state, where the fragment manager has already
|
// from its saved state, where the fragment manager has already
|
||||||
@@ -157,7 +161,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
Fragment fragment = getItem(position);
|
Fragment fragment = getItem(position);
|
||||||
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
if (DEBUG) {
|
||||||
|
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||||
|
}
|
||||||
if (mSavedState.size() > position) {
|
if (mSavedState.size() > position) {
|
||||||
Fragment.SavedState fss = mSavedState.get(position);
|
Fragment.SavedState fss = mSavedState.get(position);
|
||||||
if (fss != null) {
|
if (fss != null) {
|
||||||
@@ -183,14 +189,17 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
public void destroyItem(@NonNull final ViewGroup container, final int position,
|
||||||
|
@NonNull final Object object) {
|
||||||
Fragment fragment = (Fragment) object;
|
Fragment fragment = (Fragment) object;
|
||||||
|
|
||||||
if (mCurTransaction == null) {
|
if (mCurTransaction == null) {
|
||||||
mCurTransaction = mFragmentManager.beginTransaction();
|
mCurTransaction = mFragmentManager.beginTransaction();
|
||||||
}
|
}
|
||||||
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
|
if (DEBUG) {
|
||||||
+ " v=" + ((Fragment)object).getView());
|
Log.v(TAG, "Removing item #" + position + ": f=" + object
|
||||||
|
+ " v=" + ((Fragment) object).getView());
|
||||||
|
}
|
||||||
while (mSavedState.size() <= position) {
|
while (mSavedState.size() <= position) {
|
||||||
mSavedState.add(null);
|
mSavedState.add(null);
|
||||||
}
|
}
|
||||||
@@ -206,8 +215,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||||
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
|
||||||
Fragment fragment = (Fragment)object;
|
@NonNull final Object object) {
|
||||||
|
Fragment fragment = (Fragment) object;
|
||||||
if (fragment != mCurrentPrimaryItem) {
|
if (fragment != mCurrentPrimaryItem) {
|
||||||
if (mCurrentPrimaryItem != null) {
|
if (mCurrentPrimaryItem != null) {
|
||||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||||
@@ -235,7 +245,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finishUpdate(@NonNull ViewGroup container) {
|
public void finishUpdate(@NonNull final ViewGroup container) {
|
||||||
if (mCurTransaction != null) {
|
if (mCurTransaction != null) {
|
||||||
mCurTransaction.commitNowAllowingStateLoss();
|
mCurTransaction.commitNowAllowingStateLoss();
|
||||||
mCurTransaction = null;
|
mCurTransaction = null;
|
||||||
@@ -243,12 +253,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
|
||||||
return ((Fragment)object).getView() == view;
|
return ((Fragment) object).getView() == view;
|
||||||
}
|
}
|
||||||
|
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
private final String SELECTED_FRAGMENT = "selected_fragment";
|
private final String selectedFragment = "selected_fragment";
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -261,7 +271,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
mSavedState.toArray(fss);
|
mSavedState.toArray(fss);
|
||||||
state.putParcelableArray("states", fss);
|
state.putParcelableArray("states", fss);
|
||||||
}
|
}
|
||||||
for (int i=0; i<mFragments.size(); i++) {
|
for (int i = 0; i < mFragments.size(); i++) {
|
||||||
Fragment f = mFragments.get(i);
|
Fragment f = mFragments.get(i);
|
||||||
if (f != null && f.isAdded()) {
|
if (f != null && f.isAdded()) {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
@@ -273,7 +283,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
// Check if it's the same fragment instance
|
// Check if it's the same fragment instance
|
||||||
if (f == mCurrentPrimaryItem) {
|
if (f == mCurrentPrimaryItem) {
|
||||||
state.putString(SELECTED_FRAGMENT, key);
|
state.putString(selectedFragment, key);
|
||||||
}
|
}
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
}
|
}
|
||||||
@@ -282,16 +292,16 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
|
public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
Bundle bundle = (Bundle)state;
|
Bundle bundle = (Bundle) state;
|
||||||
bundle.setClassLoader(loader);
|
bundle.setClassLoader(loader);
|
||||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
Parcelable[] fss = bundle.getParcelableArray("states");
|
||||||
mSavedState.clear();
|
mSavedState.clear();
|
||||||
mFragments.clear();
|
mFragments.clear();
|
||||||
if (fss != null) {
|
if (fss != null) {
|
||||||
for (int i=0; i<fss.length; i++) {
|
for (int i = 0; i < fss.length; i++) {
|
||||||
mSavedState.add((Fragment.SavedState)fss[i]);
|
mSavedState.add((Fragment.SavedState) fss[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Iterable<String> keys = bundle.keySet();
|
Iterable<String> keys = bundle.keySet();
|
||||||
@@ -304,7 +314,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
mFragments.add(null);
|
mFragments.add(null);
|
||||||
}
|
}
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
final boolean wasSelected = bundle.getString(SELECTED_FRAGMENT, "").equals(key);
|
final boolean wasSelected = bundle.getString(selectedFragment, "")
|
||||||
|
.equals(key);
|
||||||
f.setMenuVisibility(wasSelected);
|
f.setMenuVisibility(wasSelected);
|
||||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
mFragments.set(index, f);
|
mFragments.set(index, f);
|
||||||
|
@@ -1,24 +1,60 @@
|
|||||||
package com.google.android.material.appbar;
|
package com.google.android.material.appbar;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.widget.OverScroller;
|
import android.widget.OverScroller;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
// check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489
|
// See https://stackoverflow.com/questions/56849221#57997489
|
||||||
public final class FlingBehavior extends AppBarLayout.Behavior {
|
public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||||
|
private final Rect focusScrollRect = new Rect();
|
||||||
|
|
||||||
public FlingBehavior(Context context, AttributeSet attrs) {
|
public FlingBehavior(final Context context, final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
|
public boolean onRequestChildRectangleOnScreen(
|
||||||
|
@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final AppBarLayout child,
|
||||||
|
@NonNull final Rect rectangle, final boolean immediate) {
|
||||||
|
|
||||||
|
focusScrollRect.set(rectangle);
|
||||||
|
|
||||||
|
coordinatorLayout.offsetDescendantRectToMyCoords(child, focusScrollRect);
|
||||||
|
|
||||||
|
int height = coordinatorLayout.getHeight();
|
||||||
|
|
||||||
|
if (focusScrollRect.top <= 0 && focusScrollRect.bottom >= height) {
|
||||||
|
// the child is too big to fit inside ourselves completely, ignore request
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dy;
|
||||||
|
|
||||||
|
if (focusScrollRect.bottom > height) {
|
||||||
|
dy = focusScrollRect.top;
|
||||||
|
} else if (focusScrollRect.top < 0) {
|
||||||
|
// scrolling up
|
||||||
|
dy = -(height - focusScrollRect.bottom);
|
||||||
|
} else {
|
||||||
|
// nothing to do
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
|
||||||
|
|
||||||
|
return consumed == dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
|
||||||
|
final MotionEvent ev) {
|
||||||
switch (ev.getActionMasked()) {
|
switch (ev.getActionMasked()) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
// remove reference to old nested scrolling child
|
// remove reference to old nested scrolling child
|
||||||
@@ -35,7 +71,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private OverScroller getScrollerField() {
|
private OverScroller getScrollerField() {
|
||||||
try {
|
try {
|
||||||
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass();
|
Class<?> headerBehaviorType = this.getClass()
|
||||||
|
.getSuperclass().getSuperclass().getSuperclass();
|
||||||
if (headerBehaviorType != null) {
|
if (headerBehaviorType != null) {
|
||||||
Field field = headerBehaviorType.getDeclaredField("scroller");
|
Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
@@ -62,12 +99,14 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetNestedScrollingChild(){
|
private void resetNestedScrollingChild() {
|
||||||
Field field = getLastNestedScrollingChildRefField();
|
Field field = getLastNestedScrollingChildRefField();
|
||||||
if(field != null){
|
if (field != null) {
|
||||||
try {
|
try {
|
||||||
Object value = field.get(this);
|
Object value = field.get(this);
|
||||||
if(value != null) field.set(this, null);
|
if (value != null) {
|
||||||
|
field.set(this, null);
|
||||||
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
// ?
|
// ?
|
||||||
}
|
}
|
||||||
@@ -76,7 +115,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
|||||||
|
|
||||||
private void stopAppBarLayoutFling() {
|
private void stopAppBarLayoutFling() {
|
||||||
OverScroller scroller = getScrollerField();
|
OverScroller scroller = getScrollerField();
|
||||||
if (scroller != null) scroller.forceFinished(true);
|
if (scroller != null) {
|
||||||
|
scroller.forceFinished(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@@ -23,17 +23,25 @@ package org.schabi.newpipe;
|
|||||||
/**
|
/**
|
||||||
* Singleton:
|
* Singleton:
|
||||||
* Used to send data between certain Activity/Services within the same process.
|
* Used to send data between certain Activity/Services within the same process.
|
||||||
* This can be considered as an ugly hack inside the Android universe. **/
|
* This can be considered as an ugly hack inside the Android universe.
|
||||||
|
**/
|
||||||
public class ActivityCommunicator {
|
public class ActivityCommunicator {
|
||||||
|
|
||||||
private static ActivityCommunicator activityCommunicator;
|
private static ActivityCommunicator activityCommunicator;
|
||||||
|
private volatile Class returnActivity;
|
||||||
|
|
||||||
public static ActivityCommunicator getCommunicator() {
|
public static ActivityCommunicator getCommunicator() {
|
||||||
if(activityCommunicator == null) {
|
if (activityCommunicator == null) {
|
||||||
activityCommunicator = new ActivityCommunicator();
|
activityCommunicator = new ActivityCommunicator();
|
||||||
}
|
}
|
||||||
return activityCommunicator;
|
return activityCommunicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public volatile Class returnActivity;
|
public Class getReturnActivity() {
|
||||||
|
return returnActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReturnActivity(final Class returnActivity) {
|
||||||
|
this.returnActivity = returnActivity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,21 +5,20 @@ import android.app.Application;
|
|||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||||
import com.squareup.leakcanary.LeakCanary;
|
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
|
||||||
|
|
||||||
import org.acra.ACRA;
|
import org.acra.ACRA;
|
||||||
import org.acra.config.ACRAConfiguration;
|
|
||||||
import org.acra.config.ACRAConfigurationException;
|
import org.acra.config.ACRAConfigurationException;
|
||||||
import org.acra.config.ConfigurationBuilder;
|
import org.acra.config.CoreConfiguration;
|
||||||
|
import org.acra.config.CoreConfigurationBuilder;
|
||||||
import org.acra.sender.ReportSenderFactory;
|
import org.acra.sender.ReportSenderFactory;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
@@ -27,7 +26,7 @@ import org.schabi.newpipe.report.AcraReportSenderFactory;
|
|||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExceptionUtils;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
import org.schabi.newpipe.util.StateSaver;
|
||||||
@@ -66,15 +65,17 @@ import io.reactivex.plugins.RxJavaPlugins;
|
|||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
protected static final String TAG = App.class.toString();
|
protected static final String TAG = App.class.toString();
|
||||||
private RefWatcher refWatcher;
|
|
||||||
private static App app;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static final Class<? extends ReportSenderFactory>[]
|
private static final Class<? extends ReportSenderFactory>[]
|
||||||
reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
|
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
|
||||||
|
private static App app;
|
||||||
|
|
||||||
|
public static App getApp() {
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(final Context base) {
|
||||||
super.attachBaseContext(base);
|
super.attachBaseContext(base);
|
||||||
|
|
||||||
initACRA();
|
initACRA();
|
||||||
@@ -84,13 +85,6 @@ public class App extends Application {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
if (LeakCanary.isInAnalyzerProcess(this)) {
|
|
||||||
// This process is dedicated to LeakCanary for heap analysis.
|
|
||||||
// You should not init your app in this process.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
refWatcher = installLeakCanary();
|
|
||||||
|
|
||||||
app = this;
|
app = this;
|
||||||
|
|
||||||
// Initialize settings first because others inits can use its values
|
// Initialize settings first because others inits can use its values
|
||||||
@@ -116,31 +110,47 @@ public class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Downloader getDownloader() {
|
protected Downloader getDownloader() {
|
||||||
return DownloaderImpl.init(null);
|
DownloaderImpl downloader = DownloaderImpl.init(null);
|
||||||
|
setCookiesToDownloader(downloader);
|
||||||
|
return downloader;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setCookiesToDownloader(final DownloaderImpl downloader) {
|
||||||
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
||||||
|
getApplicationContext());
|
||||||
|
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
|
||||||
|
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, ""));
|
||||||
|
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureRxJavaErrorHandler() {
|
private void configureRxJavaErrorHandler() {
|
||||||
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
||||||
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(@NonNull Throwable throwable) {
|
public void accept(@NonNull final Throwable throwable) {
|
||||||
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
|
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : "
|
||||||
"throwable = [" + throwable.getClass().getName() + "]");
|
+ "throwable = [" + throwable.getClass().getName() + "]");
|
||||||
|
|
||||||
|
final Throwable actualThrowable;
|
||||||
if (throwable instanceof UndeliverableException) {
|
if (throwable instanceof UndeliverableException) {
|
||||||
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
|
// As UndeliverableException is a wrapper,
|
||||||
throwable = throwable.getCause();
|
// get the cause of it to get the "real" exception
|
||||||
|
actualThrowable = throwable.getCause();
|
||||||
|
} else {
|
||||||
|
actualThrowable = throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Throwable> errors;
|
final List<Throwable> errors;
|
||||||
if (throwable instanceof CompositeException) {
|
if (actualThrowable instanceof CompositeException) {
|
||||||
errors = ((CompositeException) throwable).getExceptions();
|
errors = ((CompositeException) actualThrowable).getExceptions();
|
||||||
} else {
|
} else {
|
||||||
errors = Collections.singletonList(throwable);
|
errors = Collections.singletonList(actualThrowable);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Throwable error : errors) {
|
for (final Throwable error : errors) {
|
||||||
if (isThrowableIgnored(error)) return;
|
if (isThrowableIgnored(error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isThrowableCritical(error)) {
|
if (isThrowableCritical(error)) {
|
||||||
reportException(error);
|
reportException(error);
|
||||||
return;
|
return;
|
||||||
@@ -150,22 +160,24 @@ public class App extends Application {
|
|||||||
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
|
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
|
||||||
// When exception is not reported, log it
|
// When exception is not reported, log it
|
||||||
if (isDisposedRxExceptionsReported()) {
|
if (isDisposedRxExceptionsReported()) {
|
||||||
reportException(throwable);
|
reportException(actualThrowable);
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", throwable);
|
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
|
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
|
||||||
// Don't crash the application over a simple network problem
|
// Don't crash the application over a simple network problem
|
||||||
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
|
return ExceptionUtils.hasAssignableCause(throwable,
|
||||||
IOException.class, SocketException.class, // network api cancellation
|
// network api cancellation
|
||||||
InterruptedException.class, InterruptedIOException.class); // blocking code disposed
|
IOException.class, SocketException.class,
|
||||||
|
// blocking code disposed
|
||||||
|
InterruptedException.class, InterruptedIOException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
|
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
|
||||||
// Though these exceptions cannot be ignored
|
// Though these exceptions cannot be ignored
|
||||||
return ExtractorHelper.hasAssignableCauseThrowable(throwable,
|
return ExceptionUtils.hasAssignableCause(throwable,
|
||||||
NullPointerException.class, IllegalArgumentException.class, // bug in app
|
NullPointerException.class, IllegalArgumentException.class, // bug in app
|
||||||
OnErrorNotImplementedException.class, MissingBackpressureException.class,
|
OnErrorNotImplementedException.class, MissingBackpressureException.class,
|
||||||
IllegalStateException.class); // bug in operator
|
IllegalStateException.class); // bug in operator
|
||||||
@@ -190,8 +202,8 @@ public class App extends Application {
|
|||||||
|
|
||||||
private void initACRA() {
|
private void initACRA() {
|
||||||
try {
|
try {
|
||||||
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
|
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
|
||||||
.setReportSenderFactoryClasses(reportSenderFactoryClasses)
|
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
||||||
.setBuildConfigClass(BuildConfig.class)
|
.setBuildConfigClass(BuildConfig.class)
|
||||||
.build();
|
.build();
|
||||||
ACRA.init(this, acraConfig);
|
ACRA.init(this, acraConfig);
|
||||||
@@ -202,7 +214,7 @@ public class App extends Application {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,11 +242,11 @@ public class App extends Application {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up notification channel for app update.
|
* Set up notification channel for app update.
|
||||||
|
*
|
||||||
* @param importance
|
* @param importance
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.O)
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
private void setUpUpdateNotificationChannel(int importance) {
|
private void setUpUpdateNotificationChannel(final int importance) {
|
||||||
|
|
||||||
final String appUpdateId
|
final String appUpdateId
|
||||||
= getString(R.string.app_update_notification_channel_id);
|
= getString(R.string.app_update_notification_channel_id);
|
||||||
final CharSequence appUpdateName
|
final CharSequence appUpdateName
|
||||||
@@ -251,21 +263,7 @@ public class App extends Application {
|
|||||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static RefWatcher getRefWatcher(Context context) {
|
|
||||||
final App application = (App) context.getApplicationContext();
|
|
||||||
return application.refWatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected RefWatcher installLeakCanary() {
|
|
||||||
return RefWatcher.DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isDisposedRxExceptionsReported() {
|
protected boolean isDisposedRxExceptionsReported() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static App getApp() {
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,32 +2,31 @@ package org.schabi.newpipe;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
import leakcanary.AppWatcher;
|
||||||
|
|
||||||
public abstract class BaseFragment extends Fragment {
|
public abstract class BaseFragment extends Fragment {
|
||||||
|
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
|
||||||
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||||
protected final boolean DEBUG = MainActivity.DEBUG;
|
protected final boolean DEBUG = MainActivity.DEBUG;
|
||||||
|
|
||||||
protected AppCompatActivity activity;
|
protected AppCompatActivity activity;
|
||||||
public static final ImageLoader imageLoader = ImageLoader.getInstance();
|
//These values are used for controlling fragments when they are part of the frontpage
|
||||||
|
|
||||||
//These values are used for controlling framgents when they are part of the frontpage
|
|
||||||
@State
|
@State
|
||||||
protected boolean useAsFrontPage = false;
|
protected boolean useAsFrontPage = false;
|
||||||
protected boolean mIsVisibleToUser = false;
|
private boolean mIsVisibleToUser = false;
|
||||||
|
|
||||||
public void useAsFrontPage(boolean value) {
|
public void useAsFrontPage(final boolean value) {
|
||||||
useAsFrontPage = value;
|
useAsFrontPage = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
activity = (AppCompatActivity) context;
|
activity = (AppCompatActivity) context;
|
||||||
}
|
}
|
||||||
@@ -48,43 +47,49 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onCreate() called with: "
|
||||||
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
|
}
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||||
if (savedInstanceState != null) onRestoreInstanceState(savedInstanceState);
|
if (savedInstanceState != null) {
|
||||||
|
onRestoreInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View rootView, Bundle savedInstanceState) {
|
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.onViewCreated(rootView, savedInstanceState);
|
super.onViewCreated(rootView, savedInstanceState);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]");
|
Log.d(TAG, "onViewCreated() called with: "
|
||||||
|
+ "rootView = [" + rootView + "], "
|
||||||
|
+ "savedInstanceState = [" + savedInstanceState + "]");
|
||||||
}
|
}
|
||||||
initViews(rootView, savedInstanceState);
|
initViews(rootView, savedInstanceState);
|
||||||
initListeners();
|
initListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
Icepick.saveInstanceState(this, outState);
|
Icepick.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
RefWatcher refWatcher = App.getRefWatcher(getActivity());
|
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
||||||
if (refWatcher != null) refWatcher.watch(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
mIsVisibleToUser = isVisibleToUser;
|
mIsVisibleToUser = isVisibleToUser;
|
||||||
}
|
}
|
||||||
@@ -93,7 +98,7 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initListeners() {
|
protected void initListeners() {
|
||||||
@@ -103,10 +108,12 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public void setTitle(String title) {
|
public void setTitle(final String title) {
|
||||||
if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
if (DEBUG) {
|
||||||
if((!useAsFrontPage || mIsVisibleToUser)
|
Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
||||||
&& (activity != null && activity.getSupportActionBar() != null)) {
|
}
|
||||||
|
if ((!useAsFrontPage || mIsVisibleToUser)
|
||||||
|
&& (activity != null && activity.getSupportActionBar() != null)) {
|
||||||
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||||
activity.getSupportActionBar().setTitle(title);
|
activity.getSupportActionBar().setTitle(title);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -27,9 +26,20 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
public class ExitActivity extends Activity {
|
public class ExitActivity extends Activity {
|
||||||
|
|
||||||
|
public static void exitAndRemoveFromRecentApps(final Activity activity) {
|
||||||
|
Intent intent = new Intent(activity, ExitActivity.class);
|
||||||
|
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||||
|
| Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
@@ -40,15 +50,4 @@ public class ExitActivity extends Activity {
|
|||||||
|
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void exitAndRemoveFromRecentApps(Activity activity) {
|
|
||||||
Intent intent = new Intent(activity, ExitActivity.class);
|
|
||||||
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
|
||||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
|
||||||
|
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ public class ImageDownloader extends BaseImageDownloader {
|
|||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
private final String downloadThumbnailKey;
|
private final String downloadThumbnailKey;
|
||||||
|
|
||||||
public ImageDownloader(Context context) {
|
public ImageDownloader(final Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
this.resources = context.getResources();
|
this.resources = context.getResources();
|
||||||
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
@@ -31,7 +31,7 @@ public class ImageDownloader extends BaseImageDownloader {
|
|||||||
|
|
||||||
@SuppressLint("ResourceType")
|
@SuppressLint("ResourceType")
|
||||||
@Override
|
@Override
|
||||||
public InputStream getStream(String imageUri, Object extra) throws IOException {
|
public InputStream getStream(final String imageUri, final Object extra) throws IOException {
|
||||||
if (isDownloadingThumbnail()) {
|
if (isDownloadingThumbnail()) {
|
||||||
return super.getStream(imageUri, extra);
|
return super.getStream(imageUri, extra);
|
||||||
} else {
|
} else {
|
||||||
@@ -39,7 +39,8 @@ public class ImageDownloader extends BaseImageDownloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
|
protected InputStream getStreamFromNetwork(final String imageUri, final Object extra)
|
||||||
|
throws IOException {
|
||||||
final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
|
final DownloaderImpl downloader = (DownloaderImpl) NewPipe.getDownloader();
|
||||||
return downloader.stream(imageUri);
|
return downloader.stream(imageUri);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -13,14 +13,13 @@ import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2;
|
|||||||
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
|
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
|
||||||
|
|
||||||
public final class NewPipeDatabase {
|
public final class NewPipeDatabase {
|
||||||
|
|
||||||
private static volatile AppDatabase databaseInstance;
|
private static volatile AppDatabase databaseInstance;
|
||||||
|
|
||||||
private NewPipeDatabase() {
|
private NewPipeDatabase() {
|
||||||
//no instance
|
//no instance
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AppDatabase getDatabase(Context context) {
|
private static AppDatabase getDatabase(final Context context) {
|
||||||
return Room
|
return Room
|
||||||
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
|
||||||
@@ -28,13 +27,14 @@ public final class NewPipeDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static AppDatabase getInstance(@NonNull Context context) {
|
public static AppDatabase getInstance(@NonNull final Context context) {
|
||||||
AppDatabase result = databaseInstance;
|
AppDatabase result = databaseInstance;
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
synchronized (NewPipeDatabase.class) {
|
synchronized (NewPipeDatabase.class) {
|
||||||
result = databaseInstance;
|
result = databaseInstance;
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
databaseInstance = (result = getDatabase(context));
|
databaseInstance = getDatabase(context);
|
||||||
|
result = databaseInstance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -26,17 +25,18 @@ import android.os.Bundle;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class PanicResponderActivity extends Activity {
|
public class PanicResponderActivity extends Activity {
|
||||||
|
|
||||||
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
|
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||||
// TODO explicitly clear the search results once they are restored when the app restarts
|
// TODO: Explicitly clear the search results
|
||||||
// or if the app reloads the current video after being killed, that should be cleared also
|
// once they are restored when the app restarts
|
||||||
|
// or if the app reloads the current video after being killed,
|
||||||
|
// that should be cleared also
|
||||||
ExitActivity.exitAndRemoveFromRecentApps(this);
|
ExitActivity.exitAndRemoveFromRecentApps(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,24 +1,31 @@
|
|||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.core.app.NavUtils;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.webkit.CookieManager;
|
import android.webkit.CookieManager;
|
||||||
|
import android.webkit.WebResourceRequest;
|
||||||
import android.webkit.WebSettings;
|
import android.webkit.WebSettings;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.app.NavUtils;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by beneth <bmauduit@beneth.fr> on 06.12.16.
|
* Created by beneth <bmauduit@beneth.fr> on 06.12.16.
|
||||||
@@ -44,12 +51,13 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
|
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
|
||||||
public static final String TAG = ReCaptchaActivity.class.toString();
|
public static final String TAG = ReCaptchaActivity.class.toString();
|
||||||
public static final String YT_URL = "https://www.youtube.com";
|
public static final String YT_URL = "https://www.youtube.com";
|
||||||
|
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
|
||||||
|
|
||||||
private WebView webView;
|
private WebView webView;
|
||||||
private String foundCookies = "";
|
private String foundCookies = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_recaptcha);
|
setContentView(R.layout.activity_recaptcha);
|
||||||
@@ -72,10 +80,33 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
webSettings.setJavaScriptEnabled(true);
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
|
||||||
webView.setWebViewClient(new WebViewClient() {
|
webView.setWebViewClient(new WebViewClient() {
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
@Override
|
@Override
|
||||||
public void onPageFinished(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(final WebView view,
|
||||||
|
final WebResourceRequest request) {
|
||||||
|
String url = request.getUrl().toString();
|
||||||
|
if (MainActivity.DEBUG) {
|
||||||
|
Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCookiesFromUrl(url);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
|
||||||
|
if (MainActivity.DEBUG) {
|
||||||
|
Log.d(TAG, "shouldOverrideUrlLoading: url=" + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCookiesFromUrl(url);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(final WebView view, final String url) {
|
||||||
super.onPageFinished(view, url);
|
super.onPageFinished(view, url);
|
||||||
handleCookies(url);
|
handleCookiesFromUrl(url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,7 +115,8 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
webView.clearHistory();
|
webView.clearHistory();
|
||||||
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
cookieManager.removeAllCookies(aBoolean -> {});
|
cookieManager.removeAllCookies(aBoolean -> {
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
cookieManager.removeAllCookie();
|
cookieManager.removeAllCookie();
|
||||||
}
|
}
|
||||||
@@ -93,7 +125,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
||||||
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
@@ -112,7 +144,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case R.id.menu_item_done:
|
case R.id.menu_item_done:
|
||||||
@@ -124,10 +156,20 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void saveCookiesAndFinish() {
|
private void saveCookiesAndFinish() {
|
||||||
handleCookies(webView.getUrl()); // try to get cookies of unclosed page
|
handleCookiesFromUrl(webView.getUrl()); // try to get cookies of unclosed page
|
||||||
|
if (MainActivity.DEBUG) {
|
||||||
|
Log.d(TAG, "saveCookiesAndFinish: foundCookies=" + foundCookies);
|
||||||
|
}
|
||||||
|
|
||||||
if (!foundCookies.isEmpty()) {
|
if (!foundCookies.isEmpty()) {
|
||||||
|
// save cookies to preferences
|
||||||
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
||||||
|
getApplicationContext());
|
||||||
|
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
|
||||||
|
prefs.edit().putString(key, foundCookies).apply();
|
||||||
|
|
||||||
// give cookies to Downloader class
|
// give cookies to Downloader class
|
||||||
DownloaderImpl.getInstance().setCookies(foundCookies);
|
DownloaderImpl.getInstance().setCookie(RECAPTCHA_COOKIES_KEY, foundCookies);
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,24 +179,60 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void handleCookiesFromUrl(@Nullable final String url) {
|
||||||
|
if (MainActivity.DEBUG) {
|
||||||
|
Log.d(TAG, "handleCookiesFromUrl: url=" + (url == null ? "null" : url));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleCookies(String url) {
|
|
||||||
String cookies = CookieManager.getInstance().getCookie(url);
|
String cookies = CookieManager.getInstance().getCookie(url);
|
||||||
if (MainActivity.DEBUG) Log.d(TAG, "handleCookies: url=" + url + "; cookies=" + (cookies == null ? "null" : cookies));
|
handleCookies(cookies);
|
||||||
if (cookies == null) return;
|
|
||||||
|
|
||||||
addYoutubeCookies(cookies);
|
// sometimes cookies are inside the url
|
||||||
// add other methods to extract cookies here
|
int abuseStart = url.indexOf("google_abuse=");
|
||||||
|
if (abuseStart != -1) {
|
||||||
|
int abuseEnd = url.indexOf("+path");
|
||||||
|
|
||||||
|
try {
|
||||||
|
String abuseCookie = url.substring(abuseStart + 13, abuseEnd);
|
||||||
|
abuseCookie = URLDecoder.decode(abuseCookie, "UTF-8");
|
||||||
|
handleCookies(abuseCookie);
|
||||||
|
} catch (UnsupportedEncodingException | StringIndexOutOfBoundsException e) {
|
||||||
|
if (MainActivity.DEBUG) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Log.d(TAG, "handleCookiesFromUrl: invalid google abuse starting at "
|
||||||
|
+ abuseStart + " and ending at " + abuseEnd + " for url " + url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addYoutubeCookies(@NonNull String cookies) {
|
private void handleCookies(@Nullable final String cookies) {
|
||||||
if (cookies.contains("s_gl=") || cookies.contains("goojf=") || cookies.contains("VISITOR_INFO1_LIVE=")) {
|
if (MainActivity.DEBUG) {
|
||||||
|
Log.d(TAG, "handleCookies: cookies=" + (cookies == null ? "null" : cookies));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookies == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addYoutubeCookies(cookies);
|
||||||
|
// add here methods to extract cookies for other services
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addYoutubeCookies(@NonNull final String cookies) {
|
||||||
|
if (cookies.contains("s_gl=") || cookies.contains("goojf=")
|
||||||
|
|| cookies.contains("VISITOR_INFO1_LIVE=")
|
||||||
|
|| cookies.contains("GOOGLE_ABUSE_EXEMPTION=")) {
|
||||||
// youtube seems to also need the other cookies:
|
// youtube seems to also need the other cookies:
|
||||||
addCookie(cookies);
|
addCookie(cookies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCookie(String cookie) {
|
private void addCookie(final String cookie) {
|
||||||
if (foundCookies.contains(cookie)) {
|
if (foundCookies.contains(cookie)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -4,21 +4,22 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
@@ -27,26 +28,41 @@ import org.schabi.newpipe.util.ThemeHelper;
|
|||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
public class AboutActivity extends AppCompatActivity {
|
public class AboutActivity extends AppCompatActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all software components
|
* List of all software components.
|
||||||
*/
|
*/
|
||||||
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
|
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
|
||||||
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai", "https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
|
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
|
||||||
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger", "https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
|
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
|
||||||
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley", "https://github.com/jhy/jsoup", StandardLicenses.MIT),
|
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
|
||||||
new SoftwareComponent("Rhino", "2015", "Mozilla", "https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
|
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
|
||||||
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2),
|
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
|
||||||
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2),
|
"https://github.com/jhy/jsoup", StandardLicenses.MIT),
|
||||||
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
|
new SoftwareComponent("Rhino", "2015", "Mozilla",
|
||||||
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
|
"https://www.mozilla.org/rhino/", StandardLicenses.MPL2),
|
||||||
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
|
||||||
new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
"http://www.acra.ch", StandardLicenses.APACHE2),
|
||||||
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors", "https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
|
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
|
||||||
new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton", "https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
|
"https://github.com/nostra13/Android-Universal-Image-Loader",
|
||||||
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III", "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
|
StandardLicenses.APACHE2),
|
||||||
new SoftwareComponent("Markwon", "2017 - 2020", "Noties", "https://github.com/noties/Markwon", StandardLicenses.APACHE2),
|
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
|
||||||
new SoftwareComponent("Groupie", "2016", "Lisa Wray", "https://github.com/lisawray/groupie", StandardLicenses.MIT)
|
"https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
|
||||||
|
"https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
|
||||||
|
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google Inc",
|
||||||
|
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("RxAndroid", "2015 - 2018", "The RxAndroid authors",
|
||||||
|
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
|
||||||
|
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("RxBinding", "2015 - 2018", "Jake Wharton",
|
||||||
|
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
|
||||||
|
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("Markwon", "2017 - 2020", "Noties",
|
||||||
|
"https://github.com/noties/Markwon", StandardLicenses.APACHE2),
|
||||||
|
new SoftwareComponent("Groupie", "2016", "Lisa Wray",
|
||||||
|
"https://github.com/lisawray/groupie", StandardLicenses.MIT)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +81,7 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
private ViewPager mViewPager;
|
private ViewPager mViewPager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
assureCorrectAppLanguage(this);
|
assureCorrectAppLanguage(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
@@ -88,10 +104,8 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
tabLayout.setupWithViewPager(mViewPager);
|
tabLayout.setupWithViewPager(mViewPager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
|
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
@@ -107,21 +121,20 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
* A placeholder fragment containing a simple view.
|
* A placeholder fragment containing a simple view.
|
||||||
*/
|
*/
|
||||||
public static class AboutFragment extends Fragment {
|
public static class AboutFragment extends Fragment {
|
||||||
|
public AboutFragment() { }
|
||||||
public AboutFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new instance of this fragment for the given section
|
* Created a new instance of this fragment for the given section number.
|
||||||
* number.
|
*
|
||||||
|
* @return New instance of {@link AboutFragment}
|
||||||
*/
|
*/
|
||||||
public static AboutFragment newInstance() {
|
public static AboutFragment newInstance() {
|
||||||
return new AboutFragment();
|
return new AboutFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||||
Context context = this.getContext();
|
Context context = this.getContext();
|
||||||
|
|
||||||
@@ -129,40 +142,42 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
version.setText(BuildConfig.VERSION_NAME);
|
version.setText(BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
View githubLink = rootView.findViewById(R.id.github_link);
|
View githubLink = rootView.findViewById(R.id.github_link);
|
||||||
githubLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.github_url), context));
|
githubLink.setOnClickListener(nv ->
|
||||||
|
openWebsite(context.getString(R.string.github_url), context));
|
||||||
|
|
||||||
View donationLink = rootView.findViewById(R.id.donation_link);
|
View donationLink = rootView.findViewById(R.id.donation_link);
|
||||||
donationLink.setOnClickListener(v -> openWebsite(context.getString(R.string.donation_url), context));
|
donationLink.setOnClickListener(v ->
|
||||||
|
openWebsite(context.getString(R.string.donation_url), context));
|
||||||
|
|
||||||
View websiteLink = rootView.findViewById(R.id.website_link);
|
View websiteLink = rootView.findViewById(R.id.website_link);
|
||||||
websiteLink.setOnClickListener(nv -> openWebsite(context.getString(R.string.website_url), context));
|
websiteLink.setOnClickListener(nv ->
|
||||||
|
openWebsite(context.getString(R.string.website_url), context));
|
||||||
|
|
||||||
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||||
privacyPolicyLink.setOnClickListener(v -> openWebsite(context.getString(R.string.privacy_policy_url), context));
|
privacyPolicyLink.setOnClickListener(v ->
|
||||||
|
openWebsite(context.getString(R.string.privacy_policy_url), context));
|
||||||
|
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWebsite(String url, Context context) {
|
private void openWebsite(final String url, final Context context) {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
|
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
|
||||||
* one of the sections/tabs/pages.
|
* one of the sections/tabs/pages.
|
||||||
*/
|
*/
|
||||||
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
public SectionsPagerAdapter(final FragmentManager fm) {
|
||||||
public SectionsPagerAdapter(FragmentManager fm) {
|
|
||||||
super(fm);
|
super(fm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(final int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
return AboutFragment.newInstance();
|
return AboutFragment.newInstance();
|
||||||
@@ -179,7 +194,7 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(final int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
return getString(R.string.tab_about);
|
return getString(R.string.tab_about);
|
||||||
|
@@ -5,18 +5,17 @@ import android.os.Parcel;
|
|||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A software license
|
* Class for storing information about a software license.
|
||||||
*/
|
*/
|
||||||
public class License implements Parcelable {
|
public class License implements Parcelable {
|
||||||
|
|
||||||
public static final Creator<License> CREATOR = new Creator<License>() {
|
public static final Creator<License> CREATOR = new Creator<License>() {
|
||||||
@Override
|
@Override
|
||||||
public License createFromParcel(Parcel source) {
|
public License createFromParcel(final Parcel source) {
|
||||||
return new License(source);
|
return new License(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public License[] newArray(int size) {
|
public License[] newArray(final int size) {
|
||||||
return new License[size];
|
return new License[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -24,16 +23,22 @@ public class License implements Parcelable {
|
|||||||
private final String name;
|
private final String name;
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
||||||
public License(String name, String abbreviation, String filename) {
|
public License(final String name, final String abbreviation, final String filename) {
|
||||||
if(name == null) throw new NullPointerException("name is null");
|
if (name == null) {
|
||||||
if(abbreviation == null) throw new NullPointerException("abbreviation is null");
|
throw new NullPointerException("name is null");
|
||||||
if(filename == null) throw new NullPointerException("filename is null");
|
}
|
||||||
|
if (abbreviation == null) {
|
||||||
|
throw new NullPointerException("abbreviation is null");
|
||||||
|
}
|
||||||
|
if (filename == null) {
|
||||||
|
throw new NullPointerException("filename is null");
|
||||||
|
}
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.abbreviation = abbreviation;
|
this.abbreviation = abbreviation;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected License(Parcel in) {
|
protected License(final Parcel in) {
|
||||||
this.filename = in.readString();
|
this.filename = in.readString();
|
||||||
this.abbreviation = in.readString();
|
this.abbreviation = in.readString();
|
||||||
this.name = in.readString();
|
this.name = in.readString();
|
||||||
@@ -50,7 +55,7 @@ public class License implements Parcelable {
|
|||||||
public String getAbbreviation() {
|
public String getAbbreviation() {
|
||||||
return abbreviation;
|
return abbreviation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFilename() {
|
public String getFilename() {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
@@ -61,7 +66,7 @@ public class License implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
dest.writeString(this.filename);
|
dest.writeString(this.filename);
|
||||||
dest.writeString(this.abbreviation);
|
dest.writeString(this.abbreviation);
|
||||||
dest.writeString(this.name);
|
dest.writeString(this.name);
|
||||||
|
@@ -2,29 +2,33 @@ package org.schabi.newpipe.about;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.view.*;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment containing the software licenses
|
* Fragment containing the software licenses.
|
||||||
*/
|
*/
|
||||||
public class LicenseFragment extends Fragment {
|
public class LicenseFragment extends Fragment {
|
||||||
|
|
||||||
private static final String ARG_COMPONENTS = "components";
|
private static final String ARG_COMPONENTS = "components";
|
||||||
private SoftwareComponent[] softwareComponents;
|
private SoftwareComponent[] softwareComponents;
|
||||||
private SoftwareComponent mComponentForContextMenu;
|
private SoftwareComponent componentForContextMenu;
|
||||||
|
|
||||||
public static LicenseFragment newInstance(SoftwareComponent[] softwareComponents) {
|
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||||
if(softwareComponents == null) {
|
if (softwareComponents == null) {
|
||||||
throw new NullPointerException("softwareComponents is null");
|
throw new NullPointerException("softwareComponents is null");
|
||||||
}
|
}
|
||||||
LicenseFragment fragment = new LicenseFragment();
|
LicenseFragment fragment = new LicenseFragment();
|
||||||
@@ -35,57 +39,50 @@ public class LicenseFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a popup containing the license
|
* Shows a popup containing the license.
|
||||||
|
*
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param license the license to show
|
* @param license the license to show
|
||||||
*/
|
*/
|
||||||
public static void showLicense(Context context, License license) {
|
private static void showLicense(final Context context, final License license) {
|
||||||
new LicenseFragmentHelper((Activity) context).execute(license);
|
new LicenseFragmentHelper((Activity) context).execute(license);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
softwareComponents = (SoftwareComponent[]) getArguments().getParcelableArray(ARG_COMPONENTS);
|
softwareComponents = (SoftwareComponent[]) getArguments()
|
||||||
|
.getParcelableArray(ARG_COMPONENTS);
|
||||||
|
|
||||||
// Sort components by name
|
// Sort components by name
|
||||||
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
|
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||||
@Override
|
|
||||||
public int compare(SoftwareComponent o1, SoftwareComponent o2) {
|
|
||||||
return o1.getName().compareTo(o2.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
@Nullable final Bundle savedInstanceState) {
|
||||||
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
final View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
||||||
|
final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
||||||
|
|
||||||
View licenseLink = rootView.findViewById(R.id.app_read_license);
|
final View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||||
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
licenseLink.setOnClickListener(v ->
|
||||||
|
showLicense(getActivity(), StandardLicenses.GPL3));
|
||||||
|
|
||||||
for (final SoftwareComponent component : softwareComponents) {
|
for (final SoftwareComponent component : softwareComponents) {
|
||||||
View componentView = inflater.inflate(R.layout.item_software_component, container, false);
|
final View componentView = inflater
|
||||||
TextView softwareName = componentView.findViewById(R.id.name);
|
.inflate(R.layout.item_software_component, container, false);
|
||||||
TextView copyright = componentView.findViewById(R.id.copyright);
|
final TextView softwareName = componentView.findViewById(R.id.name);
|
||||||
|
final TextView copyright = componentView.findViewById(R.id.copyright);
|
||||||
softwareName.setText(component.getName());
|
softwareName.setText(component.getName());
|
||||||
copyright.setText(getContext().getString(R.string.copyright,
|
copyright.setText(getString(R.string.copyright,
|
||||||
component.getYears(),
|
component.getYears(),
|
||||||
component.getCopyrightOwner(),
|
component.getCopyrightOwner(),
|
||||||
component.getLicense().getAbbreviation()));
|
component.getLicense().getAbbreviation()));
|
||||||
|
|
||||||
componentView.setTag(component);
|
componentView.setTag(component);
|
||||||
componentView.setOnClickListener(new View.OnClickListener() {
|
componentView.setOnClickListener(v ->
|
||||||
@Override
|
showLicense(getActivity(), component.getLicense()));
|
||||||
public void onClick(View v) {
|
|
||||||
Context context = v.getContext();
|
|
||||||
if (context != null) {
|
|
||||||
showLicense(context, component.getLicense());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
softwareComponentsView.addView(componentView);
|
softwareComponentsView.addView(componentView);
|
||||||
registerForContextMenu(componentView);
|
registerForContextMenu(componentView);
|
||||||
}
|
}
|
||||||
@@ -93,41 +90,30 @@ public class LicenseFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
public void onCreateContextMenu(final ContextMenu menu, final View v,
|
||||||
MenuInflater inflater = getActivity().getMenuInflater();
|
final ContextMenu.ContextMenuInfo menuInfo) {
|
||||||
SoftwareComponent component = (SoftwareComponent) v.getTag();
|
final MenuInflater inflater = getActivity().getMenuInflater();
|
||||||
|
final SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||||
menu.setHeaderTitle(component.getName());
|
menu.setHeaderTitle(component.getName());
|
||||||
inflater.inflate(R.menu.software_component, menu);
|
inflater.inflate(R.menu.software_component, menu);
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
mComponentForContextMenu = (SoftwareComponent) v.getTag();
|
componentForContextMenu = (SoftwareComponent) v.getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(MenuItem item) {
|
public boolean onContextItemSelected(final MenuItem item) {
|
||||||
// item.getMenuInfo() is null so we use the tag of the view
|
// item.getMenuInfo() is null so we use the tag of the view
|
||||||
final SoftwareComponent component = mComponentForContextMenu;
|
final SoftwareComponent component = componentForContextMenu;
|
||||||
if (component == null) {
|
if (component == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_website:
|
case R.id.action_website:
|
||||||
openWebsite(component.getLink());
|
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_show_license:
|
case R.id.action_show_license:
|
||||||
showLicense(getContext(), component.getLicense());
|
showLicense(getActivity(), component.getLicense());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWebsite(String componentLink) {
|
|
||||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
|
|
||||||
startActivity(browserIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,33 +2,95 @@ package org.schabi.newpipe.about;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import android.webkit.WebView;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
|
private final WeakReference<Activity> weakReference;
|
||||||
final WeakReference<Activity> weakReference;
|
|
||||||
private License license;
|
private License license;
|
||||||
|
|
||||||
public LicenseFragmentHelper(@Nullable Activity activity) {
|
public LicenseFragmentHelper(@Nullable final Activity activity) {
|
||||||
weakReference = new WeakReference<>(activity);
|
weakReference = new WeakReference<>(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context the context to use
|
||||||
|
* @param license the license
|
||||||
|
* @return String which contains a HTML formatted license page
|
||||||
|
* styled according to the context's theme
|
||||||
|
*/
|
||||||
|
private static String getFormattedLicense(@NonNull final Context context,
|
||||||
|
@NonNull final License license) {
|
||||||
|
final StringBuilder licenseContent = new StringBuilder();
|
||||||
|
final String webViewData;
|
||||||
|
try {
|
||||||
|
final BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||||
|
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
|
||||||
|
String str;
|
||||||
|
while ((str = in.readLine()) != null) {
|
||||||
|
licenseContent.append(str);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||||
|
webViewData = licenseContent.toString().replace("</head>",
|
||||||
|
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Could not get license file: " + license.getFilename(), e);
|
||||||
|
}
|
||||||
|
return webViewData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context
|
||||||
|
* @return String which is a CSS stylesheet according to the context's theme
|
||||||
|
*/
|
||||||
|
private static String getLicenseStylesheet(final Context context) {
|
||||||
|
final boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
||||||
|
return "body{padding:12px 15px;margin:0;"
|
||||||
|
+ "background:#" + getHexRGBColor(context, isLightTheme
|
||||||
|
? R.color.light_license_background_color
|
||||||
|
: R.color.dark_license_background_color) + ";"
|
||||||
|
+ "color:#" + getHexRGBColor(context, isLightTheme
|
||||||
|
? R.color.light_license_text_color
|
||||||
|
: R.color.dark_license_text_color) + "}"
|
||||||
|
+ "a[href]{color:#" + getHexRGBColor(context, isLightTheme
|
||||||
|
? R.color.light_youtube_primary_color
|
||||||
|
: R.color.dark_youtube_primary_color) + "}"
|
||||||
|
+ "pre{white-space:pre-wrap}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast R.color to a hexadecimal color value.
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @param color the color number from R.color
|
||||||
|
* @return a six characters long String with hexadecimal RGB values
|
||||||
|
*/
|
||||||
|
private static String getHexRGBColor(final Context context, final int color) {
|
||||||
|
return context.getResources().getString(color).substring(3);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Activity getActivity() {
|
private Activity getActivity() {
|
||||||
Activity activity = weakReference.get();
|
final Activity activity = weakReference.get();
|
||||||
|
|
||||||
if (activity != null && activity.isFinishing()) {
|
if (activity != null && activity.isFinishing()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -38,99 +100,29 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer doInBackground(Object... objects) {
|
protected Integer doInBackground(final Object... objects) {
|
||||||
license = (License) objects[0];
|
license = (License) objects[0];
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Integer result) {
|
protected void onPostExecute(final Integer result) {
|
||||||
Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String webViewData = getFormattedLicense(activity, license);
|
final String webViewData = Base64.encodeToString(getFormattedLicense(activity, license)
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
|
||||||
|
final WebView webView = new WebView(activity);
|
||||||
|
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
|
||||||
|
|
||||||
|
final AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||||
alert.setTitle(license.getName());
|
alert.setTitle(license.getName());
|
||||||
|
alert.setView(webView);
|
||||||
WebView wv = new WebView(activity);
|
assureCorrectAppLanguage(activity);
|
||||||
wv.loadData(webViewData, "text/html; charset=UTF-8", null);
|
alert.setNegativeButton(activity.getString(R.string.finish),
|
||||||
|
(dialog, which) -> dialog.dismiss());
|
||||||
alert.setView(wv);
|
|
||||||
assureCorrectAppLanguage(activity.getApplicationContext());
|
|
||||||
alert.setNegativeButton(getFinishString(activity), (dialog, which) -> dialog.dismiss());
|
|
||||||
alert.show();
|
alert.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getFinishString(Activity activity) {
|
|
||||||
return activity.getApplicationContext().getResources().getString(R.string.finish);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context the context to use
|
|
||||||
* @param license the license
|
|
||||||
* @return String which contains a HTML formatted license page styled according to the context's theme
|
|
||||||
*/
|
|
||||||
public static String getFormattedLicense(Context context, License license) {
|
|
||||||
if(context == null) {
|
|
||||||
throw new NullPointerException("context is null");
|
|
||||||
}
|
|
||||||
if(license == null) {
|
|
||||||
throw new NullPointerException("license is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder licenseContent = new StringBuilder();
|
|
||||||
String webViewData;
|
|
||||||
try {
|
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(context.getAssets().open(license.getFilename()), "UTF-8"));
|
|
||||||
String str;
|
|
||||||
while ((str = in.readLine()) != null) {
|
|
||||||
licenseContent.append(str);
|
|
||||||
}
|
|
||||||
in.close();
|
|
||||||
|
|
||||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
|
||||||
String[] insert = licenseContent.toString().split("</head>");
|
|
||||||
webViewData = insert[0] + "<style type=\"text/css\">"
|
|
||||||
+ getLicenseStylesheet(context) + "</style></head>"
|
|
||||||
+ insert[1];
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new NullPointerException("could not get license file:" + getLicenseStylesheet(context));
|
|
||||||
}
|
|
||||||
return webViewData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @return String which is a CSS stylesheet according to the context's theme
|
|
||||||
*/
|
|
||||||
public static String getLicenseStylesheet(Context context) {
|
|
||||||
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
|
||||||
return "body{padding:12px 15px;margin:0;background:#"
|
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_license_background_color
|
|
||||||
: R.color.dark_license_background_color)
|
|
||||||
+ ";color:#"
|
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_license_text_color
|
|
||||||
: R.color.dark_license_text_color) + ";}"
|
|
||||||
+ "a[href]{color:#"
|
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_youtube_primary_color
|
|
||||||
: R.color.dark_youtube_primary_color) + ";}"
|
|
||||||
+ "pre{white-space: pre-wrap;}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cast R.color to a hexadecimal color value
|
|
||||||
* @param context the context to use
|
|
||||||
* @param color the color number from R.color
|
|
||||||
* @return a six characters long String with hexadecimal RGB values
|
|
||||||
*/
|
|
||||||
public static String getHexRGBColor(Context context, int color) {
|
|
||||||
return context.getResources().getString(color).substring(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,19 +4,44 @@ import android.os.Parcel;
|
|||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
public class SoftwareComponent implements Parcelable {
|
public class SoftwareComponent implements Parcelable {
|
||||||
|
|
||||||
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
|
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
|
||||||
@Override
|
@Override
|
||||||
public SoftwareComponent createFromParcel(Parcel source) {
|
public SoftwareComponent createFromParcel(final Parcel source) {
|
||||||
return new SoftwareComponent(source);
|
return new SoftwareComponent(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SoftwareComponent[] newArray(int size) {
|
public SoftwareComponent[] newArray(final int size) {
|
||||||
return new SoftwareComponent[size];
|
return new SoftwareComponent[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final License license;
|
||||||
|
private final String name;
|
||||||
|
private final String years;
|
||||||
|
private final String copyrightOwner;
|
||||||
|
private final String link;
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
public SoftwareComponent(final String name, final String years, final String copyrightOwner,
|
||||||
|
final String link, final License license) {
|
||||||
|
this.name = name;
|
||||||
|
this.years = years;
|
||||||
|
this.copyrightOwner = copyrightOwner;
|
||||||
|
this.link = link;
|
||||||
|
this.license = license;
|
||||||
|
this.version = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SoftwareComponent(final Parcel in) {
|
||||||
|
this.name = in.readString();
|
||||||
|
this.license = in.readParcelable(License.class.getClassLoader());
|
||||||
|
this.copyrightOwner = in.readString();
|
||||||
|
this.link = in.readString();
|
||||||
|
this.years = in.readString();
|
||||||
|
this.version = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -37,31 +62,6 @@ public class SoftwareComponent implements Parcelable {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final License license;
|
|
||||||
private final String name;
|
|
||||||
private final String years;
|
|
||||||
private final String copyrightOwner;
|
|
||||||
private final String link;
|
|
||||||
private final String version;
|
|
||||||
|
|
||||||
public SoftwareComponent(String name, String years, String copyrightOwner, String link, License license) {
|
|
||||||
this.name = name;
|
|
||||||
this.years = years;
|
|
||||||
this.copyrightOwner = copyrightOwner;
|
|
||||||
this.link = link;
|
|
||||||
this.license = license;
|
|
||||||
this.version = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SoftwareComponent(Parcel in) {
|
|
||||||
this.name = in.readString();
|
|
||||||
this.license = in.readParcelable(License.class.getClassLoader());
|
|
||||||
this.copyrightOwner = in.readString();
|
|
||||||
this.link = in.readString();
|
|
||||||
this.years = in.readString();
|
|
||||||
this.version = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public License getLicense() {
|
public License getLicense() {
|
||||||
return license;
|
return license;
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ public class SoftwareComponent implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
dest.writeString(name);
|
dest.writeString(name);
|
||||||
dest.writeParcelable(license, flags);
|
dest.writeParcelable(license, flags);
|
||||||
dest.writeString(copyrightOwner);
|
dest.writeString(copyrightOwner);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user