mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-01-14 10:42:40 +00:00
Compare commits
1009 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b861f21bca | ||
|
|
0e0f498c06 | ||
|
|
93af5f8fbb | ||
|
|
7056bbdf51 | ||
|
|
c4eaee1e31 | ||
|
|
4f688862a2 | ||
|
|
25d73a59ad | ||
|
|
9f701d0f2e | ||
|
|
47fb612a5a | ||
|
|
b8ff5fed7c | ||
|
|
0b7e1bcfc4 | ||
|
|
babd5336a0 | ||
|
|
4a76ba4376 | ||
|
|
8245e878e3 | ||
|
|
e64a7809e4 | ||
|
|
28ed9879aa | ||
|
|
d4a761ceff | ||
|
|
7f7313d69a | ||
|
|
6ec7dba751 | ||
|
|
33564974b8 | ||
|
|
baaa9f68dc | ||
|
|
0142f8bf0c | ||
|
|
ad534ddbb6 | ||
|
|
673a59e4c4 | ||
|
|
39d848c62c | ||
|
|
35059b7507 | ||
|
|
f698c914f6 | ||
|
|
02cccf9e27 | ||
|
|
54e763c71e | ||
|
|
4c9709a2bb | ||
|
|
297b5bc55c | ||
|
|
ab9cb063b2 | ||
|
|
b2b32ce67b | ||
|
|
7aeb1ec8d9 | ||
|
|
50b51f931e | ||
|
|
bf353875b0 | ||
|
|
87eab2a9b0 | ||
|
|
dcd5197a19 | ||
|
|
a02e12abe8 | ||
|
|
ce17ccad27 | ||
|
|
25f1660efc | ||
|
|
74c7eb616f | ||
|
|
81f1dc9839 | ||
|
|
2269a95b99 | ||
|
|
bf2a3ca043 | ||
|
|
7e4becd6b6 | ||
|
|
d400ac5705 | ||
|
|
61e57c9373 | ||
|
|
db1cd7dd6b | ||
|
|
8680bcda61 | ||
|
|
5db40a0ede | ||
|
|
d4e80ef268 | ||
|
|
262a4e2bfd | ||
|
|
1037798879 | ||
|
|
bcfdde65b0 | ||
|
|
73d145616a | ||
|
|
cf6380b3c7 | ||
|
|
0593aaa0c1 | ||
|
|
24babd47a2 | ||
|
|
b4b24a4f70 | ||
|
|
ad411344db | ||
|
|
2097d2e4e4 | ||
|
|
2a2643641e | ||
|
|
08a081437f | ||
|
|
b1d89175fc | ||
|
|
97e57dcbab | ||
|
|
f46fa72556 | ||
|
|
242c2fc04a | ||
|
|
1523cfdc45 | ||
|
|
5d9b5a063b | ||
|
|
0417c47fe2 | ||
|
|
0da5717bb5 | ||
|
|
73c4ec378b | ||
|
|
1b54791905 | ||
|
|
4299062bd0 | ||
|
|
015ef402eb | ||
|
|
378c6c576c | ||
|
|
b6cb95695e | ||
|
|
c922b49731 | ||
|
|
60c4f86b08 | ||
|
|
afdb8e0d37 | ||
|
|
502a458dfd | ||
|
|
ff986a99b6 | ||
|
|
939c2a2494 | ||
|
|
38234c005e | ||
|
|
a67e5357ef | ||
|
|
9fe4af9550 | ||
|
|
6622611ee3 | ||
|
|
ecb4bfde0b | ||
|
|
1bee297bba | ||
|
|
0e7817f7e5 | ||
|
|
fafd1126f4 | ||
|
|
a67f167666 | ||
|
|
7e65648412 | ||
|
|
003170e6d8 | ||
|
|
28accff9ba | ||
|
|
e1d2c576a9 | ||
|
|
7e6722f0f8 | ||
|
|
d909ac0e59 | ||
|
|
2869e53421 | ||
|
|
0b145682ba | ||
|
|
0ef52adb18 | ||
|
|
4b76b70ea0 | ||
|
|
586fa99065 | ||
|
|
26184aca10 | ||
|
|
79ed4d5f50 | ||
|
|
87b546e7fb | ||
|
|
a4b8fe41c0 | ||
|
|
8b44bf4d9b | ||
|
|
e09b7c592c | ||
|
|
6ea5d1a1ff | ||
|
|
c5fa1122ae | ||
|
|
7fa75a3d54 | ||
|
|
358f94a7ec | ||
|
|
07ac539c8f | ||
|
|
c36075f4e3 | ||
|
|
54746af5de | ||
|
|
862e08a06c | ||
|
|
748f70ba87 | ||
|
|
9425f0ca1a | ||
|
|
4a3d7002eb | ||
|
|
6d94ccb756 | ||
|
|
6cf83c5c1a | ||
|
|
4bce257189 | ||
|
|
c495fb92f3 | ||
|
|
3102b4bdb7 | ||
|
|
4e232602db | ||
|
|
d5ecf1e8dd | ||
|
|
c019cf57e5 | ||
|
|
0db3d7ffd3 | ||
|
|
ab846be65e | ||
|
|
1a4e83cb5a | ||
|
|
7cd5d5b4c3 | ||
|
|
ec4757d6de | ||
|
|
6447af8541 | ||
|
|
209c1f8bc2 | ||
|
|
6a37dfc35f | ||
|
|
5e101a4d32 | ||
|
|
c29ea4693d | ||
|
|
7d0df7aa80 | ||
|
|
db80be4d38 | ||
|
|
146313e7db | ||
|
|
357ac3d7fb | ||
|
|
1275d9a185 | ||
|
|
75f2ced937 | ||
|
|
cd2ac25ff6 | ||
|
|
1d603043c5 | ||
|
|
c77f2d1569 | ||
|
|
78e3e6bd5f | ||
|
|
79a854e899 | ||
|
|
4b1dbeb11f | ||
|
|
6d235b6715 | ||
|
|
05a95421c4 | ||
|
|
0be654f750 | ||
|
|
1c94a5a33d | ||
|
|
3614015d18 | ||
|
|
a8bffc013f | ||
|
|
4cdac0dbe1 | ||
|
|
74c91db35e | ||
|
|
d06cdb5ee8 | ||
|
|
cbb0d712dd | ||
|
|
465a61d599 | ||
|
|
03d8990985 | ||
|
|
00f4db17bd | ||
|
|
e4d9100196 | ||
|
|
09e2381475 | ||
|
|
af51aa81f1 | ||
|
|
bf6fd18de8 | ||
|
|
effa07ecc2 | ||
|
|
cbf9ec0901 | ||
|
|
ede18ded40 | ||
|
|
6d14ecc60a | ||
|
|
c2d7ac0c25 | ||
|
|
9f2216fdc5 | ||
|
|
7ed0fe05f3 | ||
|
|
c4fe21e87f | ||
|
|
74d2ccfb43 | ||
|
|
ea685057e0 | ||
|
|
3f150832ac | ||
|
|
c0bce9cf97 | ||
|
|
a446313119 | ||
|
|
0e8e713436 | ||
|
|
1a643126de | ||
|
|
6fa72e4a52 | ||
|
|
7f127ba52a | ||
|
|
f94182e8ba | ||
|
|
b607ab72cd | ||
|
|
5b84b37ded | ||
|
|
089c5874c9 | ||
|
|
f66ab2e008 | ||
|
|
dc7e17085d | ||
|
|
dc4dd53771 | ||
|
|
8193b86202 | ||
|
|
6616c33d2a | ||
|
|
ec6eab8a49 | ||
|
|
bd1169a8c7 | ||
|
|
e3e60facf9 | ||
|
|
fc0fa3c519 | ||
|
|
acffbe1385 | ||
|
|
cd31384885 | ||
|
|
9522ec43c7 | ||
|
|
83169fbf6d | ||
|
|
9c7629ea47 | ||
|
|
85d1888ba7 | ||
|
|
9e7e8ed7a6 | ||
|
|
cbe7967310 | ||
|
|
9e1f56c0c1 | ||
|
|
3604971111 | ||
|
|
c43ac4f787 | ||
|
|
2a15f5fb51 | ||
|
|
7cd5af5e72 | ||
|
|
cbdfb15117 | ||
|
|
10ccd4767a | ||
|
|
7ba71e3b37 | ||
|
|
670a95a01d | ||
|
|
7eb0e9db49 | ||
|
|
4f160ee943 | ||
|
|
aad1c610a6 | ||
|
|
305d14c16b | ||
|
|
6ca9fb2440 | ||
|
|
8581d33574 | ||
|
|
fab1c7e986 | ||
|
|
acea26717c | ||
|
|
e6bcb4628a | ||
|
|
c4f08d541d | ||
|
|
58546751dd | ||
|
|
5470c9a002 | ||
|
|
8885b45259 | ||
|
|
85632b24fc | ||
|
|
96802c7b5c | ||
|
|
e5207f8b42 | ||
|
|
9d573e1b1d | ||
|
|
dd276aabc1 | ||
|
|
e4d0635ae1 | ||
|
|
60f534d7a1 | ||
|
|
8036c831a5 | ||
|
|
942e042933 | ||
|
|
223ddaa9bf | ||
|
|
1d6c722c28 | ||
|
|
9c9dd6c7bf | ||
|
|
7ff48a6d84 | ||
|
|
645e16fd90 | ||
|
|
1bb58a10e2 | ||
|
|
118788436e | ||
|
|
0b0f7919a2 | ||
|
|
c8e23fb6ce | ||
|
|
bc10717f61 | ||
|
|
e621dd3b28 | ||
|
|
20208be556 | ||
|
|
9074733aab | ||
|
|
ae0ee61e7d | ||
|
|
ac797196f5 | ||
|
|
30aa64e9c6 | ||
|
|
b992add77b | ||
|
|
88ebd963f7 | ||
|
|
96baa2978d | ||
|
|
c01609b858 | ||
|
|
c89f0e5547 | ||
|
|
10dfcbf0b9 | ||
|
|
43446d56c5 | ||
|
|
e66f2ab36b | ||
|
|
63def07a0e | ||
|
|
1ba7710af8 | ||
|
|
8f13a7ec97 | ||
|
|
93dff5cf7a | ||
|
|
6a0450b9f6 | ||
|
|
fffeadd8ea | ||
|
|
b697e058d9 | ||
|
|
14db8b1283 | ||
|
|
45ad8621cf | ||
|
|
dee3a18ea8 | ||
|
|
950cf714d9 | ||
|
|
652184506b | ||
|
|
89f2cd6ec0 | ||
|
|
6457cac797 | ||
|
|
f66c2ba171 | ||
|
|
6133c97f45 | ||
|
|
0dc71ce37a | ||
|
|
c96a05a8f9 | ||
|
|
c190dc4792 | ||
|
|
ebf91d27c7 | ||
|
|
63301ee771 | ||
|
|
7da827a06a | ||
|
|
00fc5217f5 | ||
|
|
04e725bb50 | ||
|
|
e6617ff8e8 | ||
|
|
1b0a958436 | ||
|
|
5053d470f6 | ||
|
|
8de5c53485 | ||
|
|
ec3ae7c7b8 | ||
|
|
c46af7d194 | ||
|
|
5254e85840 | ||
|
|
5883f6e763 | ||
|
|
c02383d7d9 | ||
|
|
f98e5cc22d | ||
|
|
9fbb61a744 | ||
|
|
5191907af0 | ||
|
|
35a69b4b1d | ||
|
|
f947856a61 | ||
|
|
c4967532e4 | ||
|
|
7dc560e1f2 | ||
|
|
ab03198ba6 | ||
|
|
a64f520644 | ||
|
|
5aced46345 | ||
|
|
3cd485069d | ||
|
|
fabb07bb28 | ||
|
|
2328ea6d07 | ||
|
|
0375194e7d | ||
|
|
5a6a6bcc78 | ||
|
|
c8f475bba1 | ||
|
|
31f3757880 | ||
|
|
a60a9bb144 | ||
|
|
21a90bb7ee | ||
|
|
6a8e38476f | ||
|
|
2f66913813 | ||
|
|
d9b042d9e3 | ||
|
|
ef9044d933 | ||
|
|
12c9dbf1bf | ||
|
|
8cc8aa8693 | ||
|
|
e529b16956 | ||
|
|
ffe8d4b689 | ||
|
|
4e5a20ec45 | ||
|
|
7c9ef58acd | ||
|
|
d076fe72cd | ||
|
|
25fbbfaf94 | ||
|
|
9df27f43de | ||
|
|
759e9846ad | ||
|
|
3aeba7ca8a | ||
|
|
2a44a091c8 | ||
|
|
4c92aebc3c | ||
|
|
d4ecd0dfab | ||
|
|
3f790d01fa | ||
|
|
e7b068ed8e | ||
|
|
bd485937c4 | ||
|
|
8edc332a4e | ||
|
|
bb5028364b | ||
|
|
ef070a4e0e | ||
|
|
6787d0224c | ||
|
|
8a43e24095 | ||
|
|
f879f549e4 | ||
|
|
db55484163 | ||
|
|
4d8f66f28e | ||
|
|
7a44061fa3 | ||
|
|
5d4bb42e39 | ||
|
|
3a6c22da5c | ||
|
|
064f0e414a | ||
|
|
77db3cb6fa | ||
|
|
b83a1fd102 | ||
|
|
99c519c065 | ||
|
|
632e52b38d | ||
|
|
1b66ffac6c | ||
|
|
ee9052ad3d | ||
|
|
550c74da77 | ||
|
|
ccdd450283 | ||
|
|
224a607bc3 | ||
|
|
8fcd23663c | ||
|
|
ad79a71fbd | ||
|
|
d862a59349 | ||
|
|
2d6362dddb | ||
|
|
ee3ec3a4ea | ||
|
|
daecfd97c2 | ||
|
|
200a81d536 | ||
|
|
8059ac89d3 | ||
|
|
60f5f07dd6 | ||
|
|
372d5ce413 | ||
|
|
6f97819ca7 | ||
|
|
2b2ee56712 | ||
|
|
3715326034 | ||
|
|
6cbb8b1753 | ||
|
|
806896ea05 | ||
|
|
fc8746e077 | ||
|
|
e11df5bb49 | ||
|
|
37a9e98ebc | ||
|
|
80b4975188 | ||
|
|
c4ef40f4dc | ||
|
|
6a4bb6e3e1 | ||
|
|
05ef926a7f | ||
|
|
0007451735 | ||
|
|
e599de038a | ||
|
|
61472a995f | ||
|
|
2a41802f36 | ||
|
|
1d1cee17c3 | ||
|
|
e99266f9d8 | ||
|
|
1f2cd064f7 | ||
|
|
9c2cf9eef7 | ||
|
|
38b0b79644 | ||
|
|
5252834075 | ||
|
|
162df5eb6c | ||
|
|
ac5e2e0532 | ||
|
|
f0ba6afbdf | ||
|
|
04a5f43472 | ||
|
|
a15ef4b7ce | ||
|
|
f1f9147433 | ||
|
|
3c0d7de377 | ||
|
|
2a57d74f1a | ||
|
|
79717859b3 | ||
|
|
be423939ed | ||
|
|
086cceb271 | ||
|
|
a14033afb7 | ||
|
|
cc89a342ed | ||
|
|
25c3669564 | ||
|
|
21eff0b2ec | ||
|
|
472fd72c82 | ||
|
|
429a9a42d3 | ||
|
|
90c525e99a | ||
|
|
196117998a | ||
|
|
630cbc77a8 | ||
|
|
b6e4afe321 | ||
|
|
59085ff8c8 | ||
|
|
a4274c6301 | ||
|
|
b4ef44b343 | ||
|
|
08bc97582b | ||
|
|
3952c88510 | ||
|
|
b1f27b9da7 | ||
|
|
171b258d5c | ||
|
|
af971b6a19 | ||
|
|
7ca026393b | ||
|
|
f8784ae3c7 | ||
|
|
3ddc3acf4c | ||
|
|
ff430f5e33 | ||
|
|
daf2890161 | ||
|
|
4ca639323d | ||
|
|
a92bf155a3 | ||
|
|
d153772eb2 | ||
|
|
cdc8fe86ce | ||
|
|
4844037ce9 | ||
|
|
691c1e1a37 | ||
|
|
bd55b91a86 | ||
|
|
8842f53696 | ||
|
|
ffed9f6116 | ||
|
|
50e8f45601 | ||
|
|
8cbfe9e6cf | ||
|
|
99ad3dc292 | ||
|
|
6908355d38 | ||
|
|
7b948f83c3 | ||
|
|
34b2b96158 | ||
|
|
d1573a0a6e | ||
|
|
16d6bda85d | ||
|
|
4b3eb2ece5 | ||
|
|
1089de6321 | ||
|
|
d00dc798f4 | ||
|
|
f6b32823ba | ||
|
|
9e34fee58c | ||
|
|
1684a2110c | ||
|
|
5e00e34552 | ||
|
|
ce204eba62 | ||
|
|
c7cb652322 | ||
|
|
f8ccc3128e | ||
|
|
4a8baaef45 | ||
|
|
a9f3939c83 | ||
|
|
d8cb950248 | ||
|
|
aefc51db4b | ||
|
|
fb18ea7ff8 | ||
|
|
407c61e212 | ||
|
|
d8e6ad48ca | ||
|
|
bd42f4188f | ||
|
|
f766f383ea | ||
|
|
1a9922d790 | ||
|
|
6213623431 | ||
|
|
2809ee7a3e | ||
|
|
281cae7a18 | ||
|
|
1670751c94 | ||
|
|
e1ead9d2ef | ||
|
|
359a9a96d6 | ||
|
|
b6cfb8a3dc | ||
|
|
6f028ecb19 | ||
|
|
8695466690 | ||
|
|
bdb1be9967 | ||
|
|
9395df4cc3 | ||
|
|
93edb333d4 | ||
|
|
30eeef46c2 | ||
|
|
8b584f3922 | ||
|
|
0c354c4fdb | ||
|
|
bd7413119a | ||
|
|
f26915aab6 | ||
|
|
f6068dc69d | ||
|
|
bd2c65cd94 | ||
|
|
4777910644 | ||
|
|
66b26e52ce | ||
|
|
a758267d72 | ||
|
|
72eae64698 | ||
|
|
d63c18f7f0 | ||
|
|
b05e3ca8d8 | ||
|
|
0a88100b67 | ||
|
|
43b859f778 | ||
|
|
d1bd7f695f | ||
|
|
312e1378d3 | ||
|
|
55d6825f63 | ||
|
|
e4f9af7076 | ||
|
|
ff52fe4884 | ||
|
|
0ab29b7c1f | ||
|
|
d742ed7b65 | ||
|
|
a684e380b7 | ||
|
|
c90feaf3db | ||
|
|
460610f672 | ||
|
|
46511533aa | ||
|
|
40efed6580 | ||
|
|
78b1c1eb4a | ||
|
|
5a59a3dd50 | ||
|
|
0763280196 | ||
|
|
93f2518159 | ||
|
|
273f731dd5 | ||
|
|
c7cd9e86ac | ||
|
|
41fb6f5464 | ||
|
|
03b1a8bd41 | ||
|
|
e7d0685ebc | ||
|
|
aadbfe1eed | ||
|
|
4a54fbb872 | ||
|
|
8b1836d699 | ||
|
|
c47d4fd35a | ||
|
|
a58af1275c | ||
|
|
1d63b39553 | ||
|
|
c300d52b29 | ||
|
|
5314e275bc | ||
|
|
08f8b9770a | ||
|
|
a9c64b2fec | ||
|
|
1edfa78a05 | ||
|
|
1df8af35d4 | ||
|
|
f47c5e53b1 | ||
|
|
a48cbc6971 | ||
|
|
73be8cf074 | ||
|
|
002a1412cb | ||
|
|
049cd2d236 | ||
|
|
e99714eba6 | ||
|
|
4e1423d224 | ||
|
|
4751075e87 | ||
|
|
416e0fb609 | ||
|
|
f96a371464 | ||
|
|
e423192265 | ||
|
|
e9444e058c | ||
|
|
037632fbf0 | ||
|
|
6cdea85a49 | ||
|
|
f86d755890 | ||
|
|
98cc97251a | ||
|
|
562f414e3e | ||
|
|
1afc301432 | ||
|
|
115b44585b | ||
|
|
3ff47623d5 | ||
|
|
943e03f9d8 | ||
|
|
f7a534a0d0 | ||
|
|
72e30d8e40 | ||
|
|
eb265300fc | ||
|
|
704b8f61dd | ||
|
|
40957c445f | ||
|
|
8eead9fda2 | ||
|
|
aadc8168be | ||
|
|
cb33f04bfc | ||
|
|
82cb71bf3f | ||
|
|
c37b88a239 | ||
|
|
de59bf695d | ||
|
|
592627b013 | ||
|
|
9ed1fb2588 | ||
|
|
8232a92653 | ||
|
|
09eeaa92d1 | ||
|
|
1e4b1a2c70 | ||
|
|
0a1e7a7c86 | ||
|
|
e5f3b2bf6e | ||
|
|
29183c10ff | ||
|
|
46b8bdace7 | ||
|
|
9b6924ec9f | ||
|
|
c69de107e5 | ||
|
|
c4d451e420 | ||
|
|
be6bce5771 | ||
|
|
9bcccc87e6 | ||
|
|
bf845be727 | ||
|
|
e92a5414d1 | ||
|
|
657125f43c | ||
|
|
e98f68e93b | ||
|
|
bef84e9eec | ||
|
|
6a5f2402c7 | ||
|
|
d6cc6ba144 | ||
|
|
07f8dcb3ca | ||
|
|
a026143a84 | ||
|
|
73a5b6738d | ||
|
|
3e2b12ae4a | ||
|
|
8073364b7a | ||
|
|
b141d96e7c | ||
|
|
a86e8b98fe | ||
|
|
a7d77716f3 | ||
|
|
b017e439b1 | ||
|
|
a7156f665a | ||
|
|
44e34d084e | ||
|
|
065820ffa4 | ||
|
|
26991928ae | ||
|
|
f4fa68c390 | ||
|
|
8b86f9ea6d | ||
|
|
48f966e7db | ||
|
|
d8746dc592 | ||
|
|
6421d3017e | ||
|
|
f9e771f8f7 | ||
|
|
09456ce421 | ||
|
|
716f7e722b | ||
|
|
ffa4b1483f | ||
|
|
76f7165462 | ||
|
|
fdf0d8e9c8 | ||
|
|
6651aa924f | ||
|
|
369fd95e2b | ||
|
|
e242adec66 | ||
|
|
58e562f7d4 | ||
|
|
70238fd773 | ||
|
|
fc4e007cc4 | ||
|
|
b7667ce97a | ||
|
|
1315da0da7 | ||
|
|
c4d8eae547 | ||
|
|
b01fc1be62 | ||
|
|
c46e3cf5cb | ||
|
|
1a6b915112 | ||
|
|
2f38943488 | ||
|
|
06711dc6c3 | ||
|
|
cc7e342ab7 | ||
|
|
5b64743987 | ||
|
|
a84ad031d9 | ||
|
|
c4c2fe2a9c | ||
|
|
8ccaef454c | ||
|
|
27579dff37 | ||
|
|
389d08c233 | ||
|
|
5412a087fe | ||
|
|
dd0f3ac651 | ||
|
|
4d4d776e4d | ||
|
|
7877b107c1 | ||
|
|
a2aa0aa9a8 | ||
|
|
b3475d30c0 | ||
|
|
6484c8d636 | ||
|
|
587cf554f2 | ||
|
|
1d4e4eb6b3 | ||
|
|
31c4ed7d0e | ||
|
|
7f246b2d3d | ||
|
|
7d68cff700 | ||
|
|
4d80bdcc9f | ||
|
|
8b4a94e5aa | ||
|
|
111ad14ad3 | ||
|
|
aec3f19d23 | ||
|
|
d8b80f961a | ||
|
|
891bb7fa40 | ||
|
|
0e3af45466 | ||
|
|
6aebbc3109 | ||
|
|
15eb7f3186 | ||
|
|
fb4cd98014 | ||
|
|
eb692dea59 | ||
|
|
d8039fb542 | ||
|
|
5e06d19d77 | ||
|
|
963f390336 | ||
|
|
2309e15261 | ||
|
|
8491035b2f | ||
|
|
4d4107aefc | ||
|
|
67d2b9131e | ||
|
|
da8644168c | ||
|
|
c0004e988a | ||
|
|
b9187445e0 | ||
|
|
554ebf7ab9 | ||
|
|
3e54cd7284 | ||
|
|
a7afc23a9a | ||
|
|
fa3a047519 | ||
|
|
f24fab0fa2 | ||
|
|
261620fd13 | ||
|
|
9af6effad9 | ||
|
|
92602916dd | ||
|
|
f4d215664e | ||
|
|
84894a557a | ||
|
|
ffad6e4c61 | ||
|
|
93b266e6be | ||
|
|
f92ea28581 | ||
|
|
85213e4b6c | ||
|
|
4e46119e18 | ||
|
|
fdc6e9f1c3 | ||
|
|
cb20f1e212 | ||
|
|
e3fccd7125 | ||
|
|
15142c1ec3 | ||
|
|
6c7b90e1c3 | ||
|
|
d8e57144f7 | ||
|
|
49fe8a427a | ||
|
|
4587428d13 | ||
|
|
5318e77035 | ||
|
|
e69c45a246 | ||
|
|
3f87a6d714 | ||
|
|
841124b1f3 | ||
|
|
257a878ef4 | ||
|
|
07d3f82912 | ||
|
|
7d3eb4f5a6 | ||
|
|
0efcc55373 | ||
|
|
eafceb8a6c | ||
|
|
5ac8318e71 | ||
|
|
7981f9dc91 | ||
|
|
a9bf04cbc6 | ||
|
|
4b5591d884 | ||
|
|
c08197f025 | ||
|
|
5d22eabfa3 | ||
|
|
c3a38e384a | ||
|
|
0d3dd94559 | ||
|
|
9cdaa37519 | ||
|
|
202a319d69 | ||
|
|
198384b2ed | ||
|
|
9d8b070f1e | ||
|
|
28ba2d5008 | ||
|
|
37ddd63d27 | ||
|
|
d7f8b8c1e0 | ||
|
|
9e70c5bbea | ||
|
|
4dd572063e | ||
|
|
db9cf95648 | ||
|
|
df6bae4712 | ||
|
|
56cb8209b8 | ||
|
|
aba9c2e113 | ||
|
|
3d25008739 | ||
|
|
9437f057d0 | ||
|
|
917d6089a7 | ||
|
|
f19e99e3ad | ||
|
|
2d0fb05fa6 | ||
|
|
c617f2dfad | ||
|
|
747c5fc89a | ||
|
|
4f662ef203 | ||
|
|
eb274ad88f | ||
|
|
bd7b41be9b | ||
|
|
076720bfff | ||
|
|
2aadae407e | ||
|
|
b16bb07774 | ||
|
|
81dd083388 | ||
|
|
92d4cef1e2 | ||
|
|
0cb5197ccf | ||
|
|
d2cd79dcf5 | ||
|
|
0ef8b391e4 | ||
|
|
0fbd105977 | ||
|
|
5c23af541e | ||
|
|
db021ff78b | ||
|
|
31294c6f54 | ||
|
|
03d3495759 | ||
|
|
4644e105a8 | ||
|
|
af5002508d | ||
|
|
9416d2b9c7 | ||
|
|
f60f5d5a6c | ||
|
|
26d00f87a8 | ||
|
|
6c33ea423c | ||
|
|
ad354aca4e | ||
|
|
ce6a5ebf0a | ||
|
|
d66ef38e8d | ||
|
|
1b6c49f621 | ||
|
|
0262c83815 | ||
|
|
c212934130 | ||
|
|
ac30e47e59 | ||
|
|
d640057453 | ||
|
|
0cea136d4d | ||
|
|
9b3d93e734 | ||
|
|
ca3782ac62 | ||
|
|
dc9ffb7fd5 | ||
|
|
b21b3a55fe | ||
|
|
e62bd85a7a | ||
|
|
fed221b008 | ||
|
|
935298d159 | ||
|
|
909919250c | ||
|
|
d24f278f7c | ||
|
|
ca408a495c | ||
|
|
ee99719137 | ||
|
|
5fb0729eae | ||
|
|
a1db3187cd | ||
|
|
488a3cf1ff | ||
|
|
1253a620a4 | ||
|
|
64547fc4a7 | ||
|
|
d643d140cf | ||
|
|
222e2e3242 | ||
|
|
ea7b6daf26 | ||
|
|
14cfab495d | ||
|
|
ed4b4a1a3c | ||
|
|
511c552188 | ||
|
|
8c650219eb | ||
|
|
189b779abe | ||
|
|
c26074b184 | ||
|
|
6c70779ff1 | ||
|
|
73897481ac | ||
|
|
a62c523ed5 | ||
|
|
0f46f57b52 | ||
|
|
bc4d65a20c | ||
|
|
f1e9a44ad9 | ||
|
|
61972c4719 | ||
|
|
9183c9c220 | ||
|
|
1ce59f13ff | ||
|
|
1bac5db6d5 | ||
|
|
58f2c4d8e6 | ||
|
|
2c2c61b2fc | ||
|
|
98b04a4a83 | ||
|
|
3ac64c44f4 | ||
|
|
3375847302 | ||
|
|
14043c86f5 | ||
|
|
030117780a | ||
|
|
971c9fe5a1 | ||
|
|
77c6d3d576 | ||
|
|
6edbfe2a6f | ||
|
|
d0a3125df4 | ||
|
|
d8c76d4c21 | ||
|
|
a1cc0897df | ||
|
|
e88a90f242 | ||
|
|
1ae54f6f8c | ||
|
|
8a4cb484fa | ||
|
|
93defbf341 | ||
|
|
ca1089da9a | ||
|
|
dd07c7db0e | ||
|
|
dfe932e37a | ||
|
|
568cf9c259 | ||
|
|
b4ef20e785 | ||
|
|
3a56dabf42 | ||
|
|
3ac4899b96 | ||
|
|
b7b228d9ce | ||
|
|
45339fd6d2 | ||
|
|
1856a5a82f | ||
|
|
b2aa703b62 | ||
|
|
b31897d65d | ||
|
|
a92f776ebe | ||
|
|
b84ed675f5 | ||
|
|
21eb98a52c | ||
|
|
75dd8d492b | ||
|
|
338893ded4 | ||
|
|
b459900040 | ||
|
|
d31300f4f9 | ||
|
|
f2285c0b19 | ||
|
|
684cb81974 | ||
|
|
9db272f30e | ||
|
|
c4a5e8dc86 | ||
|
|
df4dd0122f | ||
|
|
8fed18b2ac | ||
|
|
ecabbb57e6 | ||
|
|
8d1d4092aa | ||
|
|
6185c4ddcf | ||
|
|
3ccbbccd10 | ||
|
|
30e0ccc77b | ||
|
|
50f7b72b09 | ||
|
|
a5828c7949 | ||
|
|
22a5e72470 | ||
|
|
5d14dca818 | ||
|
|
f97cb821f8 | ||
|
|
d02dce5562 | ||
|
|
c6bd42843b | ||
|
|
43e4fbfcd0 | ||
|
|
ac8430cbba | ||
|
|
86d619be30 | ||
|
|
fec7672598 | ||
|
|
44d2744a8c | ||
|
|
22bfbe96c8 | ||
|
|
c0aca723da | ||
|
|
28e5ee51ec | ||
|
|
6c5c42c2b5 | ||
|
|
b1653b359e | ||
|
|
11098afab5 | ||
|
|
a64051e0f1 | ||
|
|
db8ac4a9ae | ||
|
|
890474a635 | ||
|
|
a39b10eee9 | ||
|
|
97b933a990 | ||
|
|
4926e90514 | ||
|
|
6dca975844 | ||
|
|
e31743770a | ||
|
|
d8aab62d75 | ||
|
|
f90603a18f | ||
|
|
5fce9facbe | ||
|
|
32b48d5cdb | ||
|
|
3e4f0d682b | ||
|
|
98449ddfe0 | ||
|
|
26040d4e9f | ||
|
|
7ab10aeb80 | ||
|
|
9316962e47 | ||
|
|
83cea5e1ee | ||
|
|
3e73a5dbd3 | ||
|
|
dd424a4cb3 | ||
|
|
0c79f5cce3 | ||
|
|
794c3703e5 | ||
|
|
b674006fcc | ||
|
|
505c528194 | ||
|
|
559c397b2f | ||
|
|
646698f1ed | ||
|
|
c9b938ae55 | ||
|
|
e4409e8ea4 | ||
|
|
990c220fa0 | ||
|
|
38641d4edf | ||
|
|
450072bd23 | ||
|
|
25eb93fae0 | ||
|
|
bcf6f60571 | ||
|
|
2076b8f1d7 | ||
|
|
f19cfb75e6 | ||
|
|
ceaacc771d | ||
|
|
48067e3285 | ||
|
|
003855a94c | ||
|
|
e7a26b436d | ||
|
|
ab94bc18dc | ||
|
|
36e91ea155 | ||
|
|
67499bdc65 | ||
|
|
222c8fdb62 | ||
|
|
9d648bad51 | ||
|
|
1a62b9a161 | ||
|
|
33021e8fe0 | ||
|
|
5a2ae4c3e3 | ||
|
|
9eaef84cc8 | ||
|
|
fcc4d655f5 | ||
|
|
8d0ac4f5f0 | ||
|
|
d56cf003f8 | ||
|
|
34d0c0b1ba | ||
|
|
9bbbffbe7a | ||
|
|
ccb9bceecc | ||
|
|
c1a67ff1f8 | ||
|
|
bfda8dcc02 | ||
|
|
ff90f257cc | ||
|
|
9b84046865 | ||
|
|
f62bfeac08 | ||
|
|
49a58dcab1 | ||
|
|
e2acbeddc2 | ||
|
|
449b17d830 | ||
|
|
7ed460ce02 | ||
|
|
edcb692f78 | ||
|
|
f33586f062 | ||
|
|
d61bf26e17 | ||
|
|
2e662b5745 | ||
|
|
9714fa369b | ||
|
|
0e8acba08e | ||
|
|
a18e588e55 | ||
|
|
bf55e3c0cc | ||
|
|
486d114e64 | ||
|
|
ac5c060a98 | ||
|
|
86a9d197cb | ||
|
|
70aac81900 | ||
|
|
28e78d98f6 | ||
|
|
fabc5ae032 | ||
|
|
e40882455d | ||
|
|
f1483e8c8e | ||
|
|
bd40e2c3ff | ||
|
|
9479498713 | ||
|
|
6bd8523ec2 | ||
|
|
c864b15c34 | ||
|
|
069654c5f9 | ||
|
|
5ac17ee6b8 | ||
|
|
26e22f97ee | ||
|
|
ad5535af81 | ||
|
|
cdcfb4ffce | ||
|
|
5403ac8893 | ||
|
|
9b26457781 | ||
|
|
939cc56951 | ||
|
|
23309e6fdf | ||
|
|
3f60c961d9 | ||
|
|
9e7f07e196 | ||
|
|
eda4439ee8 | ||
|
|
ad02558ade | ||
|
|
0d1901cfe5 | ||
|
|
ce493d1ae2 | ||
|
|
09312ecd1d | ||
|
|
2f274d5f52 | ||
|
|
6dbf226365 | ||
|
|
6401b5f54d | ||
|
|
ac2dd81d39 | ||
|
|
44b2e62eef | ||
|
|
7d5d4df761 | ||
|
|
da7716db60 | ||
|
|
862c5d342b | ||
|
|
6167d5dbfc | ||
|
|
f9341bea79 | ||
|
|
3a608bb582 | ||
|
|
c926482b3c | ||
|
|
9562972c42 | ||
|
|
dd56a5d869 | ||
|
|
8ae45240b2 | ||
|
|
6d845af7f1 | ||
|
|
733ebb8caf | ||
|
|
695a194467 | ||
|
|
8a56257ade | ||
|
|
4d5dc0d39c | ||
|
|
8589ee14ff | ||
|
|
1727387c9c | ||
|
|
e1146e4655 | ||
|
|
c29b0645bd | ||
|
|
96dac0f979 | ||
|
|
fda9b59129 | ||
|
|
fa5896ee5b | ||
|
|
3c6d27b504 | ||
|
|
6ef25eb861 | ||
|
|
ec28e972bb | ||
|
|
d1a9033525 | ||
|
|
2d5bc3ada8 | ||
|
|
506ffb9885 | ||
|
|
8ef702fa07 | ||
|
|
9fc38b5bb8 | ||
|
|
54ac5e8940 | ||
|
|
e2341363d4 | ||
|
|
f1aa3d8c90 | ||
|
|
cf3e53eb71 | ||
|
|
b8865e925d | ||
|
|
2e9a860aaa | ||
|
|
e4bef056e6 | ||
|
|
803b8eab28 | ||
|
|
08bbfc50ee | ||
|
|
4e6722f201 | ||
|
|
a29e2116a7 | ||
|
|
515be677a9 | ||
|
|
d694c5f511 | ||
|
|
66c753f3a3 | ||
|
|
7047b62442 | ||
|
|
5cc73555cd | ||
|
|
219922cd82 | ||
|
|
7124d9bca5 | ||
|
|
08127e5806 | ||
|
|
6417bd91ef | ||
|
|
cde5f7d12e | ||
|
|
395c9587b6 | ||
|
|
17197ad670 | ||
|
|
910c10f554 | ||
|
|
04e974b326 | ||
|
|
e7abeb5ad9 | ||
|
|
2a18eacf62 | ||
|
|
af42e32ae6 | ||
|
|
12b93d6637 | ||
|
|
930c971035 | ||
|
|
06f20c66f8 | ||
|
|
7c875a8541 | ||
|
|
f85e19c75d | ||
|
|
5e2aa51627 | ||
|
|
75a44fb30a |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
liberapay: TeamNewPipe
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,7 +7,6 @@
|
||||
/app/app.iml
|
||||
/.idea
|
||||
/*.iml
|
||||
gradle.properties
|
||||
*~
|
||||
.weblate
|
||||
*.class
|
||||
|
||||
31
README.md
31
README.md
@@ -12,11 +12,13 @@
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#updates">Updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||
<p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p>
|
||||
<hr>
|
||||
|
||||
<b>WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
|
||||
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
|
||||
|
||||
<b>PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -64,15 +66,36 @@ NewPipe does not use any Google framework libraries, nor the YouTube API. Websit
|
||||
* Enqueue videos
|
||||
* Local playlists
|
||||
* Subtitles
|
||||
* Multi-service support (e.g. SoundCloud \[beta\])
|
||||
* Livestream support
|
||||
* Show comments
|
||||
|
||||
### Coming Features
|
||||
|
||||
* Cast to UPnP and Cast
|
||||
* Show comments
|
||||
* … and many more
|
||||
|
||||
### Supported Services
|
||||
|
||||
NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/documentation/) provide more info on how a new service can be added to the app and the extractor. Please get in touch with us if you intend to add a new one. Currently supported services are:
|
||||
|
||||
* YouTube
|
||||
* SoundCloud \[beta\]
|
||||
* media.ccc.de \[beta\]
|
||||
|
||||
## Updates
|
||||
When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
|
||||
* Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
|
||||
* Download the APK from [releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
|
||||
* Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
|
||||
|
||||
When you install an APK from one of these options, it will be incompatible with an APK from one of the other options. This is due to different signing keys being used. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app, and are independent. F-Droid and GitHub use different signing keys, and building an APK debug excludes a key. The signing key issue is being discussed in issue [#1981](https://github.com/TeamNewPipe/NewPipe/issues/1981), and may be fixed by setting up our own repository on F-Droid.
|
||||
|
||||
In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the update yet), we recommend following this procedure:
|
||||
1. Back up your data via "Settings>Content>Export Database" so you keep your history, subscriptions, and playlists
|
||||
2. Uninstall NewPipe
|
||||
3. Download the APK from the new source and install it
|
||||
4. Import the data from step 1 via "Settings>Content>Import Database"
|
||||
|
||||
## Contribution
|
||||
Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome.
|
||||
The more is done the better it gets!
|
||||
|
||||
@@ -8,18 +8,20 @@ android {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 70
|
||||
versionName "0.15.0"
|
||||
versionCode 790
|
||||
versionName "0.17.4"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
debuggable true
|
||||
@@ -33,6 +35,7 @@ android {
|
||||
// but continue the build even when errors are found:
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
@@ -40,38 +43,40 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
supportLibVersion = '28.0.0'
|
||||
exoPlayerLibVersion = '2.8.4' //2.9.0
|
||||
roomDbLibVersion = '1.1.1'
|
||||
androidxLibVersion = '1.0.0'
|
||||
exoPlayerLibVersion = '2.10.6'
|
||||
roomDbLibVersion = '2.1.0'
|
||||
leakCanaryLibVersion = '1.5.4' //1.6.1
|
||||
okHttpLibVersion = '3.11.0'
|
||||
okHttpLibVersion = '3.12.6'
|
||||
icepickLibVersion = '3.2.0'
|
||||
stethoLibVersion = '1.5.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2', {
|
||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
||||
exclude module: 'support-annotations'
|
||||
})
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:99915e4527c0'
|
||||
|
||||
implementation 'com.github.teamnewpipe:NewPipeExtractor:v0.17.4'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
|
||||
implementation "com.android.support:appcompat-v7:${supportLibVersion}"
|
||||
implementation "com.android.support:support-v4:${supportLibVersion}"
|
||||
implementation "com.android.support:design:${supportLibVersion}"
|
||||
implementation "com.android.support:recyclerview-v7:${supportLibVersion}"
|
||||
implementation "com.android.support:preference-v14:${supportLibVersion}"
|
||||
implementation "com.android.support:cardview-v7:${supportLibVersion}"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
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'
|
||||
|
||||
// Originally in NewPipeExtractor
|
||||
implementation 'com.grack:nanojson:1.1'
|
||||
implementation 'org.jsoup:jsoup:1.9.2'
|
||||
|
||||
implementation 'ch.acra:acra:4.9.2' //4.11
|
||||
|
||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1'
|
||||
implementation 'com.nononsenseapps:filepicker:4.2.1'
|
||||
|
||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
|
||||
@@ -79,15 +84,15 @@ dependencies {
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
|
||||
debugImplementation 'com.android.support:multidex:1.0.3'
|
||||
debugImplementation 'androidx.multidex:multidex:2.0.1'
|
||||
|
||||
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 "android.arch.persistence.room:runtime:${roomDbLibVersion}"
|
||||
implementation "android.arch.persistence.room:rxjava2:${roomDbLibVersion}"
|
||||
annotationProcessor "android.arch.persistence.room:compiler:${roomDbLibVersion}"
|
||||
implementation "androidx.room:room-runtime:${roomDbLibVersion}"
|
||||
implementation "androidx.room:room-rxjava2:${roomDbLibVersion}"
|
||||
annotationProcessor "androidx.room:room-compiler:${roomDbLibVersion}"
|
||||
|
||||
implementation "frankiesardo:icepick:${icepickLibVersion}"
|
||||
annotationProcessor "frankiesardo:icepick-processor:${icepickLibVersion}"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe.report;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.support.test.filters.LargeTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.schabi.newpipe;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.multidex.MultiDex;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.multidex.MultiDex;
|
||||
|
||||
import com.facebook.stetho.Stetho;
|
||||
import com.facebook.stetho.okhttp3.StethoInterceptor;
|
||||
|
||||
@@ -29,18 +29,12 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".player.old.PlayVideoActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/OldVideoPlayerTheme"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
|
||||
<service
|
||||
android:name=".player.BackgroundPlayer"
|
||||
android:exported="false">
|
||||
@@ -119,10 +113,9 @@
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
android:label="@string/reCaptchaActivity"/>
|
||||
<activity android:name=".download.ExtSDDownloadFailedActivity" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
@@ -184,6 +177,19 @@
|
||||
<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/"/>
|
||||
</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"/>
|
||||
|
||||
<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"/>
|
||||
</intent-filter>
|
||||
@@ -210,6 +216,42 @@
|
||||
<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"/>
|
||||
|
||||
<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"/>
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:pathPrefix="/watch"/>
|
||||
<!-- channel prefix -->
|
||||
<data android:pathPrefix="/channel/"/>
|
||||
<data android:pathPrefix="/user/"/>
|
||||
<!-- playlist prefix -->
|
||||
<data android:pathPrefix="/playlist"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Soundcloud filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.google.android.material.appbar;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
// check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489
|
||||
public final class FlingBehavior extends AppBarLayout.Behavior {
|
||||
|
||||
public FlingBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// remove reference to old nested scrolling child
|
||||
resetNestedScrollingChild();
|
||||
// Stop fling when your finger touches the screen
|
||||
stopAppBarLayoutFling();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.onInterceptTouchEvent(parent, child, ev);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private OverScroller getScrollerField() {
|
||||
try {
|
||||
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass();
|
||||
if (headerBehaviorType != null) {
|
||||
Field field = headerBehaviorType.getDeclaredField("scroller");
|
||||
field.setAccessible(true);
|
||||
return ((OverScroller) field.get(this));
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
// ?
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Field getLastNestedScrollingChildRefField() {
|
||||
try {
|
||||
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
|
||||
if (headerBehaviorType != null) {
|
||||
Field field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
// ?
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void resetNestedScrollingChild(){
|
||||
Field field = getLastNestedScrollingChildRefField();
|
||||
if(field != null){
|
||||
try {
|
||||
Object value = field.get(this);
|
||||
if(value != null) field.set(this, null);
|
||||
} catch (IllegalAccessException e) {
|
||||
// ?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAppBarLayoutFling() {
|
||||
OverScroller scroller = getScrollerField();
|
||||
if (scroller != null) scroller.forceFinished(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||
@@ -22,7 +23,6 @@ import org.acra.config.ConfigurationBuilder;
|
||||
import org.acra.sender.ReportSenderFactory;
|
||||
import org.schabi.newpipe.extractor.Downloader;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.utils.Localization;
|
||||
import org.schabi.newpipe.report.AcraReportSenderFactory;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
@@ -65,6 +65,7 @@ import io.reactivex.plugins.RxJavaPlugins;
|
||||
public class App extends Application {
|
||||
protected static final String TAG = App.class.toString();
|
||||
private RefWatcher refWatcher;
|
||||
private static App app;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Class<? extends ReportSenderFactory>[]
|
||||
@@ -88,6 +89,8 @@ public class App extends Application {
|
||||
}
|
||||
refWatcher = installLeakCanary();
|
||||
|
||||
app = this;
|
||||
|
||||
// Initialize settings first because others inits can use its values
|
||||
SettingsActivity.initSettings(this);
|
||||
|
||||
@@ -100,6 +103,9 @@ public class App extends Application {
|
||||
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
|
||||
|
||||
configureRxJavaErrorHandler();
|
||||
|
||||
// Check for new version
|
||||
new CheckForNewAppVersionTask().execute();
|
||||
}
|
||||
|
||||
protected Downloader getDownloader() {
|
||||
@@ -211,6 +217,31 @@ public class App extends Application {
|
||||
NotificationManager mNotificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
|
||||
setUpUpdateNotificationChannel(importance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up notification channel for app update.
|
||||
* @param importance
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private void setUpUpdateNotificationChannel(int importance) {
|
||||
|
||||
final String appUpdateId
|
||||
= getString(R.string.app_update_notification_channel_id);
|
||||
final CharSequence appUpdateName
|
||||
= getString(R.string.app_update_notification_channel_name);
|
||||
final String appUpdateDescription
|
||||
= getString(R.string.app_update_notification_channel_description);
|
||||
|
||||
NotificationChannel appUpdateChannel
|
||||
= new NotificationChannel(appUpdateId, appUpdateName, importance);
|
||||
appUpdateChannel.setDescription(appUpdateDescription);
|
||||
|
||||
NotificationManager appUpdateNotificationManager
|
||||
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -226,4 +257,8 @@ public class App extends Application {
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package org.schabi.newpipe;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
|
||||
* If there is a newer version we show a notification, informing the user. On tapping
|
||||
* the notification, the user will be directed to the download link.
|
||||
*/
|
||||
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
|
||||
private static final Application app = App.getApp();
|
||||
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
|
||||
private static final int timeoutPeriod = 30;
|
||||
|
||||
private SharedPreferences mPrefs;
|
||||
private OkHttpClient client;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
|
||||
// Check if user has enabled/ disabled update checking
|
||||
// and if the current apk is a github one or not.
|
||||
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|
||||
|| !isGithubApk()) {
|
||||
this.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
|
||||
if(isCancelled() || !isConnected()) return null;
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
if (client == null) {
|
||||
|
||||
client = new OkHttpClient
|
||||
.Builder()
|
||||
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(newPipeApiUrl)
|
||||
.build();
|
||||
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
return response.body().string();
|
||||
} catch (IOException ex) {
|
||||
// connectivity problems, do not alarm user and fail silently
|
||||
if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String response) {
|
||||
|
||||
// Parse the json from the response.
|
||||
if (response != null) {
|
||||
|
||||
try {
|
||||
JSONObject mainObject = new JSONObject(response);
|
||||
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
|
||||
JSONObject githubObject = flavoursObject.getJSONObject("github");
|
||||
JSONObject githubStableObject = githubObject.getJSONObject("stable");
|
||||
|
||||
String versionName = githubStableObject.getString("version");
|
||||
String versionCode = githubStableObject.getString("version_code");
|
||||
String apkLocationUrl = githubStableObject.getString("apk");
|
||||
|
||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||
|
||||
} catch (JSONException ex) {
|
||||
// connectivity problems, do not alarm user and fail silently
|
||||
if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compare the current and latest available app version.
|
||||
* If a newer version is available, we show the update notification.
|
||||
* @param versionName
|
||||
* @param apkLocationUrl
|
||||
*/
|
||||
private void compareAppVersionAndShowNotification(String versionName,
|
||||
String apkLocationUrl,
|
||||
String versionCode) {
|
||||
|
||||
int NOTIFICATION_ID = 2000;
|
||||
|
||||
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||
PendingIntent pendingIntent
|
||||
= PendingIntent.getActivity(app, 0, intent, 0);
|
||||
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
||||
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(app.getString(R.string.app_update_notification_content_text)
|
||||
+ " " + versionName);
|
||||
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the apk's SHA1 key.
|
||||
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
|
||||
*/
|
||||
private static String getCertificateSHA1Fingerprint() {
|
||||
|
||||
PackageManager pm = app.getPackageManager();
|
||||
String packageName = app.getPackageName();
|
||||
int flags = PackageManager.GET_SIGNATURES;
|
||||
PackageInfo packageInfo = null;
|
||||
|
||||
try {
|
||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not find package info", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
Signature[] signatures = packageInfo.signatures;
|
||||
byte[] cert = signatures[0].toByteArray();
|
||||
InputStream input = new ByteArrayInputStream(cert);
|
||||
|
||||
CertificateFactory cf = null;
|
||||
X509Certificate c = null;
|
||||
|
||||
try {
|
||||
cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (CertificateException ex) {
|
||||
ErrorActivity.reportError(app, ex, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Certificate error", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
String hexString = null;
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
byte[] publicKey = md.digest(c.getEncoded());
|
||||
hexString = byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException ex1) {
|
||||
ErrorActivity.reportError(app, ex1, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
} catch (CertificateEncodingException ex2) {
|
||||
ErrorActivity.reportError(app, ex2, null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
return hexString;
|
||||
}
|
||||
|
||||
private static String byte2HexFormatted(byte[] arr) {
|
||||
|
||||
StringBuilder str = new StringBuilder(arr.length * 2);
|
||||
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
String h = Integer.toHexString(arr[i]);
|
||||
int l = h.length();
|
||||
if (l == 1) h = "0" + h;
|
||||
if (l > 2) h = h.substring(l - 2, l);
|
||||
str.append(h.toUpperCase());
|
||||
if (i < (arr.length - 1)) str.append(':');
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static boolean isGithubApk() {
|
||||
|
||||
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
|
||||
}
|
||||
|
||||
private boolean isConnected() {
|
||||
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm.getActiveNetworkInfo() != null
|
||||
&& cm.getActiveNetworkInfo().isConnected();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.schabi.newpipe.extractor.DownloadRequest;
|
||||
import org.schabi.newpipe.extractor.DownloadResponse;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.utils.Localization;
|
||||
|
||||
@@ -10,11 +12,14 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@@ -139,13 +144,16 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
|
||||
private ResponseBody getBody(String siteUrl, Map<String, String> customProperties) throws IOException, ReCaptchaException {
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("GET", null).url(siteUrl)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
.method("GET", null).url(siteUrl);
|
||||
|
||||
for (Map.Entry<String, String> header : customProperties.entrySet()) {
|
||||
requestBuilder.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
if (!customProperties.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
@@ -155,7 +163,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
final ResponseBody body = response.body();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested");
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
@@ -177,4 +185,112 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
public String download(String siteUrl) throws IOException, ReCaptchaException {
|
||||
return download(siteUrl, Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DownloadResponse get(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("GET", null).url(siteUrl);
|
||||
|
||||
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
|
||||
// set custom headers in request
|
||||
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
|
||||
for(String value : pair.getValue()){
|
||||
requestBuilder.addHeader(pair.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!requestHeaders.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
|
||||
final Request okRequest = requestBuilder.build();
|
||||
final Response response = client.newCall(okRequest).execute();
|
||||
final ResponseBody body = response.body();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DownloadResponse(response.code(), body.string(), response.headers().toMultimap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException {
|
||||
return get(siteUrl, DownloadRequest.emptyRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadResponse post(String siteUrl, DownloadRequest request) throws IOException, ReCaptchaException {
|
||||
|
||||
Map<String, List<String>> requestHeaders = request.getRequestHeaders();
|
||||
if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){
|
||||
// content type header is required. maybe throw an exception here
|
||||
return null;
|
||||
}
|
||||
|
||||
String contentType = requestHeaders.get("Content-Type").get(0);
|
||||
|
||||
RequestBody okRequestBody = null;
|
||||
if (null != request.getRequestBody()) {
|
||||
okRequestBody = RequestBody.create(MediaType.parse(contentType), request.getRequestBody());
|
||||
}
|
||||
final Request.Builder requestBuilder = new Request.Builder()
|
||||
.method("POST", okRequestBody).url(siteUrl);
|
||||
|
||||
// set custom headers in request
|
||||
for (Map.Entry<String, List<String>> pair : requestHeaders.entrySet()) {
|
||||
for(String value : pair.getValue()){
|
||||
requestBuilder.addHeader(pair.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!requestHeaders.containsKey("User-Agent")) {
|
||||
requestBuilder.header("User-Agent", USER_AGENT);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
}
|
||||
|
||||
final Request okRequest = requestBuilder.build();
|
||||
final Response response = client.newCall(okRequest).execute();
|
||||
final ResponseBody body = response.body();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DownloadResponse(response.code(), body.string(), response.headers().toMultimap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadResponse head(String siteUrl) throws IOException, ReCaptchaException {
|
||||
final Request request = new Request.Builder()
|
||||
.head().url(siteUrl)
|
||||
.addHeader("User-Agent", USER_AGENT)
|
||||
.build();
|
||||
final Response response = client.newCall(request).execute();
|
||||
|
||||
if (response.code() == 429) {
|
||||
throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl);
|
||||
}
|
||||
|
||||
return new DownloadResponse(response.code(), null, response.headers().toMultimap());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,17 +28,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -49,6 +39,17 @@ import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
@@ -73,6 +74,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
private DrawerLayout drawer = null;
|
||||
private NavigationView drawerItems = null;
|
||||
private TextView headerServiceView = null;
|
||||
private Button toggleServiceButton = null;
|
||||
|
||||
private boolean servicesShown = false;
|
||||
private ImageView serviceArrow;
|
||||
@@ -266,8 +268,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
serviceArrow = hView.findViewById(R.id.drawer_arrow);
|
||||
headerServiceView = hView.findViewById(R.id.drawer_header_service_view);
|
||||
Button action = hView.findViewById(R.id.drawer_header_action_button);
|
||||
action.setOnClickListener(view -> {
|
||||
toggleServiceButton = hView.findViewById(R.id.drawer_header_action_button);
|
||||
toggleServiceButton.setOnClickListener(view -> {
|
||||
toggleServices();
|
||||
});
|
||||
}
|
||||
@@ -279,6 +281,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
|
||||
drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
|
||||
|
||||
|
||||
if(servicesShown) {
|
||||
showServices();
|
||||
} else {
|
||||
@@ -359,11 +362,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
// close drawer on return, and don't show animation, so its looks like the drawer isn't open
|
||||
// when the user returns to MainActivity
|
||||
drawer.closeDrawer(Gravity.START, false);
|
||||
drawer.closeDrawer(GravityCompat.START, false);
|
||||
try {
|
||||
String selectedServiceName = NewPipe.getService(
|
||||
ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName();
|
||||
headerServiceView.setText(selectedServiceName);
|
||||
toggleServiceButton.setContentDescription(
|
||||
getString(R.string.drawer_header_description) + selectedServiceName);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
@@ -557,6 +562,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDrawerHeaderString(String content) {
|
||||
NavigationView navigationView = findViewById(R.id.navigation);
|
||||
View hView = navigationView.getHeaderView(0);
|
||||
Button action = hView.findViewById(R.id.drawer_header_action_button);
|
||||
|
||||
action.setContentDescription(content);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
try {
|
||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.arch.persistence.room.Room;
|
||||
import androidx.room.Room;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
@@ -37,15 +36,24 @@ import android.webkit.WebViewClient;
|
||||
*/
|
||||
public class ReCaptchaActivity extends AppCompatActivity {
|
||||
public static final int RECAPTCHA_REQUEST = 10;
|
||||
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";
|
||||
|
||||
private String url;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_recaptcha);
|
||||
|
||||
url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
||||
if (url == null || url.isEmpty()) {
|
||||
url = YT_URL;
|
||||
}
|
||||
|
||||
|
||||
// Set return to Cancel by default
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
@@ -73,15 +81,12 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
myWebView.clearHistory();
|
||||
android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeAllCookies(new ValueCallback<Boolean>() {
|
||||
@Override
|
||||
public void onReceiveValue(Boolean aBoolean) {}
|
||||
});
|
||||
cookieManager.removeAllCookies(aBoolean -> {});
|
||||
} else {
|
||||
cookieManager.removeAllCookie();
|
||||
}
|
||||
|
||||
myWebView.loadUrl(YT_URL);
|
||||
myWebView.loadUrl(url);
|
||||
}
|
||||
|
||||
private class ReCaptchaWebViewClient extends WebViewClient {
|
||||
|
||||
@@ -9,12 +9,12 @@ import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -26,6 +26,8 @@ import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
@@ -36,12 +38,12 @@ 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.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
@@ -74,17 +76,23 @@ import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||
*/
|
||||
public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
@State protected int currentServiceId = -1;
|
||||
@State
|
||||
protected int currentServiceId = -1;
|
||||
private StreamingService currentService;
|
||||
@State protected LinkType currentLinkType;
|
||||
@State protected int selectedRadioPosition = -1;
|
||||
@State
|
||||
protected LinkType currentLinkType;
|
||||
@State
|
||||
protected int selectedRadioPosition = -1;
|
||||
protected int selectedPreviously = -1;
|
||||
|
||||
protected String currentUrl;
|
||||
protected boolean internalRoute = false;
|
||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private boolean selectionIsDownload = false;
|
||||
|
||||
public static final String internalRouteKey = "internalRoute";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -94,11 +102,13 @@ public class RouterActivity extends AppCompatActivity {
|
||||
currentUrl = getUrl(getIntent());
|
||||
|
||||
if (TextUtils.isEmpty(currentUrl)) {
|
||||
Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show();
|
||||
handleText();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
internalRoute = getIntent().getBooleanExtra(internalRouteKey, false);
|
||||
|
||||
setTheme(ThemeHelper.isLightThemeSelected(this)
|
||||
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
||||
}
|
||||
@@ -112,7 +122,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
|
||||
handleUrl(currentUrl);
|
||||
}
|
||||
|
||||
@@ -252,7 +262,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
|
||||
.setPositiveButton(R.string.always, dialogButtonsClickListener)
|
||||
.setOnDismissListener((dialog) -> {
|
||||
if(!selectionIsDownload) finish();
|
||||
if (!selectionIsDownload) finish();
|
||||
})
|
||||
.create();
|
||||
|
||||
@@ -353,6 +363,15 @@ public class RouterActivity extends AppCompatActivity {
|
||||
positiveButton.setEnabled(state);
|
||||
}
|
||||
|
||||
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);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
NavigationHelper.openSearch(getThemeWrapperContext(), serviceId, searchString);
|
||||
}
|
||||
|
||||
private void handleChoice(final String selectedChoiceKey) {
|
||||
final List<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
|
||||
if (validChoicesList.contains(selectedChoiceKey)) {
|
||||
@@ -383,8 +402,10 @@ public class RouterActivity extends AppCompatActivity {
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(intent -> {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
if (!internalRoute) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
}
|
||||
startActivity(intent);
|
||||
|
||||
finish();
|
||||
@@ -414,7 +435,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this,
|
||||
sortedVideoStreams);
|
||||
|
||||
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(result);
|
||||
downloadDialog.setVideoStreams(sortedVideoStreams);
|
||||
downloadDialog.setAudioStreams(result.getAudioStreams());
|
||||
@@ -431,8 +452,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
for (int i: grantResults){
|
||||
if (i == PackageManager.PERMISSION_DENIED){
|
||||
for (int i : grantResults) {
|
||||
if (i == PackageManager.PERMISSION_DENIED) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
@@ -444,7 +465,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
private static class AdapterChoiceItem {
|
||||
final String description, key;
|
||||
@DrawableRes final int icon;
|
||||
@DrawableRes
|
||||
final int icon;
|
||||
|
||||
AdapterChoiceItem(String key, String description, int icon) {
|
||||
this.description = description;
|
||||
@@ -542,7 +564,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false);
|
||||
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);;
|
||||
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);
|
||||
;
|
||||
|
||||
PlayQueue playQueue;
|
||||
String playerChoice = choice.playerChoice;
|
||||
@@ -558,7 +581,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
playQueue = new SinglePlayQueue((StreamInfo) info);
|
||||
|
||||
if (playerChoice.equals(videoPlayerKey)) {
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue);
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
|
||||
} else if (playerChoice.equals(popupPlayerKey)) {
|
||||
@@ -571,11 +594,11 @@ public class RouterActivity extends AppCompatActivity {
|
||||
playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info);
|
||||
|
||||
if (playerChoice.equals(videoPlayerKey)) {
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue);
|
||||
NavigationHelper.playOnMainPlayer(this, playQueue, true);
|
||||
} else if (playerChoice.equals(backgroundPlayerKey)) {
|
||||
NavigationHelper.playOnBackgroundPlayer(this, playQueue);
|
||||
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
|
||||
} else if (playerChoice.equals(popupPlayerKey)) {
|
||||
NavigationHelper.playOnPopupPlayer(this, playQueue);
|
||||
NavigationHelper.playOnPopupPlayer(this, playQueue, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,13 +4,15 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -36,7 +38,6 @@ public class AboutActivity extends AppCompatActivity {
|
||||
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("CircleImageView", "2014 - 2017", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("ParalaxScrollView", "2014", "Nir Hartmann", "https://github.com/nirhart/ParallaxScroll", StandardLicenses.MIT),
|
||||
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2),
|
||||
new SoftwareComponent("ExoPlayer", "2014-2017", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
||||
@@ -45,12 +46,12 @@ public class AboutActivity extends AppCompatActivity {
|
||||
};
|
||||
|
||||
/**
|
||||
* The {@link android.support.v4.view.PagerAdapter} that will provide
|
||||
* The {@link PagerAdapter} that will provide
|
||||
* fragments for each of the sections. We use a
|
||||
* {@link FragmentPagerAdapter} derivative, which will keep every
|
||||
* loaded fragment in memory. If this becomes too memory intensive, it
|
||||
* may be best to switch to a
|
||||
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
|
||||
* {@link FragmentStatePagerAdapter}.
|
||||
*/
|
||||
private SectionsPagerAdapter mSectionsPagerAdapter;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.*;
|
||||
import android.widget.TextView;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
@@ -4,8 +4,8 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.webkit.WebView;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe.database;
|
||||
|
||||
import android.arch.persistence.room.Database;
|
||||
import android.arch.persistence.room.RoomDatabase;
|
||||
import android.arch.persistence.room.TypeConverters;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.room.TypeConverters;
|
||||
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.schabi.newpipe.database;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Delete;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Update;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.database;
|
||||
|
||||
import android.arch.persistence.room.TypeConverter;
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
package org.schabi.newpipe.database;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.room.migration.Migration;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.room.migration.Migration;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
|
||||
public class Migrations {
|
||||
|
||||
public static final int DB_VER_11_0 = 1;
|
||||
public static final int DB_VER_12_0 = 2;
|
||||
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
private static final String TAG = Migrations.class.getName();
|
||||
|
||||
public static final Migration MIGRATION_11_12 = new Migration(DB_VER_11_0, DB_VER_12_0) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
if(DEBUG) {
|
||||
Log.d(TAG, "Start migrating database");
|
||||
}
|
||||
/*
|
||||
* Unfortunately these queries must be hardcoded due to the possibility of
|
||||
* schema and names changing at a later date, thus invalidating the older migration
|
||||
@@ -56,6 +65,10 @@ public class Migrations {
|
||||
"ORDER BY creation_date DESC");
|
||||
|
||||
database.execSQL("DROP TABLE IF EXISTS watch_history");
|
||||
|
||||
if(DEBUG) {
|
||||
Log.d(TAG, "Stop migrating database");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe.database.history.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Query;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.schabi.newpipe.database.history.dao;
|
||||
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Query;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
@@ -50,6 +50,11 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity
|
||||
" ORDER BY " + STREAM_ACCESS_DATE + " DESC")
|
||||
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
|
||||
|
||||
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID +
|
||||
" = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
|
||||
@Nullable
|
||||
public abstract StreamHistoryEntity getLatestEntry(final long streamId);
|
||||
|
||||
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
|
||||
public abstract int deleteStreamHistory(final long streamId);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.schabi.newpipe.database.history.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Ignore;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package org.schabi.newpipe.database.history.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Ignore;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.ForeignKey;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.Index;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
import static androidx.room.ForeignKey.CASCADE;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.database.history.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import androidx.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.database.playlist;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import androidx.room.ColumnInfo;
|
||||
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.database.playlist;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import androidx.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.schabi.newpipe.database.playlist.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Query;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe.database.playlist.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Transaction;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe.database.playlist.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Transaction;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.schabi.newpipe.database.playlist.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.schabi.newpipe.database.playlist.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Ignore;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.schabi.newpipe.database.playlist.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.ForeignKey;
|
||||
import androidx.room.Index;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
import static androidx.room.ForeignKey.CASCADE;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.database.stream;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import androidx.room.ColumnInfo;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.schabi.newpipe.database.stream.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Transaction;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.schabi.newpipe.database.stream.dao;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Transaction;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.schabi.newpipe.database.stream.model;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Ignore;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.schabi.newpipe.database.stream.model;
|
||||
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.ForeignKey;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static androidx.room.ForeignKey.CASCADE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
||||
|
||||
@@ -22,6 +25,12 @@ public class StreamStateEntity {
|
||||
final public static String JOIN_STREAM_ID = "stream_id";
|
||||
final public static String STREAM_PROGRESS_TIME = "progress_time";
|
||||
|
||||
|
||||
/** Playback state will not be saved, if playback time less than this threshold */
|
||||
private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5;
|
||||
/** Playback state will not be saved, if time left less than this threshold */
|
||||
private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10;
|
||||
|
||||
@ColumnInfo(name = JOIN_STREAM_ID)
|
||||
private long streamUid;
|
||||
|
||||
@@ -48,4 +57,18 @@ public class StreamStateEntity {
|
||||
public void setProgressTime(long progressTime) {
|
||||
this.progressTime = progressTime;
|
||||
}
|
||||
|
||||
public boolean isValid(int durationInSeconds) {
|
||||
final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime);
|
||||
return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS
|
||||
&& seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj instanceof StreamStateEntity) {
|
||||
return ((StreamStateEntity) obj).streamUid == streamUid
|
||||
&& ((StreamStateEntity) obj).progressTime == progressTime;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.schabi.newpipe.database.subscription;
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Transaction;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.schabi.newpipe.database.subscription;
|
||||
|
||||
import android.arch.persistence.room.ColumnInfo;
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Ignore;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.schabi.newpipe.download;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -47,7 +47,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
updateFragments();
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -55,12 +55,13 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
private void updateFragments() {
|
||||
MissionsFragment fragment = new MissionsFragment();
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
@@ -86,9 +87,4 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Bundle inState){
|
||||
super.onRestoreInstanceState(inState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.view.menu.ActionMenuItemView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -24,6 +34,8 @@ import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nononsenseapps.filepicker.Utils;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
@@ -34,7 +46,10 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.extractor.utils.Localization;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
import org.schabi.newpipe.util.FilenameUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
@@ -43,19 +58,28 @@ import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import icepick.Icepick;
|
||||
import icepick.State;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import us.shandian.giga.io.StoredDirectoryHelper;
|
||||
import us.shandian.giga.io.StoredFileHelper;
|
||||
import us.shandian.giga.postprocessing.Postprocessing;
|
||||
import us.shandian.giga.service.DownloadManager;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder;
|
||||
import us.shandian.giga.service.MissionState;
|
||||
|
||||
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
|
||||
private static final String TAG = "DialogFragment";
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230;
|
||||
|
||||
@State
|
||||
protected StreamInfo currentInfo;
|
||||
@@ -80,7 +104,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
|
||||
private EditText nameEditText;
|
||||
private Spinner streamsSpinner;
|
||||
private RadioGroup radioVideoAudioGroup;
|
||||
private RadioGroup radioStreamsGroup;
|
||||
private TextView threadsCountTextView;
|
||||
private SeekBar threadsSeekBar;
|
||||
|
||||
@@ -155,12 +179,15 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
super.onCreate(savedInstanceState);
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
|
||||
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
||||
getDialog().dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext()));
|
||||
context = getContext();
|
||||
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
|
||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||
|
||||
SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
|
||||
@@ -177,9 +204,33 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
}
|
||||
}
|
||||
|
||||
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, secondaryStreams);
|
||||
this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams);
|
||||
this.subtitleStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedSubtitleStreams);
|
||||
this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams, secondaryStreams);
|
||||
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
|
||||
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
|
||||
|
||||
Intent intent = new Intent(context, DownloadManagerService.class);
|
||||
context.startService(intent);
|
||||
|
||||
context.bindService(intent, new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName cname, IBinder service) {
|
||||
DownloadManagerBinder mgr = (DownloadManagerBinder) service;
|
||||
|
||||
mainStorageAudio = mgr.getMainStorageAudio();
|
||||
mainStorageVideo = mgr.getMainStorageVideo();
|
||||
downloadManager = mgr.getDownloadManager();
|
||||
askForSavePath = mgr.askForSavePath();
|
||||
|
||||
okButton.setEnabled(true);
|
||||
|
||||
context.unbindService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
// nothing to do
|
||||
}
|
||||
}, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -204,8 +255,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
threadsCountTextView = view.findViewById(R.id.threads_count);
|
||||
threadsSeekBar = view.findViewById(R.id.threads);
|
||||
|
||||
radioVideoAudioGroup = view.findViewById(R.id.video_audio_group);
|
||||
radioVideoAudioGroup.setOnCheckedChangeListener(this);
|
||||
radioStreamsGroup = view.findViewById(R.id.video_audio_group);
|
||||
radioStreamsGroup.setOnCheckedChangeListener(this);
|
||||
|
||||
initToolbar(view.findViewById(R.id.toolbar));
|
||||
setupDownloadOptions();
|
||||
@@ -240,17 +291,17 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
disposables.clear();
|
||||
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> {
|
||||
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.video_button) {
|
||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) {
|
||||
setupVideoSpinner();
|
||||
}
|
||||
}));
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> {
|
||||
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
|
||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) {
|
||||
setupAudioSpinner();
|
||||
}
|
||||
}));
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> {
|
||||
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
|
||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
|
||||
setupSubtitleSpinner();
|
||||
}
|
||||
}));
|
||||
@@ -263,21 +314,55 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
Icepick.saveInstanceState(this, outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) {
|
||||
if (data.getData() == null) {
|
||||
showFailedDialog(R.string.general_error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
|
||||
File file = Utils.getFileForUri(data.getData());
|
||||
checkSelectedDownload(null, Uri.fromFile(file), file.getName(), StoredFileHelper.DEFAULT_MIME);
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData());
|
||||
if (docFile == null) {
|
||||
showFailedDialog(R.string.general_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the selected file was previously used
|
||||
checkSelectedDownload(null, data.getData(), docFile.getName(), docFile.getType());
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Inits
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void initToolbar(Toolbar toolbar) {
|
||||
if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
||||
|
||||
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
|
||||
|
||||
toolbar.setTitle(R.string.download_dialog_title);
|
||||
toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
|
||||
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
|
||||
toolbar.inflateMenu(R.menu.dialog_url);
|
||||
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
|
||||
toolbar.setNavigationContentDescription(R.string.cancel);
|
||||
|
||||
okButton = toolbar.findViewById(R.id.okay);
|
||||
okButton.setEnabled(false);// disable until the download service connection is done
|
||||
|
||||
toolbar.setOnMenuItemClickListener(item -> {
|
||||
if (item.getItemId() == R.id.okay) {
|
||||
@@ -346,7 +431,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
|
||||
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
|
||||
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
selectedAudioIndex = position;
|
||||
break;
|
||||
@@ -370,9 +455,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
protected void setupDownloadOptions() {
|
||||
setRadioButtonsState(false);
|
||||
|
||||
final RadioButton audioButton = radioVideoAudioGroup.findViewById(R.id.audio_button);
|
||||
final RadioButton videoButton = radioVideoAudioGroup.findViewById(R.id.video_button);
|
||||
final RadioButton subtitleButton = radioVideoAudioGroup.findViewById(R.id.subtitle_button);
|
||||
final RadioButton audioButton = radioStreamsGroup.findViewById(R.id.audio_button);
|
||||
final RadioButton videoButton = radioStreamsGroup.findViewById(R.id.video_button);
|
||||
final RadioButton subtitleButton = radioStreamsGroup.findViewById(R.id.subtitle_button);
|
||||
final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
|
||||
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
|
||||
final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0;
|
||||
@@ -397,9 +482,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
}
|
||||
|
||||
private void setRadioButtonsState(boolean enabled) {
|
||||
radioVideoAudioGroup.findViewById(R.id.audio_button).setEnabled(enabled);
|
||||
radioVideoAudioGroup.findViewById(R.id.video_button).setEnabled(enabled);
|
||||
radioVideoAudioGroup.findViewById(R.id.subtitle_button).setEnabled(enabled);
|
||||
radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled);
|
||||
radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled);
|
||||
radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled);
|
||||
}
|
||||
|
||||
private int getSubtitleIndexBy(List<SubtitlesStream> streams) {
|
||||
@@ -434,99 +519,320 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
return 0;
|
||||
}
|
||||
|
||||
StoredDirectoryHelper mainStorageAudio = null;
|
||||
StoredDirectoryHelper mainStorageVideo = null;
|
||||
DownloadManager downloadManager = null;
|
||||
ActionMenuItemView okButton = null;
|
||||
Context context;
|
||||
boolean askForSavePath;
|
||||
|
||||
private String getNameEditText() {
|
||||
String str = nameEditText.getText().toString().trim();
|
||||
|
||||
return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str);
|
||||
}
|
||||
|
||||
private void showFailedDialog(@StringRes int msg) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.general_error)
|
||||
.setMessage(msg)
|
||||
.setNegativeButton(android.R.string.ok, null)
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showErrorActivity(Exception e) {
|
||||
ErrorActivity.reportError(
|
||||
context,
|
||||
Collections.singletonList(e),
|
||||
null,
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
|
||||
);
|
||||
}
|
||||
|
||||
private void prepareSelectedDownload() {
|
||||
final Context context = getContext();
|
||||
Stream stream;
|
||||
String location;
|
||||
char kind;
|
||||
StoredDirectoryHelper mainStorage;
|
||||
MediaFormat format;
|
||||
String mime;
|
||||
|
||||
String fileName = nameEditText.getText().toString().trim();
|
||||
if (fileName.isEmpty())
|
||||
fileName = FilenameUtils.createFilename(context, currentInfo.getName());
|
||||
// first, build the filename and get the output folder (if possible)
|
||||
// later, run a very very very large file checking logic
|
||||
|
||||
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
|
||||
String filename = getNameEditText().concat(".");
|
||||
|
||||
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
stream = audioStreamsAdapter.getItem(selectedAudioIndex);
|
||||
location = NewPipeSettings.getAudioDownloadPath(context);
|
||||
kind = 'a';
|
||||
mainStorage = mainStorageAudio;
|
||||
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
||||
mime = format.mimeType;
|
||||
filename += format.suffix;
|
||||
break;
|
||||
case R.id.video_button:
|
||||
stream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
location = NewPipeSettings.getVideoDownloadPath(context);
|
||||
kind = 'v';
|
||||
mainStorage = mainStorageVideo;
|
||||
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
||||
mime = format.mimeType;
|
||||
filename += format.suffix;
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video go together
|
||||
kind = 's';
|
||||
mainStorage = mainStorageVideo;// subtitle & video files go together
|
||||
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
||||
mime = format.mimeType;
|
||||
filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("No stream selected");
|
||||
}
|
||||
|
||||
if (mainStorage == null || askForSavePath) {
|
||||
// This part is called if with SAF preferred:
|
||||
// * older android version running
|
||||
// * save path not defined (via download settings)
|
||||
// * the user checked the "ask where to download" option
|
||||
|
||||
if (!askForSavePath)
|
||||
Toast.makeText(context, getString(R.string.no_available_dir), Toast.LENGTH_LONG).show();
|
||||
|
||||
if (NewPipeSettings.useStorageAccessFramework(context)) {
|
||||
StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS, filename, mime);
|
||||
} else {
|
||||
File initialSavePath;
|
||||
if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button)
|
||||
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC);
|
||||
else
|
||||
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES);
|
||||
|
||||
initialSavePath = new File(initialSavePath, filename);
|
||||
startActivityForResult(
|
||||
FilePickerActivityHelper.chooseFileToSave(context, initialSavePath.getAbsolutePath()),
|
||||
REQUEST_DOWNLOAD_SAVE_AS
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check for existing file with the same name
|
||||
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
|
||||
}
|
||||
|
||||
private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri targetFile, String filename, String mime) {
|
||||
StoredFileHelper storage;
|
||||
|
||||
try {
|
||||
if (mainStorage == null) {
|
||||
// using SAF on older android version
|
||||
storage = new StoredFileHelper(context, null, targetFile, "");
|
||||
} else if (targetFile == null) {
|
||||
// the file does not exist, but it is probably used in a pending download
|
||||
storage = new StoredFileHelper(mainStorage.getUri(), filename, mime, mainStorage.getTag());
|
||||
} else {
|
||||
// the target filename is already use, attempt to use it
|
||||
storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showErrorActivity(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if is our file
|
||||
MissionState state = downloadManager.checkForExistingMission(storage);
|
||||
@StringRes int msgBtn;
|
||||
@StringRes int msgBody;
|
||||
|
||||
switch (state) {
|
||||
case Finished:
|
||||
msgBtn = R.string.overwrite;
|
||||
msgBody = R.string.overwrite_finished_warning;
|
||||
break;
|
||||
case Pending:
|
||||
msgBtn = R.string.overwrite;
|
||||
msgBody = R.string.download_already_pending;
|
||||
break;
|
||||
case PendingRunning:
|
||||
msgBtn = R.string.generate_unique_name;
|
||||
msgBody = R.string.download_already_running;
|
||||
break;
|
||||
case None:
|
||||
if (mainStorage == null) {
|
||||
// This part is called if:
|
||||
// * using SAF on older android version
|
||||
// * save path not defined
|
||||
// * if the file exists overwrite it, is not necessary ask
|
||||
if (!storage.existsAsFile() && !storage.create()) {
|
||||
showFailedDialog(R.string.error_file_creation);
|
||||
return;
|
||||
}
|
||||
continueSelectedDownload(storage);
|
||||
return;
|
||||
} else if (targetFile == null) {
|
||||
// This part is called if:
|
||||
// * the filename is not used in a pending/finished download
|
||||
// * the file does not exists, create
|
||||
|
||||
if (!mainStorage.mkdirs()) {
|
||||
showFailedDialog(R.string.error_path_creation);
|
||||
return;
|
||||
}
|
||||
|
||||
storage = mainStorage.createFile(filename, mime);
|
||||
if (storage == null || !storage.canWrite()) {
|
||||
showFailedDialog(R.string.error_file_creation);
|
||||
return;
|
||||
}
|
||||
|
||||
continueSelectedDownload(storage);
|
||||
return;
|
||||
}
|
||||
msgBtn = R.string.overwrite;
|
||||
msgBody = R.string.overwrite_unrelated_warning;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
int threads;
|
||||
|
||||
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
|
||||
threads = 1;// use unique thread for subtitles due small file size
|
||||
fileName += ".srt";// final subtitle format
|
||||
} else {
|
||||
threads = threadsSeekBar.getProgress() + 1;
|
||||
fileName += "." + stream.getFormat().getSuffix();
|
||||
AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.download_dialog_title)
|
||||
.setMessage(msgBody)
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
final StoredFileHelper finalStorage = storage;
|
||||
|
||||
|
||||
if (mainStorage == null) {
|
||||
// This part is called if:
|
||||
// * using SAF on older android version
|
||||
// * save path not defined
|
||||
switch (state) {
|
||||
case Pending:
|
||||
case Finished:
|
||||
askDialog.setPositiveButton(msgBtn, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
downloadManager.forgetMission(finalStorage);
|
||||
continueSelectedDownload(finalStorage);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
askDialog.create().show();
|
||||
return;
|
||||
}
|
||||
|
||||
final String finalFileName = fileName;
|
||||
askDialog.setPositiveButton(msgBtn, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
|
||||
DownloadManagerService.checkForRunningMission(context, location, fileName, (listed, finished) -> {
|
||||
// should be safe run the following code without "getActivity().runOnUiThread()"
|
||||
if (listed) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.download_dialog_title)
|
||||
.setMessage(finished ? R.string.overwrite_warning : R.string.download_already_running)
|
||||
.setPositiveButton(
|
||||
finished ? R.string.overwrite : R.string.generate_unique_name,
|
||||
(dialog, which) -> downloadSelected(context, stream, location, finalFileName, kind, threads)
|
||||
)
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
dialog.cancel();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
downloadSelected(context, stream, location, finalFileName, kind, threads);
|
||||
StoredFileHelper storageNew;
|
||||
switch (state) {
|
||||
case Finished:
|
||||
case Pending:
|
||||
downloadManager.forgetMission(finalStorage);
|
||||
case None:
|
||||
if (targetFile == null) {
|
||||
storageNew = mainStorage.createFile(filename, mime);
|
||||
} else {
|
||||
try {
|
||||
// try take (or steal) the file
|
||||
storageNew = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to take (or steal) the file in " + targetFile.toString());
|
||||
storageNew = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (storageNew != null && storageNew.canWrite())
|
||||
continueSelectedDownload(storageNew);
|
||||
else
|
||||
showFailedDialog(R.string.error_file_creation);
|
||||
break;
|
||||
case PendingRunning:
|
||||
storageNew = mainStorage.createUniqueFile(filename, mime);
|
||||
if (storageNew == null)
|
||||
showFailedDialog(R.string.error_file_creation);
|
||||
else
|
||||
continueSelectedDownload(storageNew);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
askDialog.create().show();
|
||||
}
|
||||
|
||||
private void downloadSelected(Context context, Stream selectedStream, String location, String fileName, char kind, int threads) {
|
||||
private void continueSelectedDownload(@NonNull StoredFileHelper storage) {
|
||||
if (!storage.canWrite()) {
|
||||
showFailedDialog(R.string.permission_denied);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the selected file has to be overwritten, by simply checking its length
|
||||
try {
|
||||
if (storage.length() > 0) storage.truncate();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e);
|
||||
showFailedDialog(R.string.overwrite_failed);
|
||||
return;
|
||||
}
|
||||
|
||||
Stream selectedStream;
|
||||
char kind;
|
||||
int threads = threadsSeekBar.getProgress() + 1;
|
||||
String[] urls;
|
||||
String psName = null;
|
||||
String[] psArgs = null;
|
||||
String secondaryStreamUrl = null;
|
||||
long nearLength = 0;
|
||||
|
||||
if (selectedStream instanceof VideoStream) {
|
||||
SecondaryStreamHelper<AudioStream> secondaryStream = videoStreamsAdapter
|
||||
.getAllSecondary()
|
||||
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
||||
// more download logic: select muxer, subtitle converter, etc.
|
||||
switch (radioStreamsGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
kind = 'a';
|
||||
selectedStream = audioStreamsAdapter.getItem(selectedAudioIndex);
|
||||
|
||||
if (secondaryStream != null) {
|
||||
secondaryStreamUrl = secondaryStream.getStream().getUrl();
|
||||
psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_DASH_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||
psArgs = null;
|
||||
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream);
|
||||
|
||||
// set nearLength, only, if both sizes are fetched or known. this probably does not work on weak internet connections
|
||||
if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) {
|
||||
nearLength = secondaryStream.getSizeInBytes() + videoSize;
|
||||
if (selectedStream.getFormat() == MediaFormat.M4A) {
|
||||
psName = Postprocessing.ALGORITHM_M4A_NO_DASH;
|
||||
}
|
||||
}
|
||||
} else if ((selectedStream instanceof SubtitlesStream) && selectedStream.getFormat() == MediaFormat.TTML) {
|
||||
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
|
||||
psArgs = new String[]{
|
||||
selectedStream.getFormat().getSuffix(),
|
||||
"false",// ignore empty frames
|
||||
"false",// detect youtube duplicate lines
|
||||
};
|
||||
break;
|
||||
case R.id.video_button:
|
||||
kind = 'v';
|
||||
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
|
||||
SecondaryStreamHelper<AudioStream> secondaryStream = videoStreamsAdapter
|
||||
.getAllSecondary()
|
||||
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
||||
|
||||
if (secondaryStream != null) {
|
||||
secondaryStreamUrl = secondaryStream.getStream().getUrl();
|
||||
|
||||
if (selectedStream.getFormat() == MediaFormat.MPEG_4)
|
||||
psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER;
|
||||
else
|
||||
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||
|
||||
psArgs = null;
|
||||
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream);
|
||||
|
||||
// set nearLength, only, if both sizes are fetched or known. This probably
|
||||
// does not work on slow networks but is later updated in the downloader
|
||||
if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) {
|
||||
nearLength = secondaryStream.getSizeInBytes() + videoSize;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
threads = 1;// use unique thread for subtitles due small file size
|
||||
kind = 's';
|
||||
selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||
|
||||
if (selectedStream.getFormat() == MediaFormat.TTML) {
|
||||
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
|
||||
psArgs = new String[]{
|
||||
selectedStream.getFormat().getSuffix(),
|
||||
"false",// ignore empty frames
|
||||
"false",// detect youtube duplicate lines
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (secondaryStreamUrl == null) {
|
||||
@@ -535,8 +841,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl};
|
||||
}
|
||||
|
||||
DownloadManagerService.startMission(context, urls, location, fileName, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength);
|
||||
DownloadManagerService.startMission(context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength);
|
||||
|
||||
getDialog().dismiss();
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public class ExtSDDownloadFailedActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.download_to_sdcard_error_title)
|
||||
.setMessage(R.string.download_to_sdcard_error_message)
|
||||
.setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> {
|
||||
NewPipeSettings.resetDownloadFolders(this);
|
||||
finish();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> {
|
||||
dialogInterface.dismiss();
|
||||
finish();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
@@ -180,7 +179,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
}
|
||||
|
||||
if (exception instanceof ReCaptchaException) {
|
||||
onReCaptchaException();
|
||||
onReCaptchaException((ReCaptchaException) exception);
|
||||
return true;
|
||||
} else if (exception instanceof IOException) {
|
||||
showError(getString(R.string.network_error), true);
|
||||
@@ -190,11 +189,13 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onReCaptchaException() {
|
||||
public void onReCaptchaException(ReCaptchaException exception) {
|
||||
if (DEBUG) Log.d(TAG, "onReCaptchaException() called");
|
||||
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
||||
// Starting ReCaptcha Challenge Activity
|
||||
startActivityForResult(new Intent(activity, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST);
|
||||
Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
||||
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl());
|
||||
startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);
|
||||
|
||||
showError(getString(R.string.recaptcha_request_toast), false);
|
||||
}
|
||||
@@ -230,21 +231,4 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
||||
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void openUrlInBrowser(String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
|
||||
}
|
||||
|
||||
protected void shareUrl(String subject, String url) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, url);
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class EmptyFragment extends BaseFragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_empty, container, false);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,6 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -18,6 +9,17 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
@@ -50,6 +52,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
destroyOldFragments();
|
||||
|
||||
tabsManager = TabsManager.getManager(activity);
|
||||
tabsManager.setSavedTabsListener(() -> {
|
||||
if (DEBUG) {
|
||||
@@ -63,6 +67,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
});
|
||||
}
|
||||
|
||||
private void destroyOldFragments() {
|
||||
for (Fragment fragment : getChildFragmentManager().getFragments()) {
|
||||
if (fragment != null) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commitNowAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_main, container, false);
|
||||
@@ -98,6 +113,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
tabsManager.unsetSavedTabsListener();
|
||||
if (viewPager != null) viewPager.setAdapter(null);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -144,6 +160,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
|
||||
viewPager.setOffscreenPageLimit(pagerAdapter.getCount());
|
||||
updateTabsIcon();
|
||||
updateTabsContentDescription();
|
||||
updateCurrentTitle();
|
||||
}
|
||||
|
||||
@@ -156,6 +173,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTabsContentDescription() {
|
||||
for (int i = 0; i < tabsList.size(); i++) {
|
||||
final TabLayout.Tab tabToSet = tabLayout.getTabAt(i);
|
||||
if (tabToSet != null) {
|
||||
final Tab t = tabsList.get(i);
|
||||
tabToSet.setIcon(t.getTabIconRes(activity));
|
||||
tabToSet.setContentDescription(t.getTabName(activity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCurrentTitle() {
|
||||
setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext()));
|
||||
}
|
||||
@@ -177,6 +205,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
}
|
||||
|
||||
private class SelectedTabsPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private SelectedTabsPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.StaggeredGridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
/**
|
||||
* Recycler view scroll listener which calls the method {@link #onScrolledDown(RecyclerView)}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.schabi.newpipe.fragments.detail;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TabAdaptor extends FragmentPagerAdapter {
|
||||
|
||||
private final List<Fragment> mFragmentList = new ArrayList<>();
|
||||
private final List<String> mFragmentTitleList = new ArrayList<>();
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
public TabAdaptor(FragmentManager fm) {
|
||||
super(fm);
|
||||
this.fragmentManager = fm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return mFragmentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mFragmentList.size();
|
||||
}
|
||||
|
||||
public void addFragment(Fragment fragment, String title) {
|
||||
mFragmentList.add(fragment);
|
||||
mFragmentTitleList.add(title);
|
||||
}
|
||||
|
||||
public void clearAllItems() {
|
||||
mFragmentList.clear();
|
||||
mFragmentTitleList.clear();
|
||||
}
|
||||
|
||||
public void removeItem(int position){
|
||||
mFragmentList.remove(position == 0 ? 0 : position - 1);
|
||||
mFragmentTitleList.remove(position == 0 ? 0 : position - 1);
|
||||
}
|
||||
|
||||
public void updateItem(int position, Fragment fragment){
|
||||
mFragmentList.set(position, fragment);
|
||||
}
|
||||
|
||||
public void updateItem(String title, Fragment fragment){
|
||||
int index = mFragmentTitleList.indexOf(title);
|
||||
if(index != -1){
|
||||
updateItem(index, fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemPosition(Object object) {
|
||||
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
|
||||
else return POSITION_NONE;
|
||||
}
|
||||
|
||||
public int getItemPositionByTitle(String title) {
|
||||
return mFragmentTitleList.indexOf(title);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getItemTitle(int position) {
|
||||
if (position < 0 || position >= mFragmentTitleList.size()) {
|
||||
return null;
|
||||
}
|
||||
return mFragmentTitleList.get(position);
|
||||
}
|
||||
|
||||
public void notifyDataSetUpdate(){
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
fragmentManager.beginTransaction().remove((Fragment) object).commitNowAllowingStateLoss();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,18 +2,17 @@ package org.schabi.newpipe.fragments.list;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -22,20 +21,20 @@ import android.view.View;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.StreamDialogEntry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
@@ -63,6 +62,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
infoListAdapter = new InfoListAdapter(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -220,6 +224,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
}
|
||||
});
|
||||
|
||||
infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<CommentsInfoItem>() {
|
||||
@Override
|
||||
public void selected(CommentsInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
}
|
||||
});
|
||||
|
||||
itemsList.clearOnScrollListeners();
|
||||
itemsList.addOnScrollListener(new OnScrollBelowItemsListener() {
|
||||
@Override
|
||||
@@ -241,41 +252,32 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
if (context == null || context.getResources() == null || activity == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.append_playlist),
|
||||
context.getResources().getString(R.string.share)
|
||||
};
|
||||
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
} else {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.enqueue_on_popup,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.start_here_on_popup,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
}
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 2:
|
||||
if (getFragmentManager() != null) {
|
||||
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
|
||||
.show(getFragmentManager(), TAG);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) ->
|
||||
StreamDialogEntry.clickOn(which, this, item)).show();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.schabi.newpipe.fragments.list;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
@@ -61,8 +62,10 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (currentWorker != null) currentWorker.dispose();
|
||||
currentWorker = null;
|
||||
if (currentWorker != null) {
|
||||
currentWorker.dispose();
|
||||
currentWorker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package org.schabi.newpipe.fragments.list.channel;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -34,21 +32,18 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionService;
|
||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -149,56 +144,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
return headerRootLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
final Activity activity = getActivity();
|
||||
final Context context = getContext();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
context.getResources().getString(R.string.append_playlist),
|
||||
context.getResources().getString(R.string.share)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> {
|
||||
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 3:
|
||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
case 5:
|
||||
if (getFragmentManager() != null) {
|
||||
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
|
||||
.show(getFragmentManager(), TAG);
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -233,10 +178,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
openRssFeed();
|
||||
break;
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(url);
|
||||
ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl());
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, url);
|
||||
ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl());
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -439,11 +384,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
monitorSubscription(result);
|
||||
|
||||
headerPlayAllButton.setOnClickListener(
|
||||
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||
view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
headerPopupButton.setOnClickListener(
|
||||
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||
view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(
|
||||
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||
view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.schabi.newpipe.fragments.list.comments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static CommentsFragment getInstance(int serviceId, String url, String name) {
|
||||
CommentsFragment instance = new CommentsFragment();
|
||||
instance.setInitialData(serviceId, url, name);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_comments, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Load and handle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<CommentsInfo> loadResult(boolean forceLoad) {
|
||||
return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull CommentsInfo result) {
|
||||
super.handleResult(result);
|
||||
|
||||
AnimationUtils.slideUp(getView(),120, 150, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_COMMENTS,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url,
|
||||
R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnError
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
hideLoading();
|
||||
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGridLayout() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package org.schabi.newpipe.fragments.list.kiosk;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -155,9 +154,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
super.handleResult(result);
|
||||
|
||||
name = kioskTranslatedName;
|
||||
if(!useAsFrontPage) {
|
||||
setTitle(kioskTranslatedName);
|
||||
}
|
||||
setTitle(kioskTranslatedName);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
|
||||
@@ -2,11 +2,10 @@ package org.schabi.newpipe.fragments.list.playlist;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -29,17 +28,19 @@ import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
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.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.StreamDialogEntry;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -134,48 +135,40 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
infoListAdapter.useMiniItemVariants(true);
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueueStartingAt(StreamInfoItem infoItem) {
|
||||
return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showStreamDialog(final StreamInfoItem item) {
|
||||
protected void showStreamDialog(StreamInfoItem item) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
if (context == null || context.getResources() == null || activity == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
context.getResources().getString(R.string.share)
|
||||
};
|
||||
if (item.getStreamType() == StreamType.AUDIO_STREAM) {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
} else {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.enqueue_on_popup,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.start_here_on_popup,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 3:
|
||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
case 5:
|
||||
shareUrl(item.getName(), item.getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
StreamDialogEntry.start_here_on_popup.setCustomAction(
|
||||
(fragment, infoItem) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(infoItem), true));
|
||||
}
|
||||
|
||||
new InfoItemDialog(getActivity(), item, commands, actions).show();
|
||||
StreamDialogEntry.start_here_on_background.setCustomAction(
|
||||
(fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(infoItem), true));
|
||||
|
||||
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) ->
|
||||
StreamDialogEntry.clickOn(which, this, item)).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,10 +223,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_item_openInBrowser:
|
||||
openUrlInBrowser(url);
|
||||
ShareUtils.openUrlInBrowser(this.getContext(), url);
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
shareUrl(name, url);
|
||||
ShareUtils.shareUrl(this.getContext(), name, url);
|
||||
break;
|
||||
case R.id.menu_item_bookmark:
|
||||
onBookmarkClicked();
|
||||
@@ -300,11 +293,21 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
.subscribe(getPlaylistBookmarkSubscriber());
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
|
||||
|
||||
headerPopupButton.setOnLongClickListener(view -> {
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
|
||||
return true;
|
||||
});
|
||||
|
||||
headerBackgroundButton.setOnLongClickListener(view -> {
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueue() {
|
||||
|
||||
@@ -6,12 +6,13 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.TooltipCompat;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
@@ -39,15 +40,15 @@ 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.FireTvUtils;
|
||||
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.Constants;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.LayoutManagerSmoothScroller;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
||||
@@ -72,8 +73,8 @@ import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class SearchFragment
|
||||
@@ -104,8 +105,13 @@ public class SearchFragment
|
||||
// this three represet the current search query
|
||||
@State
|
||||
protected String searchString;
|
||||
|
||||
/**
|
||||
* No content filter should add like contentfilter = all
|
||||
* be aware of this when implementing an extractor.
|
||||
*/
|
||||
@State
|
||||
protected String[] contentFilter;
|
||||
protected String[] contentFilter = new String[0];
|
||||
@State
|
||||
protected String sortFilter;
|
||||
|
||||
@@ -292,7 +298,23 @@ public class SearchFragment
|
||||
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
|
||||
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
|
||||
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
|
||||
suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity));
|
||||
new ItemTouchHelper(new ItemTouchHelper.Callback() {
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return getSuggestionMovementFlags(recyclerView, viewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
onSuggestionItemSwiped(viewHolder, i);
|
||||
}
|
||||
}).attachToRecyclerView(suggestionsRecyclerView);
|
||||
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
@@ -335,7 +357,7 @@ public class SearchFragment
|
||||
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
|
||||
search(!TextUtils.isEmpty(searchString)
|
||||
? searchString
|
||||
: searchEditText.getText().toString(), new String[0], "");
|
||||
: searchEditText.getText().toString(), this.contentFilter, "");
|
||||
} else {
|
||||
if (searchEditText != null) {
|
||||
searchEditText.setText("");
|
||||
@@ -449,6 +471,9 @@ public class SearchFragment
|
||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
if(FireTvUtils.isFireTv()){
|
||||
showKeyboardSearch();
|
||||
}
|
||||
});
|
||||
|
||||
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
|
||||
@@ -499,7 +524,9 @@ public class SearchFragment
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
|
||||
}
|
||||
if (event != null
|
||||
if(actionId == EditorInfo.IME_ACTION_PREVIOUS){
|
||||
hideKeyboardSearch();
|
||||
} else if (event != null
|
||||
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|
||||
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|
||||
search(searchEditText.getText().toString(), new String[0], "");
|
||||
@@ -541,7 +568,7 @@ public class SearchFragment
|
||||
if (searchEditText.requestFocus()) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,8 +578,7 @@ public class SearchFragment
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||
|
||||
searchEditText.clearFocus();
|
||||
}
|
||||
@@ -736,6 +762,7 @@ public class SearchFragment
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
if(nextPageUrl == null || nextPageUrl.isEmpty()) return;
|
||||
isLoading.set(true);
|
||||
showListFooter(true);
|
||||
if (searchDisposable != null) searchDisposable.dispose();
|
||||
@@ -890,4 +917,28 @@ public class SearchFragment
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Suggestion item touch helper
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
||||
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
||||
}
|
||||
|
||||
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final String query = suggestionListAdapter.getItem(position).query;
|
||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> suggestionPublisher
|
||||
.onNext(searchEditText.getText().toString()),
|
||||
throwable -> showSnackBarError(throwable,
|
||||
UserAction.DELETE_FROM_HISTORY, "none",
|
||||
"Deleting item failed", R.string.general_error));
|
||||
disposables.add(onDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
|
||||
});
|
||||
}
|
||||
|
||||
private SuggestionItem getItem(int position) {
|
||||
SuggestionItem getItem(int position) {
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
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.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
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 org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.RelatedStreamInfo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> implements SharedPreferences.OnSharedPreferenceChangeListener{
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private RelatedStreamInfo relatedStreamInfo;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private View headerRootLayout;
|
||||
private Switch aSwitch;
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
|
||||
public static RelatedVideosFragment getInstance(StreamInfo info) {
|
||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
instance.setInitialData(info);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_related_streams, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
protected View getListHeader(){
|
||||
if(relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null){
|
||||
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.related_streams_header, itemsList, false);
|
||||
aSwitch = 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(CompoundButton compoundButton, boolean b) {
|
||||
SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
|
||||
prefEdit.putBoolean(getString(R.string.auto_queue_key), b);
|
||||
prefEdit.apply();
|
||||
}
|
||||
});
|
||||
return headerRootLayout;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<RelatedStreamInfo> loadResult(boolean forceLoad) {
|
||||
return Single.fromCallable(() -> relatedStreamInfo);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull RelatedStreamInfo result) {
|
||||
|
||||
super.handleResult(result);
|
||||
|
||||
if(null != headerRootLayout) headerRootLayout.setVisibility(View.VISIBLE);
|
||||
AnimationUtils.slideUp(getView(),120, 96, 0.06f);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||
}
|
||||
|
||||
if (disposables != null) disposables.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_STREAM,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url,
|
||||
R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnError
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
hideLoading();
|
||||
showSnackBarError(exception, UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, R.string.general_error);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void setInitialData(StreamInfo info) {
|
||||
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||
if(this.relatedStreamInfo == null) this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
|
||||
}
|
||||
|
||||
|
||||
private static final String INFO_KEY = "related_info_key";
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(INFO_KEY, relatedStreamInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
if (savedState != null) {
|
||||
Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
if(serializable instanceof RelatedStreamInfo){
|
||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if(null != aSwitch) aSwitch.setChecked(autoplay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGridLayout() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -10,15 +9,19 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
/*
|
||||
@@ -50,18 +53,20 @@ public class InfoItemBuilder {
|
||||
private OnClickGesture<StreamInfoItem> onStreamSelectedListener;
|
||||
private OnClickGesture<ChannelInfoItem> onChannelSelectedListener;
|
||||
private OnClickGesture<PlaylistInfoItem> onPlaylistSelectedListener;
|
||||
private OnClickGesture<CommentsInfoItem> onCommentsSelectedListener;
|
||||
|
||||
public InfoItemBuilder(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem) {
|
||||
return buildView(parent, infoItem, false);
|
||||
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
return buildView(parent, infoItem, historyRecordManager, false);
|
||||
}
|
||||
|
||||
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, boolean useMiniVariant) {
|
||||
public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem,
|
||||
final HistoryRecordManager historyRecordManager, boolean useMiniVariant) {
|
||||
InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant);
|
||||
holder.updateFromItem(infoItem);
|
||||
holder.updateFromItem(infoItem, historyRecordManager);
|
||||
return holder.itemView;
|
||||
}
|
||||
|
||||
@@ -73,8 +78,9 @@ public class InfoItemBuilder {
|
||||
return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent);
|
||||
case PLAYLIST:
|
||||
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent);
|
||||
case COMMENT:
|
||||
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent);
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
throw new RuntimeException("InfoType not expected = " + infoType.name());
|
||||
}
|
||||
}
|
||||
@@ -111,4 +117,12 @@ public class InfoItemBuilder {
|
||||
this.onPlaylistSelectedListener = listener;
|
||||
}
|
||||
|
||||
public OnClickGesture<CommentsInfoItem> getOnCommentsSelectedListener() {
|
||||
return onCommentsSelectedListener;
|
||||
}
|
||||
|
||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> onCommentsSelectedListener) {
|
||||
this.onCommentsSelectedListener = onCommentsSelectedListener;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.schabi.newpipe.info_list;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
|
||||
@@ -21,6 +27,7 @@ import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.FallbackViewHolder;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
@@ -63,9 +70,13 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
|
||||
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
|
||||
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
|
||||
private static final int MINI_COMMENT_HOLDER_TYPE = 0x400;
|
||||
private static final int COMMENT_HOLDER_TYPE = 0x401;
|
||||
|
||||
private final InfoItemBuilder infoItemBuilder;
|
||||
private final ArrayList<InfoItem> infoItemList;
|
||||
private final HistoryRecordManager recordManager;
|
||||
|
||||
private boolean useMiniVariant = false;
|
||||
private boolean useGridVariant = false;
|
||||
private boolean showFooter = false;
|
||||
@@ -81,8 +92,9 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
}
|
||||
}
|
||||
|
||||
public InfoListAdapter(Activity a) {
|
||||
infoItemBuilder = new InfoItemBuilder(a);
|
||||
public InfoListAdapter(Context context) {
|
||||
this.recordManager = new HistoryRecordManager(context);
|
||||
infoItemBuilder = new InfoItemBuilder(context);
|
||||
infoItemList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -98,6 +110,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
infoItemBuilder.setOnPlaylistSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void setOnCommentsSelectedListener(OnClickGesture<CommentsInfoItem> listener) {
|
||||
infoItemBuilder.setOnCommentsSelectedListener(listener);
|
||||
}
|
||||
|
||||
public void useMiniItemVariants(boolean useMiniVariant) {
|
||||
this.useMiniVariant = useMiniVariant;
|
||||
}
|
||||
@@ -106,50 +122,53 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
this.useGridVariant = useGridVariant;
|
||||
}
|
||||
|
||||
public void addInfoItemList(List<InfoItem> data) {
|
||||
if (data != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + infoItemList.size() + ", data.size() = " + data.size());
|
||||
}
|
||||
public void addInfoItemList(@Nullable final List<InfoItem> data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " +
|
||||
infoItemList.size() + ", data.size() = " + data.size());
|
||||
|
||||
int offsetStart = sizeConsideringHeaderOffset();
|
||||
infoItemList.addAll(data);
|
||||
int offsetStart = sizeConsideringHeaderOffset();
|
||||
infoItemList.addAll(data);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart +
|
||||
", infoItemList.size() = " + infoItemList.size() +
|
||||
", header = " + header + ", footer = " + footer +
|
||||
", showFooter = " + showFooter);
|
||||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
|
||||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + " to " + footerNow);
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart +
|
||||
" to " + footerNow);
|
||||
}
|
||||
}
|
||||
|
||||
public void addInfoItem(InfoItem data) {
|
||||
if (data != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + infoItemList.size() + ", thread = " + Thread.currentThread());
|
||||
}
|
||||
public void addInfoItem(@Nullable InfoItem data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addInfoItem() before > infoItemList.size() = " +
|
||||
infoItemList.size() + ", thread = " + Thread.currentThread());
|
||||
|
||||
int positionInserted = sizeConsideringHeaderOffset();
|
||||
infoItemList.add(data);
|
||||
int positionInserted = sizeConsideringHeaderOffset();
|
||||
infoItemList.add(data);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
|
||||
}
|
||||
notifyItemInserted(positionInserted);
|
||||
if (DEBUG) Log.d(TAG, "addInfoItem() after > position = " + positionInserted +
|
||||
", infoItemList.size() = " + infoItemList.size() +
|
||||
", header = " + header + ", footer = " + footer +
|
||||
", showFooter = " + showFooter);
|
||||
notifyItemInserted(positionInserted);
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(positionInserted, footerNow);
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeaderOffset();
|
||||
notifyItemMoved(positionInserted, footerNow);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + " to " + footerNow);
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted +
|
||||
" to " + footerNow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,15 +242,18 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
||||
case PLAYLIST:
|
||||
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
|
||||
case COMMENT:
|
||||
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]");
|
||||
switch (type) {
|
||||
case HEADER_TYPE:
|
||||
return new HFHolder(header);
|
||||
@@ -255,20 +277,23 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
|
||||
case GRID_PLAYLIST_HOLDER_TYPE:
|
||||
return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
|
||||
case MINI_COMMENT_HOLDER_TYPE:
|
||||
return new CommentsMiniInfoItemHolder(infoItemBuilder, parent);
|
||||
case COMMENT_HOLDER_TYPE:
|
||||
return new CommentsInfoItemHolder(infoItemBuilder, parent);
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return new FallbackViewHolder(new View(parent.getContext()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + position + "]");
|
||||
if (holder instanceof InfoItemHolder) {
|
||||
// If header isn't null, offset the items by -1
|
||||
if (header != null) position--;
|
||||
|
||||
((InfoItemHolder) holder).updateFromItem(infoItemList.get(position));
|
||||
((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager);
|
||||
} else if (holder instanceof HFHolder && position == 0 && header != null) {
|
||||
((HFHolder) holder).view = header;
|
||||
} else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) {
|
||||
@@ -276,6 +301,21 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
||||
if (!payloads.isEmpty() && holder instanceof InfoItemHolder) {
|
||||
for (Object payload : payloads) {
|
||||
if (payload instanceof StreamStateEntity) {
|
||||
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager);
|
||||
} else if (payload instanceof Boolean) {
|
||||
((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onBindViewHolder(holder, position);
|
||||
}
|
||||
}
|
||||
|
||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
||||
return new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
/*
|
||||
@@ -38,8 +39,8 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
super.updateFromItem(infoItem);
|
||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
super.updateFromItem(infoItem, historyRecordManager);
|
||||
|
||||
if (!(infoItem instanceof ChannelInfoItem)) return;
|
||||
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
@@ -30,7 +31,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
if (!(infoItem instanceof ChannelInfoItem)) return;
|
||||
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemHolder .java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||
|
||||
public final TextView itemTitleView;
|
||||
|
||||
public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_comments_item, parent);
|
||||
|
||||
itemTitleView = itemView.findViewById(R.id.itemTitleView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
super.updateFromItem(infoItem, historyRecordManager);
|
||||
|
||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.getAuthorName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.CommentTextOnTouchListener;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
|
||||
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
public final CircleImageView itemThumbnailView;
|
||||
private final TextView itemContentView;
|
||||
private final TextView itemLikesCountView;
|
||||
private final TextView itemDislikesCountView;
|
||||
private final TextView itemPublishedTime;
|
||||
|
||||
private static final int commentDefaultLines = 2;
|
||||
private static final int commentExpandedLines = 1000;
|
||||
|
||||
private String commentText;
|
||||
private String streamUrl;
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
|
||||
|
||||
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
|
||||
@Override
|
||||
public String transformUrl(Matcher match, String url) {
|
||||
int timestamp = 0;
|
||||
String hours = match.group(1);
|
||||
String minutes = match.group(2);
|
||||
String seconds = match.group(3);
|
||||
if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600);
|
||||
if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60);
|
||||
if(seconds != null) timestamp += (Integer.parseInt(seconds));
|
||||
return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
if (!(infoItem instanceof CommentsInfoItem)) return;
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemBuilder.getImageLoader()
|
||||
.displayImage(item.getAuthorThumbnail(),
|
||||
itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
itemThumbnailView.setOnClickListener(view -> {
|
||||
if(StringUtil.isBlank(item.getAuthorEndpoint())) return;
|
||||
try {
|
||||
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
||||
NavigationHelper.openChannelFragment(
|
||||
activity.getSupportFragmentManager(),
|
||||
item.getServiceId(),
|
||||
item.getAuthorEndpoint(),
|
||||
item.getAuthorName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||
}
|
||||
});
|
||||
|
||||
streamUrl = item.getUrl();
|
||||
|
||||
itemContentView.setLines(commentDefaultLines);
|
||||
commentText = item.getCommentText();
|
||||
itemContentView.setText(commentText);
|
||||
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
|
||||
|
||||
if (itemContentView.getLineCount() == 0) {
|
||||
itemContentView.post(this::ellipsize);
|
||||
} else {
|
||||
ellipsize();
|
||||
}
|
||||
|
||||
if (null != item.getLikeCount()) {
|
||||
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
|
||||
}
|
||||
itemPublishedTime.setText(item.getPublishedTime());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
toggleEllipsize();
|
||||
if (itemBuilder.getOnCommentsSelectedListener() != null) {
|
||||
itemBuilder.getOnCommentsSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ellipsize() {
|
||||
if (itemContentView.getLineCount() > commentDefaultLines){
|
||||
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 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) + " …";
|
||||
itemContentView.setText(newVal);
|
||||
}
|
||||
linkify();
|
||||
}
|
||||
|
||||
private void toggleEllipsize() {
|
||||
if (itemContentView.getText().toString().equals(commentText)) {
|
||||
if (itemContentView.getLineCount() > commentDefaultLines) ellipsize();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
}
|
||||
|
||||
private void expand() {
|
||||
itemContentView.setMaxLines(commentExpandedLines);
|
||||
itemContentView.setText(commentText);
|
||||
linkify();
|
||||
}
|
||||
|
||||
private void linkify(){
|
||||
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
|
||||
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink);
|
||||
itemContentView.setMovementMethod(null);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
@@ -35,5 +36,8 @@ public abstract class InfoItemHolder extends RecyclerView.ViewHolder {
|
||||
this.itemBuilder = infoItemBuilder;
|
||||
}
|
||||
|
||||
public abstract void updateFromItem(final InfoItem infoItem);
|
||||
public abstract void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager);
|
||||
|
||||
public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
|
||||
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||
@@ -30,7 +31,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
if (!(infoItem instanceof PlaylistInfoItem)) return;
|
||||
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
/*
|
||||
@@ -40,8 +41,8 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
super.updateFromItem(infoItem);
|
||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
super.updateFromItem(infoItem, historyRecordManager);
|
||||
|
||||
if (!(infoItem instanceof StreamInfoItem)) return;
|
||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
|
||||
@@ -20,6 +26,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
public final TextView itemVideoTitleView;
|
||||
public final TextView itemUploaderView;
|
||||
public final TextView itemDurationView;
|
||||
public final AnimatedProgressBar itemProgressView;
|
||||
|
||||
StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
@@ -28,6 +35,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView);
|
||||
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
||||
itemDurationView = itemView.findViewById(R.id.itemDurationView);
|
||||
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
||||
}
|
||||
|
||||
public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
@@ -35,7 +43,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem) {
|
||||
public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
if (!(infoItem instanceof StreamInfoItem)) return;
|
||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||
|
||||
@@ -47,13 +55,24 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
|
||||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
||||
if (state2 != null) {
|
||||
itemProgressView.setVisibility(View.VISIBLE);
|
||||
itemProgressView.setMax((int) item.getDuration());
|
||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state2.getProgressTime()));
|
||||
} else {
|
||||
itemProgressView.setVisibility(View.GONE);
|
||||
}
|
||||
} else if (item.getStreamType() == StreamType.LIVE_STREAM) {
|
||||
itemDurationView.setText(R.string.duration_live);
|
||||
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
|
||||
R.color.live_duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
itemProgressView.setVisibility(View.GONE);
|
||||
} else {
|
||||
itemDurationView.setVisibility(View.GONE);
|
||||
itemProgressView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||
@@ -83,6 +102,24 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) {
|
||||
final StreamInfoItem item = (StreamInfoItem) infoItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0];
|
||||
if (state != null && item.getDuration() > 0 && item.getStreamType() != StreamType.LIVE_STREAM) {
|
||||
itemProgressView.setMax((int) item.getDuration());
|
||||
if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
} else {
|
||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
AnimationUtils.animateView(itemProgressView, true, 500);
|
||||
}
|
||||
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||
AnimationUtils.animateView(itemProgressView, false, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private void enableLongClick(final StreamInfoItem item) {
|
||||
itemView.setLongClickable(true);
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
@@ -97,4 +134,4 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemView.setLongClickable(false);
|
||||
itemView.setOnLongClickListener(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.local;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
public class HeaderFooterHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package org.schabi.newpipe.local;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.local.holder.LocalItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
|
||||
@@ -64,6 +68,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
|
||||
private final LocalItemBuilder localItemBuilder;
|
||||
private final ArrayList<LocalItem> localItems;
|
||||
private final HistoryRecordManager recordManager;
|
||||
private final DateFormat dateFormat;
|
||||
|
||||
private boolean showFooter = false;
|
||||
@@ -71,11 +76,12 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
private View header = null;
|
||||
private View footer = null;
|
||||
|
||||
public LocalItemListAdapter(Activity activity) {
|
||||
localItemBuilder = new LocalItemBuilder(activity);
|
||||
public LocalItemListAdapter(Context context) {
|
||||
recordManager = new HistoryRecordManager(context);
|
||||
localItemBuilder = new LocalItemBuilder(context);
|
||||
localItems = new ArrayList<>();
|
||||
dateFormat = DateFormat.getDateInstance(DateFormat.SHORT,
|
||||
Localization.getPreferredLocale(activity));
|
||||
Localization.getPreferredLocale(context));
|
||||
}
|
||||
|
||||
public void setSelectedListener(OnClickGesture<LocalItem> listener) {
|
||||
@@ -86,38 +92,33 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
localItemBuilder.setOnItemSelectedListener(null);
|
||||
}
|
||||
|
||||
public void addItems(List<? extends LocalItem> data) {
|
||||
if (data != null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addItems() before > localItems.size() = " +
|
||||
localItems.size() + ", data.size() = " + data.size());
|
||||
}
|
||||
public void addItems(@Nullable List<? extends LocalItem> data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addItems() before > localItems.size() = " +
|
||||
localItems.size() + ", data.size() = " + data.size());
|
||||
|
||||
int offsetStart = sizeConsideringHeader();
|
||||
localItems.addAll(data);
|
||||
int offsetStart = sizeConsideringHeader();
|
||||
localItems.addAll(data);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
|
||||
", localItems.size() = " + localItems.size() +
|
||||
", header = " + header + ", footer = " + footer +
|
||||
", showFooter = " + showFooter);
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
|
||||
", localItems.size() = " + localItems.size() +
|
||||
", header = " + header + ", footer = " + footer +
|
||||
", showFooter = " + showFooter);
|
||||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
|
||||
notifyItemRangeInserted(offsetStart, data.size());
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeader();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (footer != null && showFooter) {
|
||||
int footerNow = sizeConsideringHeader();
|
||||
notifyItemMoved(offsetStart, footerNow);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
|
||||
" to " + footerNow);
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
|
||||
" to " + footerNow);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeItem(final LocalItem data) {
|
||||
final int index = localItems.indexOf(data);
|
||||
|
||||
localItems.remove(index);
|
||||
notifyItemRemoved(index + (header != null ? 1 : 0));
|
||||
}
|
||||
@@ -219,8 +220,9 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) {
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" +
|
||||
parent + "], type = [" + type + "]");
|
||||
switch (type) {
|
||||
@@ -251,7 +253,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" +
|
||||
holder.getClass().getSimpleName() + "], position = [" + position + "]");
|
||||
|
||||
@@ -259,7 +261,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
// If header isn't null, offset the items by -1
|
||||
if (header != null) position--;
|
||||
|
||||
((LocalItemHolder) holder).updateFromItem(localItems.get(position), dateFormat);
|
||||
((LocalItemHolder) holder).updateFromItem(localItems.get(position), recordManager, dateFormat);
|
||||
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
|
||||
((HeaderFooterHolder) holder).view = header;
|
||||
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()
|
||||
@@ -268,6 +270,21 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
||||
if (!payloads.isEmpty() && holder instanceof LocalItemHolder) {
|
||||
for (Object payload : payloads) {
|
||||
if (payload instanceof StreamStateEntity) {
|
||||
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager);
|
||||
} else if (payload instanceof Boolean) {
|
||||
((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onBindViewHolder(holder, position);
|
||||
}
|
||||
}
|
||||
|
||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
||||
return new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.schabi.newpipe.local.bookmark;
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package org.schabi.newpipe.local.dialog;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -28,7 +27,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
private static final String TAG = PlaylistAppendDialog.class.getCanonicalName();
|
||||
@@ -36,7 +35,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
private RecyclerView playlistRecyclerView;
|
||||
private LocalItemListAdapter playlistAdapter;
|
||||
|
||||
private Disposable playlistReactor;
|
||||
private CompositeDisposable playlistDisposables = new CompositeDisposable();
|
||||
|
||||
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
||||
PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
||||
@@ -99,9 +98,9 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
|
||||
newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog());
|
||||
|
||||
playlistReactor = playlistManager.getPlaylists()
|
||||
playlistDisposables.add(playlistManager.getPlaylists()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::onPlaylistsReceived);
|
||||
.subscribe(this::onPlaylistsReceived));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -111,10 +110,12 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (playlistReactor != null) playlistReactor.dispose();
|
||||
if (playlistAdapter != null) playlistAdapter.unsetSelectedListener();
|
||||
playlistDisposables.dispose();
|
||||
if (playlistAdapter != null) {
|
||||
playlistAdapter.unsetSelectedListener();
|
||||
}
|
||||
|
||||
playlistReactor = null;
|
||||
playlistDisposables.clear();
|
||||
playlistRecyclerView = null;
|
||||
playlistAdapter = null;
|
||||
}
|
||||
@@ -148,13 +149,12 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
||||
@NonNull List<StreamEntity> streams) {
|
||||
if (getStreams() == null) return;
|
||||
|
||||
@SuppressLint("ShowToast")
|
||||
final Toast successToast = Toast.makeText(getContext(),
|
||||
R.string.playlist_add_stream_success, Toast.LENGTH_SHORT);
|
||||
|
||||
manager.appendToPlaylist(playlist.uid, streams)
|
||||
playlistDisposables.add(manager.appendToPlaylist(playlist.uid, streams)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> successToast.show());
|
||||
.subscribe(ignored -> successToast.show()));
|
||||
|
||||
getDialog().dismiss();
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.schabi.newpipe.local.dialog;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.schabi.newpipe.local.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.view.Window;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
|
||||
@@ -2,9 +2,9 @@ package org.schabi.newpipe.local.feed;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -21,8 +21,8 @@ import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionService;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -183,7 +183,7 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@android.support.annotation.NonNull List<SubscriptionEntity> result) {
|
||||
public void handleResult(@androidx.annotation.NonNull List<SubscriptionEntity> result) {
|
||||
super.handleResult(result);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
@@ -262,7 +262,7 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
* If chosen feed already displayed, then we request another feed from another
|
||||
* subscription, until the subscription table runs out of new items.
|
||||
* <p>
|
||||
* This Observer is self-contained and will dispose itself when complete. However, this
|
||||
* This Observer is self-contained and will close itself when complete. However, this
|
||||
* does not obey the fragment lifecycle and may continue running in the background
|
||||
* until it is complete. This is done due to RxJava2 no longer propagate errors once
|
||||
* an observer is unsubscribed while the thread process is still running.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.schabi.newpipe.local.history;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.local.history;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
|
||||
@@ -21,28 +21,34 @@ package org.schabi.newpipe.local.history;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamDAO;
|
||||
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Single;
|
||||
@@ -80,9 +86,9 @@ 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();
|
||||
StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||
|
||||
if (latestEntry != null && latestEntry.getStreamUid() == streamId) {
|
||||
if (latestEntry != null) {
|
||||
streamHistoryTable.delete(latestEntry);
|
||||
latestEntry.setAccessDate(currentTime);
|
||||
latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1);
|
||||
@@ -99,7 +105,12 @@ public class HistoryRecordManager {
|
||||
}
|
||||
|
||||
public Single<Integer> deleteWholeStreamHistory() {
|
||||
return Single.fromCallable(() -> streamHistoryTable.deleteAll())
|
||||
return Single.fromCallable(streamHistoryTable::deleteAll)
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<Integer> deleteCompelteStreamStateHistory() {
|
||||
return Single.fromCallable(streamStateTable::deleteAll)
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
@@ -159,8 +170,8 @@ public class HistoryRecordManager {
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<Integer> deleteWholeSearchHistory() {
|
||||
return Single.fromCallable(() -> searchHistoryTable.deleteAll())
|
||||
public Single<Integer> deleteCompleteSearchHistory() {
|
||||
return Single.fromCallable(searchHistoryTable::deleteAll)
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
@@ -180,21 +191,104 @@ public class HistoryRecordManager {
|
||||
// Stream State History
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Maybe<StreamStateEntity> loadStreamState(final StreamInfo info) {
|
||||
return Maybe.fromCallable(() -> streamTable.upsert(new StreamEntity(info)))
|
||||
.flatMap(streamId -> streamStateTable.getState(streamId).firstElement())
|
||||
.flatMap(states -> states.isEmpty() ? Maybe.empty() : Maybe.just(states.get(0)))
|
||||
public Maybe<StreamHistoryEntity> getStreamHistory(final StreamInfo info) {
|
||||
return Maybe.fromCallable(() -> {
|
||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||
return streamHistoryTable.getLatestEntry(streamId);
|
||||
}).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Maybe<StreamStateEntity> loadStreamState(final PlayQueueItem queueItem) {
|
||||
return queueItem.getStream()
|
||||
.map((info) -> streamTable.upsert(new StreamEntity(info)))
|
||||
.flatMapPublisher(streamStateTable::getState)
|
||||
.firstElement()
|
||||
.flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0)))
|
||||
.filter(state -> state.isValid((int) queueItem.getDuration()))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Maybe<Long> saveStreamState(@NonNull final StreamInfo info, final long progressTime) {
|
||||
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
|
||||
public Maybe<StreamStateEntity> loadStreamState(final StreamInfo info) {
|
||||
return Single.fromCallable(() -> streamTable.upsert(new StreamEntity(info)))
|
||||
.flatMapPublisher(streamStateTable::getState)
|
||||
.firstElement()
|
||||
.flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0)))
|
||||
.filter(state -> state.isValid((int) info.getDuration()))
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Completable saveStreamState(@NonNull final StreamInfo info, final long progressTime) {
|
||||
return Completable.fromAction(() -> database.runInTransaction(() -> {
|
||||
final long streamId = streamTable.upsert(new StreamEntity(info));
|
||||
return streamStateTable.upsert(new StreamStateEntity(streamId, progressTime));
|
||||
final StreamStateEntity state = new StreamStateEntity(streamId, progressTime);
|
||||
if (state.isValid((int) info.getDuration())) {
|
||||
streamStateTable.upsert(state);
|
||||
} else {
|
||||
streamStateTable.deleteState(streamId);
|
||||
}
|
||||
})).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<StreamStateEntity[]> loadStreamState(final InfoItem info) {
|
||||
return Single.fromCallable(() -> {
|
||||
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
||||
if (entities.isEmpty()) {
|
||||
return new StreamStateEntity[]{null};
|
||||
}
|
||||
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst();
|
||||
if (states.isEmpty()) {
|
||||
return new StreamStateEntity[]{null};
|
||||
}
|
||||
return new StreamStateEntity[]{states.get(0)};
|
||||
}).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) {
|
||||
return Single.fromCallable(() -> {
|
||||
final List<StreamStateEntity> result = new ArrayList<>(infos.size());
|
||||
for (InfoItem info : infos) {
|
||||
final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst();
|
||||
if (entities.isEmpty()) {
|
||||
result.add(null);
|
||||
continue;
|
||||
}
|
||||
final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst();
|
||||
if (states.isEmpty()) {
|
||||
result.add(null);
|
||||
continue;
|
||||
}
|
||||
result.add(states.get(0));
|
||||
}
|
||||
return result;
|
||||
}).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(final List<? extends LocalItem> items) {
|
||||
return Single.fromCallable(() -> {
|
||||
final List<StreamStateEntity> result = new ArrayList<>(items.size());
|
||||
for (LocalItem item : items) {
|
||||
long streamId;
|
||||
if (item instanceof StreamStatisticsEntry) {
|
||||
streamId = ((StreamStatisticsEntry) item).streamId;
|
||||
} else if (item instanceof PlaylistStreamEntity) {
|
||||
streamId = ((PlaylistStreamEntity) item).getStreamUid();
|
||||
} else if (item instanceof PlaylistStreamEntry) {
|
||||
streamId = ((PlaylistStreamEntry) item).streamId;
|
||||
} else {
|
||||
result.add(null);
|
||||
continue;
|
||||
}
|
||||
final List<StreamStateEntity> states = streamStateTable.getState(streamId).blockingFirst();
|
||||
if (states.isEmpty()) {
|
||||
result.add(null);
|
||||
continue;
|
||||
}
|
||||
result.add(states.get(0));
|
||||
}
|
||||
return result;
|
||||
}).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Utility
|
||||
///////////////////////////////////////////////////////
|
||||
@@ -202,4 +296,5 @@ public class HistoryRecordManager {
|
||||
public Single<Integer> removeOrphanedRecords() {
|
||||
return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@ package org.schabi.newpipe.local.history;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
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 android.widget.ImageView;
|
||||
@@ -21,13 +24,17 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.StreamDialogEntry;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -104,6 +111,12 @@ public class StatisticsPlaylistFragment
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.menu_history, menu);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Views
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -155,6 +168,53 @@ public class StatisticsPlaylistFragment
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_history_clear:
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.delete_view_history_alert)
|
||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
||||
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> Toast.makeText(getContext(),
|
||||
R.string.watch_history_deleted,
|
||||
Toast.LENGTH_SHORT).show(),
|
||||
throwable -> ErrorActivity.reportError(getContext(),
|
||||
throwable,
|
||||
SettingsActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"none",
|
||||
"Delete view history",
|
||||
R.string.general_error)));
|
||||
|
||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDeleted -> {},
|
||||
throwable -> ErrorActivity.reportError(getContext(),
|
||||
throwable,
|
||||
SettingsActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(
|
||||
UserAction.DELETE_FROM_HISTORY,
|
||||
"none",
|
||||
"Delete search history",
|
||||
R.string.general_error)));
|
||||
disposables.add(onClearOrphans);
|
||||
disposables.add(onDelete);
|
||||
}))
|
||||
.create()
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Loading
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -250,11 +310,11 @@ public class StatisticsPlaylistFragment
|
||||
}
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
|
||||
sortButton.setOnClickListener(view -> toggleSortMode());
|
||||
|
||||
hideLoading();
|
||||
@@ -297,52 +357,44 @@ public class StatisticsPlaylistFragment
|
||||
startLoading(true);
|
||||
}
|
||||
|
||||
private PlayQueue getPlayQueueStartingAt(StreamStatisticsEntry infoItem) {
|
||||
return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0));
|
||||
}
|
||||
|
||||
private void showStreamDialog(final StreamStatisticsEntry item) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
if (context == null || context.getResources() == null || activity == null) return;
|
||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
context.getResources().getString(R.string.delete),
|
||||
context.getResources().getString(R.string.share)
|
||||
};
|
||||
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.delete,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
} else {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.enqueue_on_popup,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.start_here_on_popup,
|
||||
StreamDialogEntry.delete,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 3:
|
||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
case 5:
|
||||
deleteEntry(index);
|
||||
break;
|
||||
case 6:
|
||||
shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
StreamDialogEntry.start_here_on_popup.setCustomAction(
|
||||
(fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true));
|
||||
}
|
||||
|
||||
new InfoItemDialog(getActivity(), infoItem, commands, actions).show();
|
||||
StreamDialogEntry.start_here_on_background.setCustomAction(
|
||||
(fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true));
|
||||
StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) ->
|
||||
deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0)));
|
||||
|
||||
new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) ->
|
||||
StreamDialogEntry.clickOn(which, this, infoItem)).show();
|
||||
}
|
||||
|
||||
private void deleteEntry(final int index) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
@@ -38,5 +39,8 @@ public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
|
||||
this.itemBuilder = itemBuilder;
|
||||
}
|
||||
|
||||
public abstract void updateFromItem(final LocalItem item, final DateFormat dateFormat);
|
||||
public abstract void updateFromItem(final LocalItem item, HistoryRecordManager historyRecordManager, final DateFormat dateFormat);
|
||||
|
||||
public void updateState(final LocalItem localItem, HistoryRecordManager historyRecordManager) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.ViewGroup;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
|
||||
import java.text.DateFormat;
|
||||
@@ -21,7 +22,7 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof PlaylistMetadataEntry)) return;
|
||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
||||
|
||||
@@ -32,6 +33,6 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
|
||||
|
||||
super.updateFromItem(localItem, dateFormat);
|
||||
super.updateFromItem(localItem, historyRecordManager, dateFormat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -10,12 +10,18 @@ import android.widget.TextView;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
|
||||
@@ -24,6 +30,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
public final TextView itemAdditionalDetailsView;
|
||||
public final TextView itemDurationView;
|
||||
public final View itemHandleView;
|
||||
public final AnimatedProgressBar itemProgressView;
|
||||
|
||||
LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
@@ -33,6 +40,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
itemAdditionalDetailsView = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||
itemDurationView = itemView.findViewById(R.id.itemDurationView);
|
||||
itemHandleView = itemView.findViewById(R.id.itemHandle);
|
||||
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
||||
}
|
||||
|
||||
public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
@@ -40,7 +48,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof PlaylistStreamEntry)) return;
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||
|
||||
@@ -53,6 +61,15 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
|
||||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0);
|
||||
if (state != null) {
|
||||
itemProgressView.setVisibility(View.VISIBLE);
|
||||
itemProgressView.setMax((int) item.duration);
|
||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
} else {
|
||||
itemProgressView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
itemDurationView.setVisibility(View.GONE);
|
||||
}
|
||||
@@ -79,6 +96,25 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
itemHandleView.setOnTouchListener(getOnTouchListener(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) {
|
||||
if (!(localItem instanceof PlaylistStreamEntry)) return;
|
||||
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0);
|
||||
if (state != null && item.duration > 0) {
|
||||
itemProgressView.setMax((int) item.duration);
|
||||
if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
} else {
|
||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
AnimationUtils.animateView(itemProgressView, true, 500);
|
||||
}
|
||||
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||
AnimationUtils.animateView(itemProgressView, false, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) {
|
||||
return (view, motionEvent) -> {
|
||||
view.performClick();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
@@ -10,12 +10,18 @@ import android.widget.TextView;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
@@ -45,6 +51,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
public final TextView itemDurationView;
|
||||
@Nullable
|
||||
public final TextView itemAdditionalDetails;
|
||||
public final AnimatedProgressBar itemProgressView;
|
||||
|
||||
public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) {
|
||||
this(itemBuilder, R.layout.list_stream_item, parent);
|
||||
@@ -58,6 +65,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
itemUploaderView = itemView.findViewById(R.id.itemUploaderView);
|
||||
itemDurationView = itemView.findViewById(R.id.itemDurationView);
|
||||
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||
itemProgressView = itemView.findViewById(R.id.itemProgressView);
|
||||
}
|
||||
|
||||
private String getStreamInfoDetailLine(final StreamStatisticsEntry entry,
|
||||
@@ -70,7 +78,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof StreamStatisticsEntry)) return;
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
||||
|
||||
@@ -82,8 +90,18 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
|
||||
R.color.duration_background_color));
|
||||
itemDurationView.setVisibility(View.VISIBLE);
|
||||
|
||||
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0);
|
||||
if (state != null) {
|
||||
itemProgressView.setVisibility(View.VISIBLE);
|
||||
itemProgressView.setMax((int) item.duration);
|
||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
} else {
|
||||
itemProgressView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
itemDurationView.setVisibility(View.GONE);
|
||||
itemProgressView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (itemAdditionalDetails != null) {
|
||||
@@ -108,4 +126,23 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) {
|
||||
if (!(localItem instanceof StreamStatisticsEntry)) return;
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
|
||||
|
||||
StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0);
|
||||
if (state != null && item.duration > 0) {
|
||||
itemProgressView.setMax((int) item.duration);
|
||||
if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
} else {
|
||||
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()));
|
||||
AnimationUtils.animateView(itemProgressView, true, 500);
|
||||
}
|
||||
} else if (itemProgressView.getVisibility() == View.VISIBLE) {
|
||||
AnimationUtils.animateView(itemProgressView, false, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.widget.TextView;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
@@ -31,7 +32,7 @@ public abstract class PlaylistItemHolder extends LocalItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
itemBuilder.getOnItemSelectedListener().selected(localItem);
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
@@ -21,7 +22,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof PlaylistRemoteEntity)) return;
|
||||
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
|
||||
|
||||
@@ -33,6 +34,6 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||
itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
|
||||
|
||||
super.updateFromItem(localItem, dateFormat);
|
||||
super.updateFromItem(localItem, historyRecordManager, dateFormat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ package org.schabi.newpipe.local.playlist;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -26,14 +25,16 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.StreamDialogEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -318,11 +319,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
setVideoCount(itemListAdapter.getItemsList().size());
|
||||
|
||||
headerPlayAllButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false));
|
||||
headerPopupButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
|
||||
headerBackgroundButton.setOnClickListener(view ->
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
|
||||
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
|
||||
|
||||
hideLoading();
|
||||
}
|
||||
@@ -510,59 +511,48 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private PlayQueue getPlayQueueStartingAt(PlaylistStreamEntry infoItem) {
|
||||
return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0));
|
||||
}
|
||||
|
||||
protected void showStreamItemDialog(final PlaylistStreamEntry item) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
if (context == null || context.getResources() == null || activity == null) return;
|
||||
final StreamInfoItem infoItem = item.toStreamInfoItem();
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.enqueue_on_background),
|
||||
context.getResources().getString(R.string.enqueue_on_popup),
|
||||
context.getResources().getString(R.string.start_here_on_main),
|
||||
context.getResources().getString(R.string.start_here_on_background),
|
||||
context.getResources().getString(R.string.start_here_on_popup),
|
||||
context.getResources().getString(R.string.set_as_playlist_thumbnail),
|
||||
context.getResources().getString(R.string.delete),
|
||||
context.getResources().getString(R.string.share)
|
||||
};
|
||||
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.set_as_playlist_thumbnail,
|
||||
StreamDialogEntry.delete,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
} else {
|
||||
StreamDialogEntry.setEnabledEntries(
|
||||
StreamDialogEntry.enqueue_on_background,
|
||||
StreamDialogEntry.enqueue_on_popup,
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.start_here_on_popup,
|
||||
StreamDialogEntry.set_as_playlist_thumbnail,
|
||||
StreamDialogEntry.delete,
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share);
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
NavigationHelper.enqueueOnBackgroundPlayer(context,
|
||||
new SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 1:
|
||||
NavigationHelper.enqueueOnPopupPlayer(activity, new
|
||||
SinglePlayQueue(infoItem));
|
||||
break;
|
||||
case 2:
|
||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 3:
|
||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||
break;
|
||||
case 4:
|
||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||
break;
|
||||
case 5:
|
||||
changeThumbnailUrl(item.thumbnailUrl);
|
||||
break;
|
||||
case 6:
|
||||
deleteItem(item);
|
||||
break;
|
||||
case 7:
|
||||
shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
StreamDialogEntry.start_here_on_popup.setCustomAction(
|
||||
(fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true));
|
||||
}
|
||||
|
||||
new InfoItemDialog(getActivity(), infoItem, commands, actions).show();
|
||||
StreamDialogEntry.start_here_on_background.setCustomAction(
|
||||
(fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true));
|
||||
StreamDialogEntry.set_as_playlist_thumbnail.setCustomAction(
|
||||
(fragment, infoItemDuplicate) -> changeThumbnailUrl(item.thumbnailUrl));
|
||||
StreamDialogEntry.delete.setCustomAction(
|
||||
(fragment, infoItemDuplicate) -> deleteItem(item));
|
||||
|
||||
new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) ->
|
||||
StreamDialogEntry.clickOn(which, this, infoItem)).show();
|
||||
}
|
||||
|
||||
private void setInitialData(long playlistId, String name) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.schabi.newpipe.local.playlist;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
|
||||
@@ -4,10 +4,10 @@ import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
package org.schabi.newpipe.local.subscription;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.grack.nanojson.JsonAppendableWriter;
|
||||
import com.grack.nanojson.JsonArray;
|
||||
|
||||
@@ -17,16 +17,15 @@ import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Parcelable;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -48,15 +47,14 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService;
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.CollapsibleView;
|
||||
|
||||
@@ -130,6 +128,11 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
subscriptionService = SubscriptionService.getInstance(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -376,7 +379,6 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
|
||||
});
|
||||
|
||||
//noinspection ConstantConditions
|
||||
whatsNewItemListHeader.setOnClickListener(v -> {
|
||||
FragmentManager fragmentManager = getFM();
|
||||
NavigationHelper.openWhatsNewFragment(fragmentManager);
|
||||
@@ -390,17 +392,17 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
if (context == null || context.getResources() == null || getActivity() == null) return;
|
||||
|
||||
final String[] commands = new String[]{
|
||||
context.getResources().getString(R.string.share),
|
||||
context.getResources().getString(R.string.unsubscribe)
|
||||
context.getResources().getString(R.string.unsubscribe),
|
||||
context.getResources().getString(R.string.share)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
shareChannel(selectedItem);
|
||||
deleteChannel(selectedItem);
|
||||
break;
|
||||
case 1:
|
||||
deleteChannel(selectedItem);
|
||||
shareChannel(selectedItem);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -424,12 +426,12 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
|
||||
}
|
||||
|
||||
private void shareChannel (ChannelInfoItem selectedItem) {
|
||||
shareUrl(selectedItem.getName(), selectedItem.getUrl());
|
||||
private void shareChannel(ChannelInfoItem selectedItem) {
|
||||
ShareUtils.shareUrl(getContext(), selectedItem.getName(), selectedItem.getUrl());
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void deleteChannel (ChannelInfoItem selectedItem) {
|
||||
private void deleteChannel(ChannelInfoItem selectedItem) {
|
||||
subscriptionService.subscriptionTable()
|
||||
.getSubscription(selectedItem.getServiceId(), selectedItem.getUrl())
|
||||
.toObservable()
|
||||
@@ -441,7 +443,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
|
||||
|
||||
|
||||
private Observer<List<SubscriptionEntity>> getDeleteObserver(){
|
||||
private Observer<List<SubscriptionEntity>> getDeleteObserver() {
|
||||
return new Observer<List<SubscriptionEntity>>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.schabi.newpipe.local.subscription;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
@@ -93,7 +93,7 @@ public class SubscriptionService {
|
||||
* in the cooldown interval, only the latest changes are emitted to the subscribers.
|
||||
* This reduces the amount of observations caused by frequent updates to the database.
|
||||
*/
|
||||
@android.support.annotation.NonNull
|
||||
@androidx.annotation.NonNull
|
||||
public Flowable<List<SubscriptionEntity>> getSubscription() {
|
||||
return subscription;
|
||||
}
|
||||
@@ -147,11 +147,16 @@ public class SubscriptionService {
|
||||
}
|
||||
|
||||
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
|
||||
return info.getUrl().equals(entity.getUrl()) &&
|
||||
return equalsAndNotNull(info.getUrl(), entity.getUrl()) &&
|
||||
info.getServiceId() == entity.getServiceId() &&
|
||||
info.getName().equals(entity.getName()) &&
|
||||
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
|
||||
info.getDescription().equals(entity.getDescription()) &&
|
||||
equalsAndNotNull(info.getAvatarUrl(), entity.getAvatarUrl()) &&
|
||||
equalsAndNotNull(info.getDescription(), entity.getDescription()) &&
|
||||
info.getSubscriberCount() == entity.getSubscriberCount();
|
||||
}
|
||||
|
||||
private boolean equalsAndNotNull(final Object o1, final Object o2) {
|
||||
return (o1 != null && o2 != null)
|
||||
&& o1.equals(o2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package org.schabi.newpipe.local.subscription;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.text.util.LinkifyCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.text.util.LinkifyCompat;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import android.text.TextUtils;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
@@ -23,11 +23,11 @@ import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user