mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-01-13 18:22:41 +00:00
Compare commits
836 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25d555126d | ||
|
|
f529d15d7a | ||
|
|
9eda30fc71 | ||
|
|
e1024e59c3 | ||
|
|
175652f23b | ||
|
|
3329e0c4a1 | ||
|
|
ea0a0c7c5a | ||
|
|
535a0504d8 | ||
|
|
365c49d6d2 | ||
|
|
b70bea48f2 | ||
|
|
996f8644c4 | ||
|
|
b3882ec6e3 | ||
|
|
f87d447397 | ||
|
|
9d8570d0d2 | ||
|
|
796755dad8 | ||
|
|
9387753995 | ||
|
|
618d36dc07 | ||
|
|
f11b0be483 | ||
|
|
1b8b15b136 | ||
|
|
bb63673cce | ||
|
|
6770ad68d5 | ||
|
|
bfe90c58d1 | ||
|
|
f1cbeb3c29 | ||
|
|
554ab4ea16 | ||
|
|
a2becac2e6 | ||
|
|
67a651f5e9 | ||
|
|
ac8efe19d8 | ||
|
|
ffd65d5afa | ||
|
|
e86677178f | ||
|
|
3c49a3341a | ||
|
|
3433b2a73e | ||
|
|
730988e7b7 | ||
|
|
2a3b89e596 | ||
|
|
f1a31bf58c | ||
|
|
f1b62a9056 | ||
|
|
dd943d24c8 | ||
|
|
801320a3f3 | ||
|
|
222ed2debd | ||
|
|
7aab782c5f | ||
|
|
3836f2f353 | ||
|
|
5bfaa9a5db | ||
|
|
95581771d6 | ||
|
|
db5e3f2479 | ||
|
|
b5a9631bcc | ||
|
|
4a2d62ece0 | ||
|
|
c3836decee | ||
|
|
f7a030c895 | ||
|
|
f171a692d3 | ||
|
|
df5e73192b | ||
|
|
fc1447d614 | ||
|
|
77fd206b06 | ||
|
|
23bdc03490 | ||
|
|
9fe4de5709 | ||
|
|
54fd601809 | ||
|
|
71d027a966 | ||
|
|
8b63aa2fe6 | ||
|
|
5a35842c28 | ||
|
|
d6a1ae3b3a | ||
|
|
be5f4cb562 | ||
|
|
d8b5464833 | ||
|
|
5e7bbcd3bc | ||
|
|
5383e53c4d | ||
|
|
5b6fc713d6 | ||
|
|
272be025e1 | ||
|
|
e4ab250729 | ||
|
|
4dcca9d5af | ||
|
|
1988a08631 | ||
|
|
34de0e569f | ||
|
|
343d0fa09d | ||
|
|
8eb6686103 | ||
|
|
9e9687b5b8 | ||
|
|
ef888d1afe | ||
|
|
0e70e1a37a | ||
|
|
06aaceb673 | ||
|
|
703a4b7858 | ||
|
|
32ba2ba83d | ||
|
|
272b03ed92 | ||
|
|
ecf19214ee | ||
|
|
f3eb0c497f | ||
|
|
c1f29a7565 | ||
|
|
fb745b9108 | ||
|
|
9410bf40d3 | ||
|
|
c7a695cb04 | ||
|
|
b991d5cab6 | ||
|
|
42fd318321 | ||
|
|
903aeec383 | ||
|
|
8768fe4dcf | ||
|
|
d8ba2ceed4 | ||
|
|
ef8a1bcf47 | ||
|
|
2b1469e02e | ||
|
|
83ea91586b | ||
|
|
dbb86d25e1 | ||
|
|
794c74e514 | ||
|
|
fbcdaa77e3 | ||
|
|
dbdc04c45e | ||
|
|
a4bb22280f | ||
|
|
c0e1bbbfb6 | ||
|
|
196b9dc771 | ||
|
|
09578b4e46 | ||
|
|
4d88dadf8c | ||
|
|
d4fda5847d | ||
|
|
b1ea7d6cbc | ||
|
|
4e7632949d | ||
|
|
26a8bd147b | ||
|
|
dd726fac02 | ||
|
|
3a3ecc7775 | ||
|
|
6665d630ec | ||
|
|
7706d7471a | ||
|
|
ed87d6b268 | ||
|
|
ed51c8b318 | ||
|
|
6ffbb7b1ed | ||
|
|
06764db118 | ||
|
|
4864fa3f2d | ||
|
|
2d25b6a1f4 | ||
|
|
be76b3d105 | ||
|
|
81cbeb4b24 | ||
|
|
f0b658ba14 | ||
|
|
295836fc7e | ||
|
|
54e9858148 | ||
|
|
b68f015825 | ||
|
|
87ae26ede3 | ||
|
|
49615f81b4 | ||
|
|
87ce5140fa | ||
|
|
3ba9fb375c | ||
|
|
f4bd20361a | ||
|
|
54f8a17aac | ||
|
|
7a1e5026c4 | ||
|
|
323161c6de | ||
|
|
1ac4890893 | ||
|
|
e9c88fecc5 | ||
|
|
33deaaefac | ||
|
|
bb6438ebe4 | ||
|
|
d42af74afa | ||
|
|
ea1f2f4ad4 | ||
|
|
95b45651bb | ||
|
|
0d5730d33e | ||
|
|
0625a35ddf | ||
|
|
2d3271ee13 | ||
|
|
6da2e80027 | ||
|
|
439edbf85c | ||
|
|
e0237a0b86 | ||
|
|
e1845ba603 | ||
|
|
1cf757d401 | ||
|
|
14985b1727 | ||
|
|
6b2788be57 | ||
|
|
ac888f4cb2 | ||
|
|
df06cfc4c5 | ||
|
|
dcba3a681c | ||
|
|
7fd49c22a8 | ||
|
|
314287a6d9 | ||
|
|
f5e7b8f229 | ||
|
|
de84db070e | ||
|
|
c1d5a5cd98 | ||
|
|
160a04c3c7 | ||
|
|
23bfc30c57 | ||
|
|
d9329bffd1 | ||
|
|
bafc1df988 | ||
|
|
30b8835919 | ||
|
|
44b19e75f6 | ||
|
|
2a558ad11d | ||
|
|
123d8972e1 | ||
|
|
e550a8ea27 | ||
|
|
8b29460fed | ||
|
|
e380d63c57 | ||
|
|
89b4f2c4d4 | ||
|
|
6c2f63f738 | ||
|
|
77c612f0f5 | ||
|
|
2d06c01192 | ||
|
|
d13c19f05f | ||
|
|
3d2ba05c77 | ||
|
|
6898b9d9a4 | ||
|
|
a65aaa6b83 | ||
|
|
b3136c20c4 | ||
|
|
0ae3dfd9cc | ||
|
|
0370fa6c00 | ||
|
|
7ab323b00c | ||
|
|
541eb70b9c | ||
|
|
e53e5ca20e | ||
|
|
609bf64856 | ||
|
|
a9fafe91a5 | ||
|
|
0466b320dd | ||
|
|
5b74d22d0a | ||
|
|
4152c7f956 | ||
|
|
d5f603303d | ||
|
|
fc9c073a60 | ||
|
|
9a0c2c40bd | ||
|
|
d0fc9fda71 | ||
|
|
df9823988e | ||
|
|
eeb09c074c | ||
|
|
af0928e2bd | ||
|
|
d9cf4de3f7 | ||
|
|
2d6dd4b3be | ||
|
|
160312393a | ||
|
|
e3ff9f9c86 | ||
|
|
95570d796d | ||
|
|
cd0d58a915 | ||
|
|
2f1007c725 | ||
|
|
b53d5d8c00 | ||
|
|
3c4a4e5384 | ||
|
|
0e5f85db95 | ||
|
|
ad3364671d | ||
|
|
3add24b8aa | ||
|
|
e0f02d4080 | ||
|
|
00c4c10472 | ||
|
|
a2b8cc9dc2 | ||
|
|
3465002cbb | ||
|
|
631cb73305 | ||
|
|
b3b6384bef | ||
|
|
941ca575fb | ||
|
|
2d65c3595d | ||
|
|
b3812d913a | ||
|
|
adcc420c81 | ||
|
|
b97ad99bb4 | ||
|
|
e93a2850d6 | ||
|
|
411d0691fa | ||
|
|
c843e77183 | ||
|
|
093d6e5336 | ||
|
|
8a3c752d42 | ||
|
|
b4e073cde7 | ||
|
|
814efbf8df | ||
|
|
11e048abb1 | ||
|
|
34e7855af6 | ||
|
|
de54dc27ad | ||
|
|
790133978d | ||
|
|
b914d67d9d | ||
|
|
518eb97e3a | ||
|
|
f41549ccf1 | ||
|
|
b69e477ecd | ||
|
|
0062ff9cfa | ||
|
|
f8de72f59f | ||
|
|
7317737e90 | ||
|
|
5b8eda4805 | ||
|
|
886a949a00 | ||
|
|
92e13dafe5 | ||
|
|
c9be812330 | ||
|
|
59e7ebabfa | ||
|
|
1afc48fce3 | ||
|
|
a1e4ef9e8e | ||
|
|
5ada0ae2c7 | ||
|
|
a5312c1341 | ||
|
|
150e156d26 | ||
|
|
6d38615ea8 | ||
|
|
011cc7d337 | ||
|
|
b747d09836 | ||
|
|
4b7311bafd | ||
|
|
eeba9c0a5f | ||
|
|
11d9a037f7 | ||
|
|
883e4fcd7c | ||
|
|
2017e6a3e3 | ||
|
|
bccfe500b3 | ||
|
|
5846fbabce | ||
|
|
52e89c1d1c | ||
|
|
1605e50cef | ||
|
|
2215ce58a4 | ||
|
|
a13e6b69e3 | ||
|
|
1d6370e11c | ||
|
|
bd34c7ede3 | ||
|
|
bc8954fbba | ||
|
|
9cf0bc6c82 | ||
|
|
71b32fe641 | ||
|
|
530f745e44 | ||
|
|
a5a2313851 | ||
|
|
4e98c2e7f6 | ||
|
|
14486782dc | ||
|
|
31814b70da | ||
|
|
408e819d32 | ||
|
|
622676f9bc | ||
|
|
5b631e0387 | ||
|
|
06d54ef77e | ||
|
|
6c5ef567ed | ||
|
|
f86b40302d | ||
|
|
273c287fbf | ||
|
|
6cb16be5df | ||
|
|
a4feb3fc09 | ||
|
|
ba6c7de35a | ||
|
|
79e2bb382f | ||
|
|
0090256ded | ||
|
|
00ce077758 | ||
|
|
a801d0994f | ||
|
|
628575dc5f | ||
|
|
0a22f21410 | ||
|
|
97ff9e9c5b | ||
|
|
8b3a09306b | ||
|
|
7766fd13fd | ||
|
|
c79997ebe3 | ||
|
|
0fd1e2fcd9 | ||
|
|
99442b6e04 | ||
|
|
b8a35e9e4a | ||
|
|
e833d415e3 | ||
|
|
8030312924 | ||
|
|
a84b54f940 | ||
|
|
c66c81294e | ||
|
|
bfdc215c65 | ||
|
|
e10c7beedb | ||
|
|
18c3286364 | ||
|
|
8d2ec30818 | ||
|
|
e5ffddfc6b | ||
|
|
59fc1e4b5f | ||
|
|
7ead581953 | ||
|
|
b860980df4 | ||
|
|
97a366d62e | ||
|
|
9ad68097d0 | ||
|
|
331999fb95 | ||
|
|
6519d7051d | ||
|
|
552d585fca | ||
|
|
24c24d6c72 | ||
|
|
b7f50c3e12 | ||
|
|
aed1687a45 | ||
|
|
daa427dc15 | ||
|
|
e9d4303fdb | ||
|
|
5485e994ee | ||
|
|
87228673b4 | ||
|
|
d306513319 | ||
|
|
e08480f345 | ||
|
|
13c9096417 | ||
|
|
d3d65c8e3a | ||
|
|
12ac5ef781 | ||
|
|
adef9a8acf | ||
|
|
5ef407d15f | ||
|
|
970b636eb4 | ||
|
|
fcf9131aae | ||
|
|
7fd27fac45 | ||
|
|
6e17af91fb | ||
|
|
68555573ad | ||
|
|
fb9905a89e | ||
|
|
1e7504dc5a | ||
|
|
d7af019511 | ||
|
|
04bb070afa | ||
|
|
e693d80857 | ||
|
|
45ae05f1b5 | ||
|
|
05a83beb44 | ||
|
|
8a1a42e83b | ||
|
|
1b3f3cedb3 | ||
|
|
f815ae5973 | ||
|
|
fb7035bf22 | ||
|
|
02bcbc3221 | ||
|
|
f0a51d4ab4 | ||
|
|
24fe8fe9a0 | ||
|
|
244e95d959 | ||
|
|
ad72c64e32 | ||
|
|
767ac6a51b | ||
|
|
3a6f87659a | ||
|
|
b7ac16c7d9 | ||
|
|
f9f84cbd89 | ||
|
|
701c87eefa | ||
|
|
5379cf0544 | ||
|
|
6b5c37f17f | ||
|
|
50b2fad180 | ||
|
|
b9cb65b24e | ||
|
|
d7574973e9 | ||
|
|
ff48c93d59 | ||
|
|
d2e6700dd1 | ||
|
|
05b8c3f35f | ||
|
|
70b643e7ba | ||
|
|
dce973a519 | ||
|
|
eb2f75579a | ||
|
|
773316ce4f | ||
|
|
5fd7ae33b4 | ||
|
|
13a065f2dc | ||
|
|
1a8ff81087 | ||
|
|
65637fce40 | ||
|
|
b11fa7a28e | ||
|
|
45408caf33 | ||
|
|
963ee4dbab | ||
|
|
3b46d5a440 | ||
|
|
212fddd8e1 | ||
|
|
433485470e | ||
|
|
e160a1f794 | ||
|
|
7911b7e637 | ||
|
|
7fc5a77e7e | ||
|
|
f0fb55640e | ||
|
|
d5685f2255 | ||
|
|
a732233db6 | ||
|
|
a5918c29ee | ||
|
|
3b719803bb | ||
|
|
d77463c9f1 | ||
|
|
2d8fd9bedf | ||
|
|
b17a667a9d | ||
|
|
b7287a070b | ||
|
|
fbb5c8cdd6 | ||
|
|
baaf2815e4 | ||
|
|
d8b5549fd9 | ||
|
|
6de03f2bf0 | ||
|
|
caf7c55069 | ||
|
|
7d499ffba1 | ||
|
|
4abf6b2f5c | ||
|
|
a842b06301 | ||
|
|
0bca4925d7 | ||
|
|
ae3953cbec | ||
|
|
a99667c54c | ||
|
|
465963a8c2 | ||
|
|
b9b4762faf | ||
|
|
a56b128a4b | ||
|
|
d43cc089fd | ||
|
|
771513d287 | ||
|
|
c387678217 | ||
|
|
847368718b | ||
|
|
458b3daac3 | ||
|
|
3ea5278b12 | ||
|
|
20b9748a8c | ||
|
|
79487adbec | ||
|
|
89f3fca6b1 | ||
|
|
f290b2bf5a | ||
|
|
04e7d13043 | ||
|
|
e41218c46b | ||
|
|
8562fbdbbe | ||
|
|
c841d7a32b | ||
|
|
8827ae4d2c | ||
|
|
2e1029e157 | ||
|
|
a7dc0c2d55 | ||
|
|
7f1749d853 | ||
|
|
b4a34d58db | ||
|
|
4a50fcab2c | ||
|
|
c9ef089199 | ||
|
|
94ecf9a081 | ||
|
|
67aaa9a655 | ||
|
|
823f5640f7 | ||
|
|
45d1c63895 | ||
|
|
d3b6781bb8 | ||
|
|
21d1f69d6d | ||
|
|
1b9f5989ef | ||
|
|
348e46ff3b | ||
|
|
7918e3a1aa | ||
|
|
d6d8c7830c | ||
|
|
f53a0f0d07 | ||
|
|
17edd1c3d4 | ||
|
|
b0685c153a | ||
|
|
a5ca20ee4c | ||
|
|
03685db2fc | ||
|
|
68ed738dcd | ||
|
|
5293d17e32 | ||
|
|
f2e4b69466 | ||
|
|
ec8b00042b | ||
|
|
08db1d59e5 | ||
|
|
7c79d421e8 | ||
|
|
ade5e38fa5 | ||
|
|
7385aa09a8 | ||
|
|
185a5fad88 | ||
|
|
a1e2477d14 | ||
|
|
a1200a5fff | ||
|
|
91a0257c8f | ||
|
|
53ffc82fe2 | ||
|
|
801267df18 | ||
|
|
6e73e0b395 | ||
|
|
7aa8a5c368 | ||
|
|
3ecbbea7cb | ||
|
|
77cd3182f1 | ||
|
|
c7ccf9bab8 | ||
|
|
06e70abb86 | ||
|
|
88c86e88b0 | ||
|
|
2d6cf48532 | ||
|
|
19e152a54b | ||
|
|
2898bead66 | ||
|
|
381c329441 | ||
|
|
a3de3705f7 | ||
|
|
dc3dc6b77f | ||
|
|
e028a63f30 | ||
|
|
d196f8b4b2 | ||
|
|
4274827dbe | ||
|
|
7a30f4a7d2 | ||
|
|
d0c03a0211 | ||
|
|
787b136d13 | ||
|
|
08412d6108 | ||
|
|
d8f7db4715 | ||
|
|
bff238774e | ||
|
|
d2aaa6f691 | ||
|
|
c900ef036c | ||
|
|
b2164ce5fc | ||
|
|
d088d432c5 | ||
|
|
c9fafbe198 | ||
|
|
2d909b0514 | ||
|
|
c2a012553d | ||
|
|
085f0266ac | ||
|
|
7ede2daa3c | ||
|
|
713c53d170 | ||
|
|
110b3a6a8f | ||
|
|
e12e6dd7a7 | ||
|
|
e183fc6118 | ||
|
|
dd57e246b8 | ||
|
|
f4a4172369 | ||
|
|
b96d1714b5 | ||
|
|
dbd809b040 | ||
|
|
ff4e6b139d | ||
|
|
af098aaac8 | ||
|
|
5d7e62c736 | ||
|
|
e2ead011f5 | ||
|
|
a067c950e1 | ||
|
|
3369618cfa | ||
|
|
4e9b6520e5 | ||
|
|
a074203fae | ||
|
|
42092e3f28 | ||
|
|
c07b34e298 | ||
|
|
ef5f181328 | ||
|
|
a7ea2fcf92 | ||
|
|
e81730715c | ||
|
|
c09f2ad482 | ||
|
|
84aef8b5b5 | ||
|
|
8fac3e8221 | ||
|
|
5874ed781d | ||
|
|
d8f29bd7a7 | ||
|
|
8120b6aaaa | ||
|
|
13a0d1de70 | ||
|
|
20e828be51 | ||
|
|
ccd82fb8b8 | ||
|
|
0711650ff8 | ||
|
|
4194ac2226 | ||
|
|
248e2d7ee0 | ||
|
|
1daa654051 | ||
|
|
d751434979 | ||
|
|
8cc21920b7 | ||
|
|
248212588d | ||
|
|
13c0fdef08 | ||
|
|
dfc27b2480 | ||
|
|
b2d78d380b | ||
|
|
faa6cb5c7d | ||
|
|
452977abdf | ||
|
|
93570b2f59 | ||
|
|
073f5c2c8c | ||
|
|
1b4313f847 | ||
|
|
07cead7e99 | ||
|
|
f128751aba | ||
|
|
9516d9da17 | ||
|
|
e9aafc2a56 | ||
|
|
a91a6575e0 | ||
|
|
5bd4093dfb | ||
|
|
f9890e2016 | ||
|
|
00529fe134 | ||
|
|
5e9dce7d39 | ||
|
|
734680b9f0 | ||
|
|
d0b5345252 | ||
|
|
c2b4a44a59 | ||
|
|
38c79bbc11 | ||
|
|
0d7028a36c | ||
|
|
b63f687491 | ||
|
|
952b636569 | ||
|
|
e14ba48244 | ||
|
|
720c8c31ec | ||
|
|
4e8407ed8f | ||
|
|
26c5f69161 | ||
|
|
078ae15794 | ||
|
|
16ae90dc9f | ||
|
|
3de5afc68e | ||
|
|
a7e8f5087e | ||
|
|
5cc60ed760 | ||
|
|
2e6e75cd4e | ||
|
|
9f3b35634a | ||
|
|
c24dfc63dc | ||
|
|
c029929850 | ||
|
|
d9100913d5 | ||
|
|
a7fbe05a73 | ||
|
|
e03d970bc2 | ||
|
|
fe4516ea23 | ||
|
|
039b47b872 | ||
|
|
0a57a8a7f3 | ||
|
|
8c823f3a2d | ||
|
|
33f3a4f455 | ||
|
|
2ecf8044d7 | ||
|
|
c9a1eb55b5 | ||
|
|
76bb1fd61e | ||
|
|
1275b26ba0 | ||
|
|
a470a4af9b | ||
|
|
487c9ebbd4 | ||
|
|
c796e2ae3c | ||
|
|
746cab92f0 | ||
|
|
33266a96ff | ||
|
|
2816889d8d | ||
|
|
58e177b3e4 | ||
|
|
5cfd8bbb56 | ||
|
|
e6fe6fd645 | ||
|
|
63e167b38e | ||
|
|
abe77c4783 | ||
|
|
72af51fe9d | ||
|
|
36b4134838 | ||
|
|
cf6ee26fdb | ||
|
|
858111e623 | ||
|
|
367e625804 | ||
|
|
ae4d9c7f80 | ||
|
|
6c6ee41346 | ||
|
|
9ef7688f9e | ||
|
|
7d6e226c2b | ||
|
|
17d1346a8a | ||
|
|
59e0c10c42 | ||
|
|
0d29e66092 | ||
|
|
caa000f447 | ||
|
|
267e114354 | ||
|
|
b5375396d2 | ||
|
|
e34f666b70 | ||
|
|
19334b4f96 | ||
|
|
1fa609e539 | ||
|
|
d9ce25a721 | ||
|
|
5b8fc25da6 | ||
|
|
c70968dcf1 | ||
|
|
77147510fb | ||
|
|
7092577482 | ||
|
|
3e70050056 | ||
|
|
1f23c814e5 | ||
|
|
145e0a0b7b | ||
|
|
e68e787e7a | ||
|
|
903308d285 | ||
|
|
a4d8388b2e | ||
|
|
3e83f9f956 | ||
|
|
4063221313 | ||
|
|
c3c8d80919 | ||
|
|
3ae71c73c4 | ||
|
|
b3db8c9549 | ||
|
|
596eb4a0f9 | ||
|
|
6b0381b903 | ||
|
|
049c8f70cd | ||
|
|
353bf69550 | ||
|
|
cdb989ede3 | ||
|
|
74079e4238 | ||
|
|
c89746214c | ||
|
|
1d97db3ef9 | ||
|
|
b2a5ff5f9d | ||
|
|
f47ef2b5ea | ||
|
|
bd7ec3b692 | ||
|
|
52895e7b6b | ||
|
|
a6a82c6477 | ||
|
|
af66ed94b2 | ||
|
|
e43fdf5ef9 | ||
|
|
3984fc075f | ||
|
|
ee2a159374 | ||
|
|
57c9b29ba3 | ||
|
|
927ea72337 | ||
|
|
123bdbd13b | ||
|
|
0baa5a2f04 | ||
|
|
7d98a70028 | ||
|
|
73b72ab01c | ||
|
|
e8cf71f41c | ||
|
|
5f2d2a64d2 | ||
|
|
27bf3901de | ||
|
|
16d4fa03a5 | ||
|
|
bef579ec26 | ||
|
|
0cff10e02e | ||
|
|
4c6f7238dd | ||
|
|
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 | ||
|
|
55027a9b2b | ||
|
|
0a984ca8c8 | ||
|
|
929d13bfea | ||
|
|
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 | ||
|
|
56d53d8805 | ||
|
|
7433fe049c | ||
|
|
bb2be49d3b | ||
|
|
cd1b578e84 | ||
|
|
c3df9b4105 | ||
|
|
b0415a5289 | ||
|
|
ff7344438b | ||
|
|
f5f8e5d279 | ||
|
|
b4ddc8f96c | ||
|
|
8ebb1e29fa | ||
|
|
72c9845174 | ||
|
|
1975973ff2 | ||
|
|
f60cce54ea | ||
|
|
63087a4311 | ||
|
|
5a193d50f6 | ||
|
|
08a6e999b9 | ||
|
|
e33cdca1ef | ||
|
|
9ede7a3c42 | ||
|
|
430d4e1ccd | ||
|
|
de4d6037d3 | ||
|
|
63afacc067 | ||
|
|
3175199787 | ||
|
|
562754c0b9 | ||
|
|
d9c6f7acb6 | ||
|
|
398cbe9284 | ||
|
|
d87e488c23 | ||
|
|
5c2ff9b777 | ||
|
|
6d7e37610c | ||
|
|
a47e6dd8c5 | ||
|
|
f334a2740f | ||
|
|
26e487c01a | ||
|
|
cc438fdb7b | ||
|
|
92ff98d99a | ||
|
|
d1609cba90 | ||
|
|
0c394b123c | ||
|
|
421b8214cb | ||
|
|
6fc91312d2 | ||
|
|
22bb129bd9 | ||
|
|
4c57893312 | ||
|
|
a2d5314cf7 | ||
|
|
e063967734 | ||
|
|
4519dd010d | ||
|
|
bc2dc8d933 | ||
|
|
fc9b63298c | ||
|
|
c45514b989 |
1
.github/CONTRIBUTING.md
vendored
1
.github/CONTRIBUTING.md
vendored
@@ -33,6 +33,7 @@ with your GitHub account.
|
||||
|
||||
## Code contribution
|
||||
|
||||
* If you want to add a feature or change one, please open an issue describing your change. This gives the team and community a chance to give feedback before you spend any time on something that could be done differently or not done at all. It also prevents two contributors from working on the same thing and one being disappointed when only one user's code can be added.
|
||||
* 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
|
||||
libraries.
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -10,3 +10,11 @@
|
||||
*~
|
||||
.weblate
|
||||
*.class
|
||||
|
||||
# vscode / eclipse files
|
||||
*.classpath
|
||||
*.project
|
||||
*.settings
|
||||
bin/
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
@@ -5,13 +5,13 @@ android:
|
||||
components:
|
||||
# The BuildTools version used by NewPipe
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- build-tools-29.0.3
|
||||
|
||||
# The SDK version used to compile NewPipe
|
||||
- android-28
|
||||
- android-29
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-28"
|
||||
- yes | sdkmanager "platforms;android-29"
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||
|
||||
licenses:
|
||||
|
||||
168
app/build.gradle
168
app/build.gradle
@@ -5,16 +5,16 @@ apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.schabi.newpipe"
|
||||
resValue "string", "app_name", "NewPipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 930
|
||||
versionName "0.19.3"
|
||||
targetSdkVersion 29
|
||||
versionCode 956
|
||||
versionName "0.20.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -33,7 +33,7 @@ android {
|
||||
|
||||
// suffix the app id and the app name with git branch name
|
||||
def workingBranch = getGitWorkingBranch()
|
||||
def normalizedWorkingBranch = workingBranch.replaceAll("[^A-Za-z]+", "").toLowerCase()
|
||||
def normalizedWorkingBranch = workingBranch.replaceFirst("^[^A-Za-z]+", "").replaceAll("[^0-9A-Za-z]+", "")
|
||||
if (normalizedWorkingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") {
|
||||
// default values when branch name could not be determined or is master or dev
|
||||
applicationIdSuffix ".debug"
|
||||
@@ -50,7 +50,7 @@ android {
|
||||
// TODO: update Gradle version
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
shrinkResources false // disabled to fix F-Droid's reproducible build
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
archivesBaseName = 'app'
|
||||
}
|
||||
@@ -66,6 +66,7 @@ android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
encoding 'utf-8'
|
||||
}
|
||||
|
||||
// Required and used only by groupie
|
||||
@@ -79,22 +80,28 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
androidxLibVersion = '1.0.0'
|
||||
exoPlayerLibVersion = '2.10.8'
|
||||
roomDbLibVersion = '2.1.0'
|
||||
leakCanaryLibVersion = '1.5.4' //1.6.1
|
||||
okHttpLibVersion = '3.12.6'
|
||||
icepickLibVersion = '3.2.0'
|
||||
stethoLibVersion = '1.5.0'
|
||||
markwonVersion = '4.2.1'
|
||||
checkstyleVersion = '8.31'
|
||||
icepickVersion = '3.2.0'
|
||||
checkstyleVersion = '8.32'
|
||||
stethoVersion = '1.5.1'
|
||||
leakCanaryVersion = '2.2'
|
||||
exoPlayerVersion = '2.11.8'
|
||||
androidxLifecycleVersion = '2.2.0'
|
||||
androidxRoomVersion = '2.2.5'
|
||||
groupieVersion = '2.8.0'
|
||||
markwonVersion = '4.3.1'
|
||||
googleAutoServiceVersion = '1.0-rc7'
|
||||
}
|
||||
|
||||
configurations {
|
||||
checkstyle
|
||||
ktlint
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
configFile rootProject.file('checkstyle.xml')
|
||||
ignoreFailures false
|
||||
showViolations true
|
||||
toolVersion = "${checkstyleVersion}"
|
||||
toolVersion = checkstyleVersion
|
||||
}
|
||||
|
||||
task runCheckstyle(type: Checkstyle) {
|
||||
@@ -105,8 +112,7 @@ task runCheckstyle(type: Checkstyle) {
|
||||
exclude '**/BuildConfig.java'
|
||||
exclude 'main/java/us/shandian/giga/**'
|
||||
|
||||
// empty classpath
|
||||
classpath = files()
|
||||
classpath = configurations.checkstyle
|
||||
|
||||
showViolations true
|
||||
|
||||
@@ -116,88 +122,94 @@ task runCheckstyle(type: Checkstyle) {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Checkstyle).each {
|
||||
checkstyleTask -> checkstyleTask.doLast {
|
||||
reports.all { report ->
|
||||
def outputFile = report.destination
|
||||
if (outputFile.exists() && outputFile.text.contains("severity=\"error\"")) {
|
||||
throw new GradleException("There were checkstyle errors! For more info check $outputFile")
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
preDebugBuild.dependsOn runCheckstyle, runKtlint
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
|
||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
||||
checkstyle "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'
|
||||
})
|
||||
}
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:fc3a69ed54b393e3e4e3a78ae6e89edc1d47c45a'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:19c0e8700db3af35e5dfc58e4eea50e75d7c4c61'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation "androidx.legacy:legacy-support-v4:${androidxLibVersion}"
|
||||
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.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||
implementation "org.jsoup:jsoup:1.13.1"
|
||||
|
||||
implementation 'com.xwray:groupie:2.7.0'
|
||||
implementation 'com.xwray:groupie-kotlin-android-extensions:2.7.0'
|
||||
implementation "com.squareup.okhttp3:okhttp:3.12.12"
|
||||
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
|
||||
|
||||
// Originally in NewPipeExtractor
|
||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||
implementation 'org.jsoup:jsoup:1.9.2'
|
||||
implementation "com.google.android.material:material:1.1.0"
|
||||
|
||||
implementation 'ch.acra:acra:4.9.2' //4.11
|
||||
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
|
||||
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
|
||||
|
||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.nononsenseapps:filepicker:4.2.1'
|
||||
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.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerLibVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
|
||||
debugImplementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
|
||||
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
|
||||
implementation 'org.ocpsoft.prettytime:prettytime:4.0.3.Final'
|
||||
implementation "com.xwray:groupie:${groupieVersion}"
|
||||
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
|
||||
|
||||
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 "de.hdodenhof:circleimageview:3.1.0"
|
||||
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
|
||||
|
||||
implementation "io.noties.markwon:core:${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.6.Final"
|
||||
}
|
||||
|
||||
static String getGitWorkingBranch() {
|
||||
|
||||
@@ -116,4 +116,4 @@ class AppDatabaseTest {
|
||||
testHelper.closeWhenFinished(database)
|
||||
return database
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ public class ErrorInfoTest {
|
||||
|
||||
@Test
|
||||
public void errorInfoTestParcelable() {
|
||||
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
||||
final ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
||||
R.string.general_error);
|
||||
// Obtain a Parcel object and write the parcelable object to it:
|
||||
Parcel parcel = Parcel.obtain();
|
||||
final Parcel parcel = Parcel.obtain();
|
||||
info.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
||||
final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
||||
|
||||
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
|
||||
assertEquals("youtube", infoFromParcel.serviceName);
|
||||
|
||||
@@ -1,107 +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(final Context base) {
|
||||
super.attachBaseContext(base);
|
||||
MultiDex.install(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
initStetho();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Downloader getDownloader() {
|
||||
DownloaderImpl downloader = DownloaderImpl.init(new OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(new StethoInterceptor()));
|
||||
setCookiesToDownloader(downloader);
|
||||
return downloader;
|
||||
}
|
||||
|
||||
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 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 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
|
||||
}
|
||||
|
||||
override fun initACRA() {
|
||||
// install MultiDex before initializing ACRA
|
||||
MultiDex.install(this)
|
||||
super.initACRA()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.schabi.newpipe">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:banner="@mipmap/newpipe_tv_banner"
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:banner="@mipmap/newpipe_tv_banner"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:logo="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/OpeningTheme"
|
||||
android:resizeableActivity="true"
|
||||
tools:ignore="AllowBackup">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".player.BackgroundPlayer"
|
||||
android:exported="false">
|
||||
android:name=".player.MainPlayer"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="mediaPlayback">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
@@ -49,36 +54,20 @@
|
||||
|
||||
<activity
|
||||
android:name=".player.BackgroundPlayerActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/title_activity_background_player"/>
|
||||
|
||||
<activity
|
||||
android:name=".player.PopupVideoPlayerActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/title_activity_popup_player"/>
|
||||
|
||||
<service
|
||||
android:name=".player.PopupVideoPlayer"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:name=".player.MainVideoPlayer"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/VideoPlayerTheme"/>
|
||||
android:label="@string/title_activity_play_queue"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity
|
||||
android:name=".settings.SettingsActivity"
|
||||
android:label="@string/settings"/>
|
||||
android:label="@string/settings" />
|
||||
|
||||
<activity
|
||||
android:name=".about.AboutActivity"
|
||||
android:label="@string/title_activity_about"/>
|
||||
android:label="@string/title_activity_about" />
|
||||
|
||||
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
|
||||
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
|
||||
<service android:name=".local.feed.service.FeedLoadService"/>
|
||||
<service android:name=".local.subscription.services.SubscriptionsImportService" />
|
||||
<service android:name=".local.subscription.services.SubscriptionsExportService" />
|
||||
<service android:name=".local.feed.service.FeedLoadService" />
|
||||
|
||||
<activity
|
||||
android:name=".PanicResponderActivity"
|
||||
@@ -86,25 +75,25 @@
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ExitActivity"
|
||||
android:label="@string/general_error"
|
||||
android:theme="@android:style/Theme.NoDisplay"/>
|
||||
<activity android:name=".report.ErrorActivity"/>
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<activity android:name=".report.ErrorActivity" />
|
||||
|
||||
<!-- giga get related -->
|
||||
<activity
|
||||
android:name=".download.DownloadActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"/>
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<service android:name="us.shandian.giga.service.DownloadManagerService"/>
|
||||
<service android:name="us.shandian.giga.service.DownloadManagerService" />
|
||||
|
||||
<activity
|
||||
android:name=".util.FilePickerActivityHelper"
|
||||
@@ -118,7 +107,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
android:label="@string/recaptcha"/>
|
||||
android:label="@string/recaptcha" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
@@ -127,7 +116,7 @@
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/nnf_provider_paths"/>
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
@@ -139,165 +128,203 @@
|
||||
|
||||
<!-- Youtube filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="youtube.com"/>
|
||||
<data android:host="m.youtube.com"/>
|
||||
<data android:host="www.youtube.com"/>
|
||||
<data android:host="music.youtube.com"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="youtube.com" />
|
||||
<data android:host="m.youtube.com" />
|
||||
<data android:host="www.youtube.com" />
|
||||
<data android:host="music.youtube.com" />
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/v/"/>
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:pathPrefix="/watch"/>
|
||||
<data android:pathPrefix="/attribution_link"/>
|
||||
<data android:pathPrefix="/v/" />
|
||||
<data android:pathPrefix="/embed/" />
|
||||
<data android:pathPrefix="/watch" />
|
||||
<data android:pathPrefix="/attribution_link" />
|
||||
<!-- channel prefix -->
|
||||
<data android:pathPrefix="/channel/"/>
|
||||
<data android:pathPrefix="/user/"/>
|
||||
<data android:pathPrefix="/c/"/>
|
||||
<data android:pathPrefix="/channel/" />
|
||||
<data android:pathPrefix="/user/" />
|
||||
<data android:pathPrefix="/c/" />
|
||||
<!-- playlist prefix -->
|
||||
<data android:pathPrefix="/playlist"/>
|
||||
<data android:pathPrefix="/playlist" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="youtu.be"/>
|
||||
<data android:pathPrefix="/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="youtu.be" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="www.youtube-nocookie.com"/>
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="www.youtube-nocookie.com" />
|
||||
<data android:pathPrefix="/embed/" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="vnd.youtube"/>
|
||||
<data android:scheme="vnd.youtube.launch"/>
|
||||
<data android:scheme="vnd.youtube" />
|
||||
<data android:scheme="vnd.youtube.launch" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Hooktube filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="hooktube.com"/>
|
||||
<data android:host="*.hooktube.com"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hooktube.com" />
|
||||
<data android:host="*.hooktube.com" />
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/v/"/>
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:pathPrefix="/watch"/>
|
||||
<data android:pathPrefix="/v/" />
|
||||
<data android:pathPrefix="/embed/" />
|
||||
<data android:pathPrefix="/watch" />
|
||||
<!-- channel prefix -->
|
||||
<data android:pathPrefix="/channel/"/>
|
||||
<data android:pathPrefix="/user/"/>
|
||||
<data android:pathPrefix="/channel/" />
|
||||
<data android:pathPrefix="/user/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Invidious filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="invidio.us"/>
|
||||
<data android:host="dev.invidio.us"/>
|
||||
<data android:host="www.invidio.us"/>
|
||||
<data android:host="invidious.snopyta.org"/>
|
||||
<data android:host="de.invidious.snopyta.org"/>
|
||||
<data android:host="fi.invidious.snopyta.org"/>
|
||||
<data android:host="vid.wxzm.sx"/>
|
||||
<data android:host="invidious.kabi.tk"/>
|
||||
<data android:host="invidiou.sh"/>
|
||||
<data android:host="www.invidiou.sh"/>
|
||||
<data android:host="no.invidiou.sh"/>
|
||||
<data android:host="invidious.enkirton.net"/>
|
||||
<data android:host="tube.poal.co"/>
|
||||
<data android:host="invidious.13ad.de"/>
|
||||
<data android:host="yt.elukerio.org"/>
|
||||
<data android:pathPrefix="/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="invidio.us" />
|
||||
<data android:host="dev.invidio.us" />
|
||||
<data android:host="www.invidio.us" />
|
||||
<data android:host="invidious.snopyta.org" />
|
||||
<data android:host="fi.invidious.snopyta.org" />
|
||||
<data android:host="yewtu.be" />
|
||||
<data android:host="invidious.ggc-project.de" />
|
||||
<data android:host="yt.maisputain.ovh" />
|
||||
<data android:host="invidious.13ad.de" />
|
||||
<data android:host="invidious.toot.koeln" />
|
||||
<data android:host="invidious.fdn.fr" />
|
||||
<data android:host="watch.nettohikari.com" />
|
||||
<data android:host="invidious.snwmds.net" />
|
||||
<data android:host="invidious.snwmds.org" />
|
||||
<data android:host="invidious.snwmds.com" />
|
||||
<data android:host="invidious.sunsetravens.com" />
|
||||
<data android:host="invidious.gachirangers.com" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Soundcloud filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="soundcloud.com"/>
|
||||
<data android:host="m.soundcloud.com"/>
|
||||
<data android:host="www.soundcloud.com"/>
|
||||
<data android:pathPrefix="/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="soundcloud.com" />
|
||||
<data android:host="m.soundcloud.com" />
|
||||
<data android:host="www.soundcloud.com" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Share filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- MediaCCC filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="media.ccc.de"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="media.ccc.de" />
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/v/"/>
|
||||
<data android:pathPrefix="/v/" />
|
||||
<!-- channel prefix-->
|
||||
<data android:pathPrefix="/c/"/>
|
||||
<data android:pathPrefix="/b/"/>
|
||||
<data android:pathPrefix="/c/" />
|
||||
<data android:pathPrefix="/b/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- PeerTube filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
|
||||
<data android:host="framatube.org" />
|
||||
<data android:host="media.assassinate-you.net" />
|
||||
<data android:host="peertube.co.uk" />
|
||||
<data android:host="peertube.cpy.re" />
|
||||
<data android:host="peertube.mastodon.host" />
|
||||
<data android:host="peertube.fr" />
|
||||
<data android:host="peertube.live" />
|
||||
<data android:host="peertube.video" />
|
||||
<data android:host="tube.privacytools.io" />
|
||||
<data android:host="video.ploud.fr" />
|
||||
<data android:host="video.lqdn.fr" />
|
||||
<data android:host="skeptikon.fr" />
|
||||
|
||||
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
||||
<data android:pathPrefix="/accounts/" />
|
||||
<data android:pathPrefix="/video-channels/" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".RouterActivity$FetcherService"
|
||||
android:exported="false"/>
|
||||
android:exported="false" />
|
||||
|
||||
<!-- see https://github.com/TeamNewPipe/NewPipe/issues/3947 -->
|
||||
<!-- Version < 3.0. DeX Mode and Screen Mirroring support -->
|
||||
<meta-data android:name="com.samsung.android.keepalive.density" android:value="true"/>
|
||||
<!-- Version >= 3.0. DeX Dual Mode support -->
|
||||
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true"/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -150,7 +150,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||
// from its saved state, where the fragment manager has already
|
||||
// taken care of restoring the fragments we previously had instantiated.
|
||||
if (mFragments.size() > position) {
|
||||
Fragment f = mFragments.get(position);
|
||||
final Fragment f = mFragments.get(position);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
@@ -160,12 +160,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
Fragment fragment = getItem(position);
|
||||
final Fragment fragment = getItem(position);
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||
}
|
||||
if (mSavedState.size() > position) {
|
||||
Fragment.SavedState fss = mSavedState.get(position);
|
||||
final Fragment.SavedState fss = mSavedState.get(position);
|
||||
if (fss != null) {
|
||||
fragment.setInitialSavedState(fss);
|
||||
}
|
||||
@@ -191,7 +191,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||
@Override
|
||||
public void destroyItem(@NonNull final ViewGroup container, final int position,
|
||||
@NonNull final Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
final Fragment fragment = (Fragment) object;
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
@@ -217,7 +217,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
|
||||
@NonNull final Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
final Fragment fragment = (Fragment) object;
|
||||
if (fragment != mCurrentPrimaryItem) {
|
||||
if (mCurrentPrimaryItem != null) {
|
||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||
@@ -267,17 +267,17 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||
Bundle state = null;
|
||||
if (mSavedState.size() > 0) {
|
||||
state = new Bundle();
|
||||
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||
final Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||
mSavedState.toArray(fss);
|
||||
state.putParcelableArray("states", fss);
|
||||
}
|
||||
for (int i = 0; i < mFragments.size(); i++) {
|
||||
Fragment f = mFragments.get(i);
|
||||
final Fragment f = mFragments.get(i);
|
||||
if (f != null && f.isAdded()) {
|
||||
if (state == null) {
|
||||
state = new Bundle();
|
||||
}
|
||||
String key = "f" + i;
|
||||
final String key = "f" + i;
|
||||
mFragmentManager.putFragment(state, key, f);
|
||||
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
@@ -294,21 +294,21 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
||||
@Override
|
||||
public void restoreState(@Nullable final Parcelable state, @Nullable final ClassLoader loader) {
|
||||
if (state != null) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
final Bundle bundle = (Bundle) state;
|
||||
bundle.setClassLoader(loader);
|
||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
||||
final Parcelable[] fss = bundle.getParcelableArray("states");
|
||||
mSavedState.clear();
|
||||
mFragments.clear();
|
||||
if (fss != null) {
|
||||
for (int i = 0; i < fss.length; i++) {
|
||||
mSavedState.add((Fragment.SavedState) fss[i]);
|
||||
for (final Parcelable parcelable : fss) {
|
||||
mSavedState.add((Fragment.SavedState) parcelable);
|
||||
}
|
||||
}
|
||||
Iterable<String> keys = bundle.keySet();
|
||||
for (String key: keys) {
|
||||
final Iterable<String> keys = bundle.keySet();
|
||||
for (final String key : keys) {
|
||||
if (key.startsWith("f")) {
|
||||
int index = Integer.parseInt(key.substring(1));
|
||||
Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||
final int index = Integer.parseInt(key.substring(1));
|
||||
final Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||
if (f != null) {
|
||||
while (mFragments.size() <= index) {
|
||||
mFragments.add(null);
|
||||
|
||||
@@ -4,13 +4,17 @@ import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// See https://stackoverflow.com/questions/56849221#57997489
|
||||
public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
@@ -20,23 +24,28 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
private boolean allowScroll = true;
|
||||
private final Rect globalRect = new Rect();
|
||||
private final List<Integer> skipInterceptionOfElements = Arrays.asList(
|
||||
R.id.playQueuePanel, R.id.playbackSeekBar,
|
||||
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);
|
||||
|
||||
@Override
|
||||
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();
|
||||
final 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;
|
||||
final int dy;
|
||||
|
||||
if (focusScrollRect.bottom > height) {
|
||||
dy = focusScrollRect.top;
|
||||
@@ -48,13 +57,24 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
return false;
|
||||
}
|
||||
|
||||
int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
|
||||
final int consumed = scroll(coordinatorLayout, child, dy, getMaxDragOffset(child), 0);
|
||||
|
||||
return consumed == dy;
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(final CoordinatorLayout parent, final AppBarLayout child,
|
||||
final MotionEvent ev) {
|
||||
for (final Integer element : skipInterceptionOfElements) {
|
||||
final View view = child.findViewById(element);
|
||||
if (view != null) {
|
||||
final boolean visible = view.getGlobalVisibleRect(globalRect);
|
||||
if (visible && globalRect.contains((int) ev.getRawX(), (int) ev.getRawY())) {
|
||||
allowScroll = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
allowScroll = true;
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// remove reference to old nested scrolling child
|
||||
@@ -68,17 +88,37 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
return super.onInterceptTouchEvent(parent, child, ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartNestedScroll(@NonNull final CoordinatorLayout parent,
|
||||
@NonNull final AppBarLayout child,
|
||||
@NonNull final View directTargetChild,
|
||||
final View target,
|
||||
final int nestedScrollAxes,
|
||||
final int type) {
|
||||
return allowScroll && super.onStartNestedScroll(
|
||||
parent, child, directTargetChild, target, nestedScrollAxes, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNestedFling(@NonNull final CoordinatorLayout coordinatorLayout,
|
||||
@NonNull final AppBarLayout child,
|
||||
@NonNull final View target, final float velocityX,
|
||||
final float velocityY, final boolean consumed) {
|
||||
return allowScroll && super.onNestedFling(
|
||||
coordinatorLayout, child, target, velocityX, velocityY, consumed);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private OverScroller getScrollerField() {
|
||||
try {
|
||||
Class<?> headerBehaviorType = this.getClass()
|
||||
final Class<?> headerBehaviorType = this.getClass()
|
||||
.getSuperclass().getSuperclass().getSuperclass();
|
||||
if (headerBehaviorType != null) {
|
||||
Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||
final Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||
field.setAccessible(true);
|
||||
return ((OverScroller) field.get(this));
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
} catch (final NoSuchFieldException | IllegalAccessException e) {
|
||||
// ?
|
||||
}
|
||||
return null;
|
||||
@@ -87,34 +127,35 @@ public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
@Nullable
|
||||
private Field getLastNestedScrollingChildRefField() {
|
||||
try {
|
||||
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
|
||||
final Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
|
||||
if (headerBehaviorType != null) {
|
||||
Field field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
|
||||
final Field field
|
||||
= headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
} catch (final NoSuchFieldException e) {
|
||||
// ?
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void resetNestedScrollingChild() {
|
||||
Field field = getLastNestedScrollingChildRefField();
|
||||
final Field field = getLastNestedScrollingChildRefField();
|
||||
if (field != null) {
|
||||
try {
|
||||
Object value = field.get(this);
|
||||
final Object value = field.get(this);
|
||||
if (value != null) {
|
||||
field.set(this, null);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch (final IllegalAccessException e) {
|
||||
// ?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAppBarLayoutFling() {
|
||||
OverScroller scroller = getScrollerField();
|
||||
final OverScroller scroller = getScrollerField();
|
||||
if (scroller != null) {
|
||||
scroller.forceFinished(true);
|
||||
}
|
||||
|
||||
@@ -9,23 +9,19 @@ import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.config.ACRAConfiguration;
|
||||
import org.acra.config.ACRAConfigurationException;
|
||||
import org.acra.config.ConfigurationBuilder;
|
||||
import org.acra.sender.ReportSenderFactory;
|
||||
import org.acra.config.CoreConfiguration;
|
||||
import org.acra.config.CoreConfigurationBuilder;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
@@ -40,7 +36,6 @@ import java.net.SocketException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.exceptions.CompositeException;
|
||||
import io.reactivex.exceptions.MissingBackpressureException;
|
||||
import io.reactivex.exceptions.OnErrorNotImplementedException;
|
||||
@@ -68,17 +63,7 @@ import io.reactivex.plugins.RxJavaPlugins;
|
||||
|
||||
public class App extends Application {
|
||||
protected static final String TAG = App.class.toString();
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Class<? extends ReportSenderFactory>[]
|
||||
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
|
||||
private static App app;
|
||||
private RefWatcher refWatcher;
|
||||
|
||||
@Nullable
|
||||
public static RefWatcher getRefWatcher(final Context context) {
|
||||
final App application = (App) context.getApplicationContext();
|
||||
return application.refWatcher;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
return app;
|
||||
@@ -87,7 +72,6 @@ public class App extends Application {
|
||||
@Override
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(base);
|
||||
|
||||
initACRA();
|
||||
}
|
||||
|
||||
@@ -95,13 +79,6 @@ public class App extends Application {
|
||||
public void 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;
|
||||
|
||||
// Initialize settings first because others inits can use its values
|
||||
@@ -127,7 +104,7 @@ public class App extends Application {
|
||||
}
|
||||
|
||||
protected Downloader getDownloader() {
|
||||
DownloaderImpl downloader = DownloaderImpl.init(null);
|
||||
final DownloaderImpl downloader = DownloaderImpl.init(null);
|
||||
setCookiesToDownloader(downloader);
|
||||
return downloader;
|
||||
}
|
||||
@@ -136,7 +113,8 @@ public class App extends Application {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext());
|
||||
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
|
||||
downloader.setCookies(prefs.getString(key, ""));
|
||||
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, ""));
|
||||
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
|
||||
}
|
||||
|
||||
private void configureRxJavaErrorHandler() {
|
||||
@@ -216,14 +194,21 @@ public class App extends Application {
|
||||
.build();
|
||||
}
|
||||
|
||||
private void initACRA() {
|
||||
/**
|
||||
* Called in {@link #attachBaseContext(Context)} after calling the {@code super} method.
|
||||
* Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
|
||||
*/
|
||||
protected void initACRA() {
|
||||
if (ACRA.isACRASenderServiceProcess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
|
||||
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
||||
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
|
||||
.setBuildConfigClass(BuildConfig.class)
|
||||
.build();
|
||||
ACRA.init(this, acraConfig);
|
||||
} catch (ACRAConfigurationException ace) {
|
||||
} catch (final ACRAConfigurationException ace) {
|
||||
ace.printStackTrace();
|
||||
ErrorActivity.reportError(this,
|
||||
ace,
|
||||
@@ -235,7 +220,7 @@ public class App extends Application {
|
||||
}
|
||||
|
||||
public void initNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -246,10 +231,10 @@ public class App extends Application {
|
||||
// Keep this below DEFAULT to avoid making noise on every notification update
|
||||
final int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
|
||||
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
|
||||
final NotificationChannel mChannel = new NotificationChannel(id, name, importance);
|
||||
mChannel.setDescription(description);
|
||||
|
||||
NotificationManager mNotificationManager =
|
||||
final NotificationManager mNotificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
|
||||
@@ -270,19 +255,15 @@ public class App extends Application {
|
||||
final String appUpdateDescription
|
||||
= getString(R.string.app_update_notification_channel_description);
|
||||
|
||||
NotificationChannel appUpdateChannel
|
||||
final NotificationChannel appUpdateChannel
|
||||
= new NotificationChannel(appUpdateId, appUpdateName, importance);
|
||||
appUpdateChannel.setDescription(appUpdateDescription);
|
||||
|
||||
NotificationManager appUpdateNotificationManager
|
||||
final NotificationManager appUpdateNotificationManager
|
||||
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||
}
|
||||
|
||||
protected RefWatcher installLeakCanary() {
|
||||
return RefWatcher.DISABLED;
|
||||
}
|
||||
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import icepick.Icepick;
|
||||
import icepick.State;
|
||||
import leakcanary.AppWatcher;
|
||||
|
||||
public abstract class BaseFragment extends Fragment {
|
||||
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
|
||||
@@ -78,16 +78,14 @@ public abstract class BaseFragment extends Fragment {
|
||||
Icepick.saveInstanceState(this, outState);
|
||||
}
|
||||
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { }
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
RefWatcher refWatcher = App.getRefWatcher(getActivity());
|
||||
if (refWatcher != null) {
|
||||
refWatcher.watch(this);
|
||||
}
|
||||
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,9 +98,11 @@ public abstract class BaseFragment extends Fragment {
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void initViews(final View rootView, final Bundle savedInstanceState) { }
|
||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
protected void initListeners() { }
|
||||
protected void initListeners() {
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
|
||||
@@ -11,7 +11,7 @@ import android.content.pm.Signature;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
@@ -62,7 +62,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
|
||||
try {
|
||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
ErrorActivity.reportError(APP, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not find package info", R.string.app_ui_crash));
|
||||
@@ -77,7 +77,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
try {
|
||||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (CertificateException e) {
|
||||
} catch (final CertificateException e) {
|
||||
ErrorActivity.reportError(APP, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Certificate error", R.string.app_ui_crash));
|
||||
@@ -86,7 +86,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
String hexString = null;
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
final MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
final byte[] publicKey = md.digest(c.getEncoded());
|
||||
hexString = byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||
@@ -167,7 +167,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
|
||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
// connectivity problems, do not alarm user and fail silently
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, Log.getStackTraceString(e));
|
||||
@@ -187,7 +187,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
private void compareAppVersionAndShowNotification(final String versionName,
|
||||
final String apkLocationUrl,
|
||||
final int versionCode) {
|
||||
int notificationId = 2000;
|
||||
final int notificationId = 2000;
|
||||
|
||||
if (BuildConfig.VERSION_CODE < versionCode) {
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -10,6 +11,8 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.downloader.Request;
|
||||
import org.schabi.newpipe.extractor.downloader.Response;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.util.CookieUtils;
|
||||
import org.schabi.newpipe.util.InfoCache;
|
||||
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -20,6 +23,7 @@ import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -40,9 +44,13 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||
public final class DownloaderImpl extends Downloader {
|
||||
public static final String USER_AGENT
|
||||
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY
|
||||
= "youtube_restricted_mode_key";
|
||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
||||
public static final String YOUTUBE_DOMAIN = "youtube.com";
|
||||
|
||||
private static DownloaderImpl instance;
|
||||
private String mCookies;
|
||||
private Map<String, String> mCookies;
|
||||
private OkHttpClient client;
|
||||
|
||||
private DownloaderImpl(final OkHttpClient.Builder builder) {
|
||||
@@ -54,6 +62,7 @@ public final class DownloaderImpl extends Downloader {
|
||||
// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
|
||||
// 16 * 1024 * 1024))
|
||||
.build();
|
||||
this.mCookies = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,18 +94,18 @@ public final class DownloaderImpl extends Downloader {
|
||||
private static void enableModernTLS(final OkHttpClient.Builder builder) {
|
||||
try {
|
||||
// get the default TrustManager
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:"
|
||||
+ Arrays.toString(trustManagers));
|
||||
}
|
||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
final X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
|
||||
// insert our own TLSSocketFactory
|
||||
SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
||||
final SSLSocketFactory sslSocketFactory = TLSSocketFactoryCompat.getInstance();
|
||||
|
||||
builder.sslSocketFactory(sslSocketFactory, trustManager);
|
||||
|
||||
@@ -105,28 +114,66 @@ public final class DownloaderImpl extends Downloader {
|
||||
// Necessary because some servers (e.g. Framatube.org)
|
||||
// don't support the old cipher suites.
|
||||
// https://github.com/square/okhttp/issues/4053#issuecomment-402579554
|
||||
List<CipherSuite> cipherSuites = new ArrayList<>();
|
||||
cipherSuites.addAll(ConnectionSpec.MODERN_TLS.cipherSuites());
|
||||
final List<CipherSuite> cipherSuites =
|
||||
new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites());
|
||||
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
|
||||
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
|
||||
ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||
final ConnectionSpec legacyTLS = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||
.cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
|
||||
.build();
|
||||
|
||||
builder.connectionSpecs(Arrays.asList(legacyTLS, ConnectionSpec.CLEARTEXT));
|
||||
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
} catch (final KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
if (DEBUG) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getCookies() {
|
||||
return mCookies;
|
||||
public String getCookies(final String url) {
|
||||
final List<String> resultCookies = new ArrayList<>();
|
||||
if (url.contains(YOUTUBE_DOMAIN)) {
|
||||
final String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
||||
if (youtubeCookie != null) {
|
||||
resultCookies.add(youtubeCookie);
|
||||
}
|
||||
}
|
||||
// Recaptcha cookie is always added TODO: not sure if this is necessary
|
||||
final String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
|
||||
if (recaptchaCookie != null) {
|
||||
resultCookies.add(recaptchaCookie);
|
||||
}
|
||||
return CookieUtils.concatCookies(resultCookies);
|
||||
}
|
||||
|
||||
public void setCookies(final String cookies) {
|
||||
mCookies = cookies;
|
||||
public String getCookie(final String key) {
|
||||
return mCookies.get(key);
|
||||
}
|
||||
|
||||
public void setCookie(final String key, final String cookie) {
|
||||
mCookies.put(key, cookie);
|
||||
}
|
||||
|
||||
public void removeCookie(final String key) {
|
||||
mCookies.remove(key);
|
||||
}
|
||||
|
||||
public void updateYoutubeRestrictedModeCookies(final Context context) {
|
||||
final String restrictedModeEnabledKey =
|
||||
context.getString(R.string.youtube_restricted_mode_enabled);
|
||||
final boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(restrictedModeEnabledKey, false);
|
||||
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
|
||||
}
|
||||
|
||||
public void updateYoutubeRestrictedModeCookies(final boolean youtubeRestrictedModeEnabled) {
|
||||
if (youtubeRestrictedModeEnabled) {
|
||||
setCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY,
|
||||
YOUTUBE_RESTRICTED_MODE_COOKIE);
|
||||
} else {
|
||||
removeCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
||||
}
|
||||
InfoCache.getInstance().clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,9 +186,9 @@ public final class DownloaderImpl extends Downloader {
|
||||
try {
|
||||
final Response response = head(url);
|
||||
return Long.parseLong(response.getHeader("Content-Length"));
|
||||
} catch (NumberFormatException e) {
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new IOException("Invalid content length", e);
|
||||
} catch (ReCaptchaException e) {
|
||||
} catch (final ReCaptchaException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
@@ -152,8 +199,9 @@ public final class DownloaderImpl extends Downloader {
|
||||
.method("GET", null).url(siteUrl)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
final String cookies = getCookies(siteUrl);
|
||||
if (!cookies.isEmpty()) {
|
||||
requestBuilder.addHeader("Cookie", cookies);
|
||||
}
|
||||
|
||||
final okhttp3.Request request = requestBuilder.build();
|
||||
@@ -170,7 +218,7 @@ public final class DownloaderImpl extends Downloader {
|
||||
}
|
||||
|
||||
return body.byteStream();
|
||||
} catch (ReCaptchaException e) {
|
||||
} catch (final ReCaptchaException e) {
|
||||
throw new IOException(e.getMessage(), e.getCause());
|
||||
}
|
||||
}
|
||||
@@ -192,17 +240,18 @@ public final class DownloaderImpl extends Downloader {
|
||||
.method(httpMethod, requestBody).url(url)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
final String cookies = getCookies(url);
|
||||
if (!cookies.isEmpty()) {
|
||||
requestBuilder.addHeader("Cookie", cookies);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
||||
for (final Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
||||
final String headerName = pair.getKey();
|
||||
final List<String> headerValueList = pair.getValue();
|
||||
|
||||
if (headerValueList.size() > 1) {
|
||||
requestBuilder.removeHeader(headerName);
|
||||
for (String headerValue : headerValueList) {
|
||||
for (final String headerValue : headerValueList) {
|
||||
requestBuilder.addHeader(headerName, headerValue);
|
||||
}
|
||||
} else if (headerValueList.size() == 1) {
|
||||
|
||||
@@ -27,7 +27,7 @@ import android.os.Bundle;
|
||||
public class ExitActivity extends Activity {
|
||||
|
||||
public static void exitAndRemoveFromRecentApps(final Activity activity) {
|
||||
Intent intent = new Intent(activity, ExitActivity.class);
|
||||
final Intent intent = new Intent(activity, ExitActivity.class);
|
||||
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||
@@ -42,7 +42,7 @@ public class ExitActivity extends Activity {
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
finishAndRemoveTask();
|
||||
} else {
|
||||
finish();
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
|
||||
|
||||
|
||||
@@ -20,29 +20,32 @@
|
||||
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
@@ -53,6 +56,7 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
@@ -63,14 +67,18 @@ import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.player.VideoPlayer;
|
||||
import org.schabi.newpipe.player.event.OnKeyDownListener;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PeertubeHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
||||
@@ -96,6 +104,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
private boolean servicesShown = false;
|
||||
private ImageView serviceArrow;
|
||||
|
||||
private BroadcastReceiver broadcastReceiver;
|
||||
|
||||
private static final int ITEM_ID_SUBSCRIPTIONS = -1;
|
||||
private static final int ITEM_ID_FEED = -2;
|
||||
private static final int ITEM_ID_BOOKMARKS = -3;
|
||||
@@ -127,12 +137,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Window w = getWindow();
|
||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
|
||||
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
|
||||
if (getSupportFragmentManager() != null
|
||||
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
initFragments();
|
||||
@@ -141,13 +145,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
try {
|
||||
setupDrawer();
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
|
||||
if (AndroidTvUtils.isTv(this)) {
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
FocusOverlayView.setupFocusObserver(this);
|
||||
}
|
||||
setupBroadcastReceiver();
|
||||
}
|
||||
|
||||
private void setupDrawer() throws Exception {
|
||||
@@ -156,8 +161,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerItems = findViewById(R.id.navigation);
|
||||
|
||||
//Tabs
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
||||
int kioskId = 0;
|
||||
|
||||
@@ -165,7 +170,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
|
||||
.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||
kioskId++;
|
||||
}
|
||||
|
||||
@@ -175,24 +180,24 @@ public class MainActivity extends AppCompatActivity {
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||
|
||||
//Settings and About
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||
|
||||
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
|
||||
R.string.drawer_close);
|
||||
@@ -229,7 +234,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
case R.id.menu_tabs_group:
|
||||
try {
|
||||
tabSelected(item);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
break;
|
||||
@@ -270,8 +275,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
||||
break;
|
||||
default:
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
String serviceName = "";
|
||||
|
||||
int kioskId = 0;
|
||||
@@ -300,8 +305,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void setupDrawerHeader() {
|
||||
NavigationView navigationView = findViewById(R.id.navigation);
|
||||
View hView = navigationView.getHeaderView(0);
|
||||
final NavigationView navigationView = findViewById(R.id.navigation);
|
||||
final View hView = navigationView.getHeaderView(0);
|
||||
|
||||
serviceArrow = hView.findViewById(R.id.drawer_arrow);
|
||||
headerServiceIcon = hView.findViewById(R.id.drawer_header_service_icon);
|
||||
@@ -336,7 +341,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
} else {
|
||||
try {
|
||||
showTabs();
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
}
|
||||
@@ -345,11 +350,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
private void showServices() {
|
||||
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
|
||||
|
||||
for (StreamingService s : NewPipe.getServices()) {
|
||||
for (final StreamingService s : NewPipe.getServices()) {
|
||||
final String title = s.getServiceInfo().getName()
|
||||
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||
|
||||
MenuItem menuItem = drawerItems.getMenu()
|
||||
final MenuItem menuItem = drawerItems.getMenu()
|
||||
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
||||
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
||||
|
||||
@@ -363,20 +368,20 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) {
|
||||
PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
|
||||
final PeertubeInstance currentInstace = PeertubeHelper.getCurrentInstance();
|
||||
menuItem.setTitle(currentInstace.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : ""));
|
||||
Spinner spinner = (Spinner) LayoutInflater.from(this)
|
||||
final Spinner spinner = (Spinner) LayoutInflater.from(this)
|
||||
.inflate(R.layout.instance_spinner_layout, null);
|
||||
List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
||||
List<String> items = new ArrayList<>();
|
||||
final List<PeertubeInstance> instances = PeertubeHelper.getInstanceList(this);
|
||||
final List<String> items = new ArrayList<>();
|
||||
int defaultSelect = 0;
|
||||
for (PeertubeInstance instance : instances) {
|
||||
for (final PeertubeInstance instance : instances) {
|
||||
items.add(instance.getName());
|
||||
if (instance.getUrl().equals(currentInstace.getUrl())) {
|
||||
defaultSelect = items.size() - 1;
|
||||
}
|
||||
}
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||
R.layout.instance_spinner_item, items);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
@@ -385,7 +390,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onItemSelected(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
PeertubeInstance newInstance = instances.get(position);
|
||||
final PeertubeInstance newInstance = instances.get(position);
|
||||
if (newInstance.getUrl().equals(PeertubeHelper.getCurrentInstance().getUrl())) {
|
||||
return;
|
||||
}
|
||||
@@ -411,8 +416,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
serviceArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
|
||||
|
||||
//Tabs
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
||||
int kioskId = 0;
|
||||
|
||||
@@ -420,7 +425,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, kioskId, ORDER,
|
||||
KioskTranslator.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||
kioskId++;
|
||||
}
|
||||
|
||||
@@ -429,24 +434,24 @@ public class MainActivity extends AppCompatActivity {
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||
|
||||
//Settings and About
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -455,6 +460,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (!isChangingConfigurations()) {
|
||||
StateSaver.clearStateFiles();
|
||||
}
|
||||
if (broadcastReceiver != null) {
|
||||
unregisterReceiver(broadcastReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -477,11 +485,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
headerServiceView.post(() -> headerServiceView.setSelected(true));
|
||||
toggleServiceButton.setContentDescription(
|
||||
getString(R.string.drawer_header_description) + selectedServiceName);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final SharedPreferences sharedPreferences
|
||||
= PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Theme has changed, recreating activity...");
|
||||
@@ -514,7 +523,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (intent != null) {
|
||||
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
|
||||
// to not destroy the already created backstack
|
||||
String action = intent.getAction();
|
||||
final String action = intent.getAction();
|
||||
if ((action != null && action.equals(Intent.ACTION_MAIN))
|
||||
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
|
||||
return;
|
||||
@@ -526,25 +535,60 @@ public class MainActivity extends AppCompatActivity {
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
|
||||
final Fragment fragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_player_holder);
|
||||
if (fragment instanceof OnKeyDownListener
|
||||
&& !bottomSheetHiddenOrCollapsed()) {
|
||||
// Provide keyDown event to fragment which then sends this event
|
||||
// to the main player service
|
||||
return ((OnKeyDownListener) fragment).onKeyDown(keyCode)
|
||||
|| super.onKeyDown(keyCode, event);
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onBackPressed() called");
|
||||
}
|
||||
|
||||
if (AndroidTvUtils.isTv(this)) {
|
||||
View drawerPanel = findViewById(R.id.navigation);
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
final View drawerPanel = findViewById(R.id.navigation);
|
||||
if (drawer.isDrawerOpen(drawerPanel)) {
|
||||
drawer.closeDrawers();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
|
||||
// delegate the back press to it
|
||||
if (fragment instanceof BackPressable) {
|
||||
if (((BackPressable) fragment).onBackPressed()) {
|
||||
// In case bottomSheet is not visible on the screen or collapsed we can assume that the user
|
||||
// interacts with a fragment inside fragment_holder so all back presses should be
|
||||
// handled by it
|
||||
if (bottomSheetHiddenOrCollapsed()) {
|
||||
final Fragment fragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_holder);
|
||||
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
|
||||
// delegate the back press to it
|
||||
if (fragment instanceof BackPressable) {
|
||||
if (((BackPressable) fragment).onBackPressed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
final Fragment fragmentPlayer = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_player_holder);
|
||||
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
|
||||
// delegate the back press to it
|
||||
if (fragmentPlayer instanceof BackPressable) {
|
||||
if (!((BackPressable) fragmentPlayer).onBackPressed()) {
|
||||
final FrameLayout bottomSheetLayout =
|
||||
findViewById(R.id.fragment_player_holder);
|
||||
BottomSheetBehavior.from(bottomSheetLayout)
|
||||
.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -560,7 +604,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
public void onRequestPermissionsResult(final int requestCode,
|
||||
@NonNull final String[] permissions,
|
||||
@NonNull final int[] grantResults) {
|
||||
for (int i : grantResults) {
|
||||
for (final int i : grantResults) {
|
||||
if (i == PackageManager.PERMISSION_DENIED) {
|
||||
return;
|
||||
}
|
||||
@@ -570,8 +614,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
NavigationHelper.openDownloads(this);
|
||||
break;
|
||||
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
|
||||
Fragment fragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_holder);
|
||||
final Fragment fragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_player_holder);
|
||||
if (fragment instanceof VideoDetailFragment) {
|
||||
((VideoDetailFragment) fragment).openDownloadDialog();
|
||||
}
|
||||
@@ -622,17 +666,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (!(fragment instanceof VideoDetailFragment)) {
|
||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
final Fragment fragment
|
||||
= getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
|
||||
if (!(fragment instanceof SearchFragment)) {
|
||||
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container)
|
||||
.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
@@ -647,7 +688,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
||||
}
|
||||
int id = item.getItemId();
|
||||
final int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
case android.R.id.home:
|
||||
@@ -668,6 +709,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
StateSaver.clearStateFiles();
|
||||
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||
// When user watch a video inside popup and then tries to open the video in main player
|
||||
// while the app is closed he will see a blank fragment on place of kiosk.
|
||||
// Let's open it first
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
NavigationHelper.openMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
|
||||
handleIntent(getIntent());
|
||||
} else {
|
||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
@@ -708,16 +756,22 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||
String url = intent.getStringExtra(Constants.KEY_URL);
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||
final String url = intent.getStringExtra(Constants.KEY_URL);
|
||||
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
final String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||
switch (((StreamingService.LinkType) intent
|
||||
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
||||
case STREAM:
|
||||
boolean autoPlay = intent
|
||||
final boolean autoPlay = intent
|
||||
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
||||
final String intentCacheKey = intent
|
||||
.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY);
|
||||
final PlayQueue playQueue = intentCacheKey != null
|
||||
? SerializedCache.getInstance()
|
||||
.take(intentCacheKey, PlayQueue.class)
|
||||
: null;
|
||||
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
|
||||
serviceId, url, title, autoPlay);
|
||||
serviceId, url, title, autoPlay, playQueue);
|
||||
break;
|
||||
case CHANNEL:
|
||||
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
|
||||
@@ -737,7 +791,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (searchString == null) {
|
||||
searchString = "";
|
||||
}
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
NavigationHelper.openSearchFragment(
|
||||
getSupportFragmentManager(),
|
||||
serviceId,
|
||||
@@ -746,8 +800,48 @@ public class MainActivity extends AppCompatActivity {
|
||||
} else {
|
||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupBroadcastReceiver() {
|
||||
broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
if (intent.getAction().equals(VideoDetailFragment.ACTION_PLAYER_STARTED)) {
|
||||
final Fragment fragmentPlayer = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_player_holder);
|
||||
if (fragmentPlayer == null) {
|
||||
/*
|
||||
* We still don't have a fragment attached to the activity.
|
||||
* It can happen when a user started popup or background players
|
||||
* without opening a stream inside the fragment.
|
||||
* Adding it in a collapsed state (only mini player will be visible)
|
||||
* */
|
||||
NavigationHelper.showMiniPlayer(getSupportFragmentManager());
|
||||
}
|
||||
/*
|
||||
* At this point the player is added 100%, we can unregister.
|
||||
* Other actions are useless since the fragment will not be removed after that
|
||||
* */
|
||||
unregisterReceiver(broadcastReceiver);
|
||||
broadcastReceiver = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
final IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
|
||||
registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
|
||||
private boolean bottomSheetHiddenOrCollapsed() {
|
||||
final FrameLayout bottomSheetLayout = findViewById(R.id.fragment_player_holder);
|
||||
final BottomSheetBehavior<FrameLayout> bottomSheetBehavior =
|
||||
BottomSheetBehavior.from(bottomSheetLayout);
|
||||
|
||||
final int sheetState = bottomSheetBehavior.getState();
|
||||
return sheetState == BottomSheetBehavior.STATE_HIDDEN
|
||||
|| sheetState == BottomSheetBehavior.STATE_COLLAPSED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public final class NewPipeDatabase {
|
||||
if (databaseInstance == null) {
|
||||
throw new IllegalStateException("database is not initialized");
|
||||
}
|
||||
Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
|
||||
final Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
|
||||
if (c.moveToFirst() && c.getInt(0) == 1) {
|
||||
throw new RuntimeException("Checkpoint was blocked from completing");
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class PanicResponderActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = getIntent();
|
||||
final Intent intent = getIntent();
|
||||
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||
// TODO: Explicitly clear the search results
|
||||
// once they are restored when the app restarts
|
||||
@@ -40,7 +40,7 @@ public class PanicResponderActivity extends Activity {
|
||||
ExitActivity.exitAndRemoveFromRecentApps(this);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
finishAndRemoveTask();
|
||||
} else {
|
||||
finish();
|
||||
|
||||
@@ -51,6 +51,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
|
||||
public static final String TAG = ReCaptchaActivity.class.toString();
|
||||
public static final String YT_URL = "https://www.youtube.com";
|
||||
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
|
||||
|
||||
private WebView webView;
|
||||
private String foundCookies = "";
|
||||
@@ -60,7 +61,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
ThemeHelper.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_recaptcha);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
||||
@@ -75,7 +76,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
webView = findViewById(R.id.reCaptchaWebView);
|
||||
|
||||
// enable Javascript
|
||||
WebSettings webSettings = webView.getSettings();
|
||||
final WebSettings webSettings = webView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@@ -83,7 +84,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(final WebView view,
|
||||
final WebResourceRequest request) {
|
||||
String url = request.getUrl().toString();
|
||||
final String url = request.getUrl().toString();
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
|
||||
}
|
||||
@@ -112,7 +113,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
// cleaning cache, history and cookies from webView
|
||||
webView.clearCache(true);
|
||||
webView.clearHistory();
|
||||
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||
final android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeAllCookies(aBoolean -> {
|
||||
});
|
||||
@@ -127,7 +128,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_recaptcha, menu);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setTitle(R.string.title_activity_recaptcha);
|
||||
@@ -144,7 +145,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
final int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.menu_item_done:
|
||||
saveCookiesAndFinish();
|
||||
@@ -168,11 +169,11 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
prefs.edit().putString(key, foundCookies).apply();
|
||||
|
||||
// give cookies to Downloader class
|
||||
DownloaderImpl.getInstance().setCookies(foundCookies);
|
||||
DownloaderImpl.getInstance().setCookie(RECAPTCHA_COOKIES_KEY, foundCookies);
|
||||
setResult(RESULT_OK);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
|
||||
final Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
}
|
||||
@@ -187,13 +188,13 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
String cookies = CookieManager.getInstance().getCookie(url);
|
||||
final String cookies = CookieManager.getInstance().getCookie(url);
|
||||
handleCookies(cookies);
|
||||
|
||||
// sometimes cookies are inside the url
|
||||
int abuseStart = url.indexOf("google_abuse=");
|
||||
final int abuseStart = url.indexOf("google_abuse=");
|
||||
if (abuseStart != -1) {
|
||||
int abuseEnd = url.indexOf("+path");
|
||||
final int abuseEnd = url.indexOf("+path");
|
||||
|
||||
try {
|
||||
String abuseCookie = url.substring(abuseStart + 13, abuseEnd);
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
@@ -38,20 +39,22 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
@@ -158,27 +161,36 @@ public class RouterActivity extends AppCompatActivity {
|
||||
if (result) {
|
||||
onSuccess();
|
||||
} else {
|
||||
onError();
|
||||
showUnsupportedUrlDialog(url);
|
||||
}
|
||||
}, this::handleError));
|
||||
}, throwable -> handleError(throwable, url)));
|
||||
}
|
||||
|
||||
private void handleError(final Throwable error) {
|
||||
error.printStackTrace();
|
||||
private void handleError(final Throwable throwable, final String url) {
|
||||
throwable.printStackTrace();
|
||||
|
||||
if (error instanceof ExtractionException) {
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||
if (throwable instanceof ExtractionException) {
|
||||
showUnsupportedUrlDialog(url);
|
||||
} else {
|
||||
ExtractorHelper.handleGeneralException(this, -1, null, error,
|
||||
ExtractorHelper.handleGeneralException(this, -1, url, throwable,
|
||||
UserAction.SOMETHING_ELSE, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
private void onError() {
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
private void showUnsupportedUrlDialog(final String url) {
|
||||
final Context context = getThemeWrapperContext();
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.unsupported_url)
|
||||
.setMessage(R.string.unsupported_url_dialog_message)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_share))
|
||||
.setPositiveButton(R.string.open_in_browser,
|
||||
(dialog, which) -> ShareUtils.openUrlInBrowser(this, url))
|
||||
.setNegativeButton(R.string.share,
|
||||
(dialog, which) -> ShareUtils.shareUrl(this, "", url)) // no subject
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.setOnDismissListener(dialog -> finish())
|
||||
.show();
|
||||
}
|
||||
|
||||
protected void onSuccess() {
|
||||
@@ -257,7 +269,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
|
||||
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
|
||||
R.layout.preferred_player_dialog_view, null, false);
|
||||
R.layout.single_choice_dialog_view, null, false);
|
||||
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
|
||||
|
||||
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
|
||||
@@ -267,6 +279,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
handleChoice(choice.key);
|
||||
|
||||
// open future streams always like this one, because "always" button was used by user
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
preferences.edit()
|
||||
.putString(getString(R.string.preferred_open_action_key), choice.key)
|
||||
@@ -309,11 +322,13 @@ public class RouterActivity extends AppCompatActivity {
|
||||
};
|
||||
|
||||
int id = 12345;
|
||||
for (AdapterChoiceItem item : choices) {
|
||||
for (final AdapterChoiceItem item : choices) {
|
||||
final RadioButton radioButton
|
||||
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
||||
radioButton.setText(item.description);
|
||||
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
|
||||
radioButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||
AppCompatResources.getDrawable(getApplicationContext(), item.icon),
|
||||
null, null, null);
|
||||
radioButton.setChecked(false);
|
||||
radioButton.setId(id++);
|
||||
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
|
||||
@@ -327,7 +342,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
getString(R.string.preferred_open_action_last_selected_key), null);
|
||||
if (!TextUtils.isEmpty(lastSelectedPlayer)) {
|
||||
for (int i = 0; i < choices.size(); i++) {
|
||||
AdapterChoiceItem c = choices.get(i);
|
||||
final AdapterChoiceItem c = choices.get(i);
|
||||
if (lastSelectedPlayer.equals(c.key)) {
|
||||
selectedRadioPosition = i;
|
||||
break;
|
||||
@@ -344,7 +359,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
alertDialog.show();
|
||||
|
||||
if (AndroidTvUtils.isTv(this)) {
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
FocusOverlayView.setupFocusObserver(alertDialog);
|
||||
}
|
||||
}
|
||||
@@ -359,33 +374,60 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(this);
|
||||
boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
final boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
getString(R.string.use_external_video_player_key), false);
|
||||
boolean isExtAudioEnabled = preferences.getBoolean(
|
||||
final boolean isExtAudioEnabled = preferences.getBoolean(
|
||||
getString(R.string.use_external_audio_player_key), false);
|
||||
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
||||
getString(R.string.show_info),
|
||||
resolveResourceIdFromAttr(context, R.attr.info)));
|
||||
final AdapterChoiceItem videoPlayer = new AdapterChoiceItem(
|
||||
getString(R.string.video_player_key), getString(R.string.video_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_play_arrow));
|
||||
final AdapterChoiceItem showInfo = new AdapterChoiceItem(
|
||||
getString(R.string.show_info_key), getString(R.string.show_info),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_info_outline));
|
||||
final AdapterChoiceItem popupPlayer = new AdapterChoiceItem(
|
||||
getString(R.string.popup_player_key), getString(R.string.popup_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_popup));
|
||||
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
|
||||
getString(R.string.background_player_key), getString(R.string.background_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_headset));
|
||||
|
||||
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
|
||||
getString(R.string.video_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.play)));
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
|
||||
getString(R.string.popup_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.popup)));
|
||||
}
|
||||
if (linkType == LinkType.STREAM) {
|
||||
if (isExtVideoEnabled) {
|
||||
// show both "show info" and "video player", they are two different activities
|
||||
returnList.add(showInfo);
|
||||
returnList.add(videoPlayer);
|
||||
} else if (capabilities.contains(VIDEO)
|
||||
&& PlayerHelper.isAutoplayAllowedByUser(context)) {
|
||||
// show only "video player" since the details activity will be opened and the video
|
||||
// will be autoplayed there and "show info" would do the exact same thing
|
||||
returnList.add(videoPlayer);
|
||||
} else {
|
||||
// show only "show info" if video player is not applicable or autoplay is disabled
|
||||
returnList.add(showInfo);
|
||||
}
|
||||
|
||||
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
|
||||
getString(R.string.background_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.audio)));
|
||||
if (capabilities.contains(VIDEO)) {
|
||||
returnList.add(popupPlayer);
|
||||
}
|
||||
if (capabilities.contains(AUDIO)) {
|
||||
returnList.add(backgroundPlayer);
|
||||
}
|
||||
|
||||
} else {
|
||||
returnList.add(showInfo);
|
||||
if (capabilities.contains(VIDEO) && !isExtVideoEnabled) {
|
||||
returnList.add(videoPlayer);
|
||||
returnList.add(popupPlayer);
|
||||
}
|
||||
if (capabilities.contains(AUDIO) && !isExtAudioEnabled) {
|
||||
returnList.add(backgroundPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
|
||||
getString(R.string.download),
|
||||
resolveResourceIdFromAttr(context, R.attr.download)));
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_file_download)));
|
||||
|
||||
return returnList;
|
||||
}
|
||||
@@ -407,9 +449,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void handleText() {
|
||||
String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
||||
final String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
final int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
final Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
NavigationHelper.openSearch(getThemeWrapperContext(), serviceId, searchString);
|
||||
@@ -456,7 +498,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
startActivity(intent);
|
||||
|
||||
finish();
|
||||
}, this::handleError)
|
||||
}, throwable -> handleError(throwable, currentUrl))
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -476,24 +518,22 @@ public class RouterActivity extends AppCompatActivity {
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe((@NonNull StreamInfo result) -> {
|
||||
List<VideoStream> sortedVideoStreams = ListHelper
|
||||
final List<VideoStream> sortedVideoStreams = ListHelper
|
||||
.getSortedStreamVideosList(this, result.getVideoStreams(),
|
||||
result.getVideoOnlyStreams(), false);
|
||||
int selectedVideoStreamIndex = ListHelper
|
||||
final int selectedVideoStreamIndex = ListHelper
|
||||
.getDefaultResolutionIndex(this, sortedVideoStreams);
|
||||
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
||||
final FragmentManager fm = getSupportFragmentManager();
|
||||
final DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
||||
downloadDialog.setVideoStreams(sortedVideoStreams);
|
||||
downloadDialog.setAudioStreams(result.getAudioStreams());
|
||||
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
|
||||
downloadDialog.show(fm, "downloadDialog");
|
||||
fm.executePendingTransactions();
|
||||
downloadDialog.getDialog().setOnDismissListener(dialog -> {
|
||||
finish();
|
||||
});
|
||||
downloadDialog.getDialog().setOnDismissListener(dialog -> finish());
|
||||
}, (@NonNull Throwable throwable) -> {
|
||||
onError();
|
||||
showUnsupportedUrlDialog(currentUrl);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -501,7 +541,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
public void onRequestPermissionsResult(final int requestCode,
|
||||
@NonNull final String[] permissions,
|
||||
@NonNull final int[] grantResults) {
|
||||
for (int i : grantResults) {
|
||||
for (final int i : grantResults) {
|
||||
if (i == PackageManager.PERMISSION_DENIED) {
|
||||
finish();
|
||||
return;
|
||||
@@ -569,7 +609,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static class AdapterChoiceItem {
|
||||
@@ -631,7 +671,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
if (!(serializable instanceof Choice)) {
|
||||
return;
|
||||
}
|
||||
Choice playerChoice = (Choice) serializable;
|
||||
final Choice playerChoice = (Choice) serializable;
|
||||
handleChoice(playerChoice);
|
||||
}
|
||||
|
||||
@@ -679,13 +719,13 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(this);
|
||||
boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
final boolean isExtVideoEnabled = preferences.getBoolean(
|
||||
getString(R.string.use_external_video_player_key), false);
|
||||
boolean isExtAudioEnabled = preferences.getBoolean(
|
||||
final boolean isExtAudioEnabled = preferences.getBoolean(
|
||||
getString(R.string.use_external_audio_player_key), false);
|
||||
|
||||
PlayQueue playQueue;
|
||||
String playerChoice = choice.playerChoice;
|
||||
final String playerChoice = choice.playerChoice;
|
||||
|
||||
if (info instanceof StreamInfo) {
|
||||
if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
|
||||
@@ -698,7 +738,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
playQueue = new SinglePlayQueue((StreamInfo) info);
|
||||
|
||||
if (playerChoice.equals(videoPlayerKey)) {
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
||||
openMainPlayer(playQueue, choice);
|
||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
|
||||
} else if (playerChoice.equals(popupPlayerKey)) {
|
||||
@@ -713,7 +753,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
: new PlaylistPlayQueue((PlaylistInfo) info);
|
||||
|
||||
if (playerChoice.equals(videoPlayerKey)) {
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
||||
openMainPlayer(playQueue, choice);
|
||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
|
||||
} else if (playerChoice.equals(popupPlayerKey)) {
|
||||
@@ -723,6 +763,11 @@ public class RouterActivity extends AppCompatActivity {
|
||||
};
|
||||
}
|
||||
|
||||
private void openMainPlayer(final PlayQueue playQueue, final Choice choice) {
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue, choice.linkType,
|
||||
choice.url, "", true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -26,6 +24,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
import static org.schabi.newpipe.util.ShareUtils.openUrlInBrowser;
|
||||
|
||||
public class AboutActivity extends AppCompatActivity {
|
||||
/**
|
||||
@@ -89,7 +88,7 @@ public class AboutActivity extends AppCompatActivity {
|
||||
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
// Create the adapter that will return a fragment for each of the three
|
||||
@@ -100,13 +99,13 @@ public class AboutActivity extends AppCompatActivity {
|
||||
mViewPager = findViewById(R.id.container);
|
||||
mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||
|
||||
TabLayout tabLayout = findViewById(R.id.tabs);
|
||||
final TabLayout tabLayout = findViewById(R.id.tabs);
|
||||
tabLayout.setupWithViewPager(mViewPager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
final int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
case android.R.id.home:
|
||||
@@ -135,36 +134,31 @@ public class AboutActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||
Context context = this.getContext();
|
||||
final View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||
final Context context = this.getContext();
|
||||
|
||||
TextView version = rootView.findViewById(R.id.app_version);
|
||||
final TextView version = rootView.findViewById(R.id.app_version);
|
||||
version.setText(BuildConfig.VERSION_NAME);
|
||||
|
||||
View githubLink = rootView.findViewById(R.id.github_link);
|
||||
final View githubLink = rootView.findViewById(R.id.github_link);
|
||||
githubLink.setOnClickListener(nv ->
|
||||
openWebsite(context.getString(R.string.github_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.github_url)));
|
||||
|
||||
View donationLink = rootView.findViewById(R.id.donation_link);
|
||||
final View donationLink = rootView.findViewById(R.id.donation_link);
|
||||
donationLink.setOnClickListener(v ->
|
||||
openWebsite(context.getString(R.string.donation_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.donation_url)));
|
||||
|
||||
View websiteLink = rootView.findViewById(R.id.website_link);
|
||||
final View websiteLink = rootView.findViewById(R.id.website_link);
|
||||
websiteLink.setOnClickListener(nv ->
|
||||
openWebsite(context.getString(R.string.website_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.website_url)));
|
||||
|
||||
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||
final View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||
privacyPolicyLink.setOnClickListener(v ->
|
||||
openWebsite(context.getString(R.string.privacy_policy_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void openWebsite(final String url, final Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +167,7 @@ public class AboutActivity extends AppCompatActivity {
|
||||
*/
|
||||
public class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||
public SectionsPagerAdapter(final FragmentManager fm) {
|
||||
super(fm);
|
||||
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,10 +4,12 @@ import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Class for storing information about a software license.
|
||||
*/
|
||||
public class License implements Parcelable {
|
||||
public class License implements Parcelable, Serializable {
|
||||
public static final Creator<License> CREATOR = new Creator<License>() {
|
||||
@Override
|
||||
public License createFromParcel(final Parcel source) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -13,13 +10,15 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Fragment containing the software licenses.
|
||||
@@ -27,14 +26,16 @@ import java.util.Comparator;
|
||||
public class LicenseFragment extends Fragment {
|
||||
private static final String ARG_COMPONENTS = "components";
|
||||
private SoftwareComponent[] softwareComponents;
|
||||
private SoftwareComponent mComponentForContextMenu;
|
||||
private SoftwareComponent componentForContextMenu;
|
||||
private License activeLicense;
|
||||
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
|
||||
|
||||
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||
if (softwareComponents == null) {
|
||||
throw new NullPointerException("softwareComponents is null");
|
||||
}
|
||||
LicenseFragment fragment = new LicenseFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
final LicenseFragment fragment = new LicenseFragment();
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArray(ARG_COMPONENTS, softwareComponents);
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
@@ -46,8 +47,8 @@ public class LicenseFragment extends Fragment {
|
||||
* @param context the context to use
|
||||
* @param license the license to show
|
||||
*/
|
||||
public static void showLicense(final Context context, final License license) {
|
||||
new LicenseFragmentHelper((Activity) context).execute(license);
|
||||
private static void showLicense(final Activity context, final License license) {
|
||||
new LicenseFragmentHelper(context).execute(license);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,89 +57,87 @@ public class LicenseFragment extends Fragment {
|
||||
softwareComponents = (SoftwareComponent[]) getArguments()
|
||||
.getParcelableArray(ARG_COMPONENTS);
|
||||
|
||||
// Sort components by name
|
||||
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
|
||||
@Override
|
||||
public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
if (savedInstanceState != null) {
|
||||
final Serializable license = savedInstanceState.getSerializable(LICENSE_KEY);
|
||||
if (license != null) {
|
||||
activeLicense = (License) license;
|
||||
}
|
||||
});
|
||||
}
|
||||
// Sort components by name
|
||||
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
||||
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);
|
||||
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
||||
final View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||
licenseLink.setOnClickListener(v -> {
|
||||
activeLicense = StandardLicenses.GPL3;
|
||||
showLicense(getActivity(), StandardLicenses.GPL3);
|
||||
});
|
||||
|
||||
for (final SoftwareComponent component : softwareComponents) {
|
||||
View componentView = inflater
|
||||
final View componentView = inflater
|
||||
.inflate(R.layout.item_software_component, container, false);
|
||||
TextView softwareName = componentView.findViewById(R.id.name);
|
||||
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());
|
||||
copyright.setText(getContext().getString(R.string.copyright,
|
||||
copyright.setText(getString(R.string.copyright,
|
||||
component.getYears(),
|
||||
component.getCopyrightOwner(),
|
||||
component.getLicense().getAbbreviation()));
|
||||
|
||||
componentView.setTag(component);
|
||||
componentView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
Context context = v.getContext();
|
||||
if (context != null) {
|
||||
showLicense(context, component.getLicense());
|
||||
}
|
||||
}
|
||||
componentView.setOnClickListener(v -> {
|
||||
activeLicense = component.getLicense();
|
||||
showLicense(getActivity(), component.getLicense());
|
||||
});
|
||||
softwareComponentsView.addView(componentView);
|
||||
registerForContextMenu(componentView);
|
||||
}
|
||||
if (activeLicense != null) {
|
||||
showLicense(getActivity(), activeLicense);
|
||||
}
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(final ContextMenu menu, final View v,
|
||||
final ContextMenu.ContextMenuInfo menuInfo) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||
final MenuInflater inflater = getActivity().getMenuInflater();
|
||||
final SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||
menu.setHeaderTitle(component.getName());
|
||||
inflater.inflate(R.menu.software_component, menu);
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
mComponentForContextMenu = (SoftwareComponent) v.getTag();
|
||||
componentForContextMenu = (SoftwareComponent) v.getTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(final MenuItem item) {
|
||||
public boolean onContextItemSelected(@NonNull final MenuItem item) {
|
||||
// item.getMenuInfo() is null so we use the tag of the view
|
||||
final SoftwareComponent component = mComponentForContextMenu;
|
||||
final SoftwareComponent component = componentForContextMenu;
|
||||
if (component == null) {
|
||||
return false;
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_website:
|
||||
openWebsite(component.getLink());
|
||||
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
|
||||
return true;
|
||||
case R.id.action_show_license:
|
||||
showLicense(getContext(), component.getLicense());
|
||||
showLicense(getActivity(), component.getLicense());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void openWebsite(final 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(final View v) {
|
||||
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
if (activeLicense != null) {
|
||||
savedInstanceState.putSerializable(LICENSE_KEY, activeLicense);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package org.schabi.newpipe.about;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Base64;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
@@ -12,6 +14,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -26,28 +29,18 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
weakReference = new WeakReference<>(activity);
|
||||
}
|
||||
|
||||
private static String getFinishString(final 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(final Context context, final 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;
|
||||
private static String getFormattedLicense(@NonNull final Context context,
|
||||
@NonNull final License license) {
|
||||
final StringBuilder licenseContent = new StringBuilder();
|
||||
final String webViewData;
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
final BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
|
||||
String str;
|
||||
while ((str = in.readLine()) != null) {
|
||||
@@ -56,13 +49,11 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
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));
|
||||
webViewData = licenseContent.toString().replace("</head>",
|
||||
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not get license file: " + license.getFilename(), e);
|
||||
}
|
||||
return webViewData;
|
||||
}
|
||||
@@ -71,21 +62,19 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
* @param context
|
||||
* @return String which is a CSS stylesheet according to the context's theme
|
||||
*/
|
||||
public static String getLicenseStylesheet(final Context context) {
|
||||
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
||||
return "body{padding:12px 15px;margin:0;background:#"
|
||||
+ getHexRGBColor(context, isLightTheme
|
||||
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.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.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;}";
|
||||
: R.color.dark_youtube_primary_color) + "}"
|
||||
+ "pre{white-space:pre-wrap}";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,13 +84,13 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
* @param color the color number from R.color
|
||||
* @return a six characters long String with hexadecimal RGB values
|
||||
*/
|
||||
public static String getHexRGBColor(final Context context, final int color) {
|
||||
private static String getHexRGBColor(final Context context, final int color) {
|
||||
return context.getResources().getString(color).substring(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Activity getActivity() {
|
||||
Activity activity = weakReference.get();
|
||||
final Activity activity = weakReference.get();
|
||||
|
||||
if (activity != null && activity.isFinishing()) {
|
||||
return null;
|
||||
@@ -118,22 +107,22 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Integer result) {
|
||||
Activity activity = getActivity();
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String webViewData = getFormattedLicense(activity, license);
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
final String webViewData = Base64.encodeToString(getFormattedLicense(activity, license)
|
||||
.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());
|
||||
|
||||
WebView wv = new WebView(activity);
|
||||
wv.loadData(webViewData, "text/html; charset=UTF-8", null);
|
||||
|
||||
alert.setView(wv);
|
||||
assureCorrectAppLanguage(activity.getApplicationContext());
|
||||
alert.setNegativeButton(getFinishString(activity), (dialog, which) -> dialog.dismiss());
|
||||
alert.setView(webView);
|
||||
assureCorrectAppLanguage(activity);
|
||||
alert.setNegativeButton(activity.getString(R.string.finish),
|
||||
(dialog, which) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ import io.reactivex.Flowable;
|
||||
@Dao
|
||||
public interface BasicDAO<Entity> {
|
||||
/* Inserts */
|
||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
long insert(Entity entity);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
List<Long> insertAll(Entity... entities);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.FAIL)
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
List<Long> insertAll(Collection<Entity> entities);
|
||||
|
||||
/* Searches */
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class Converters {
|
||||
|
||||
@TypeConverter
|
||||
public static FeedGroupIcon feedGroupIconOf(final Integer id) {
|
||||
for (FeedGroupIcon icon : FeedGroupIcon.values()) {
|
||||
for (final FeedGroupIcon icon : FeedGroupIcon.values()) {
|
||||
if (icon.getId() == id) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.room.migration.Migration;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
public final class Migrations {
|
||||
public static final int DB_VER_1 = 1;
|
||||
@@ -14,7 +14,7 @@ public final class Migrations {
|
||||
public static final int DB_VER_3 = 3;
|
||||
|
||||
private static final String TAG = Migrations.class.getName();
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package org.schabi.newpipe.database.feed.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.reactivex.Flowable
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import java.util.*
|
||||
|
||||
@Dao
|
||||
abstract class FeedDAO {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package org.schabi.newpipe.database.feed.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
|
||||
@@ -27,11 +27,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
]
|
||||
)
|
||||
data class FeedEntity(
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var streamId: Long,
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var streamId: Long,
|
||||
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -40,4 +40,4 @@ data class FeedEntity(
|
||||
const val STREAM_ID = "stream_id"
|
||||
const val SUBSCRIPTION_ID = "subscription_id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,18 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
indices = [Index(SORT_ORDER)]
|
||||
)
|
||||
data class FeedGroupEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = ID)
|
||||
val uid: Long,
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = ID)
|
||||
val uid: Long,
|
||||
|
||||
@ColumnInfo(name = NAME)
|
||||
var name: String,
|
||||
@ColumnInfo(name = NAME)
|
||||
var name: String,
|
||||
|
||||
@ColumnInfo(name = ICON)
|
||||
var icon: FeedGroupIcon,
|
||||
@ColumnInfo(name = ICON)
|
||||
var icon: FeedGroupIcon,
|
||||
|
||||
@ColumnInfo(name = SORT_ORDER)
|
||||
var sortOrder: Long = -1
|
||||
@ColumnInfo(name = SORT_ORDER)
|
||||
var sortOrder: Long = -1
|
||||
) {
|
||||
companion object {
|
||||
const val FEED_GROUP_TABLE = "feed_group"
|
||||
@@ -36,4 +36,4 @@ data class FeedGroupEntity(
|
||||
|
||||
const val GROUP_ALL_ID = -1L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
]
|
||||
)
|
||||
data class FeedGroupSubscriptionEntity(
|
||||
@ColumnInfo(name = GROUP_ID)
|
||||
var feedGroupId: Long,
|
||||
@ColumnInfo(name = GROUP_ID)
|
||||
var feedGroupId: Long,
|
||||
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -42,4 +42,4 @@ data class FeedGroupSubscriptionEntity(
|
||||
const val GROUP_ID = "group_id"
|
||||
const val SUBSCRIPTION_ID = "subscription_id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import java.util.*
|
||||
|
||||
@Entity(
|
||||
tableName = FEED_LAST_UPDATED_TABLE,
|
||||
@@ -20,12 +20,12 @@ import java.util.*
|
||||
]
|
||||
)
|
||||
data class FeedLastUpdatedEntity(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long,
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long,
|
||||
|
||||
@ColumnInfo(name = LAST_UPDATED)
|
||||
var lastUpdated: Date? = null
|
||||
@ColumnInfo(name = LAST_UPDATED)
|
||||
var lastUpdated: Date? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -34,4 +34,4 @@ data class FeedLastUpdatedEntity(
|
||||
const val SUBSCRIPTION_ID = "subscription_id"
|
||||
const val LAST_UPDATED = "last_updated"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,21 @@ package org.schabi.newpipe.database.history.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import java.util.*
|
||||
|
||||
data class StreamHistoryEntry(
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
||||
val accessDate: Date,
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
||||
val accessDate: Date,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
||||
val repeatCount: Long
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
||||
val repeatCount: Long
|
||||
) {
|
||||
|
||||
fun toStreamHistoryEntity(): StreamHistoryEntity {
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
package org.schabi.newpipe.database.playlist;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public interface PlaylistLocalItem extends LocalItem {
|
||||
String getOrderingName();
|
||||
|
||||
static List<PlaylistLocalItem> merge(
|
||||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
final List<PlaylistLocalItem> items = new ArrayList<>(
|
||||
localPlaylists.size() + remotePlaylists.size());
|
||||
items.addAll(localPlaylists);
|
||||
items.addAll(remotePlaylists);
|
||||
|
||||
Collections.sort(items, (left, right) -> {
|
||||
final String on1 = left.getOrderingName();
|
||||
final String on2 = right.getOrderingName();
|
||||
if (on1 == null) {
|
||||
return on2 == null ? 0 : 1;
|
||||
} else {
|
||||
return on2 == null ? -1 : on1.compareToIgnoreCase(on2);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
|
||||
class PlaylistStreamEntry(
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
||||
val joinIndex: Int
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
||||
val joinIndex: Int
|
||||
) : LocalItem {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
|
||||
@@ -2,24 +2,24 @@ package org.schabi.newpipe.database.stream
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.LocalItem
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import java.util.*
|
||||
|
||||
class StreamStatisticsEntry(
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
|
||||
@ColumnInfo(name = STREAM_LATEST_DATE)
|
||||
val latestAccessDate: Date,
|
||||
@ColumnInfo(name = STREAM_LATEST_DATE)
|
||||
val latestAccessDate: Date,
|
||||
|
||||
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
||||
val watchCount: Long
|
||||
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
||||
val watchCount: Long
|
||||
) : LocalItem {
|
||||
|
||||
fun toStreamInfoItem(): StreamInfoItem {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package org.schabi.newpipe.database.stream.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.reactivex.Flowable
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.BasicDAO
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
|
||||
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@Dao
|
||||
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||
@@ -94,7 +98,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||
if (existentMinimalStream.duration > 0 && newerStream.duration < 0) {
|
||||
newerStream.duration = existentMinimalStream.duration
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,21 +119,22 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||
* Minimal entry class used when comparing/updating an existent stream.
|
||||
*/
|
||||
internal data class StreamCompareFeed(
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||
var duration: Long)
|
||||
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||
var duration: Long
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
package org.schabi.newpipe.database.stream.model
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
|
||||
@@ -9,8 +16,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
@Entity(tableName = STREAM_TABLE,
|
||||
indices = [
|
||||
@@ -18,42 +23,42 @@ import java.util.*
|
||||
]
|
||||
)
|
||||
data class StreamEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
|
||||
@ColumnInfo(name = STREAM_SERVICE_ID)
|
||||
var serviceId: Int,
|
||||
@ColumnInfo(name = STREAM_SERVICE_ID)
|
||||
var serviceId: Int,
|
||||
|
||||
@ColumnInfo(name = STREAM_URL)
|
||||
var url: String,
|
||||
@ColumnInfo(name = STREAM_URL)
|
||||
var url: String,
|
||||
|
||||
@ColumnInfo(name = STREAM_TITLE)
|
||||
var title: String,
|
||||
@ColumnInfo(name = STREAM_TITLE)
|
||||
var title: String,
|
||||
|
||||
@ColumnInfo(name = STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
@ColumnInfo(name = STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
|
||||
@ColumnInfo(name = STREAM_DURATION)
|
||||
var duration: Long,
|
||||
@ColumnInfo(name = STREAM_DURATION)
|
||||
var duration: Long,
|
||||
|
||||
@ColumnInfo(name = STREAM_UPLOADER)
|
||||
var uploader: String,
|
||||
@ColumnInfo(name = STREAM_UPLOADER)
|
||||
var uploader: String,
|
||||
|
||||
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
|
||||
var thumbnailUrl: String? = null,
|
||||
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
|
||||
var thumbnailUrl: String? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_VIEWS)
|
||||
var viewCount: Long? = null,
|
||||
@ColumnInfo(name = STREAM_VIEWS)
|
||||
var viewCount: Long? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null
|
||||
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null
|
||||
) : Serializable {
|
||||
|
||||
@Ignore
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package org.schabi.newpipe.database.subscription
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import org.schabi.newpipe.database.BasicDAO
|
||||
@@ -16,6 +20,45 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
||||
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions
|
||||
|
||||
WHERE name LIKE '%' || :filter || '%'
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions s
|
||||
|
||||
LEFT JOIN feed_group_subscription_join fgs
|
||||
ON s.uid = fgs.subscription_id
|
||||
|
||||
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsOnlyUngrouped(
|
||||
currentGroupId: Long
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions s
|
||||
|
||||
LEFT JOIN feed_group_subscription_join fgs
|
||||
ON s.uid = fgs.subscription_id
|
||||
|
||||
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||
AND s.name LIKE '%' || :filter || '%'
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsOnlyUngroupedFiltered(
|
||||
currentGroupId: Long,
|
||||
filter: String
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@@ -48,7 +91,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||
entity.uid = uidFromInsert
|
||||
} else {
|
||||
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
|
||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||
entity.uid = subscriptionIdFromDb
|
||||
|
||||
update(entity)
|
||||
|
||||
@@ -50,7 +50,7 @@ public class SubscriptionEntity {
|
||||
|
||||
@Ignore
|
||||
public static SubscriptionEntity from(@NonNull final ChannelInfo info) {
|
||||
SubscriptionEntity result = new SubscriptionEntity();
|
||||
final SubscriptionEntity result = new SubscriptionEntity();
|
||||
result.setServiceId(info.getServiceId());
|
||||
result.setUrl(info.getUrl());
|
||||
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
|
||||
@@ -124,10 +124,61 @@ public class SubscriptionEntity {
|
||||
|
||||
@Ignore
|
||||
public ChannelInfoItem toChannelInfoItem() {
|
||||
ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
|
||||
final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
|
||||
item.setThumbnailUrl(getAvatarUrl());
|
||||
item.setSubscriberCount(getSubscriberCount());
|
||||
item.setDescription(getDescription());
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Remove these generated methods by migrating this class to a data class from Kotlin.
|
||||
@Override
|
||||
@SuppressWarnings("EqualsReplaceableByObjectsCall")
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final SubscriptionEntity that = (SubscriptionEntity) o;
|
||||
|
||||
if (uid != that.uid) {
|
||||
return false;
|
||||
}
|
||||
if (serviceId != that.serviceId) {
|
||||
return false;
|
||||
}
|
||||
if (!url.equals(that.url)) {
|
||||
return false;
|
||||
}
|
||||
if (name != null ? !name.equals(that.name) : that.name != null) {
|
||||
return false;
|
||||
}
|
||||
if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) {
|
||||
return false;
|
||||
}
|
||||
if (subscriberCount != null
|
||||
? !subscriberCount.equals(that.subscriberCount)
|
||||
: that.subscriberCount != null) {
|
||||
return false;
|
||||
}
|
||||
return description != null
|
||||
? description.equals(that.description)
|
||||
: that.description == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) (uid ^ (uid >>> 32));
|
||||
result = 31 * result + serviceId;
|
||||
result = 31 * result + url.hashCode();
|
||||
result = 31 * result + (name != null ? name.hashCode() : 0);
|
||||
result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
|
||||
result = 31 * result + (subscriberCount != null ? subscriberCount.hashCode() : 0);
|
||||
result = 31 * result + (description != null ? description.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
@@ -11,9 +10,10 @@ import android.view.ViewTreeObserver;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
// Service
|
||||
Intent i = new Intent();
|
||||
final Intent i = new Intent();
|
||||
i.setClass(this, DownloadManagerService.class);
|
||||
startService(i);
|
||||
|
||||
@@ -38,10 +38,10 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_downloader);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.downloads_title);
|
||||
@@ -57,13 +57,13 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
if (AndroidTvUtils.isTv(this)) {
|
||||
if (DeviceUtils.isTv(this)) {
|
||||
FocusOverlayView.setupFocusObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFragments() {
|
||||
MissionsFragment fragment = new MissionsFragment();
|
||||
final MissionsFragment fragment = new MissionsFragment();
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
|
||||
@@ -74,7 +74,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
final MenuInflater inflater = getMenuInflater();
|
||||
|
||||
inflater.inflate(R.menu.download_menu, menu);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -124,7 +124,7 @@ public class DownloadDialog extends DialogFragment
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public static DownloadDialog newInstance(final StreamInfo info) {
|
||||
DownloadDialog dialog = new DownloadDialog();
|
||||
final DownloadDialog dialog = new DownloadDialog();
|
||||
dialog.setInfo(info);
|
||||
return dialog;
|
||||
}
|
||||
@@ -208,14 +208,15 @@ public class DownloadDialog extends DialogFragment
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
|
||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||
|
||||
SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
|
||||
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||
final SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams
|
||||
= new SparseArray<>(4);
|
||||
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
if (!videoStreams.get(i).isVideoOnly()) {
|
||||
continue;
|
||||
}
|
||||
AudioStream audioStream = SecondaryStreamHelper
|
||||
final AudioStream audioStream = SecondaryStreamHelper
|
||||
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
||||
|
||||
if (audioStream != null) {
|
||||
@@ -232,13 +233,13 @@ public class DownloadDialog extends DialogFragment
|
||||
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
|
||||
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
|
||||
|
||||
Intent intent = new Intent(context, DownloadManagerService.class);
|
||||
final Intent intent = new Intent(context, DownloadManagerService.class);
|
||||
context.startService(intent);
|
||||
|
||||
context.bindService(intent, new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(final ComponentName cname, final IBinder service) {
|
||||
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
||||
final DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
||||
|
||||
mainStorageAudio = mgr.getMainStorageAudio();
|
||||
mainStorageVideo = mgr.getMainStorageVideo();
|
||||
@@ -294,9 +295,9 @@ public class DownloadDialog extends DialogFragment
|
||||
initToolbar(view.findViewById(R.id.toolbar));
|
||||
setupDownloadOptions();
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||
|
||||
int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
|
||||
final int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
|
||||
threadsCountTextView.setText(String.valueOf(threads));
|
||||
threadsSeekBar.setProgress(threads - 1);
|
||||
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@@ -373,13 +374,13 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
|
||||
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
|
||||
File file = Utils.getFileForUri(data.getData());
|
||||
final File file = Utils.getFileForUri(data.getData());
|
||||
checkSelectedDownload(null, Uri.fromFile(file), file.getName(),
|
||||
StoredFileHelper.DEFAULT_MIME);
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
|
||||
final DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
|
||||
if (docFile == null) {
|
||||
showFailedDialog(R.string.general_error);
|
||||
return;
|
||||
@@ -396,13 +397,11 @@ public class DownloadDialog extends DialogFragment
|
||||
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
||||
}
|
||||
|
||||
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
|
||||
|
||||
toolbar.setTitle(R.string.download_dialog_title);
|
||||
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp
|
||||
: R.drawable.ic_arrow_back_white_24dp);
|
||||
toolbar.setNavigationIcon(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_arrow_back));
|
||||
toolbar.inflateMenu(R.menu.dialog_url);
|
||||
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
|
||||
toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss());
|
||||
toolbar.setNavigationContentDescription(R.string.cancel);
|
||||
|
||||
okButton = toolbar.findViewById(R.id.okay);
|
||||
@@ -517,7 +516,23 @@ public class DownloadDialog extends DialogFragment
|
||||
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
|
||||
subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (isVideoStreamsAvailable) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
final String defaultMedia = prefs.getString(getString(R.string.last_used_download_type),
|
||||
getString(R.string.last_download_type_video_key));
|
||||
|
||||
if (isVideoStreamsAvailable
|
||||
&& (defaultMedia.equals(getString(R.string.last_download_type_video_key)))) {
|
||||
videoButton.setChecked(true);
|
||||
setupVideoSpinner();
|
||||
} else if (isAudioStreamsAvailable
|
||||
&& (defaultMedia.equals(getString(R.string.last_download_type_audio_key)))) {
|
||||
audioButton.setChecked(true);
|
||||
setupAudioSpinner();
|
||||
} else if (isSubtitleStreamsAvailable
|
||||
&& (defaultMedia.equals(getString(R.string.last_download_type_subtitle_key)))) {
|
||||
subtitleButton.setChecked(true);
|
||||
setupSubtitleSpinner();
|
||||
} else if (isVideoStreamsAvailable) {
|
||||
videoButton.setChecked(true);
|
||||
setupVideoSpinner();
|
||||
} else if (isAudioStreamsAvailable) {
|
||||
@@ -566,7 +581,7 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
|
||||
private String getNameEditText() {
|
||||
String str = nameEditText.getText().toString().trim();
|
||||
final String str = nameEditText.getText().toString().trim();
|
||||
|
||||
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
|
||||
}
|
||||
@@ -593,9 +608,10 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
|
||||
private void prepareSelectedDownload() {
|
||||
StoredDirectoryHelper mainStorage;
|
||||
MediaFormat format;
|
||||
String mime;
|
||||
final StoredDirectoryHelper mainStorage;
|
||||
final MediaFormat format;
|
||||
final String mime;
|
||||
final String selectedMediaType;
|
||||
|
||||
// first, build the filename and get the output folder (if possible)
|
||||
// later, run a very very very large file checking logic
|
||||
@@ -604,6 +620,7 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_audio_key);
|
||||
mainStorage = mainStorageAudio;
|
||||
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
||||
switch (format) {
|
||||
@@ -618,12 +635,14 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
break;
|
||||
case R.id.video_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_video_key);
|
||||
mainStorage = mainStorageVideo;
|
||||
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
||||
mime = format.mimeType;
|
||||
filename += format.suffix;
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
|
||||
mainStorage = mainStorageVideo; // subtitle & video files go together
|
||||
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
||||
mime = format.mimeType;
|
||||
@@ -665,6 +684,11 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
// check for existing file with the same name
|
||||
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
|
||||
|
||||
// remember the last media type downloaded by the user
|
||||
prefs.edit()
|
||||
.putString(getString(R.string.last_used_download_type), selectedMediaType)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
|
||||
@@ -685,15 +709,17 @@ public class DownloadDialog extends DialogFragment
|
||||
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile,
|
||||
mainStorage.getTag());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
showErrorActivity(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if is our file
|
||||
MissionState state = downloadManager.checkForExistingMission(storage);
|
||||
@StringRes int msgBtn;
|
||||
@StringRes int msgBody;
|
||||
final MissionState state = downloadManager.checkForExistingMission(storage);
|
||||
@StringRes
|
||||
final int msgBtn;
|
||||
@StringRes
|
||||
final int msgBody;
|
||||
|
||||
switch (state) {
|
||||
case Finished:
|
||||
@@ -746,8 +772,7 @@ public class DownloadDialog extends DialogFragment
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
|
||||
final AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.download_dialog_title)
|
||||
.setMessage(msgBody)
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
@@ -789,7 +814,7 @@ public class DownloadDialog extends DialogFragment
|
||||
// try take (or steal) the file
|
||||
storageNew = new StoredFileHelper(context, mainStorage.getUri(),
|
||||
targetFile, mainStorage.getTag());
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "Failed to take (or steal) the file in "
|
||||
+ targetFile.toString());
|
||||
storageNew = null;
|
||||
@@ -827,18 +852,18 @@ public class DownloadDialog extends DialogFragment
|
||||
if (storage.length() > 0) {
|
||||
storage.truncate();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
|
||||
showFailedDialog(R.string.overwrite_failed);
|
||||
return;
|
||||
}
|
||||
|
||||
Stream selectedStream;
|
||||
final Stream selectedStream;
|
||||
Stream secondaryStream = null;
|
||||
char kind;
|
||||
final char kind;
|
||||
int threads = threadsSeekBar.getProgress() + 1;
|
||||
String[] urls;
|
||||
MissionRecoveryInfo[] recoveryInfo;
|
||||
final String[] urls;
|
||||
final MissionRecoveryInfo[] recoveryInfo;
|
||||
String psName = null;
|
||||
String[] psArgs = null;
|
||||
long nearLength = 0;
|
||||
@@ -859,7 +884,7 @@ public class DownloadDialog extends DialogFragment
|
||||
kind = 'v';
|
||||
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
|
||||
SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
||||
final SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
||||
.getAllSecondary()
|
||||
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
||||
|
||||
@@ -873,7 +898,7 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
|
||||
psArgs = null;
|
||||
long videoSize = wrappedVideoStreams
|
||||
final long videoSize = wrappedVideoStreams
|
||||
.getSizeInBytes((VideoStream) selectedStream);
|
||||
|
||||
// set nearLength, only, if both sizes are fetched or known. This probably
|
||||
|
||||
@@ -230,7 +230,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
}
|
||||
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
||||
// Starting ReCaptcha Challenge Activity
|
||||
Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
||||
final Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
||||
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl());
|
||||
startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -30,6 +32,7 @@ import org.schabi.newpipe.settings.tabs.Tab;
|
||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.ScrollableTabLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -45,6 +48,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
|
||||
private boolean hasTabsChanged = false;
|
||||
|
||||
private boolean previousYoutubeRestrictedModeEnabled;
|
||||
private String youtubeRestrictedModeEnabledKey;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Fragment's LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -53,7 +59,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
tabsManager = TabsManager.getManager(activity);
|
||||
tabsManager.setSavedTabsListener(() -> {
|
||||
if (DEBUG) {
|
||||
@@ -66,6 +71,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
hasTabsChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||
previousYoutubeRestrictedModeEnabled =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,6 +92,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
||||
viewPager = rootView.findViewById(R.id.pager);
|
||||
|
||||
tabLayout.setTabIconTint(ColorStateList.valueOf(
|
||||
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
tabLayout.addOnTabSelectedListener(this);
|
||||
|
||||
@@ -92,7 +104,13 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (hasTabsChanged) {
|
||||
final boolean youtubeRestrictedModeEnabled =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
|
||||
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
|
||||
setupTabs();
|
||||
} else if (hasTabsChanged) {
|
||||
setupTabs();
|
||||
}
|
||||
}
|
||||
@@ -119,7 +137,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
}
|
||||
inflater.inflate(R.menu.main_fragment_menu, menu);
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
@@ -130,11 +148,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_search:
|
||||
try {
|
||||
NavigationHelper.openSearchFragment(
|
||||
getFragmentManager(),
|
||||
ServiceHelper.getSelectedServiceId(activity),
|
||||
"");
|
||||
} catch (Exception e) {
|
||||
NavigationHelper.openSearchFragment(getFM(),
|
||||
ServiceHelper.getSelectedServiceId(activity), "");
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
return true;
|
||||
@@ -221,7 +237,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
Fragment fragment = null;
|
||||
try {
|
||||
fragment = tab.getFragment(context);
|
||||
} catch (ExtractionException e) {
|
||||
} catch (final ExtractionException e) {
|
||||
throwable = e;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
if (dy > 0) {
|
||||
int pastVisibleItems = 0;
|
||||
int visibleItemCount;
|
||||
int totalItemCount;
|
||||
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
final int visibleItemCount;
|
||||
final int totalItemCount;
|
||||
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
|
||||
|
||||
visibleItemCount = layoutManager.getChildCount();
|
||||
totalItemCount = layoutManager.getItemCount();
|
||||
@@ -26,7 +26,7 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
|
||||
pastVisibleItems = ((LinearLayoutManager) layoutManager)
|
||||
.findFirstVisibleItemPosition();
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager)
|
||||
final int[] positions = ((StaggeredGridLayoutManager) layoutManager)
|
||||
.findFirstVisibleItemPositions(null);
|
||||
if (positions != null && positions.length > 0) {
|
||||
pastVisibleItems = positions[0];
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
class StackItem implements Serializable {
|
||||
private final int serviceId;
|
||||
private final String url;
|
||||
private String url;
|
||||
private String title;
|
||||
private PlayQueue playQueue;
|
||||
|
||||
StackItem(final int serviceId, final String url, final String title) {
|
||||
StackItem(final int serviceId, final String url,
|
||||
final String title, final PlayQueue playQueue) {
|
||||
this.serviceId = serviceId;
|
||||
this.url = url;
|
||||
this.title = title;
|
||||
this.playQueue = playQueue;
|
||||
}
|
||||
|
||||
public void setUrl(final String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void setPlayQueue(final PlayQueue queue) {
|
||||
this.playQueue = queue;
|
||||
}
|
||||
|
||||
public int getServiceId() {
|
||||
@@ -29,6 +42,10 @@ class StackItem implements Serializable {
|
||||
return url;
|
||||
}
|
||||
|
||||
public PlayQueue getPlayQueue() {
|
||||
return playQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getServiceId() + ":" + getUrl() + " > " + getTitle();
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
@@ -10,16 +11,20 @@ import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TabAdaptor extends FragmentPagerAdapter {
|
||||
public class TabAdapter extends FragmentPagerAdapter {
|
||||
private final List<Fragment> mFragmentList = new ArrayList<>();
|
||||
private final List<String> mFragmentTitleList = new ArrayList<>();
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
public TabAdaptor(final FragmentManager fm) {
|
||||
super(fm);
|
||||
public TabAdapter(final FragmentManager fm) {
|
||||
// if changed to BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT => crash if enqueueing stream in
|
||||
// the background and then clicking on it to open VideoDetailFragment:
|
||||
// "Cannot setMaxLifecycle for Fragment not attached to FragmentManager"
|
||||
super(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
||||
this.fragmentManager = fm;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment getItem(final int position) {
|
||||
return mFragmentList.get(position);
|
||||
@@ -50,14 +55,14 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
||||
}
|
||||
|
||||
public void updateItem(final String title, final Fragment fragment) {
|
||||
int index = mFragmentTitleList.indexOf(title);
|
||||
final int index = mFragmentTitleList.indexOf(title);
|
||||
if (index != -1) {
|
||||
updateItem(index, fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemPosition(final Object object) {
|
||||
public int getItemPosition(@NonNull final Object object) {
|
||||
if (mFragmentList.contains(object)) {
|
||||
return mFragmentList.indexOf(object);
|
||||
} else {
|
||||
@@ -82,7 +87,9 @@ public class TabAdaptor extends FragmentPagerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(final ViewGroup container, final int position, final Object object) {
|
||||
public void destroyItem(@NonNull final ViewGroup container,
|
||||
final int position,
|
||||
@NonNull final Object object) {
|
||||
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -136,7 +136,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
final RecyclerView.ViewHolder itemHolder =
|
||||
itemsList.findContainingViewHolder(focusedItem);
|
||||
return itemHolder.getAdapterPosition();
|
||||
} catch (NullPointerException e) {
|
||||
} catch (final NullPointerException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
}
|
||||
|
||||
itemsList.post(() -> {
|
||||
RecyclerView.ViewHolder focusedHolder =
|
||||
final RecyclerView.ViewHolder focusedHolder =
|
||||
itemsList.findViewHolderForAdapterPosition(position);
|
||||
|
||||
if (focusedHolder != null) {
|
||||
@@ -279,7 +279,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
@@ -294,7 +294,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
@@ -367,7 +367,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||
if (useAsFrontPage) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
||||
|
||||
@@ -30,7 +31,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
protected String url;
|
||||
|
||||
protected I currentInfo;
|
||||
protected String currentNextPageUrl;
|
||||
protected Page currentNextPage;
|
||||
protected Disposable currentWorker;
|
||||
|
||||
@Override
|
||||
@@ -78,7 +79,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentInfo);
|
||||
objectsToSave.add(currentNextPageUrl);
|
||||
objectsToSave.add(currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,7 +87,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentInfo = (I) savedObjects.poll();
|
||||
currentNextPageUrl = (String) savedObjects.poll();
|
||||
currentNextPage = (Page) savedObjects.poll();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -130,9 +131,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
.subscribe((@NonNull I result) -> {
|
||||
isLoading.set(false);
|
||||
currentInfo = result;
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
handleResult(result);
|
||||
}, (@NonNull Throwable throwable) -> onError(throwable));
|
||||
}, this::onError);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,11 +158,10 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doFinally(this::allowDownwardFocusScroll)
|
||||
.subscribe((@io.reactivex.annotations.NonNull
|
||||
ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||
isLoading.set(false);
|
||||
handleNextItems(InfoItemsPage);
|
||||
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
|
||||
}, (@NonNull Throwable throwable) -> {
|
||||
isLoading.set(false);
|
||||
onError(throwable);
|
||||
});
|
||||
@@ -182,7 +182,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
@Override
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
|
||||
showListFooter(hasMoreItems());
|
||||
@@ -190,7 +190,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
|
||||
@Override
|
||||
protected boolean hasMoreItems() {
|
||||
return !TextUtils.isEmpty(currentNextPageUrl);
|
||||
return Page.isValid(currentNextPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.jakewharton.rxbinding2.view.RxView;
|
||||
@@ -38,6 +39,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
@@ -45,6 +47,7 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -65,7 +68,8 @@ import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||
implements View.OnClickListener {
|
||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
private Disposable subscribeButtonMonitor;
|
||||
@@ -79,6 +83,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
private ImageView headerChannelBanner;
|
||||
private ImageView headerAvatarView;
|
||||
private TextView headerTitleView;
|
||||
private ImageView headerSubChannelAvatarView;
|
||||
private TextView headerSubChannelTitleView;
|
||||
private TextView headerSubscribersTextView;
|
||||
private Button headerSubscribeButton;
|
||||
private View playlistCtrl;
|
||||
@@ -92,7 +98,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
|
||||
public static ChannelFragment getInstance(final int serviceId, final String url,
|
||||
final String name) {
|
||||
ChannelFragment instance = new ChannelFragment();
|
||||
final ChannelFragment instance = new ChannelFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
@@ -156,7 +162,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
|
||||
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
|
||||
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||
|
||||
headerSubChannelAvatarView =
|
||||
headerRootLayout.findViewById(R.id.sub_channel_avatar_view);
|
||||
headerSubChannelTitleView =
|
||||
headerRootLayout.findViewById(R.id.sub_channel_title_view);
|
||||
|
||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||
@@ -165,6 +174,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
return headerRootLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
headerSubChannelTitleView.setOnClickListener(this);
|
||||
headerSubChannelAvatarView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -172,7 +189,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (useAsFrontPage && supportActionBar != null) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
} else {
|
||||
@@ -189,7 +206,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
private void openRssFeed() {
|
||||
final ChannelInfo info = currentInfo;
|
||||
if (info != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getFeedUrl()));
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -328,7 +345,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No subscription to this channel!");
|
||||
}
|
||||
SubscriptionEntity channel = new SubscriptionEntity();
|
||||
final SubscriptionEntity channel = new SubscriptionEntity();
|
||||
channel.setServiceId(info.getServiceId());
|
||||
channel.setUrl(info.getUrl());
|
||||
channel.setData(info.getName(),
|
||||
@@ -354,16 +371,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
+ "isSubscribed = [" + isSubscribed + "]");
|
||||
}
|
||||
|
||||
boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
||||
int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||
int textDuration = isButtonVisible ? 200 : 0;
|
||||
final boolean isButtonVisible = headerSubscribeButton.getVisibility() == View.VISIBLE;
|
||||
final int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||
final int textDuration = isButtonVisible ? 200 : 0;
|
||||
|
||||
int subscribeBackground = ContextCompat
|
||||
.getColor(activity, R.color.subscribe_background_color);
|
||||
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||
int subscribedBackground = ContextCompat
|
||||
final int subscribeBackground = ThemeHelper
|
||||
.resolveColorFromAttr(activity, R.attr.colorPrimary);
|
||||
final int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||
final int subscribedBackground = ContextCompat
|
||||
.getColor(activity, R.color.subscribed_background_color);
|
||||
int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
|
||||
final int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
|
||||
|
||||
if (!isSubscribed) {
|
||||
headerSubscribeButton.setText(R.string.subscribe_button_title);
|
||||
@@ -386,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -394,6 +411,34 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnClick
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if (isLoading.get() || currentInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (v.getId()) {
|
||||
case R.id.sub_channel_avatar_view:
|
||||
case R.id.sub_channel_title_view:
|
||||
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
||||
currentInfo.getParentChannelUrl(),
|
||||
currentInfo.getParentChannelName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
} else if (DEBUG) {
|
||||
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -404,6 +449,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
|
||||
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
|
||||
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
|
||||
IMAGE_LOADER.cancelDisplayTask(headerSubChannelAvatarView);
|
||||
animateView(headerSubscribeButton, false, 100);
|
||||
}
|
||||
|
||||
@@ -416,6 +462,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
|
||||
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), headerSubChannelAvatarView,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
|
||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||
if (result.getSubscriberCount() >= 0) {
|
||||
@@ -425,19 +473,30 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
|
||||
headerSubChannelTitleView.setText(String.format(
|
||||
getString(R.string.channel_created_by),
|
||||
currentInfo.getParentChannelName())
|
||||
);
|
||||
headerSubChannelTitleView.setVisibility(View.VISIBLE);
|
||||
headerSubChannelAvatarView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
headerSubChannelTitleView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (menuRssButton != null) {
|
||||
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||
}
|
||||
|
||||
playlistCtrl.setVisibility(View.VISIBLE);
|
||||
|
||||
List<Throwable> errors = new ArrayList<>(result.getErrors());
|
||||
final List<Throwable> errors = new ArrayList<>(result.getErrors());
|
||||
if (!errors.isEmpty()) {
|
||||
|
||||
// handling ContentNotSupportedException not to show the error but an appropriate string
|
||||
// so that crashes won't be sent uselessly and the user will understand what happened
|
||||
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
|
||||
Throwable throwable = it.next();
|
||||
final Throwable throwable = it.next();
|
||||
if (throwable instanceof ContentNotSupportedException) {
|
||||
showContentNotSupported();
|
||||
it.remove();
|
||||
@@ -460,7 +519,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
monitorSubscription(result);
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
|
||||
.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view -> NavigationHelper
|
||||
.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view -> NavigationHelper
|
||||
@@ -490,13 +549,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
final List<StreamInfoItem> streamItems = new ArrayList<>();
|
||||
for (InfoItem i : infoListAdapter.getItemsList()) {
|
||||
for (final InfoItem i : infoListAdapter.getItemsList()) {
|
||||
if (i instanceof StreamInfoItem) {
|
||||
streamItems.add((StreamInfoItem) i);
|
||||
}
|
||||
}
|
||||
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(), streamItems, index);
|
||||
currentInfo.getNextPage(), streamItems, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -522,7 +581,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
return true;
|
||||
}
|
||||
|
||||
int errorId = exception instanceof ExtractionException
|
||||
final int errorId = exception instanceof ExtractionException
|
||||
? R.string.parsing_error : R.string.general_error;
|
||||
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
|
||||
|
||||
@@ -26,11 +26,9 @@ import io.reactivex.disposables.CompositeDisposable;
|
||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static CommentsFragment getInstance(final int serviceId, final String url,
|
||||
final String name) {
|
||||
CommentsFragment instance = new CommentsFragment();
|
||||
final CommentsFragment instance = new CommentsFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
@@ -39,12 +37,6 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -71,7 +63,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,7 +84,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
public void handleResult(@NonNull final CommentsInfo result) {
|
||||
super.handleResult(result);
|
||||
|
||||
AnimationUtils.slideUp(getView(), 120, 150, 0.06f);
|
||||
AnimationUtils.slideUp(requireView(), 120, 150, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
||||
|
||||
@@ -44,8 +44,8 @@ public class DefaultKioskFragment extends KioskFragment {
|
||||
name = kioskTranslatedName;
|
||||
|
||||
currentInfo = null;
|
||||
currentNextPageUrl = null;
|
||||
} catch (ExtractionException e) {
|
||||
currentNextPage = null;
|
||||
} catch (final ExtractionException e) {
|
||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
||||
"Loading default kiosk from selected service", 0);
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
|
||||
public static KioskFragment getInstance(final int serviceId, final String kioskId)
|
||||
throws ExtractionException {
|
||||
KioskFragment instance = new KioskFragment();
|
||||
StreamingService service = NewPipe.getService(serviceId);
|
||||
ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
|
||||
final KioskFragment instance = new KioskFragment();
|
||||
final StreamingService service = NewPipe.getService(serviceId);
|
||||
final ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
|
||||
.getListLinkHandlerFactoryByType(kioskId);
|
||||
instance.setInitialData(serviceId,
|
||||
kioskLinkHandlerFactory.fromId(kioskId).getUrl(), kioskId);
|
||||
@@ -101,7 +101,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
if (useAsFrontPage && isVisibleToUser && activity != null) {
|
||||
try {
|
||||
setTitle(kioskTranslatedName);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
onUnrecoverableError(e, UserAction.UI_ERROR,
|
||||
"none",
|
||||
"none", R.string.app_ui_crash);
|
||||
@@ -132,7 +132,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null && useAsFrontPage) {
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
|
||||
@Override
|
||||
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -85,7 +85,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
|
||||
public static PlaylistFragment getInstance(final int serviceId, final String url,
|
||||
final String name) {
|
||||
PlaylistFragment instance = new PlaylistFragment();
|
||||
final PlaylistFragment instance = new PlaylistFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
@@ -229,7 +229,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -286,11 +286,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
||||
headerUploaderLayout.setOnClickListener(v -> {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(),
|
||||
result.getServiceId(),
|
||||
result.getUploaderUrl(),
|
||||
result.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
||||
result.getUploaderUrl(), result.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
});
|
||||
@@ -318,7 +316,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
.subscribe(getPlaylistBookmarkSubscriber());
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
@@ -341,7 +339,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
|
||||
private PlayQueue getPlayQueue(final int index) {
|
||||
final List<StreamInfoItem> infoItems = new ArrayList<>();
|
||||
for (InfoItem i : infoListAdapter.getItemsList()) {
|
||||
for (final InfoItem i : infoListAdapter.getItemsList()) {
|
||||
if (i instanceof StreamInfoItem) {
|
||||
infoItems.add((StreamInfoItem) i);
|
||||
}
|
||||
@@ -349,7 +347,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
return new PlaylistPlayQueue(
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(),
|
||||
currentInfo.getNextPage(),
|
||||
infoItems,
|
||||
index
|
||||
);
|
||||
@@ -375,7 +373,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
return true;
|
||||
}
|
||||
|
||||
int errorId = exception instanceof ExtractionException
|
||||
final int errorId = exception instanceof ExtractionException
|
||||
? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
|
||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
||||
|
||||
@@ -5,8 +5,10 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
@@ -37,18 +39,20 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExceptionUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
@@ -75,7 +79,7 @@ import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovement
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
|
||||
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage<?>>
|
||||
implements BackPressable {
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Search
|
||||
@@ -118,14 +122,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@State
|
||||
String lastSearchedString;
|
||||
|
||||
@State
|
||||
String searchSuggestion;
|
||||
|
||||
@State
|
||||
boolean isCorrectedSearch;
|
||||
|
||||
@State
|
||||
boolean wasSearchFocused = false;
|
||||
|
||||
private Map<Integer, String> menuItemToFilterName;
|
||||
private StreamingService service;
|
||||
private String currentPageUrl;
|
||||
private String nextPageUrl;
|
||||
private String contentCountry;
|
||||
private Page nextPage;
|
||||
private boolean isSuggestionsEnabled = true;
|
||||
|
||||
private Disposable searchDisposable;
|
||||
@@ -143,7 +151,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
private EditText searchEditText;
|
||||
private View searchClear;
|
||||
|
||||
private TextView correctSuggestion;
|
||||
|
||||
private View suggestionsPanel;
|
||||
private boolean suggestionsPanelVisible = false;
|
||||
private RecyclerView suggestionsRecyclerView;
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -151,7 +162,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
private TextWatcher textWatcher;
|
||||
|
||||
public static SearchFragment getInstance(final int serviceId, final String searchString) {
|
||||
SearchFragment searchFragment = new SearchFragment();
|
||||
final SearchFragment searchFragment = new SearchFragment();
|
||||
searchFragment.setQuery(serviceId, searchString, new String[0], "");
|
||||
|
||||
if (!TextUtils.isEmpty(searchString)) {
|
||||
@@ -177,8 +188,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
super.onAttach(context);
|
||||
|
||||
suggestionListAdapter = new SuggestionListAdapter(activity);
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
boolean isSearchHistoryEnabled = preferences
|
||||
final SharedPreferences preferences
|
||||
= PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
final boolean isSearchHistoryEnabled = preferences
|
||||
.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||
|
||||
@@ -189,11 +201,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
final SharedPreferences preferences
|
||||
= PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
isSuggestionsEnabled = preferences
|
||||
.getBoolean(getString(R.string.show_search_suggestions_key), true);
|
||||
contentCountry = preferences.getString(getString(R.string.content_country_key),
|
||||
getString(R.string.default_localization_key));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,9 +232,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
if (suggestionDisposable != null) {
|
||||
suggestionDisposable.dispose();
|
||||
}
|
||||
if (disposables != null) {
|
||||
disposables.clear();
|
||||
}
|
||||
disposables.clear();
|
||||
hideKeyboardSearch();
|
||||
}
|
||||
|
||||
@@ -236,9 +245,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
try {
|
||||
service = NewPipe.getService(serviceId);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportError(getActivity(), e, getActivity().getClass(),
|
||||
getActivity().findViewById(android.R.id.content),
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportError(getActivity(), e, requireActivity().getClass(),
|
||||
requireActivity().findViewById(android.R.id.content),
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"",
|
||||
"", R.string.general_error));
|
||||
@@ -257,6 +266,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||
initSuggestionObserver();
|
||||
}
|
||||
@@ -289,26 +300,20 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
if (suggestionDisposable != null) {
|
||||
suggestionDisposable.dispose();
|
||||
}
|
||||
if (disposables != null) {
|
||||
disposables.clear();
|
||||
}
|
||||
disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
|
||||
switch (requestCode) {
|
||||
case ReCaptchaActivity.RECAPTCHA_REQUEST:
|
||||
if (resultCode == Activity.RESULT_OK
|
||||
&& !TextUtils.isEmpty(searchString)) {
|
||||
search(searchString, contentFilter, sortFilter);
|
||||
} else {
|
||||
Log.e(TAG, "ReCaptcha failed");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
|
||||
break;
|
||||
if (requestCode == ReCaptchaActivity.RECAPTCHA_REQUEST) {
|
||||
if (resultCode == Activity.RESULT_OK
|
||||
&& !TextUtils.isEmpty(searchString)) {
|
||||
search(searchString, contentFilter, sortFilter);
|
||||
} else {
|
||||
Log.e(TAG, "ReCaptcha failed");
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +331,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull final RecyclerView recyclerView,
|
||||
@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
return getSuggestionMovementFlags(recyclerView, viewHolder);
|
||||
return getSuggestionMovementFlags(viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -338,13 +343,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int i) {
|
||||
onSuggestionItemSwiped(viewHolder, i);
|
||||
onSuggestionItemSwiped(viewHolder);
|
||||
}
|
||||
}).attachToRecyclerView(suggestionsRecyclerView);
|
||||
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||
|
||||
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -354,15 +361,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@Override
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentPageUrl);
|
||||
objectsToSave.add(nextPageUrl);
|
||||
objectsToSave.add(nextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentPageUrl = (String) savedObjects.poll();
|
||||
nextPageUrl = (String) savedObjects.poll();
|
||||
nextPage = (Page) savedObjects.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -401,7 +406,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(false);
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
@@ -412,16 +417,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
int itemId = 0;
|
||||
boolean isFirstItem = true;
|
||||
final Context c = getContext();
|
||||
for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||
for (final String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||
if (filter.equals("music_songs")) {
|
||||
MenuItem musicItem = menu.add(2,
|
||||
final MenuItem musicItem = menu.add(2,
|
||||
itemId++,
|
||||
0,
|
||||
"YouTube Music");
|
||||
musicItem.setEnabled(false);
|
||||
}
|
||||
menuItemToFilterName.put(itemId, filter);
|
||||
MenuItem item = menu.add(1,
|
||||
final MenuItem item = menu.add(1,
|
||||
itemId++,
|
||||
0,
|
||||
ServiceHelper.getTranslatedFilterString(filter, c));
|
||||
@@ -437,7 +442,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
List<String> cf = new ArrayList<>(1);
|
||||
final List<String> cf = new ArrayList<>(1);
|
||||
cf.add(menuItemToFilterName.get(item.getItemId()));
|
||||
changeContentFilter(item, cf);
|
||||
|
||||
@@ -446,7 +451,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
private void restoreFilterChecked(final Menu menu, final int itemId) {
|
||||
if (itemId != -1) {
|
||||
MenuItem item = menu.findItem(itemId);
|
||||
final MenuItem item = menu.findItem(itemId);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
@@ -470,16 +475,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||
searchToolbarContainer.setTranslationX(100);
|
||||
searchToolbarContainer.setAlpha(0f);
|
||||
searchToolbarContainer.setAlpha(0.0f);
|
||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||
searchToolbarContainer.animate()
|
||||
.translationX(0)
|
||||
.alpha(1f)
|
||||
.alpha(1.0f)
|
||||
.setDuration(200)
|
||||
.setInterpolator(new DecelerateInterpolator()).start();
|
||||
} else {
|
||||
searchToolbarContainer.setTranslationX(0);
|
||||
searchToolbarContainer.setAlpha(1f);
|
||||
searchToolbarContainer.setAlpha(1.0f);
|
||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
@@ -493,10 +498,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
}
|
||||
if (TextUtils.isEmpty(searchEditText.getText())) {
|
||||
NavigationHelper.gotoMainFragment(getFragmentManager());
|
||||
NavigationHelper.gotoMainFragment(getFM());
|
||||
return;
|
||||
}
|
||||
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
|
||||
searchEditText.setText("");
|
||||
suggestionListAdapter.setItems(new ArrayList<>());
|
||||
showKeyboardSearch();
|
||||
@@ -511,7 +518,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
if (AndroidTvUtils.isTv(getContext())) {
|
||||
if (DeviceUtils.isTv(getContext())) {
|
||||
showKeyboardSearch();
|
||||
}
|
||||
});
|
||||
@@ -554,15 +561,17 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
textWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start,
|
||||
final int count, final int after) { }
|
||||
final int count, final int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, final int start,
|
||||
final int before, final int count) { }
|
||||
final int before, final int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
String newText = searchEditText.getText().toString();
|
||||
final String newText = searchEditText.getText().toString();
|
||||
suggestionPublisher.onNext(newText);
|
||||
}
|
||||
};
|
||||
@@ -609,6 +618,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showSuggestionsPanel() called");
|
||||
}
|
||||
suggestionsPanelVisible = true;
|
||||
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200);
|
||||
}
|
||||
|
||||
@@ -616,6 +626,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hideSuggestionsPanel() called");
|
||||
}
|
||||
suggestionsPanelVisible = false;
|
||||
animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200);
|
||||
}
|
||||
|
||||
@@ -628,7 +639,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
}
|
||||
|
||||
if (searchEditText.requestFocus()) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
@@ -642,7 +653,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
return;
|
||||
}
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) activity
|
||||
final InputMethodManager imm = (InputMethodManager) activity
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||
@@ -651,8 +662,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
}
|
||||
|
||||
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
||||
if (activity == null || historyRecordManager == null || suggestionPublisher == null
|
||||
|| searchEditText == null || disposables == null) {
|
||||
if (activity == null || historyRecordManager == null || searchEditText == null) {
|
||||
return;
|
||||
}
|
||||
final String query = item.query;
|
||||
@@ -677,7 +687,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
if (suggestionsPanel.getVisibility() == View.VISIBLE
|
||||
if (suggestionsPanelVisible
|
||||
&& infoListAdapter.getItemsList().size() > 0
|
||||
&& !isLoading.get()) {
|
||||
hideSuggestionsPanel();
|
||||
@@ -688,10 +698,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
return false;
|
||||
}
|
||||
|
||||
public void giveSearchEditTextFocus() {
|
||||
showKeyboardSearch();
|
||||
}
|
||||
|
||||
private void initSuggestionObserver() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "initSuggestionObserver() called");
|
||||
@@ -713,8 +719,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
.getRelatedSearches(query, 3, 25);
|
||||
final Observable<List<SuggestionItem>> local = flowable.toObservable()
|
||||
.map(searchHistoryEntries -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (SearchHistoryEntry entry : searchHistoryEntries) {
|
||||
final List<SuggestionItem> result = new ArrayList<>();
|
||||
for (final SearchHistoryEntry entry : searchHistoryEntries) {
|
||||
result.add(new SuggestionItem(true, entry.getSearch()));
|
||||
}
|
||||
return result;
|
||||
@@ -728,17 +734,24 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
final Observable<List<SuggestionItem>> network = ExtractorHelper
|
||||
.suggestionsFor(serviceId, query)
|
||||
.onErrorReturn(throwable -> {
|
||||
if (!ExceptionUtils.isNetworkRelated(throwable)) {
|
||||
showSnackBarError(throwable, UserAction.GET_SUGGESTIONS,
|
||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||
}
|
||||
return new ArrayList<>();
|
||||
})
|
||||
.toObservable()
|
||||
.map(strings -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
for (String entry : strings) {
|
||||
final List<SuggestionItem> result = new ArrayList<>();
|
||||
for (final String entry : strings) {
|
||||
result.add(new SuggestionItem(false, entry));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
return Observable.zip(local, network, (localResult, networkResult) -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
final List<SuggestionItem> result = new ArrayList<>();
|
||||
if (localResult.size() > 0) {
|
||||
result.addAll(localResult);
|
||||
}
|
||||
@@ -747,7 +760,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
final Iterator<SuggestionItem> iterator = networkResult.iterator();
|
||||
while (iterator.hasNext() && localResult.size() > 0) {
|
||||
final SuggestionItem next = iterator.next();
|
||||
for (SuggestionItem item : localResult) {
|
||||
for (final SuggestionItem item : localResult) {
|
||||
if (item.query.equals(next.query)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
@@ -777,58 +790,58 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
// no-op
|
||||
}
|
||||
|
||||
private void search(final String ss, final String[] cf, final String sf) {
|
||||
private void search(final String theSearchString,
|
||||
final String[] theContentFilter,
|
||||
final String theSortFilter) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "search() called with: query = [" + ss + "]");
|
||||
Log.d(TAG, "search() called with: query = [" + theSearchString + "]");
|
||||
}
|
||||
if (ss.isEmpty()) {
|
||||
if (theSearchString.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final StreamingService streamingService = NewPipe.getServiceByUrl(ss);
|
||||
final StreamingService streamingService = NewPipe.getServiceByUrl(theSearchString);
|
||||
if (streamingService != null) {
|
||||
showLoading();
|
||||
disposables.add(Observable
|
||||
.fromCallable(() ->
|
||||
NavigationHelper.getIntentByLink(activity, streamingService, ss))
|
||||
.fromCallable(() -> NavigationHelper.getIntentByLink(activity,
|
||||
streamingService, theSearchString))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(intent -> {
|
||||
getFragmentManager().popBackStackImmediate();
|
||||
getFM().popBackStackImmediate();
|
||||
activity.startActivity(intent);
|
||||
}, throwable ->
|
||||
showError(getString(R.string.url_not_supported_toast), false)));
|
||||
showError(getString(R.string.unsupported_url), false)));
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} catch (final Exception ignored) {
|
||||
// Exception occurred, it's not a url
|
||||
}
|
||||
|
||||
lastSearchedString = this.searchString;
|
||||
this.searchString = ss;
|
||||
this.searchString = theSearchString;
|
||||
infoListAdapter.clearStreamItemList();
|
||||
hideSuggestionsPanel();
|
||||
hideKeyboardSearch();
|
||||
|
||||
historyRecordManager.onSearched(serviceId, ss)
|
||||
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
ignored -> {
|
||||
},
|
||||
error -> showSnackBarError(error, UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId), ss, 0)
|
||||
);
|
||||
suggestionPublisher.onNext(ss);
|
||||
NewPipe.getNameOfService(serviceId), theSearchString, 0)
|
||||
));
|
||||
suggestionPublisher.onNext(theSearchString);
|
||||
startLoading(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startLoading(final boolean forceLoad) {
|
||||
super.startLoading(forceLoad);
|
||||
if (disposables != null) {
|
||||
disposables.clear();
|
||||
}
|
||||
disposables.clear();
|
||||
if (searchDisposable != null) {
|
||||
searchDisposable.dispose();
|
||||
}
|
||||
@@ -845,7 +858,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
|
||||
if (!Page.isValid(nextPage)) {
|
||||
return;
|
||||
}
|
||||
isLoading.set(true);
|
||||
@@ -858,7 +871,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
searchString,
|
||||
asList(contentFilter),
|
||||
sortFilter,
|
||||
nextPageUrl)
|
||||
nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||
@@ -867,8 +880,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
protected boolean hasMoreItems() {
|
||||
// TODO: No way to tell if search has more items in the moment
|
||||
return true;
|
||||
return Page.isValid(nextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -881,22 +893,25 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void changeContentFilter(final MenuItem item, final List<String> cf) {
|
||||
this.filterItemCheckedId = item.getItemId();
|
||||
private void changeContentFilter(final MenuItem item, final List<String> theContentFilter) {
|
||||
filterItemCheckedId = item.getItemId();
|
||||
item.setChecked(true);
|
||||
|
||||
this.contentFilter = new String[]{cf.get(0)};
|
||||
contentFilter = new String[]{theContentFilter.get(0)};
|
||||
|
||||
if (!TextUtils.isEmpty(searchString)) {
|
||||
search(searchString, this.contentFilter, sortFilter);
|
||||
search(searchString, contentFilter, sortFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuery(final int sid, final String ss, final String[] cf, final String sf) {
|
||||
this.serviceId = sid;
|
||||
this.searchString = searchString;
|
||||
this.contentFilter = cf;
|
||||
this.sortFilter = sf;
|
||||
private void setQuery(final int theServiceId,
|
||||
final String theSearchString,
|
||||
final String[] theContentFilter,
|
||||
final String theSortFilter) {
|
||||
serviceId = theServiceId;
|
||||
searchString = theSearchString;
|
||||
contentFilter = theContentFilter;
|
||||
sortFilter = theSortFilter;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -910,7 +925,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
suggestionsRecyclerView.smoothScrollToPosition(0);
|
||||
suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions));
|
||||
|
||||
if (errorPanelRoot.getVisibility() == View.VISIBLE) {
|
||||
if (suggestionsPanelVisible && errorPanelRoot.getVisibility() == View.VISIBLE) {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
@@ -923,7 +938,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
return;
|
||||
}
|
||||
|
||||
int errorId = exception instanceof ParsingException
|
||||
final int errorId = exception instanceof ParsingException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
|
||||
@@ -961,9 +976,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||
}
|
||||
|
||||
searchSuggestion = result.getSearchSuggestion();
|
||||
isCorrectedSearch = result.isCorrectedSearch();
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
lastSearchedString = searchString;
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
currentPageUrl = result.getUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (!result.getRelatedItems().isEmpty()) {
|
||||
@@ -978,17 +997,48 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
super.handleResult(result);
|
||||
}
|
||||
|
||||
private void handleSearchSuggestion() {
|
||||
if (TextUtils.isEmpty(searchSuggestion)) {
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
} else {
|
||||
final String helperText = getString(isCorrectedSearch
|
||||
? R.string.search_showing_result_for
|
||||
: R.string.did_you_mean);
|
||||
|
||||
final String highlightedSearchSuggestion =
|
||||
"<b><i>" + Html.escapeHtml(searchSuggestion) + "</i></b>";
|
||||
final String text = String.format(helperText, highlightedSearchSuggestion);
|
||||
correctSuggestion.setText(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY));
|
||||
|
||||
correctSuggestion.setOnClickListener(v -> {
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
search(searchSuggestion, contentFilter, sortFilter);
|
||||
searchEditText.setText(searchSuggestion);
|
||||
});
|
||||
|
||||
correctSuggestion.setOnLongClickListener(v -> {
|
||||
searchEditText.setText(searchSuggestion);
|
||||
searchEditText.setSelection(searchSuggestion.length());
|
||||
showKeyboardSearch();
|
||||
return true;
|
||||
});
|
||||
|
||||
correctSuggestion.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage<?> result) {
|
||||
showListFooter(false);
|
||||
currentPageUrl = result.getNextPageUrl();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
||||
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||
+ "pageIds: " + nextPage.getIds() + ", "
|
||||
+ "pageCookies: " + nextPage.getCookies(), 0);
|
||||
}
|
||||
super.handleNextItems(result);
|
||||
}
|
||||
@@ -1003,7 +1053,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
infoListAdapter.clearStreamItemList();
|
||||
showEmptyState();
|
||||
} else {
|
||||
int errorId = exception instanceof ParsingException
|
||||
final int errorId = exception instanceof ParsingException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.SEARCHED,
|
||||
@@ -1017,16 +1067,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
// Suggestion item touch helper
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
|
||||
@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
||||
return item.fromHistory ? makeMovementFlags(0,
|
||||
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
||||
}
|
||||
|
||||
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||
final int i) {
|
||||
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final String query = suggestionListAdapter.getItem(position).query;
|
||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
||||
|
||||
@@ -33,7 +33,7 @@ public class SuggestionListAdapter
|
||||
this.items.addAll(items);
|
||||
} else {
|
||||
// remove history items if history is disabled
|
||||
for (SuggestionItem item : items) {
|
||||
for (final SuggestionItem item : items) {
|
||||
if (!item.fromHistory) {
|
||||
this.items.add(item);
|
||||
}
|
||||
@@ -117,14 +117,14 @@ public class SuggestionListAdapter
|
||||
queryView = rootView.findViewById(R.id.suggestion_search);
|
||||
insertView = rootView.findViewById(R.id.suggestion_insert);
|
||||
|
||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history);
|
||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
|
||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_history);
|
||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_search);
|
||||
}
|
||||
|
||||
private static int resolveResourceIdFromAttr(final Context context,
|
||||
@AttrRes final int attr) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
int attributeResourceId = a.getResourceId(0, 0);
|
||||
final TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
final int attributeResourceId = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
return attributeResourceId;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@ package org.schabi.newpipe.fragments.list.videos;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -40,22 +39,14 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private View headerRootLayout;
|
||||
private Switch aSwitch;
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
private Switch autoplaySwitch;
|
||||
|
||||
public static RelatedVideosFragment getInstance(final StreamInfo info) {
|
||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
final RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
instance.setInitialData(info);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -81,22 +72,18 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
}
|
||||
|
||||
protected View getListHeader() {
|
||||
if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
|
||||
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) {
|
||||
headerRootLayout = activity.getLayoutInflater()
|
||||
.inflate(R.layout.related_streams_header, itemsList, false);
|
||||
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||
autoplaySwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
aSwitch.setChecked(autoplay);
|
||||
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton compoundButton,
|
||||
final boolean b) {
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
|
||||
.putBoolean(getString(R.string.auto_queue_key), b).apply();
|
||||
}
|
||||
});
|
||||
final SharedPreferences pref = PreferenceManager
|
||||
.getDefaultSharedPreferences(requireContext());
|
||||
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
autoplaySwitch.setChecked(autoplay);
|
||||
autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
|
||||
.putBoolean(getString(R.string.auto_queue_key), b).apply());
|
||||
return headerRootLayout;
|
||||
} else {
|
||||
return null;
|
||||
@@ -105,7 +92,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
||||
return Single.fromCallable(ListExtractor.InfoItemsPage::emptyPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -179,12 +166,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
|
||||
@Override
|
||||
public void setTitle(final String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void setInitialData(final StreamInfo info) {
|
||||
@@ -204,7 +189,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
if (savedState != null) {
|
||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
if (serializable instanceof RelatedStreamInfo) {
|
||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||
}
|
||||
@@ -214,10 +199,11 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||
final String s) {
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if (null != aSwitch) {
|
||||
aSwitch.setChecked(autoplay);
|
||||
final SharedPreferences pref =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if (autoplaySwitch != null) {
|
||||
autoplaySwitch.setChecked(autoplay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ public class InfoItemBuilder {
|
||||
public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem,
|
||||
final HistoryRecordManager historyRecordManager,
|
||||
final boolean useMiniVariant) {
|
||||
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
||||
final InfoItemHolder holder
|
||||
= holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
||||
holder.updateFromItem(infoItem, historyRecordManager);
|
||||
return holder.itemView;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ public class InfoItemDialog {
|
||||
final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
|
||||
bannerView.setSelected(true);
|
||||
|
||||
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
||||
final TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
||||
titleView.setText(title);
|
||||
|
||||
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
||||
final TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
||||
if (additionalDetail != null) {
|
||||
detailsView.setText(additionalDetail);
|
||||
detailsView.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -123,7 +123,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
+ infoItemList.size() + ", data.size() = " + data.size());
|
||||
}
|
||||
|
||||
int offsetStart = sizeConsideringHeaderOffset();
|
||||
final int offsetStart = sizeConsideringHeaderOffset();
|
||||
infoItemList.addAll(data);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -135,7 +135,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
final int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -160,7 +160,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
+ infoItemList.size() + ", thread = " + Thread.currentThread());
|
||||
}
|
||||
|
||||
int positionInserted = sizeConsideringHeaderOffset();
|
||||
final int positionInserted = sizeConsideringHeaderOffset();
|
||||
infoItemList.add(data);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -172,7 +172,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
notifyItemInserted(positionInserted);
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
final int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(positionInserted, footerNow);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -191,7 +191,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
}
|
||||
|
||||
public void setHeader(final View header) {
|
||||
boolean changed = header != this.header;
|
||||
final boolean changed = header != this.header;
|
||||
this.header = header;
|
||||
if (changed) {
|
||||
notifyDataSetChanged();
|
||||
@@ -219,7 +219,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
}
|
||||
|
||||
private int sizeConsideringHeaderOffset() {
|
||||
int i = infoItemList.size() + (header != null ? 1 : 0);
|
||||
final int i = infoItemList.size() + (header != null ? 1 : 0);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "sizeConsideringHeaderOffset() called → " + i);
|
||||
}
|
||||
@@ -347,7 +347,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
||||
@NonNull final List<Object> payloads) {
|
||||
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
|
||||
for (Object payload : payloads) {
|
||||
for (final Object payload : payloads) {
|
||||
if (payload instanceof StreamStateEntity) {
|
||||
((InfoItemHolder) holder).updateState(infoItemList
|
||||
.get(header == null ? position : position - 1), recordManager);
|
||||
|
||||
@@ -56,8 +56,8 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
|
||||
String details = super.getDetailLine(item);
|
||||
|
||||
if (item.getStreamCount() >= 0) {
|
||||
String formattedVideoAmount = Localization.localizeStreamCount(itemBuilder.getContext(),
|
||||
item.getStreamCount());
|
||||
final String formattedVideoAmount = Localization.localizeStreamCount(
|
||||
itemBuilder.getContext(), item.getStreamCount());
|
||||
|
||||
if (!details.isEmpty()) {
|
||||
details += " • " + formattedVideoAmount;
|
||||
|
||||
@@ -48,6 +48,6 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||
}
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.getAuthorName());
|
||||
itemTitleView.setText(item.getUploaderName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
|
||||
import org.jsoup.helper.StringUtil;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -34,7 +36,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
private static final int COMMENT_DEFAULT_LINES = 2;
|
||||
private static final int COMMENT_EXPANDED_LINES = 1000;
|
||||
private static final Pattern PATTERN = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
|
||||
private final String downloadThumbnailKey;
|
||||
private final int commentHorizontalPadding;
|
||||
private final int commentVerticalPadding;
|
||||
|
||||
private SharedPreferences preferences = null;
|
||||
private final RelativeLayout itemRoot;
|
||||
public final CircleImageView itemThumbnailView;
|
||||
private final TextView itemContentView;
|
||||
private final TextView itemLikesCountView;
|
||||
@@ -48,9 +55,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
@Override
|
||||
public String transformUrl(final Matcher match, final String url) {
|
||||
int timestamp = 0;
|
||||
String hours = match.group(1);
|
||||
String minutes = match.group(2);
|
||||
String seconds = match.group(3);
|
||||
final String hours = match.group(1);
|
||||
final String minutes = match.group(2);
|
||||
final String seconds = match.group(3);
|
||||
if (hours != null) {
|
||||
timestamp += (Integer.parseInt(hours.replace(":", "")) * 3600);
|
||||
}
|
||||
@@ -68,11 +75,20 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
final ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
itemRoot = itemView.findViewById(R.id.itemRoot);
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
|
||||
itemDislikesCountView = itemView.findViewById(R.id.detail_thumbs_down_count_view);
|
||||
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
|
||||
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
|
||||
|
||||
downloadThumbnailKey = infoItemBuilder.getContext().
|
||||
getString(R.string.download_thumbnail_key);
|
||||
|
||||
commentHorizontalPadding = (int) infoItemBuilder.getContext()
|
||||
.getResources().getDimension(R.dimen.comments_horizontal_padding);
|
||||
commentVerticalPadding = (int) infoItemBuilder.getContext()
|
||||
.getResources().getDimension(R.dimen.comments_vertical_padding);
|
||||
}
|
||||
|
||||
public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
|
||||
@@ -88,11 +104,24 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext());
|
||||
|
||||
itemBuilder.getImageLoader()
|
||||
.displayImage(item.getAuthorThumbnail(),
|
||||
.displayImage(item.getUploaderAvatarUrl(),
|
||||
itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
if (preferences.getBoolean(downloadThumbnailKey, true)) {
|
||||
itemThumbnailView.setVisibility(View.VISIBLE);
|
||||
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
|
||||
commentVerticalPadding, commentVerticalPadding);
|
||||
} else {
|
||||
itemThumbnailView.setVisibility(View.GONE);
|
||||
itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
|
||||
commentHorizontalPadding, commentVerticalPadding);
|
||||
}
|
||||
|
||||
|
||||
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
|
||||
|
||||
streamUrl = item.getUrl();
|
||||
@@ -114,10 +143,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemLikesCountView.setText("-");
|
||||
}
|
||||
|
||||
if (item.getPublishedTime() != null) {
|
||||
itemPublishedTime.setText(Localization.relativeTime(item.getPublishedTime().date()));
|
||||
if (item.getUploadDate() != null) {
|
||||
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate().date()));
|
||||
} else {
|
||||
itemPublishedTime.setText(item.getTextualPublishedTime());
|
||||
itemPublishedTime.setText(item.getTextualUploadDate());
|
||||
}
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
@@ -129,21 +158,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
|
||||
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (!AndroidTvUtils.isTv(itemBuilder.getContext())) {
|
||||
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
|
||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText));
|
||||
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
if (DeviceUtils.isTv(itemBuilder.getContext())) {
|
||||
openCommentAuthor(item);
|
||||
} else {
|
||||
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void openCommentAuthor(final CommentsInfoItem item) {
|
||||
if (StringUtil.isBlank(item.getAuthorEndpoint())) {
|
||||
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -151,9 +176,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
NavigationHelper.openChannelFragment(
|
||||
activity.getSupportFragmentManager(),
|
||||
item.getServiceId(),
|
||||
item.getAuthorEndpoint(),
|
||||
item.getAuthorName());
|
||||
} catch (Exception e) {
|
||||
item.getUploaderUrl(),
|
||||
item.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||
}
|
||||
}
|
||||
@@ -171,7 +196,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
return false;
|
||||
}
|
||||
|
||||
URLSpan[] urls = itemContentView.getUrls();
|
||||
final URLSpan[] urls = itemContentView.getUrls();
|
||||
|
||||
return urls != null && urls.length != 0;
|
||||
}
|
||||
@@ -188,12 +213,13 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
boolean hasEllipsis = false;
|
||||
|
||||
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
|
||||
int endOfLastLine = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
|
||||
final int endOfLastLine
|
||||
= itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1);
|
||||
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
|
||||
if (end == -1) {
|
||||
end = Math.max(endOfLastLine - 2, 0);
|
||||
}
|
||||
String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
||||
final String newVal = itemContentView.getText().subSequence(0, end) + " …";
|
||||
itemContentView.setText(newVal);
|
||||
hasEllipsis = true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -60,7 +60,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
|
||||
final StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
|
||||
.blockingGet()[0];
|
||||
if (state2 != null) {
|
||||
itemProgressView.setVisibility(View.VISIBLE);
|
||||
@@ -113,7 +113,8 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
final HistoryRecordManager historyRecordManager) {
|
||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
||||
final StreamStateEntity state
|
||||
= historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
||||
if (state != null && item.getDuration() > 0
|
||||
&& item.getStreamType() != StreamType.LIVE_STREAM) {
|
||||
itemProgressView.setMax((int) item.getDuration());
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
||||
@@ -101,7 +101,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
+ localItems.size() + ", data.size() = " + data.size());
|
||||
}
|
||||
|
||||
int offsetStart = sizeConsideringHeader();
|
||||
final int offsetStart = sizeConsideringHeader();
|
||||
localItems.addAll(data);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -113,7 +113,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeader();
|
||||
final int footerNow = sizeConsideringHeader();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -158,7 +158,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
}
|
||||
|
||||
public void setHeader(final View header) {
|
||||
boolean changed = header != this.header;
|
||||
final boolean changed = header != this.header;
|
||||
this.header = header;
|
||||
if (changed) {
|
||||
notifyDataSetChanged();
|
||||
@@ -316,7 +316,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position,
|
||||
@NonNull final List<Object> payloads) {
|
||||
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
|
||||
for (Object payload : payloads) {
|
||||
for (final Object payload : payloads) {
|
||||
if (payload instanceof StreamStateEntity) {
|
||||
((LocalItemHolder) holder).updateState(localItems
|
||||
.get(header == null ? position : position - 1), recordManager);
|
||||
|
||||
@@ -30,8 +30,6 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
@@ -54,31 +52,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
// Fragment LifeCycle - Creation
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static List<PlaylistLocalItem> merge(
|
||||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
List<PlaylistLocalItem> items = new ArrayList<>(
|
||||
localPlaylists.size() + remotePlaylists.size());
|
||||
items.addAll(localPlaylists);
|
||||
items.addAll(remotePlaylists);
|
||||
|
||||
Collections.sort(items, (left, right) -> {
|
||||
String on1 = left.getOrderingName();
|
||||
String on2 = right.getOrderingName();
|
||||
if (on1 == null && on2 == null) {
|
||||
return 0;
|
||||
} else if (on1 != null && on2 == null) {
|
||||
return -1;
|
||||
} else if (on1 == null && on2 != null) {
|
||||
return 1;
|
||||
} else {
|
||||
return on1.compareToIgnoreCase(on2);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -164,7 +137,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
super.startLoading(forceLoad);
|
||||
|
||||
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
|
||||
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistsSubscriber());
|
||||
@@ -292,15 +265,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
}
|
||||
|
||||
private void showLocalDialog(final PlaylistMetadataEntry selectedItem) {
|
||||
View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
||||
EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_bookmark, null);
|
||||
final EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
||||
editText.setText(selectedItem.name);
|
||||
|
||||
Builder builder = new AlertDialog.Builder(activity);
|
||||
final Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setView(dialogView)
|
||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> {
|
||||
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString());
|
||||
})
|
||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) ->
|
||||
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
||||
showDeleteDialog(selectedItem.name,
|
||||
|
||||
@@ -39,14 +39,14 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
private CompositeDisposable playlistDisposables = new CompositeDisposable();
|
||||
|
||||
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final StreamInfoItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
@@ -55,8 +55,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
}
|
||||
|
||||
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
||||
for (final PlayQueueItem item : items) {
|
||||
entities.add(new StreamEntity(item));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
||||
PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
||||
dialog.setInfo(streams);
|
||||
return dialog;
|
||||
}
|
||||
@@ -37,8 +37,8 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||
return super.onCreateDialog(savedInstanceState);
|
||||
}
|
||||
|
||||
View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||
EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||
final EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
||||
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.create_playlist)
|
||||
|
||||
@@ -7,6 +7,8 @@ import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.MainActivity.DEBUG
|
||||
import org.schabi.newpipe.NewPipeDatabase
|
||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||
@@ -16,8 +18,6 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FeedDatabaseManager(context: Context) {
|
||||
private val database = NewPipeDatabase.getInstance(context)
|
||||
@@ -70,8 +70,11 @@ class FeedDatabaseManager(context: Context) {
|
||||
fun markAsOutdated(subscriptionId: Long) = feedTable
|
||||
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
|
||||
|
||||
fun upsertAll(subscriptionId: Long, items: List<StreamInfoItem>,
|
||||
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time) {
|
||||
fun upsertAll(
|
||||
subscriptionId: Long,
|
||||
items: List<StreamInfoItem>,
|
||||
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time
|
||||
) {
|
||||
val itemsToInsert = ArrayList<StreamInfoItem>()
|
||||
loop@ for (streamItem in items) {
|
||||
val uploadDate = streamItem.uploadDate
|
||||
@@ -107,9 +110,9 @@ class FeedDatabaseManager(context: Context) {
|
||||
if (DEBUG) Log.d(this::class.java.simpleName, "clear() → streamTable.deleteOrphans() → $deletedOrphans")
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Feed Groups
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
|
||||
return feedGroupTable.getSubscriptionIdsFor(groupId)
|
||||
@@ -161,6 +164,5 @@ class FeedDatabaseManager(context: Context) {
|
||||
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
|
||||
else -> feedTable.oldestSubscriptionUpdate(groupId)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,28 @@ package org.schabi.newpipe.local.feed
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import icepick.State
|
||||
import kotlinx.android.synthetic.main.error_retry.*
|
||||
import kotlinx.android.synthetic.main.fragment_feed.*
|
||||
import java.util.Calendar
|
||||
import kotlinx.android.synthetic.main.error_retry.error_button_retry
|
||||
import kotlinx.android.synthetic.main.error_retry.error_message_view
|
||||
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
|
||||
import kotlinx.android.synthetic.main.fragment_feed.error_panel
|
||||
import kotlinx.android.synthetic.main.fragment_feed.items_list
|
||||
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_bar
|
||||
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_text
|
||||
import kotlinx.android.synthetic.main.fragment_feed.refresh_root_view
|
||||
import kotlinx.android.synthetic.main.fragment_feed.refresh_subtitle_text
|
||||
import kotlinx.android.synthetic.main.fragment_feed.refresh_text
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment
|
||||
@@ -37,7 +51,6 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
|
||||
import org.schabi.newpipe.report.UserAction
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import java.util.*
|
||||
|
||||
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
private lateinit var viewModel: FeedViewModel
|
||||
@@ -69,7 +82,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(rootView, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
|
||||
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
|
||||
}
|
||||
|
||||
@@ -98,9 +111,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
@@ -150,9 +163,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
activity?.supportActionBar?.subtitle = null
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun showLoading() {
|
||||
animateView(refresh_root_view, false, 0)
|
||||
@@ -259,7 +272,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
||||
hideLoading()
|
||||
errorState.error?.let {
|
||||
@@ -283,9 +295,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
refresh_text?.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Load Service Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun doInitialLoadLogic() {}
|
||||
override fun reloadContent() = triggerUpdate()
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package org.schabi.newpipe.local.feed
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import java.util.Calendar
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import java.util.*
|
||||
|
||||
sealed class FeedState {
|
||||
data class ProgressState(
|
||||
val currentProgress: Int = -1,
|
||||
val maxProgress: Int = -1,
|
||||
@StringRes val progressMessage: Int = 0
|
||||
val currentProgress: Int = -1,
|
||||
val maxProgress: Int = -1,
|
||||
@StringRes val progressMessage: Int = 0
|
||||
) : FeedState()
|
||||
|
||||
data class LoadedState(
|
||||
val items: List<StreamInfoItem>,
|
||||
val oldestUpdate: Calendar? = null,
|
||||
val notLoadedCount: Long,
|
||||
val itemsErrors: List<Throwable> = emptyList()
|
||||
val items: List<StreamInfoItem>,
|
||||
val oldestUpdate: Calendar? = null,
|
||||
val notLoadedCount: Long,
|
||||
val itemsErrors: List<Throwable> = emptyList()
|
||||
) : FeedState()
|
||||
|
||||
data class ErrorState(
|
||||
val error: Throwable? = null
|
||||
val error: Throwable? = null
|
||||
) : FeedState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,17 @@ import io.reactivex.Flowable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.Function4
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
||||
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||
@@ -68,4 +72,4 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
|
||||
}
|
||||
|
||||
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamInfoItem>, val t3: Long, val t4: Date?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.schabi.newpipe.local.feed.service
|
||||
import androidx.annotation.StringRes
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
|
||||
object FeedEventManager {
|
||||
private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create()
|
||||
@@ -34,5 +34,4 @@ object FeedEventManager {
|
||||
data class SuccessResultEvent(val itemsErrors: List<Throwable> = emptyList()) : Event()
|
||||
data class ErrorResultEvent(val error: Throwable) : Event()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Notification
|
||||
import io.reactivex.Single
|
||||
@@ -40,6 +40,11 @@ import io.reactivex.functions.Consumer
|
||||
import io.reactivex.functions.Function
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.io.IOException
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import org.reactivestreams.Subscriber
|
||||
import org.reactivestreams.Subscription
|
||||
import org.schabi.newpipe.MainActivity.DEBUG
|
||||
@@ -49,17 +54,14 @@ import org.schabi.newpipe.extractor.ListInfo
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||
import org.schabi.newpipe.util.ExceptionUtils
|
||||
import org.schabi.newpipe.util.ExtractorHelper
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FeedLoadService : Service() {
|
||||
companion object {
|
||||
@@ -94,9 +96,9 @@ class FeedLoadService : Service() {
|
||||
private var disposables = CompositeDisposable()
|
||||
private var notificationUpdater = PublishProcessor.create<String>()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -151,9 +153,9 @@ class FeedLoadService : Service() {
|
||||
return null
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Loading & Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) {
|
||||
companion object {
|
||||
@@ -312,7 +314,6 @@ class FeedLoadService : Service() {
|
||||
feedResultsHolder.addErrors(RequestException.wrapList(subscriptionId, info))
|
||||
feedDatabaseManager.markAsOutdated(subscriptionId)
|
||||
}
|
||||
|
||||
} else if (notification.isOnError) {
|
||||
val error = notification.error!!
|
||||
feedResultsHolder.addError(error)
|
||||
@@ -325,7 +326,6 @@ class FeedLoadService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
|
||||
get() = Consumer {
|
||||
if (it.isOnError) {
|
||||
@@ -354,9 +354,9 @@ class FeedLoadService : Service() {
|
||||
broadcastProgress()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Notification
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private lateinit var notificationManager: NotificationManagerCompat
|
||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||
@@ -412,9 +412,9 @@ class FeedLoadService : Service() {
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Notification Actions
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private lateinit var broadcastReceiver: BroadcastReceiver
|
||||
private val cancelSignal = AtomicBoolean()
|
||||
@@ -430,18 +430,18 @@ class FeedLoadService : Service() {
|
||||
registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL))
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Error handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun handleError(error: Throwable) {
|
||||
postEvent(ErrorResultEvent(error))
|
||||
stopService()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Results Holder
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ResultsHolder {
|
||||
/**
|
||||
@@ -463,4 +463,4 @@ class FeedLoadService : Service() {
|
||||
itemsErrors = itemsErrorsHolder.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.schabi.newpipe.local.history;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -88,7 +88,7 @@ public class HistoryRecordManager {
|
||||
final Date currentTime = new Date();
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||
StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||
|
||||
if (latestEntry != null) {
|
||||
streamHistoryTable.delete(latestEntry);
|
||||
@@ -129,7 +129,7 @@ public class HistoryRecordManager {
|
||||
}
|
||||
|
||||
public Single<List<Long>> insertStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
||||
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||
final List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||
for (final StreamHistoryEntry entry : entries) {
|
||||
entities.add(entry.toStreamHistoryEntity());
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public class HistoryRecordManager {
|
||||
}
|
||||
|
||||
public Single<Integer> deleteStreamHistory(final Collection<StreamHistoryEntry> entries) {
|
||||
List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||
final List<StreamHistoryEntity> entities = new ArrayList<>(entries.size());
|
||||
for (final StreamHistoryEntry entry : entries) {
|
||||
entities.add(entry.toStreamHistoryEntity());
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class HistoryRecordManager {
|
||||
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
|
||||
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
||||
final SearchHistoryEntry latestEntry = searchHistoryTable.getLatestEntry();
|
||||
if (latestEntry != null && latestEntry.hasEqualValues(newEntry)) {
|
||||
latestEntry.setCreationDate(currentTime);
|
||||
return (long) searchHistoryTable.update(latestEntry);
|
||||
@@ -256,7 +256,7 @@ public class HistoryRecordManager {
|
||||
public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) {
|
||||
return Single.fromCallable(() -> {
|
||||
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
|
||||
for (InfoItem info : infos) {
|
||||
for (final InfoItem info : infos) {
|
||||
final List<StreamEntity> entities = streamTable
|
||||
.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
||||
if (entities.isEmpty()) {
|
||||
@@ -279,8 +279,8 @@ public class HistoryRecordManager {
|
||||
final List<? extends LocalItem> items) {
|
||||
return Single.fromCallable(() -> {
|
||||
final List<StreamStateEntity> result = new ArrayList<>(items.size());
|
||||
for (LocalItem item : items) {
|
||||
long streamId;
|
||||
for (final LocalItem item : items) {
|
||||
final long streamId;
|
||||
if (item instanceof StreamStatisticsEntry) {
|
||||
streamId = ((StreamStatisticsEntry) item).getStreamId();
|
||||
} else if (item instanceof PlaylistStreamEntity) {
|
||||
|
||||
@@ -321,7 +321,7 @@ public class StatisticsPlaylistFragment
|
||||
}
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
@@ -362,14 +362,14 @@ public class StatisticsPlaylistFragment
|
||||
if (sortMode == StatisticSortMode.LAST_PLAYED) {
|
||||
sortMode = StatisticSortMode.MOST_PLAYED;
|
||||
setTitle(getString(R.string.title_most_played));
|
||||
sortButtonIcon
|
||||
.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
|
||||
sortButtonIcon.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_history));
|
||||
sortButtonText.setText(R.string.title_last_played);
|
||||
} else {
|
||||
sortMode = StatisticSortMode.LAST_PLAYED;
|
||||
setTitle(getString(R.string.title_last_played));
|
||||
sortButtonIcon
|
||||
.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
|
||||
sortButtonIcon.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_filter_list));
|
||||
sortButtonText.setText(R.string.title_most_played);
|
||||
}
|
||||
startLoading(true);
|
||||
@@ -455,7 +455,7 @@ public class StatisticsPlaylistFragment
|
||||
}
|
||||
|
||||
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
||||
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||
final List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||
for (final LocalItem item : infoItems) {
|
||||
if (item instanceof StreamStatisticsEntry) {
|
||||
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());
|
||||
|
||||
@@ -70,7 +70,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
@@ -116,7 +116,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
}
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
|
||||
@@ -98,7 +98,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
@@ -146,7 +146,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
}
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager
|
||||
final StreamStateEntity state = historyRecordManager
|
||||
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
|
||||
add(localItem);
|
||||
}}).blockingGet().get(0);
|
||||
|
||||
@@ -98,7 +98,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
private boolean isRemovingWatched = false;
|
||||
|
||||
public static LocalPlaylistFragment getInstance(final long playlistId, final String name) {
|
||||
LocalPlaylistFragment instance = new LocalPlaylistFragment();
|
||||
final LocalPlaylistFragment instance = new LocalPlaylistFragment();
|
||||
instance.setInitialData(playlistId, name);
|
||||
return instance;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
public void selected(final LocalItem selectedItem) {
|
||||
if (selectedItem instanceof PlaylistStreamEntry) {
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem;
|
||||
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
|
||||
NavigationHelper.openVideoDetailFragment(getFM(),
|
||||
item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(),
|
||||
item.getStreamEntity().getTitle());
|
||||
}
|
||||
@@ -411,7 +411,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
if (removePartiallyWatched) {
|
||||
while (playlistIter.hasNext()) {
|
||||
final PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||
int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||
final int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||
playlistItem.getStreamId());
|
||||
|
||||
if (indexInHistory < 0) {
|
||||
@@ -427,7 +427,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
.loadLocalStreamStateBatch(playlist).blockingGet().iterator();
|
||||
|
||||
while (playlistIter.hasNext()) {
|
||||
PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||
final PlaylistStreamEntry playlistItem = playlistIter.next();
|
||||
final int indexInHistory = Collections.binarySearch(historyStreamIds,
|
||||
playlistItem.getStreamId());
|
||||
|
||||
@@ -492,7 +492,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
setVideoCount(itemListAdapter.getItemsList().size());
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
@@ -544,7 +544,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
}
|
||||
|
||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||
EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
|
||||
final EditText nameEdit = dialogView.findViewById(R.id.playlist_name);
|
||||
nameEdit.setText(name);
|
||||
nameEdit.setSelection(nameEdit.getText().length());
|
||||
|
||||
@@ -553,9 +553,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
.setView(dialogView)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.rename, (dialogInterface, i) -> {
|
||||
changePlaylistName(nameEdit.getText().toString());
|
||||
});
|
||||
.setPositiveButton(R.string.rename, (dialogInterface, i) ->
|
||||
changePlaylistName(nameEdit.getText().toString()));
|
||||
|
||||
dialogBuilder.show();
|
||||
}
|
||||
@@ -601,7 +600,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
}
|
||||
|
||||
private void updateThumbnailUrl() {
|
||||
String newThumbnailUrl;
|
||||
final String newThumbnailUrl;
|
||||
|
||||
if (!itemListAdapter.getItemsList().isEmpty()) {
|
||||
newThumbnailUrl = ((PlaylistStreamEntry) itemListAdapter.getItemsList().get(0))
|
||||
@@ -662,7 +661,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
}
|
||||
|
||||
final List<LocalItem> items = itemListAdapter.getItemsList();
|
||||
List<Long> streamIds = new ArrayList<>(items.size());
|
||||
final List<Long> streamIds = new ArrayList<>(items.size());
|
||||
for (final LocalItem item : items) {
|
||||
if (item instanceof PlaylistStreamEntry) {
|
||||
streamIds.add(((PlaylistStreamEntry) item).getStreamId());
|
||||
@@ -815,7 +814,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
}
|
||||
|
||||
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
|
||||
List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||
final List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
|
||||
for (final LocalItem item : infoItems) {
|
||||
if (item instanceof PlaylistStreamEntry) {
|
||||
streamInfoItems.add(((PlaylistStreamEntry) item).toStreamInfoItem());
|
||||
|
||||
@@ -61,7 +61,7 @@ public class LocalPlaylistManager {
|
||||
final List<StreamEntity> streams,
|
||||
final int indexOffset) {
|
||||
|
||||
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
||||
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streams.size());
|
||||
final List<Long> streamIds = streamTable.upsertAll(streams);
|
||||
for (int index = 0; index < streamIds.size(); index++) {
|
||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(index),
|
||||
@@ -71,7 +71,7 @@ public class LocalPlaylistManager {
|
||||
}
|
||||
|
||||
public Completable updateJoin(final long playlistId, final List<Long> streamIds) {
|
||||
List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
||||
final List<PlaylistStreamEntity> joinEntities = new ArrayList<>(streamIds.size());
|
||||
for (int i = 0; i < streamIds.size(); i++) {
|
||||
joinEntities.add(new PlaylistStreamEntity(playlistId, streamIds.get(i), i));
|
||||
}
|
||||
@@ -115,7 +115,7 @@ public class LocalPlaylistManager {
|
||||
.firstElement()
|
||||
.filter(playlistEntities -> !playlistEntities.isEmpty())
|
||||
.map(playlistEntities -> {
|
||||
PlaylistEntity playlist = playlistEntities.get(0);
|
||||
final PlaylistEntity playlist = playlistEntities.get(0);
|
||||
if (name != null) {
|
||||
playlist.setName(name);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class RemotePlaylistManager {
|
||||
|
||||
public Single<Integer> onUpdate(final long playlistId, final PlaylistInfo playlistInfo) {
|
||||
return Single.fromCallable(() -> {
|
||||
PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
|
||||
final PlaylistRemoteEntity playlist = new PlaylistRemoteEntity(playlistInfo);
|
||||
playlist.setUid(playlistId);
|
||||
return playlistRemoteTable.update(playlist);
|
||||
}).subscribeOn(Schedulers.io());
|
||||
|
||||
@@ -7,23 +7,23 @@ import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
enum class FeedGroupIcon(
|
||||
/**
|
||||
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
|
||||
*/
|
||||
val id: Int,
|
||||
/**
|
||||
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
|
||||
*/
|
||||
val id: Int,
|
||||
|
||||
/**
|
||||
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
|
||||
*/
|
||||
@AttrRes val drawableResourceAttr: Int
|
||||
/**
|
||||
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
|
||||
*/
|
||||
@AttrRes val drawableResourceAttr: Int
|
||||
) {
|
||||
ALL(0, R.attr.ic_asterisk),
|
||||
MUSIC(1, R.attr.ic_music_note),
|
||||
EDUCATION(2, R.attr.ic_school),
|
||||
FITNESS(3, R.attr.ic_fitness),
|
||||
FITNESS(3, R.attr.ic_fitness_center),
|
||||
SPACE(4, R.attr.ic_telescope),
|
||||
COMPUTER(5, R.attr.ic_computer),
|
||||
GAMING(6, R.attr.ic_videogame),
|
||||
GAMING(6, R.attr.ic_videogame_asset),
|
||||
SPORTS(7, R.attr.ic_sports),
|
||||
NEWS(8, R.attr.ic_megaphone),
|
||||
FAVORITES(9, R.attr.ic_heart),
|
||||
@@ -32,32 +32,32 @@ enum class FeedGroupIcon(
|
||||
TREND(12, R.attr.ic_trending_up),
|
||||
MOVIE(13, R.attr.ic_movie),
|
||||
BACKUP(14, R.attr.ic_backup),
|
||||
ART(15, R.attr.palette),
|
||||
ART(15, R.attr.ic_palette),
|
||||
PERSON(16, R.attr.ic_person),
|
||||
PEOPLE(17, R.attr.ic_people),
|
||||
MONEY(18, R.attr.ic_money),
|
||||
KIDS(19, R.attr.ic_kids),
|
||||
KIDS(19, R.attr.ic_child_care),
|
||||
FOOD(20, R.attr.ic_fastfood),
|
||||
SMILE(21, R.attr.ic_smile),
|
||||
EXPLORE(22, R.attr.ic_explore),
|
||||
RESTAURANT(23, R.attr.ic_restaurant),
|
||||
MIC(24, R.attr.ic_mic),
|
||||
HEADSET(25, R.attr.audio),
|
||||
HEADSET(25, R.attr.ic_headset),
|
||||
RADIO(26, R.attr.ic_radio),
|
||||
SHOPPING_CART(27, R.attr.ic_shopping_cart),
|
||||
WATCH_LATER(28, R.attr.ic_watch_later),
|
||||
WORK(29, R.attr.ic_work),
|
||||
HOT(30, R.attr.ic_hot),
|
||||
HOT(30, R.attr.ic_kiosk_hot),
|
||||
CHANNEL(31, R.attr.ic_channel),
|
||||
BOOKMARK(32, R.attr.ic_bookmark),
|
||||
PETS(33, R.attr.ic_pets),
|
||||
WORLD(34, R.attr.ic_world),
|
||||
STAR(35, R.attr.ic_stars),
|
||||
SUN(36, R.attr.ic_sunny),
|
||||
RSS(37, R.attr.rss);
|
||||
RSS(37, R.attr.ic_rss);
|
||||
|
||||
@DrawableRes
|
||||
fun getDrawableRes(context: Context): Int {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, drawableResourceAttr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,24 @@ package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.nononsenseapps.filepicker.Utils
|
||||
import com.xwray.groupie.Group
|
||||
@@ -21,8 +29,15 @@ import com.xwray.groupie.Section
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.State
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.*
|
||||
import kotlinx.android.synthetic.main.fragment_subscription.*
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
|
||||
import kotlinx.android.synthetic.main.fragment_subscription.items_list
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||
@@ -30,21 +45,29 @@ import org.schabi.newpipe.fragments.BaseStateFragment
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
|
||||
import org.schabi.newpipe.local.subscription.item.*
|
||||
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedImportExportItem
|
||||
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem
|
||||
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.*
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE_ACTION
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
||||
import org.schabi.newpipe.report.UserAction
|
||||
import org.schabi.newpipe.util.*
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
import org.schabi.newpipe.util.ShareUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
private lateinit var viewModel: SubscriptionViewModel
|
||||
@@ -74,9 +97,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -120,9 +143,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
@@ -150,7 +173,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
importExportItem.isExpanded = false
|
||||
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,9 +220,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
// Fragment Views
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun setupInitialLayout() {
|
||||
Section().apply {
|
||||
@@ -243,7 +265,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
{ onExportSelected() },
|
||||
importExportItemExpandedState ?: false)
|
||||
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
|
||||
|
||||
}
|
||||
|
||||
override fun initViews(rootView: View, savedInstanceState: Bundle?) {
|
||||
@@ -256,7 +277,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
}
|
||||
items_list.adapter = groupAdapter
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(SubscriptionViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
|
||||
viewModel.stateLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleResult) })
|
||||
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, androidx.lifecycle.Observer { it?.let(this::handleFeedGroups) })
|
||||
}
|
||||
@@ -366,9 +387,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun showLoading() {
|
||||
super.showLoading()
|
||||
@@ -380,9 +401,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
animateView(items_list, true, 200)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Fragment Error Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onError(exception: Throwable): Boolean {
|
||||
if (super.onError(exception)) return true
|
||||
@@ -391,9 +412,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
return true
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Grid Mode
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO: Move these out of this class, as it can be reused
|
||||
|
||||
@@ -405,8 +426,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
getString(R.string.list_view_mode_auto_key) -> {
|
||||
val configuration = resources.configuration
|
||||
|
||||
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
|
||||
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
|
||||
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
|
||||
}
|
||||
getString(R.string.list_view_mode_grid_key) -> true
|
||||
else -> false
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.content.Context
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.NewPipeDatabase
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.extractor.ListInfo
|
||||
@@ -21,9 +23,28 @@ class SubscriptionManager(context: Context) {
|
||||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||
fun subscriptions() = subscriptionTable.all
|
||||
|
||||
fun getSubscriptions(
|
||||
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
filterQuery: String = "",
|
||||
showOnlyUngrouped: Boolean = false
|
||||
): Flowable<List<SubscriptionEntity>> {
|
||||
return when {
|
||||
filterQuery.isNotEmpty() -> {
|
||||
return if (showOnlyUngrouped) {
|
||||
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
|
||||
currentGroupId, filterQuery)
|
||||
} else {
|
||||
subscriptionTable.getSubscriptionsFiltered(filterQuery)
|
||||
}
|
||||
}
|
||||
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
|
||||
else -> subscriptionTable.all
|
||||
}
|
||||
}
|
||||
|
||||
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
|
||||
val listEntities = subscriptionTable.upsertAll(
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
|
||||
database.runInTransaction {
|
||||
infoList.forEachIndexed { index, info ->
|
||||
@@ -35,13 +56,13 @@ class SubscriptionManager(context: Context) {
|
||||
}
|
||||
|
||||
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
|
||||
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
|
||||
@@ -57,8 +78,8 @@ class SubscriptionManager(context: Context) {
|
||||
|
||||
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
||||
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {
|
||||
|
||||
@@ -6,11 +6,11 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.xwray.groupie.Group
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
||||
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
|
||||
|
||||
@@ -64,7 +64,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
||||
private Button inputButton;
|
||||
|
||||
public static SubscriptionsImportFragment getInstance(final int serviceId) {
|
||||
SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
|
||||
final SubscriptionsImportFragment instance = new SubscriptionsImportFragment();
|
||||
instance.setInitialData(serviceId);
|
||||
return instance;
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
||||
setInfoText("");
|
||||
}
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
final ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||
setTitle(getString(R.string.import_title));
|
||||
@@ -206,7 +206,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
||||
relatedUrl = extractor.getRelatedUrl();
|
||||
instructionsString = ServiceHelper.getImportInstructions(currentServiceId);
|
||||
return;
|
||||
} catch (ExtractionException ignored) {
|
||||
} catch (final ExtractionException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,4 +32,4 @@ class FeedGroupCarouselDecoration(context: Context) : RecyclerView.ItemDecoratio
|
||||
outRect.right = marginStartEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -13,30 +14,36 @@ import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.xwray.groupie.GroupAdapter
|
||||
import com.xwray.groupie.OnItemClickListener
|
||||
import com.xwray.groupie.Section
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.Icepick
|
||||
import icepick.State
|
||||
import java.io.Serializable
|
||||
import kotlin.collections.contains
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
|
||||
import kotlinx.android.synthetic.main.toolbar_search_layout.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.fragments.BackPressable
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.*
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.InitialScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.SubscriptionsPickerScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.ProcessingEvent
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.SuccessEvent
|
||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
import org.schabi.newpipe.util.DeviceUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
import java.io.Serializable
|
||||
|
||||
class FeedGroupDialog : DialogFragment() {
|
||||
class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
private lateinit var viewModel: FeedGroupDialogViewModel
|
||||
private var groupId: Long = NO_GROUP_SELECTED
|
||||
private var groupIcon: FeedGroupIcon? = null
|
||||
@@ -49,22 +56,20 @@ class FeedGroupDialog : DialogFragment() {
|
||||
object DeleteScreen : ScreenState()
|
||||
}
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var selectedIcon: FeedGroupIcon? = null
|
||||
@State
|
||||
@JvmField
|
||||
var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State
|
||||
@JvmField
|
||||
var currentScreen: ScreenState = InitialScreen
|
||||
@State @JvmField var selectedIcon: FeedGroupIcon? = null
|
||||
@State @JvmField var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State @JvmField var wasSubscriptionSelectionChanged: Boolean = false
|
||||
@State @JvmField var currentScreen: ScreenState = InitialScreen
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var subscriptionsListState: Parcelable? = null
|
||||
@State
|
||||
@JvmField
|
||||
var iconsListState: Parcelable? = null
|
||||
@State @JvmField var subscriptionsListState: Parcelable? = null
|
||||
@State @JvmField var iconsListState: Parcelable? = null
|
||||
@State @JvmField var wasSearchSubscriptionsVisible = false
|
||||
@State @JvmField var subscriptionsCurrentSearchQuery = ""
|
||||
@State @JvmField var subscriptionsShowOnlyUngrouped = false
|
||||
|
||||
private val subscriptionMainSection = Section()
|
||||
private val subscriptionEmptyFooter = Section()
|
||||
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -74,22 +79,30 @@ class FeedGroupDialog : DialogFragment() {
|
||||
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.dialog_feed_group_create, container)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return object : Dialog(requireActivity(), theme) {
|
||||
override fun onBackPressed() {
|
||||
if (currentScreen !is InitialScreen) {
|
||||
showScreen(InitialScreen)
|
||||
} else {
|
||||
if (!this@FeedGroupDialog.onBackPressed()) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
wasSearchSubscriptionsVisible = isSearchVisible()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
@@ -102,11 +115,15 @@ class FeedGroupDialog : DialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, FeedGroupDialogViewModel.Factory(requireContext(), groupId))
|
||||
.get(FeedGroupDialogViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this,
|
||||
FeedGroupDialogViewModel.Factory(requireContext(),
|
||||
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
|
||||
).get(FeedGroupDialogViewModel::class.java)
|
||||
|
||||
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
|
||||
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer { setupSubscriptionPicker(it.first, it.second) })
|
||||
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
|
||||
setupSubscriptionPicker(it.first, it.second)
|
||||
})
|
||||
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||
when (it) {
|
||||
ProcessingEvent -> disableInput()
|
||||
@@ -114,15 +131,54 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
})
|
||||
|
||||
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
|
||||
add(subscriptionMainSection)
|
||||
add(subscriptionEmptyFooter)
|
||||
spanCount = 4
|
||||
}
|
||||
subscriptions_selector_list.apply {
|
||||
// Disable animations, too distracting.
|
||||
itemAnimator = null
|
||||
adapter = subscriptionGroupAdapter
|
||||
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
|
||||
RecyclerView.VERTICAL, false).apply {
|
||||
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
|
||||
}
|
||||
}
|
||||
|
||||
setupIconPicker()
|
||||
setupListeners()
|
||||
|
||||
showScreen(currentScreen)
|
||||
|
||||
if (currentScreen == SubscriptionsPickerScreen && wasSearchSubscriptionsVisible) {
|
||||
showSearch()
|
||||
} else if (currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED) {
|
||||
showKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
subscriptions_selector_list?.adapter = null
|
||||
icon_selector?.adapter = null
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Setup
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
if (currentScreen is SubscriptionsPickerScreen && isSearchVisible()) {
|
||||
hideSearch()
|
||||
return true
|
||||
} else if (currentScreen !is InitialScreen) {
|
||||
showScreen(InitialScreen)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
||||
@@ -146,13 +202,64 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
})
|
||||
|
||||
confirm_button.setOnClickListener {
|
||||
when (currentScreen) {
|
||||
InitialScreen -> handlePositiveButtonInitialScreen()
|
||||
DeleteScreen -> viewModel.deleteGroup()
|
||||
else -> showScreen(InitialScreen)
|
||||
confirm_button.setOnClickListener { handlePositiveButton() }
|
||||
|
||||
select_channel_button.setOnClickListener {
|
||||
subscriptions_selector_list.scrollToPosition(0)
|
||||
showScreen(SubscriptionsPickerScreen)
|
||||
}
|
||||
|
||||
val headerMenu = subscriptions_header_toolbar.menu
|
||||
requireActivity().menuInflater.inflate(R.menu.menu_feed_group_dialog, headerMenu)
|
||||
|
||||
headerMenu.findItem(R.id.action_search).setOnMenuItemClickListener {
|
||||
showSearch()
|
||||
true
|
||||
}
|
||||
|
||||
headerMenu.findItem(R.id.feed_group_toggle_show_only_ungrouped_subscriptions).apply {
|
||||
isChecked = subscriptionsShowOnlyUngrouped
|
||||
setOnMenuItemClickListener {
|
||||
subscriptionsShowOnlyUngrouped = !subscriptionsShowOnlyUngrouped
|
||||
it.isChecked = subscriptionsShowOnlyUngrouped
|
||||
viewModel.toggleShowOnlyUngrouped(subscriptionsShowOnlyUngrouped)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
toolbar_search_clear.setOnClickListener {
|
||||
if (TextUtils.isEmpty(toolbar_search_edit_text.text)) {
|
||||
hideSearch()
|
||||
return@setOnClickListener
|
||||
}
|
||||
resetSearch()
|
||||
showKeyboardSearch()
|
||||
}
|
||||
|
||||
toolbar_search_edit_text.setOnClickListener {
|
||||
if (DeviceUtils.isTv(context)) {
|
||||
showKeyboardSearch()
|
||||
}
|
||||
}
|
||||
|
||||
toolbar_search_edit_text.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||
override fun afterTextChanged(s: Editable) = Unit
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
val newQuery: String = toolbar_search_edit_text.text.toString()
|
||||
subscriptionsCurrentSearchQuery = newQuery
|
||||
viewModel.filterSubscriptionsBy(newQuery)
|
||||
}
|
||||
})
|
||||
|
||||
subscriptionGroupAdapter.setOnItemClickListener(subscriptionPickerItemListener)
|
||||
}
|
||||
|
||||
private fun handlePositiveButton() = when {
|
||||
currentScreen is InitialScreen -> handlePositiveButtonInitialScreen()
|
||||
currentScreen is DeleteScreen -> viewModel.deleteGroup()
|
||||
currentScreen is SubscriptionsPickerScreen && isSearchVisible() -> hideSearch()
|
||||
else -> showScreen(InitialScreen)
|
||||
}
|
||||
|
||||
private fun handlePositiveButtonInitialScreen() {
|
||||
@@ -185,80 +292,73 @@ class FeedGroupDialog : DialogFragment() {
|
||||
groupIcon = feedGroupEntity?.icon
|
||||
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
|
||||
|
||||
icon_preview.setImageResource((if (selectedIcon == null) icon else selectedIcon!!).getDrawableRes(requireContext()))
|
||||
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
|
||||
icon_preview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
|
||||
|
||||
if (group_name_input.text.isNullOrBlank()) {
|
||||
group_name_input.setText(name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSubscriptionPicker(subscriptions: List<SubscriptionEntity>, selectedSubscriptions: Set<Long>) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
val useGridLayout = subscriptions.isNotEmpty()
|
||||
private val subscriptionPickerItemListener = OnItemClickListener { item, view ->
|
||||
if (item is PickerSubscriptionItem) {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
wasSubscriptionSelectionChanged = true
|
||||
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.spanCount = if (useGridLayout) 4 else 1
|
||||
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val selectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = selectedCountText
|
||||
subscriptions_selector_header_info.text = selectedCountText
|
||||
|
||||
Section().apply {
|
||||
addAll(subscriptions.map {
|
||||
val isSelected = this@FeedGroupDialog.selectedSubscriptions.contains(it.uid)
|
||||
PickerSubscriptionItem(it, isSelected)
|
||||
})
|
||||
setPlaceholder(EmptyPlaceholderItem())
|
||||
|
||||
groupAdapter.add(this)
|
||||
}
|
||||
|
||||
subscriptions_selector_list.apply {
|
||||
layoutManager = if (useGridLayout) {
|
||||
GridLayoutManager(requireContext(), groupAdapter.spanCount, RecyclerView.VERTICAL, false)
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
true
|
||||
}
|
||||
|
||||
adapter = groupAdapter
|
||||
item.updateSelected(view, isSelected)
|
||||
updateSubscriptionSelectedCount()
|
||||
}
|
||||
}
|
||||
|
||||
if (subscriptionsListState != null) {
|
||||
layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||
subscriptionsListState = null
|
||||
}
|
||||
private fun setupSubscriptionPicker(
|
||||
subscriptions: List<PickerSubscriptionItem>,
|
||||
selectedSubscriptions: Set<Long>
|
||||
) {
|
||||
if (!wasSubscriptionSelectionChanged) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
}
|
||||
|
||||
groupAdapter.setOnItemClickListener { item, _ ->
|
||||
when (item) {
|
||||
is PickerSubscriptionItem -> {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
updateSubscriptionSelectedCount()
|
||||
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
true
|
||||
}
|
||||
|
||||
item.isSelected = isSelected
|
||||
item.notifyChanged(PickerSubscriptionItem.UPDATE_SELECTED)
|
||||
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val updateSelectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = updateSelectedCountText
|
||||
subscriptions_selector_header_info.text = updateSelectedCountText
|
||||
}
|
||||
}
|
||||
if (subscriptions.isEmpty()) {
|
||||
subscriptionEmptyFooter.clear()
|
||||
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
|
||||
} else {
|
||||
subscriptionEmptyFooter.clear()
|
||||
}
|
||||
|
||||
select_channel_button.setOnClickListener {
|
||||
subscriptions.forEach {
|
||||
it.isSelected = this@FeedGroupDialog.selectedSubscriptions
|
||||
.contains(it.subscriptionEntity.uid)
|
||||
}
|
||||
|
||||
subscriptionMainSection.update(subscriptions, false)
|
||||
|
||||
if (subscriptionsListState != null) {
|
||||
subscriptions_selector_list.layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||
subscriptionsListState = null
|
||||
} else {
|
||||
subscriptions_selector_list.scrollToPosition(0)
|
||||
showScreen(SubscriptionsPickerScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSubscriptionSelectedCount() {
|
||||
val selectedCount = this.selectedSubscriptions.size
|
||||
val selectedCountText = resources.getQuantityString(
|
||||
R.plurals.feed_group_dialog_selection_count,
|
||||
selectedCount, selectedCount)
|
||||
selected_subscription_count_view.text = selectedCountText
|
||||
subscriptions_header_info.text = selectedCountText
|
||||
}
|
||||
|
||||
private fun setupIconPicker() {
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
|
||||
@@ -294,9 +394,9 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Screen Selector
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
private fun showScreen(screen: ScreenState) {
|
||||
currentScreen = screen
|
||||
@@ -320,7 +420,8 @@ class FeedGroupDialog : DialogFragment() {
|
||||
else -> View.VISIBLE
|
||||
}
|
||||
|
||||
if (currentScreen != InitialScreen) hideKeyboard()
|
||||
hideKeyboard()
|
||||
hideSearch()
|
||||
}
|
||||
|
||||
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
|
||||
@@ -330,13 +431,58 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
private fun isSearchVisible() = subscriptions_header_search_container?.visibility == View.VISIBLE
|
||||
|
||||
private fun resetSearch() {
|
||||
toolbar_search_edit_text.setText("")
|
||||
subscriptionsCurrentSearchQuery = ""
|
||||
viewModel.clearSubscriptionsFilter()
|
||||
}
|
||||
|
||||
private fun hideSearch() {
|
||||
resetSearch()
|
||||
subscriptions_header_search_container.visibility = View.GONE
|
||||
subscriptions_header_info_container.visibility = View.VISIBLE
|
||||
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = true
|
||||
hideKeyboardSearch()
|
||||
}
|
||||
|
||||
private fun showSearch() {
|
||||
subscriptions_header_search_container.visibility = View.VISIBLE
|
||||
subscriptions_header_info_container.visibility = View.GONE
|
||||
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = false
|
||||
showKeyboardSearch()
|
||||
}
|
||||
|
||||
private val inputMethodManager by lazy {
|
||||
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
}
|
||||
|
||||
private fun showKeyboardSearch() {
|
||||
if (toolbar_search_edit_text.requestFocus()) {
|
||||
inputMethodManager.showSoftInput(toolbar_search_edit_text, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboardSearch() {
|
||||
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
toolbar_search_edit_text.clearFocus()
|
||||
}
|
||||
|
||||
private fun showKeyboard() {
|
||||
if (group_name_input.requestFocus()) {
|
||||
inputMethodManager.showSoftInput(group_name_input, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
group_name_input.clearFocus()
|
||||
}
|
||||
|
||||
@@ -363,4 +509,4 @@ class FeedGroupDialog : DialogFragment() {
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,43 +9,56 @@ import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
|
||||
|
||||
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
|
||||
}
|
||||
}
|
||||
class FeedGroupDialogViewModel(
|
||||
applicationContext: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
initialQuery: String = "",
|
||||
initialShowOnlyUngrouped: Boolean = false
|
||||
) : ViewModel() {
|
||||
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
||||
private var subscriptionManager = SubscriptionManager(applicationContext)
|
||||
|
||||
private var filterSubscriptions = BehaviorProcessor.create<String>()
|
||||
private var toggleShowOnlyUngrouped = BehaviorProcessor.create<Boolean>()
|
||||
|
||||
private var subscriptionsFlowable = Flowable
|
||||
.combineLatest(
|
||||
filterSubscriptions.startWith(initialQuery),
|
||||
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
|
||||
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.switchMap { filter ->
|
||||
subscriptionManager.getSubscriptions(groupId, filter.query, filter.showOnlyUngrouped)
|
||||
}.map { list -> list.map { PickerSubscriptionItem(it) } }
|
||||
|
||||
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
|
||||
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
|
||||
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<SubscriptionEntity>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
|
||||
|
||||
private var actionProcessingDisposable: Disposable? = null
|
||||
|
||||
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
|
||||
private var subscriptionsDisposable = Flowable
|
||||
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
@@ -56,14 +69,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
||||
|
||||
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
||||
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
}
|
||||
|
||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
||||
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
}
|
||||
|
||||
fun deleteGroup() {
|
||||
@@ -75,13 +88,40 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
||||
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
|
||||
|
||||
actionProcessingDisposable = completable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
}
|
||||
}
|
||||
|
||||
fun filterSubscriptionsBy(query: String) {
|
||||
filterSubscriptions.onNext(query)
|
||||
}
|
||||
|
||||
fun clearSubscriptionsFilter() {
|
||||
filterSubscriptions.onNext("")
|
||||
}
|
||||
|
||||
fun toggleShowOnlyUngrouped(showOnlyUngrouped: Boolean) {
|
||||
toggleShowOnlyUngrouped.onNext(showOnlyUngrouped)
|
||||
}
|
||||
|
||||
sealed class DialogEvent {
|
||||
object ProcessingEvent : DialogEvent()
|
||||
object SuccessEvent : DialogEvent()
|
||||
}
|
||||
}
|
||||
|
||||
data class Filter(val query: String, val showOnlyUngrouped: Boolean)
|
||||
|
||||
class Factory(
|
||||
private val context: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
private val initialQuery: String = "",
|
||||
private val initialShowOnlyUngrouped: Boolean = false
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext,
|
||||
groupId, initialQuery, initialShowOnlyUngrouped) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@@ -16,15 +16,15 @@ import com.xwray.groupie.TouchCallback
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.Icepick
|
||||
import icepick.State
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.*
|
||||
import java.util.Collections
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.ProcessingEvent
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FeedGroupReorderDialog : DialogFragment() {
|
||||
private lateinit var viewModel: FeedGroupReorderDialogViewModel
|
||||
@@ -49,7 +49,7 @@ class FeedGroupReorderDialog : DialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(FeedGroupReorderDialogViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java)
|
||||
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
|
||||
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||
when (it) {
|
||||
@@ -93,8 +93,11 @@ class FeedGroupReorderDialog : DialogFragment() {
|
||||
private fun getItemTouchCallback(): SimpleCallback {
|
||||
return object : TouchCallback() {
|
||||
|
||||
override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder): Boolean {
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
source: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val sourceIndex = source.adapterPosition
|
||||
val targetIndex = target.adapterPosition
|
||||
|
||||
@@ -109,4 +112,4 @@ class FeedGroupReorderDialog : DialogFragment() {
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,4 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
|
||||
object ProcessingEvent : DialogEvent()
|
||||
object SuccessEvent : DialogEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,21 @@ import android.content.Context
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.list_channel_item.*
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemAdditionalDetails
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemChannelDescriptionView
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemThumbnailView
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemTitleView
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
|
||||
|
||||
class ChannelItem(
|
||||
private val infoItem: ChannelInfoItem,
|
||||
private val subscriptionId: Long = -1L,
|
||||
var itemVersion: ItemVersion = ItemVersion.NORMAL,
|
||||
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
|
||||
private val infoItem: ChannelInfoItem,
|
||||
private val subscriptionId: Long = -1L,
|
||||
var itemVersion: ItemVersion = ItemVersion.NORMAL,
|
||||
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
|
||||
) : Item() {
|
||||
|
||||
override fun getId(): Long = if (subscriptionId == -1L) super.getId() else subscriptionId
|
||||
@@ -62,4 +64,4 @@ class ChannelItem(
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int {
|
||||
return if (itemVersion == ItemVersion.GRID) 1 else spanCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ import org.schabi.newpipe.R
|
||||
class EmptyPlaceholderItem : Item() {
|
||||
override fun getLayout(): Int = R.layout.list_empty_view
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||
}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ import org.schabi.newpipe.R
|
||||
class FeedGroupAddItem : Item() {
|
||||
override fun getLayout(): Int = R.layout.feed_group_add_new_item
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user