mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-01-14 02:32:40 +00:00
Compare commits
497 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5d1271894 | ||
|
|
050d3058f9 | ||
|
|
2ae99afa21 | ||
|
|
b094c9190f | ||
|
|
1a9e1e6f7c | ||
|
|
8c94926693 | ||
|
|
880a176e65 | ||
|
|
8de1b5f3d9 | ||
|
|
178f4546fc | ||
|
|
7564a49344 | ||
|
|
c7941a85ed | ||
|
|
fab5f26e09 | ||
|
|
3599ab3caf | ||
|
|
6035be9ce6 | ||
|
|
ccd0f7d9cc | ||
|
|
8746e7c9ad | ||
|
|
e2aa36d083 | ||
|
|
51434a39f8 | ||
|
|
3e2031be7c | ||
|
|
ea4e8805b7 | ||
|
|
b9042c37f9 | ||
|
|
9f4a7e664f | ||
|
|
38eb75b13f | ||
|
|
0ad56874b4 | ||
|
|
c1168693fa | ||
|
|
feb8c27f1f | ||
|
|
c20ebd66e5 | ||
|
|
4d78d530dc | ||
|
|
22b20c15de | ||
|
|
c69b224c65 | ||
|
|
805d328d6c | ||
|
|
b8293f134d | ||
|
|
7e9bcff0f3 | ||
|
|
eba3b32708 | ||
|
|
e911dbb9d4 | ||
|
|
e26d123f67 | ||
|
|
22e3cddd91 | ||
|
|
f3d4d4747a | ||
|
|
5bbb0cd666 | ||
|
|
7b5ba3bdc2 | ||
|
|
d647555e3a | ||
|
|
84976a65e0 | ||
|
|
fef9d541ed | ||
|
|
6784522195 | ||
|
|
f42d077f30 | ||
|
|
eaca47ebc6 | ||
|
|
4d57223847 | ||
|
|
d9ab96236b | ||
|
|
2856fb029f | ||
|
|
8fb945312a | ||
|
|
25d6806ebd | ||
|
|
51070d9afd | ||
|
|
1048caa496 | ||
|
|
0cd7ac05aa | ||
|
|
2793c42d91 | ||
|
|
2eaa7288e4 | ||
|
|
0df8d13020 | ||
|
|
d27622de1e | ||
|
|
47c3da131c | ||
|
|
64f9228ee3 | ||
|
|
b462b7fcc4 | ||
|
|
b34f9d7fd3 | ||
|
|
57732c3e5f | ||
|
|
eb1f56488f | ||
|
|
5825843f68 | ||
|
|
86f82c0e61 | ||
|
|
45fea983b9 | ||
|
|
642b499e70 | ||
|
|
5c5575bb72 | ||
|
|
fba0a8036b | ||
|
|
d82274f5d4 | ||
|
|
3cf81230b2 | ||
|
|
17bab456e4 | ||
|
|
e1cc006db7 | ||
|
|
7e95dd3c76 | ||
|
|
ff3ce46a26 | ||
|
|
4d61c2c5e0 | ||
|
|
afaba7ccdf | ||
|
|
ccb3ceae4f | ||
|
|
d0d93f6d2d | ||
|
|
67f091c731 | ||
|
|
fa3aebb7b1 | ||
|
|
988251deb6 | ||
|
|
d99a389c49 | ||
|
|
7508f9d3bb | ||
|
|
b2512d7aee | ||
|
|
eb74e1e3b2 | ||
|
|
bd91e82b17 | ||
|
|
bae60acfe7 | ||
|
|
426cefe8ee | ||
|
|
a36fe3ef21 | ||
|
|
ee0756c7c0 | ||
|
|
44b96121e4 | ||
|
|
8add777b34 | ||
|
|
6932b15144 | ||
|
|
c09edda797 | ||
|
|
0e86010565 | ||
|
|
6fe3fdce11 | ||
|
|
90769a12b1 | ||
|
|
1d49b725b6 | ||
|
|
204feb97ce | ||
|
|
e12389a748 | ||
|
|
907e5a10d6 | ||
|
|
670591e221 | ||
|
|
8006a7d578 | ||
|
|
8b9078b82e | ||
|
|
56d4edd3ae | ||
|
|
a7f5ea865e | ||
|
|
2a62144abd | ||
|
|
c7ea7a4f65 | ||
|
|
7ea090ba8d | ||
|
|
9294e353d3 | ||
|
|
71c50c58ee | ||
|
|
e322299fda | ||
|
|
06602a568a | ||
|
|
a0f0529ad8 | ||
|
|
c371d863d9 | ||
|
|
16d8c75953 | ||
|
|
fa48c0e76b | ||
|
|
2d17cfaf2b | ||
|
|
f379de0a3a | ||
|
|
d3fcb0aa6a | ||
|
|
046740f10b | ||
|
|
ffae2eda83 | ||
|
|
8334b8fe46 | ||
|
|
c6c6e58d28 | ||
|
|
2ff0d6665c | ||
|
|
dc6108c970 | ||
|
|
6f52f9b4af | ||
|
|
b6eb896f0b | ||
|
|
adf861c36d | ||
|
|
4e1bcdf209 | ||
|
|
d107fe19f7 | ||
|
|
fa03ff51d4 | ||
|
|
f6c6536b48 | ||
|
|
38e4249182 | ||
|
|
2dd8c0beee | ||
|
|
6a35494ef1 | ||
|
|
bf1c42e085 | ||
|
|
0ef6bf8067 | ||
|
|
9d63e2ae97 | ||
|
|
fdd8060296 | ||
|
|
ebbb23ffbb | ||
|
|
0e413109bd | ||
|
|
f7e29c1e00 | ||
|
|
42f777d49a | ||
|
|
efb8ea4c25 | ||
|
|
52bf5690c0 | ||
|
|
607dff9263 | ||
|
|
0510db75fb | ||
|
|
859eecabc0 | ||
|
|
7a0253631b | ||
|
|
c12a60c1ad | ||
|
|
df9dbd6130 | ||
|
|
a98e745116 | ||
|
|
61f1b10a06 | ||
|
|
4bcb2a5c9d | ||
|
|
7e48648f9e | ||
|
|
bcc97d1aa7 | ||
|
|
49f9d36718 | ||
|
|
f9cb258a5e | ||
|
|
97381a4908 | ||
|
|
7a15acc546 | ||
|
|
0265de2137 | ||
|
|
595b9910f5 | ||
|
|
f4d4d2cc35 | ||
|
|
b4fb4d7dcf | ||
|
|
1a375fb523 | ||
|
|
658c0ff5c4 | ||
|
|
6092f06d46 | ||
|
|
4671b956b3 | ||
|
|
552ba6999a | ||
|
|
b86bc4455f | ||
|
|
b6d36ee117 | ||
|
|
5d57f864dc | ||
|
|
c6ee2816db | ||
|
|
0aa898b13f | ||
|
|
b22e82c8ce | ||
|
|
63dee1e1ac | ||
|
|
91c87ac301 | ||
|
|
42371a6e8d | ||
|
|
d7aae849f6 | ||
|
|
4fe9413662 | ||
|
|
37bf51cebf | ||
|
|
eee9cb8b87 | ||
|
|
1724bca5f0 | ||
|
|
ae6581fb55 | ||
|
|
0c632307ea | ||
|
|
479887eb84 | ||
|
|
af280a7343 | ||
|
|
802b26e870 | ||
|
|
0ab86937d2 | ||
|
|
3ab06bf383 | ||
|
|
5db0cc5241 | ||
|
|
a588ec084b | ||
|
|
5660b5ddc6 | ||
|
|
27fbe69033 | ||
|
|
1672ecb892 | ||
|
|
784e01347c | ||
|
|
5cc21a831b | ||
|
|
dcb9380b50 | ||
|
|
b1957773bb | ||
|
|
c4f4583f20 | ||
|
|
440cdbdb23 | ||
|
|
58bfde33a9 | ||
|
|
7c33e49ef6 | ||
|
|
3bf318e2b7 | ||
|
|
f56193ac18 | ||
|
|
31efa7a8f8 | ||
|
|
372932abaf | ||
|
|
4c6dd2cdf2 | ||
|
|
6d04e39151 | ||
|
|
afa257e79a | ||
|
|
05f8ee9747 | ||
|
|
818ae03928 | ||
|
|
97ad3c1e6d | ||
|
|
97555645f8 | ||
|
|
612228bb73 | ||
|
|
e350f43adc | ||
|
|
2257f3484d | ||
|
|
6e75d41956 | ||
|
|
9883a38698 | ||
|
|
07256e2e34 | ||
|
|
43674ae80a | ||
|
|
f4ba8df02b | ||
|
|
c066ebd76f | ||
|
|
6e382c64a4 | ||
|
|
99ebf03876 | ||
|
|
995d79e373 | ||
|
|
0cd153ab61 | ||
|
|
81e76f260c | ||
|
|
b24baa68ba | ||
|
|
d4c1b8d321 | ||
|
|
1e53d6bfab | ||
|
|
5931cd6af7 | ||
|
|
a1be03543c | ||
|
|
b1a5547de2 | ||
|
|
93571961ee | ||
|
|
ee4942dfd7 | ||
|
|
cb24347b23 | ||
|
|
146b7be825 | ||
|
|
50203c5f87 | ||
|
|
b188073fb0 | ||
|
|
8aef24be1e | ||
|
|
fb25f6c7ac | ||
|
|
fbd983217d | ||
|
|
ce21fe2087 | ||
|
|
17cd395712 | ||
|
|
bfe9de05cd | ||
|
|
91c60df0e9 | ||
|
|
ad065e9281 | ||
|
|
b1429366da | ||
|
|
8bf7af2e74 | ||
|
|
2003f51d49 | ||
|
|
ce83fd9a10 | ||
|
|
eacbaa3680 | ||
|
|
98c65fb9b7 | ||
|
|
44a71d8565 | ||
|
|
badd4d3207 | ||
|
|
0f517b803b | ||
|
|
c2d11e786f | ||
|
|
b0efe49e29 | ||
|
|
2d029b9f76 | ||
|
|
8ad917cff0 | ||
|
|
4e478c65d3 | ||
|
|
a469086915 | ||
|
|
bf05ff6048 | ||
|
|
a817d8cbf9 | ||
|
|
4a19c78fa5 | ||
|
|
e8bb7da906 | ||
|
|
523477fc2b | ||
|
|
c730426be0 | ||
|
|
57d6c97203 | ||
|
|
fce17aa1d4 | ||
|
|
01abc244b1 | ||
|
|
7bedacf5ad | ||
|
|
552a1d0464 | ||
|
|
8dde25532a | ||
|
|
f29fa939ab | ||
|
|
614bdb33b4 | ||
|
|
71761675cf | ||
|
|
d9194aa859 | ||
|
|
f15081a474 | ||
|
|
2f99ff4a0c | ||
|
|
3a7d26aa46 | ||
|
|
3f35bc593c | ||
|
|
e5e708d781 | ||
|
|
d694561980 | ||
|
|
8d6d18e875 | ||
|
|
072e27ed27 | ||
|
|
6d64215614 | ||
|
|
33f5ed5b14 | ||
|
|
27f509c8e0 | ||
|
|
890b3e13c9 | ||
|
|
b730cb099f | ||
|
|
fc94f184d2 | ||
|
|
cbf6540889 | ||
|
|
40804a7fb3 | ||
|
|
d4101c4f43 | ||
|
|
409bebd5bc | ||
|
|
8e3ad69adb | ||
|
|
c8e46d9e21 | ||
|
|
c56241ffc1 | ||
|
|
be62a2bfc5 | ||
|
|
5cb7771484 | ||
|
|
6675d3e2cd | ||
|
|
8ecbe4c8ad | ||
|
|
edb75c4bab | ||
|
|
54b21c716a | ||
|
|
4704274b87 | ||
|
|
78547aa119 | ||
|
|
3887231c73 | ||
|
|
8a29cfbb7e | ||
|
|
a01d6eaf72 | ||
|
|
69fc571b56 | ||
|
|
4cfd9c322b | ||
|
|
4326354ca6 | ||
|
|
4208c852e1 | ||
|
|
7330b4532e | ||
|
|
1e0f6f9e41 | ||
|
|
216e2367c6 | ||
|
|
d2dce8801b | ||
|
|
5b8bb9f678 | ||
|
|
2076f146cf | ||
|
|
f4416fe007 | ||
|
|
510591ef0f | ||
|
|
a5e89d1dd1 | ||
|
|
5d4f2b7862 | ||
|
|
e3815e40d2 | ||
|
|
6dccfb4774 | ||
|
|
1ccc1f4c1a | ||
|
|
042809620a | ||
|
|
cb4c8abd94 | ||
|
|
e86302f5b9 | ||
|
|
de080d5811 | ||
|
|
21b7045f93 | ||
|
|
627301f83d | ||
|
|
607ca84fcc | ||
|
|
a7f36248d0 | ||
|
|
d008d15167 | ||
|
|
607dc436bd | ||
|
|
4384948f6c | ||
|
|
5e05e9ec93 | ||
|
|
ac1fe66cf9 | ||
|
|
86732b6ae4 | ||
|
|
0713f55e9c | ||
|
|
5c32d73409 | ||
|
|
5e13a1735d | ||
|
|
6a1fbb00d9 | ||
|
|
20c3badfac | ||
|
|
7817cfe0c1 | ||
|
|
9ed823b5a5 | ||
|
|
c6a5dedf0a | ||
|
|
01c9ab36b7 | ||
|
|
27f5bdeef1 | ||
|
|
723898f87d | ||
|
|
bc05cc1445 | ||
|
|
dcf4e43e28 | ||
|
|
3868c53908 | ||
|
|
a71c693ca3 | ||
|
|
691f93f01c | ||
|
|
4cff749186 | ||
|
|
e1ac1547fd | ||
|
|
e53bd505fb | ||
|
|
cfa926542e | ||
|
|
79097eca47 | ||
|
|
d1741e40e3 | ||
|
|
5d0528d195 | ||
|
|
a9ea06f753 | ||
|
|
62e121c12c | ||
|
|
02ef05160f | ||
|
|
6e66c013c0 | ||
|
|
dcb11f01e1 | ||
|
|
8a0e4b577c | ||
|
|
b57f420261 | ||
|
|
7d1790abe3 | ||
|
|
1fc494571b | ||
|
|
e6d97bc773 | ||
|
|
0e53323fb7 | ||
|
|
eb4764d2b2 | ||
|
|
298a91adbf | ||
|
|
2cb9912039 | ||
|
|
e52bfe4335 | ||
|
|
761a249e05 | ||
|
|
c42df3a0c2 | ||
|
|
3d359b7a98 | ||
|
|
deef6417ad | ||
|
|
f55a8deb97 | ||
|
|
333506e00b | ||
|
|
bbc1642b90 | ||
|
|
8209eda27a | ||
|
|
b13f7a599b | ||
|
|
cb0f700be1 | ||
|
|
7b6d6b466a | ||
|
|
76f97e5c2e | ||
|
|
4669a1ab57 | ||
|
|
b1ad0edbe1 | ||
|
|
51a695d047 | ||
|
|
396e2d14f3 | ||
|
|
245479c339 | ||
|
|
092215f47a | ||
|
|
fcb46db718 | ||
|
|
60c58c8b9c | ||
|
|
124a2839b5 | ||
|
|
ededfe10ab | ||
|
|
81895c20d6 | ||
|
|
46fabe065c | ||
|
|
7ac338756a | ||
|
|
640b8edd78 | ||
|
|
c5d98752fa | ||
|
|
a6a5bef447 | ||
|
|
042885c155 | ||
|
|
cbb9dcf7d0 | ||
|
|
9b080800e1 | ||
|
|
6effbf50a8 | ||
|
|
398f9aa19a | ||
|
|
935d89747f | ||
|
|
21c2fbfd39 | ||
|
|
bd337f3aac | ||
|
|
4a673eee81 | ||
|
|
cebf349f9a | ||
|
|
3683deb51c | ||
|
|
99ee076db9 | ||
|
|
04f759041f | ||
|
|
75f89059e7 | ||
|
|
a81f31156d | ||
|
|
f706452e67 | ||
|
|
b0126afbcf | ||
|
|
5b8393ff89 | ||
|
|
d2235da06a | ||
|
|
a87f6a0791 | ||
|
|
7e7cfb79a4 | ||
|
|
fb43a5265c | ||
|
|
439a814133 | ||
|
|
d9dfcc04bf | ||
|
|
0cfac137b7 | ||
|
|
4575ee805a | ||
|
|
67f70ce2cc | ||
|
|
2f641ffb13 | ||
|
|
50f92269c2 | ||
|
|
f220f397ae | ||
|
|
cdb4096124 | ||
|
|
01938af65b | ||
|
|
47a1fca32f | ||
|
|
321342cf6d | ||
|
|
086e9beb59 | ||
|
|
3519d4b219 | ||
|
|
9034b9a9ae | ||
|
|
90ba8440a0 | ||
|
|
4988b37d6f | ||
|
|
ad4799ee60 | ||
|
|
35229f8ae5 | ||
|
|
7a011d9e75 | ||
|
|
3d86835979 | ||
|
|
4073306538 | ||
|
|
b86aa28d6a | ||
|
|
d43cee29f2 | ||
|
|
acad468b4a | ||
|
|
4cbe842cfa | ||
|
|
d93e227190 | ||
|
|
a9cf424998 | ||
|
|
6d8fdf46d5 | ||
|
|
781f98ef91 | ||
|
|
1313f685da | ||
|
|
4779a993d3 | ||
|
|
342b2ae5dc | ||
|
|
ce8ae40206 | ||
|
|
d0704f621f | ||
|
|
c6da4043ed | ||
|
|
1aa3761d1a | ||
|
|
ff769caf82 | ||
|
|
be6bc68b56 | ||
|
|
feb3d11f63 | ||
|
|
d2f9b063b2 | ||
|
|
315089c361 | ||
|
|
e95df0dbd5 | ||
|
|
8fed029ee3 | ||
|
|
522daf5aff | ||
|
|
e948eebe84 | ||
|
|
783d4e7e8a | ||
|
|
1ce2198621 | ||
|
|
f521def4a5 | ||
|
|
75202921a1 | ||
|
|
4ef8b93344 | ||
|
|
881b191b8d | ||
|
|
b39e071d1e | ||
|
|
84cb3a1060 | ||
|
|
f4ea3980c2 | ||
|
|
ef73720a5e | ||
|
|
d8cdc57702 | ||
|
|
b48c251b36 | ||
|
|
181a14ce59 | ||
|
|
b9ea7ce066 | ||
|
|
f2f275512d | ||
|
|
5150c2ee62 | ||
|
|
0b7593ad28 | ||
|
|
a68823491c |
46
.github/CONTRIBUTING.md
vendored
46
.github/CONTRIBUTING.md
vendored
@@ -5,44 +5,64 @@ PLEASE READ THESE GUIDELINES CAREFULLY BEFORE ANY CONTRIBUTION!
|
||||
|
||||
## Crash reporting
|
||||
|
||||
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report via e-mail when a crash occurs. This contains all the data we need for debugging, and allows you to even add a comment to it. You'll see exactly what is sent, the system is 100% transparent.
|
||||
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to
|
||||
send a report via e-mail when a crash occurs. This contains all the data we need for debugging, and allows you to even
|
||||
add a comment to it. You'll see exactly what is sent, the system is 100% transparent.
|
||||
|
||||
## Issue reporting/feature requests
|
||||
|
||||
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature hasn't been reported/requested before
|
||||
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
|
||||
hasn't been reported/requested before
|
||||
* Check whether your issue/feature is already fixed/implemented
|
||||
* Check if the issue still exists in the latest release/beta version
|
||||
* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
|
||||
* We use English for development. Issues in other languages will be closed and ignored.
|
||||
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
|
||||
* When reporting a bug please give us a context, and a description how to reproduce it.
|
||||
* Issues that only contain a generated bug report, but no describtion might be closed.
|
||||
* Issues that only contain a generated bug report, but no description might be closed.
|
||||
|
||||
## Bug Fixing
|
||||
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request, register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
|
||||
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
|
||||
tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request,
|
||||
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
|
||||
|
||||
## Translation
|
||||
|
||||
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there with your GitHub account.
|
||||
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
|
||||
with your GitHub account.
|
||||
|
||||
## Code contribution
|
||||
|
||||
* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
|
||||
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google libraries.
|
||||
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google
|
||||
libraries.
|
||||
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
|
||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
|
||||
* When submitting changes, you confirm that your code is licensed under the terms of the [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
|
||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You
|
||||
may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might
|
||||
not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
|
||||
* When submitting changes, you confirm that your code is licensed under the terms of the
|
||||
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
|
||||
description. Untested code will **not** be merged!
|
||||
* Try to figure out yourself why builds on our CI fail.
|
||||
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job, but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the maintainers' jobs way easier.
|
||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about submission, or clearly state that in the description of your PR.
|
||||
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
|
||||
but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the
|
||||
maintainers' jobs way easier.
|
||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
|
||||
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
|
||||
about submission, or clearly state that in the description of your PR.
|
||||
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
|
||||
* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
|
||||
* Check if your submission can be build with the current fdroid build server setup.
|
||||
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
|
||||
independent solutions.
|
||||
|
||||
## Communication
|
||||
|
||||
* WE DO NOW HAVE A MAILING LIST: [newpipe@list.schabi.org](https://list.schabi.org/cgi-bin/mailman/listinfo/newpipe).
|
||||
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers: [#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
||||
* If you want to get in touch with the core team or one of our other contributors you can send an email to tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue tracker described above!
|
||||
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
|
||||
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
||||
* If you want to get in touch with the core team or one of our other contributors you can send an email to
|
||||
tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
|
||||
tracker described above!
|
||||
* Feel free to post suggestions, changes, ideas etc. on GitHub, IRC or the mailing list!
|
||||
|
||||
@@ -5,13 +5,13 @@ android:
|
||||
components:
|
||||
# The BuildTools version used by NewPipe
|
||||
- tools
|
||||
- build-tools-27.0.3
|
||||
- build-tools-28.0.3
|
||||
|
||||
# The SDK version used to compile NewPipe
|
||||
- android-27
|
||||
- android-28
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-27"
|
||||
- yes | sdkmanager "platforms;android-28"
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||
|
||||
licenses:
|
||||
|
||||
94
README.md
94
README.md
@@ -1,74 +1,77 @@
|
||||
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"/></a></p>
|
||||
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||
<h2 align="center"><b>NewPipe</b></h2>
|
||||
<h4 align="center">A free lightweight YouTube frontend for Android.</h4>
|
||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"/></a></p>
|
||||
<h4 align="center">A libre lightweight streaming frontend for Android.</h4>
|
||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" /></a>
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPL v3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg" /></a>
|
||||
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg" /></a>
|
||||
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg" /></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg" /></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"/></a>
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
||||
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg"></a>
|
||||
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr />
|
||||
<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="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 />
|
||||
WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
|
||||
<hr>
|
||||
|
||||
<b>WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
|
||||
|
||||
## Screenshots
|
||||
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
|
||||
|
||||
## Description
|
||||
|
||||
NewPipe does not use any Google framework libraries, or the YouTube API. It only parses the website in order to gain the information it needs. Therefore this app can be used on devices without Google Services installed. Also, you don't need a YouTube account to use NewPipe, and it's FLOSS.
|
||||
NewPipe does not use any Google framework libraries, nor the YouTube API. Websites are only parsed to fetch required info, so this app can be used on devices without Google services installed. Also, you don't need a YouTube account to use NewPipe, which is copylefted libre software.
|
||||
|
||||
### Features
|
||||
|
||||
* Search videos
|
||||
* Display general information about a video
|
||||
* Display general info about videos
|
||||
* Watch YouTube videos
|
||||
* Listen to YouTube videos
|
||||
* Popup mode (floating player)
|
||||
* Select the streaming player to watch the video with
|
||||
* Download videos
|
||||
* Select streaming player to watch video with
|
||||
* Download videos
|
||||
* Download audio only
|
||||
* Open a video in Kodi
|
||||
* Show Next/Related videos
|
||||
* Show next/related videos
|
||||
* Search YouTube in a specific language
|
||||
* Watch/Block age restricted material
|
||||
* Display general information about channels
|
||||
* Display general info about channels
|
||||
* Search channels
|
||||
* Watch videos from a channel
|
||||
* Orbot/Tor support (not yet directly)
|
||||
* 1080p/2k/4k support
|
||||
* 1080p/2K/4K support
|
||||
* View history
|
||||
* Subscribe to channels
|
||||
* Search history
|
||||
* Search/Watch Playlists
|
||||
* Watch as queues Playlists
|
||||
* Queuing videos
|
||||
* Search/watch playlists
|
||||
* Watch as enqueued playlists
|
||||
* Enqueue videos
|
||||
* Local playlists
|
||||
* Subtitles
|
||||
* Multi-service support (eg. SoundCloud in NewPipe Beta)
|
||||
* Multi-service support (e.g. SoundCloud \[beta\])
|
||||
* Livestream support
|
||||
|
||||
### Coming Features
|
||||
|
||||
* Livestream support
|
||||
* Cast to UPnP and Cast
|
||||
* Show comments
|
||||
* ... and many more
|
||||
* … and many more
|
||||
|
||||
## Contribution
|
||||
Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome.
|
||||
@@ -77,26 +80,31 @@ The more is done the better it gets!
|
||||
If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
|
||||
|
||||
## Donate
|
||||
If you like NewPipe we'd be happy about a donation. You can either donate via Bitcoin, Bountysource or Liberapay. For further information about donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).
|
||||
If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin" /></td>
|
||||
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR Code" width="100px"/></td>
|
||||
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin"></td>
|
||||
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR code" width="100px"></td>
|
||||
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Liberapay_logo_v2_white-on-yellow.svg" alt="Liberapay" width="80px" /></a></td>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visit NewPipe at liberapay.com" width="100px"/></a></td>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px" /></a></td>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Liberapay_logo_v2_white-on-yellow.svg" alt="Liberapay" width="80px" ></a></td>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visit NewPipe at liberapay.com" width="100px"></a></td>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px" /></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visit NewPipe at bountysource.com" width="100px"/></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Check out how many bounties you can earn." /></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px"></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visit NewPipe at bountysource.com" width="100px"></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Check out how many bounties you can earn."></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Privacy Policy
|
||||
|
||||
The NewPipe project aims to provide a private, anonymous experience for using media web services.
|
||||
Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.schabi.org/legal/privacy/).
|
||||
|
||||
## License
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '27.0.3'
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 27
|
||||
versionCode 66
|
||||
versionName "0.13.7"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 70
|
||||
versionName "0.15.0"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -22,7 +22,6 @@ android {
|
||||
}
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
|
||||
debuggable true
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
@@ -41,58 +40,61 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
supportLibVersion = '27.1.1'
|
||||
exoPlayerLibVersion = '2.8.2'
|
||||
supportLibVersion = '28.0.0'
|
||||
exoPlayerLibVersion = '2.8.4' //2.9.0
|
||||
roomDbLibVersion = '1.1.1'
|
||||
leakCanaryLibVersion = '1.5.4'
|
||||
okHttpLibVersion = '3.10.0'
|
||||
leakCanaryLibVersion = '1.5.4' //1.6.1
|
||||
okHttpLibVersion = '3.11.0'
|
||||
icepickLibVersion = '3.2.0'
|
||||
stethoLibVersion = '1.5.0'
|
||||
}
|
||||
dependencies {
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:1eff8c5708'
|
||||
dependencies {
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2', {
|
||||
exclude module: 'support-annotations'
|
||||
})
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:99915e4527c0'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||
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: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 'ch.acra:acra:4.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"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion"
|
||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerLibVersion}"
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:$stethoLibVersion"
|
||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:$stethoLibVersion"
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
|
||||
debugImplementation 'com.android.support:multidex:1.0.3'
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.1.14'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
|
||||
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 "android.arch.persistence.room:runtime:${roomDbLibVersion}"
|
||||
implementation "android.arch.persistence.room:rxjava2:${roomDbLibVersion}"
|
||||
annotationProcessor "android.arch.persistence.room:compiler:${roomDbLibVersion}"
|
||||
|
||||
implementation "frankiesardo:icepick:$icepickLibVersion"
|
||||
annotationProcessor "frankiesardo:icepick-processor:$icepickLibVersion"
|
||||
implementation "frankiesardo:icepick:${icepickLibVersion}"
|
||||
annotationProcessor "frankiesardo:icepick-processor:${icepickLibVersion}"
|
||||
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryLibVersion"
|
||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion"
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}"
|
||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}"
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:$okHttpLibVersion"
|
||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:$stethoLibVersion"
|
||||
implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@@ -76,10 +77,6 @@
|
||||
android:name=".about.AboutActivity"
|
||||
android:label="@string/title_activity_about"/>
|
||||
|
||||
<activity
|
||||
android:name=".history.HistoryActivity"
|
||||
android:label="@string/title_activity_history"/>
|
||||
|
||||
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
|
||||
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
|
||||
|
||||
@@ -122,6 +119,7 @@
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
android:label="@string/reCaptchaActivity"/>
|
||||
<activity android:name=".download.ExtSDDownloadFailedActivity" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
@@ -241,4 +239,4 @@
|
||||
android:name=".RouterActivity$FetcherService"
|
||||
android:exported="false"/>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 android.util.Log;
|
||||
|
||||
@@ -21,6 +22,7 @@ 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,7 +67,8 @@ public class App extends Application {
|
||||
private RefWatcher refWatcher;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Class<? extends ReportSenderFactory>[] reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
|
||||
private static final Class<? extends ReportSenderFactory>[]
|
||||
reportSenderFactoryClasses = new Class[]{AcraReportSenderFactory.class};
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
@@ -88,7 +91,8 @@ public class App extends Application {
|
||||
// Initialize settings first because others inits can use its values
|
||||
SettingsActivity.initSettings(this);
|
||||
|
||||
NewPipe.init(getDownloader());
|
||||
NewPipe.init(getDownloader(),
|
||||
org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(this));
|
||||
StateSaver.init(this);
|
||||
initNotificationChannel();
|
||||
|
||||
@@ -106,7 +110,7 @@ public class App extends Application {
|
||||
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
|
||||
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
||||
public void accept(@NonNull Throwable throwable) {
|
||||
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " +
|
||||
"throwable = [" + throwable.getClass().getName() + "]");
|
||||
|
||||
@@ -180,7 +184,11 @@ public class App extends Application {
|
||||
ACRA.init(this, acraConfig);
|
||||
} catch (ACRAConfigurationException ace) {
|
||||
ace.printStackTrace();
|
||||
ErrorActivity.reportError(this, ace, null, null, ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
ErrorActivity.reportError(this,
|
||||
ace,
|
||||
null,
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
||||
}
|
||||
}
|
||||
@@ -200,7 +208,8 @@ public class App extends Application {
|
||||
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
|
||||
mChannel.setDescription(description);
|
||||
|
||||
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationManager mNotificationManager =
|
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 android.util.Log;
|
||||
import android.view.View;
|
||||
@@ -12,14 +13,24 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import icepick.Icepick;
|
||||
import icepick.State;
|
||||
|
||||
public abstract class BaseFragment extends Fragment {
|
||||
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||
protected boolean DEBUG = MainActivity.DEBUG;
|
||||
protected final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
protected AppCompatActivity activity;
|
||||
public static final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
|
||||
//These values are used for controlling framgents when they are part of the frontpage
|
||||
@State
|
||||
protected boolean useAsFrontPage = false;
|
||||
protected boolean mIsVisibleToUser = false;
|
||||
|
||||
public void useAsFrontPage(boolean value) {
|
||||
useAsFrontPage = value;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Fragment's Lifecycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -72,6 +83,12 @@ public abstract class BaseFragment extends Fragment {
|
||||
if (refWatcher != null) refWatcher.watch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -88,8 +105,15 @@ public abstract class BaseFragment extends Fragment {
|
||||
|
||||
public void setTitle(String title) {
|
||||
if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
||||
if (activity != null && activity.getSupportActionBar() != null) {
|
||||
if((!useAsFrontPage || mIsVisibleToUser)
|
||||
&& (activity != null && activity.getSupportActionBar() != null)) {
|
||||
activity.getSupportActionBar().setTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
protected FragmentManager getFM() {
|
||||
return getParentFragment() == null
|
||||
? getFragmentManager()
|
||||
: getParentFragment().getFragmentManager();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.utils.Localization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -43,7 +44,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
|
||||
private static Downloader instance;
|
||||
private String mCookies;
|
||||
private OkHttpClient client;
|
||||
private final OkHttpClient client;
|
||||
|
||||
private Downloader(OkHttpClient.Builder builder) {
|
||||
this.client = builder
|
||||
@@ -88,7 +89,8 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
.build();
|
||||
response = client.newCall(request).execute();
|
||||
|
||||
return Long.parseLong(response.header("Content-Length"));
|
||||
String contentLength = response.header("Content-Length");
|
||||
return contentLength == null ? -1 : Long.parseLong(contentLength);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("Invalid content length", e);
|
||||
} finally {
|
||||
@@ -103,13 +105,13 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
* but set the HTTP header field "Accept-Language" to the supplied string.
|
||||
*
|
||||
* @param siteUrl the URL of the text file to return the contents of
|
||||
* @param language the language (usually a 2-character code) to set as the preferred language
|
||||
* @param localization the language and country (usually a 2-character code) to set
|
||||
* @return the contents of the specified text file
|
||||
*/
|
||||
@Override
|
||||
public String download(String siteUrl, String language) throws IOException, ReCaptchaException {
|
||||
public String download(String siteUrl, Localization localization) throws IOException, ReCaptchaException {
|
||||
Map<String, String> requestProperties = new HashMap<>();
|
||||
requestProperties.put("Accept-Language", language);
|
||||
requestProperties.put("Accept-Language", localization.getLanguage());
|
||||
return download(siteUrl, requestProperties);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ package org.schabi.newpipe;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -43,26 +43,28 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.extractor.InfoItem.InfoType.PLAYLIST;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
@@ -72,6 +74,19 @@ public class MainActivity extends AppCompatActivity {
|
||||
private NavigationView drawerItems = null;
|
||||
private TextView headerServiceView = null;
|
||||
|
||||
private boolean servicesShown = false;
|
||||
private ImageView serviceArrow;
|
||||
|
||||
private static final int ITEM_ID_SUBSCRIPTIONS = - 1;
|
||||
private static final int ITEM_ID_FEED = - 2;
|
||||
private static final int ITEM_ID_BOOKMARKS = - 3;
|
||||
private static final int ITEM_ID_DOWNLOADS = - 4;
|
||||
private static final int ITEM_ID_HISTORY = - 5;
|
||||
private static final int ITEM_ID_SETTINGS = 0;
|
||||
private static final int ITEM_ID_ABOUT = 1;
|
||||
|
||||
private static final int ORDER = 0;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Activity's LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -85,42 +100,66 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Window w = getWindow();
|
||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
|
||||
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
initFragments();
|
||||
}
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
setupDrawer();
|
||||
try {
|
||||
setupDrawer();
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDrawer() {
|
||||
private void setupDrawer() throws Exception {
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
drawer = findViewById(R.id.drawer_layout);
|
||||
drawerItems = findViewById(R.id.navigation);
|
||||
|
||||
for(StreamingService s : NewPipe.getServices()) {
|
||||
final String title = s.getServiceInfo().getName() +
|
||||
(ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||
final MenuItem item = drawerItems.getMenu()
|
||||
.add(R.id.menu_services_group, s.getServiceId(), 0, title);
|
||||
item.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
||||
//Tabs
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
||||
int kioskId = 0;
|
||||
|
||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||
kioskId ++;
|
||||
}
|
||||
|
||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_whats_new)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
||||
|
||||
toggle = new ActionBarDrawerToggle(this, drawer, toolbar,
|
||||
R.string.drawer_open, R.string.drawer_close) {
|
||||
@Override
|
||||
public void onDrawerClosed(View view) { super.onDrawerClosed(view); }
|
||||
//Settings and About
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
||||
|
||||
@Override
|
||||
public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); }
|
||||
|
||||
@Override
|
||||
public void onDrawerSlide(View drawerView, float slideOffset) {
|
||||
super.onDrawerSlide(drawerView, 0);
|
||||
}
|
||||
};
|
||||
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);
|
||||
toggle.syncState();
|
||||
drawer.addDrawerListener(toggle);
|
||||
drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
||||
@@ -133,51 +172,179 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onDrawerClosed(View drawerView) {
|
||||
if(servicesShown) {
|
||||
toggleServices();
|
||||
}
|
||||
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
|
||||
new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
drawerItems.setNavigationItemSelectedListener(this::changeService);
|
||||
|
||||
setupDrawerFooter();
|
||||
drawerItems.setNavigationItemSelectedListener(this::drawerItemSelected);
|
||||
setupDrawerHeader();
|
||||
}
|
||||
|
||||
private boolean drawerItemSelected(MenuItem item) {
|
||||
switch (item.getGroupId()) {
|
||||
case R.id.menu_services_group:
|
||||
changeService(item);
|
||||
break;
|
||||
case R.id.menu_tabs_group:
|
||||
try {
|
||||
tabSelected(item);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
break;
|
||||
case R.id.menu_options_about_group:
|
||||
optionsAboutSelected(item);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean changeService(MenuItem item) {
|
||||
if (item.getGroupId() != R.id.menu_services_group)
|
||||
return false;
|
||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
|
||||
ServiceHelper.setSelectedServiceId(this, item.getItemId());
|
||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
|
||||
drawer.closeDrawers();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setupDrawerFooter() {
|
||||
ImageButton settings = findViewById(R.id.drawer_settings);
|
||||
ImageButton downloads = findViewById(R.id.drawer_downloads);
|
||||
ImageButton history = findViewById(R.id.drawer_history);
|
||||
private void changeService(MenuItem item) {
|
||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false);
|
||||
ServiceHelper.setSelectedServiceId(this, item.getItemId());
|
||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
|
||||
}
|
||||
|
||||
settings.setOnClickListener(view -> NavigationHelper.openSettings(this));
|
||||
downloads.setOnClickListener(view ->NavigationHelper.openDownloads(this));
|
||||
history.setOnClickListener(view ->
|
||||
NavigationHelper.openStatisticFragment(getSupportFragmentManager()));
|
||||
private void tabSelected(MenuItem item) throws ExtractionException {
|
||||
switch(item.getItemId()) {
|
||||
case ITEM_ID_SUBSCRIPTIONS:
|
||||
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
|
||||
break;
|
||||
case ITEM_ID_FEED:
|
||||
NavigationHelper.openWhatsNewFragment(getSupportFragmentManager());
|
||||
break;
|
||||
case ITEM_ID_BOOKMARKS:
|
||||
NavigationHelper.openBookmarksFragment(getSupportFragmentManager());
|
||||
break;
|
||||
case ITEM_ID_DOWNLOADS:
|
||||
NavigationHelper.openDownloads(this);
|
||||
break;
|
||||
case ITEM_ID_HISTORY:
|
||||
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
||||
break;
|
||||
default:
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
String serviceName = "";
|
||||
|
||||
int kioskId = 0;
|
||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||
if(kioskId == item.getItemId()) {
|
||||
serviceName = ks;
|
||||
}
|
||||
kioskId ++;
|
||||
}
|
||||
|
||||
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId, serviceName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void optionsAboutSelected(MenuItem item) {
|
||||
switch(item.getItemId()) {
|
||||
case ITEM_ID_SETTINGS:
|
||||
NavigationHelper.openSettings(this);
|
||||
break;
|
||||
case ITEM_ID_ABOUT:
|
||||
NavigationHelper.openAbout(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDrawerHeader() {
|
||||
headerServiceView = findViewById(R.id.drawer_header_service_view);
|
||||
Button action = findViewById(R.id.drawer_header_action_button);
|
||||
NavigationView navigationView = findViewById(R.id.navigation);
|
||||
View hView = navigationView.getHeaderView(0);
|
||||
|
||||
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 -> {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("https://newpipe.schabi.org/blog/"));
|
||||
startActivity(intent);
|
||||
drawer.closeDrawers();
|
||||
toggleServices();
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleServices() {
|
||||
servicesShown = !servicesShown;
|
||||
|
||||
drawerItems.getMenu().removeGroup(R.id.menu_services_group);
|
||||
drawerItems.getMenu().removeGroup(R.id.menu_tabs_group);
|
||||
drawerItems.getMenu().removeGroup(R.id.menu_options_about_group);
|
||||
|
||||
if(servicesShown) {
|
||||
showServices();
|
||||
} else {
|
||||
try {
|
||||
showTabs();
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showServices() {
|
||||
serviceArrow.setImageResource(R.drawable.ic_arrow_up_white);
|
||||
|
||||
for(StreamingService s : NewPipe.getServices()) {
|
||||
final String title = s.getServiceInfo().getName() +
|
||||
(ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
|
||||
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
|
||||
}
|
||||
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
|
||||
}
|
||||
|
||||
private void showTabs() throws ExtractionException {
|
||||
serviceArrow.setImageResource(R.drawable.ic_arrow_down_white);
|
||||
|
||||
//Tabs
|
||||
int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
|
||||
int kioskId = 0;
|
||||
|
||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, kioskId, ORDER, KioskTranslator.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||
kioskId ++;
|
||||
}
|
||||
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_whats_new)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
||||
|
||||
//Settings and About
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
@@ -341,16 +508,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
onHomeButtonPressed();
|
||||
return true;
|
||||
case R.id.action_show_downloads:
|
||||
return NavigationHelper.openDownloads(this);
|
||||
return NavigationHelper.openDownloads(this);
|
||||
case R.id.action_history:
|
||||
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
||||
return true;
|
||||
case R.id.action_about:
|
||||
NavigationHelper.openAbout(this);
|
||||
return true;
|
||||
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
|
||||
return true;
|
||||
case R.id.action_settings:
|
||||
NavigationHelper.openSettings(this);
|
||||
return true;
|
||||
NavigationHelper.openSettings(this);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private class ReCaptchaWebViewClient extends WebViewClient {
|
||||
private Activity context;
|
||||
private final Activity context;
|
||||
private String mCookies;
|
||||
|
||||
ReCaptchaWebViewClient(Activity ctx) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -13,7 +12,6 @@ import android.preference.PreferenceManager;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
@@ -38,7 +36,6 @@ 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.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
@@ -51,14 +48,12 @@ import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Observer;
|
||||
|
||||
import icepick.Icepick;
|
||||
import icepick.State;
|
||||
@@ -86,7 +81,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
protected int selectedPreviously = -1;
|
||||
|
||||
protected String currentUrl;
|
||||
protected CompositeDisposable disposables = new CompositeDisposable();
|
||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private boolean selectionIsDownload = false;
|
||||
|
||||
@@ -184,12 +179,16 @@ public class RouterActivity extends AppCompatActivity {
|
||||
if (selectedChoiceKey.equals(alwaysAskKey)) {
|
||||
final List<AdapterChoiceItem> choices = getChoicesForService(currentService, currentLinkType);
|
||||
|
||||
if (choices.size() == 1) {
|
||||
handleChoice(choices.get(0).key);
|
||||
} else if (choices.size() == 0) {
|
||||
handleChoice(showInfoKey);
|
||||
} else {
|
||||
showDialog(choices);
|
||||
switch (choices.size()) {
|
||||
case 1:
|
||||
handleChoice(choices.get(0).key);
|
||||
break;
|
||||
case 0:
|
||||
handleChoice(showInfoKey);
|
||||
break;
|
||||
default:
|
||||
showDialog(choices);
|
||||
break;
|
||||
}
|
||||
} else if (selectedChoiceKey.equals(showInfoKey)) {
|
||||
handleChoice(showInfoKey);
|
||||
@@ -543,8 +542,7 @@ 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 useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this);
|
||||
boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);;
|
||||
|
||||
PlayQueue playQueue;
|
||||
String playerChoice = choice.playerChoice;
|
||||
@@ -556,9 +554,6 @@ public class RouterActivity extends AppCompatActivity {
|
||||
} else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
|
||||
NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
|
||||
|
||||
} else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) {
|
||||
NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info);
|
||||
|
||||
} else {
|
||||
playQueue = new SinglePlayQueue((StreamInfo) info);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.webkit.WebView;
|
||||
@@ -17,7 +16,7 @@ import java.lang.ref.WeakReference;
|
||||
|
||||
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
|
||||
WeakReference<Activity> weakReference;
|
||||
final WeakReference<Activity> weakReference;
|
||||
private License license;
|
||||
|
||||
public LicenseFragmentHelper(@Nullable Activity activity) {
|
||||
@@ -78,18 +77,18 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
throw new NullPointerException("license is null");
|
||||
}
|
||||
|
||||
String licenseContent = "";
|
||||
StringBuilder licenseContent = new StringBuilder();
|
||||
String webViewData;
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(context.getAssets().open(license.getFilename()), "UTF-8"));
|
||||
String str;
|
||||
while ((str = in.readLine()) != null) {
|
||||
licenseContent += str;
|
||||
licenseContent.append(str);
|
||||
}
|
||||
in.close();
|
||||
|
||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||
String[] insert = licenseContent.split("</head>");
|
||||
String[] insert = licenseContent.toString().split("</head>");
|
||||
webViewData = insert[0] + "<style type=\"text/css\">"
|
||||
+ getLicenseStylesheet(context) + "</style></head>"
|
||||
+ insert[1];
|
||||
|
||||
@@ -30,7 +30,7 @@ public interface BasicDAO<Entity> {
|
||||
|
||||
/* Deletes */
|
||||
@Delete
|
||||
int delete(final Entity entity);
|
||||
void delete(final Entity entity);
|
||||
|
||||
@Delete
|
||||
int delete(final Collection<Entity> entities);
|
||||
@@ -42,5 +42,5 @@ public interface BasicDAO<Entity> {
|
||||
int update(final Entity entity);
|
||||
|
||||
@Update
|
||||
int update(final Collection<Entity> entities);
|
||||
void update(final Collection<Entity> entities);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
|
||||
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
|
||||
@@ -12,7 +11,6 @@ import java.util.List;
|
||||
import io.reactivex.Flowable;
|
||||
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||
|
||||
@Dao
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
|
||||
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.arch.persistence.room.Ignore;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.schabi.newpipe.database.BasicDAO;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
|
||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,7 +22,6 @@ import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_SERVI
|
||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
|
||||
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
|
||||
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
|
||||
|
||||
@Dao
|
||||
public abstract class StreamDAO implements BasicDAO<StreamEntity> {
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.BaseTransientBottomBar;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
|
||||
public class DeleteDownloadManager {
|
||||
|
||||
private static final String KEY_STATE = "delete_manager_state";
|
||||
|
||||
private View mView;
|
||||
private HashSet<String> mPendingMap;
|
||||
private List<Disposable> mDisposableList;
|
||||
private DownloadManager mDownloadManager;
|
||||
private PublishSubject<DownloadMission> publishSubject = PublishSubject.create();
|
||||
|
||||
DeleteDownloadManager(Activity activity) {
|
||||
mPendingMap = new HashSet<>();
|
||||
mDisposableList = new ArrayList<>();
|
||||
mView = activity.findViewById(android.R.id.content);
|
||||
}
|
||||
|
||||
public Observable<DownloadMission> getUndoObservable() {
|
||||
return publishSubject;
|
||||
}
|
||||
|
||||
public boolean contains(@NonNull DownloadMission mission) {
|
||||
return mPendingMap.contains(mission.url);
|
||||
}
|
||||
|
||||
public void add(@NonNull DownloadMission mission) {
|
||||
mPendingMap.add(mission.url);
|
||||
|
||||
if (mPendingMap.size() == 1) {
|
||||
showUndoDeleteSnackbar(mission);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDownloadManager(@NonNull DownloadManager downloadManager) {
|
||||
mDownloadManager = downloadManager;
|
||||
|
||||
if (mPendingMap.size() < 1) return;
|
||||
|
||||
showUndoDeleteSnackbar();
|
||||
}
|
||||
|
||||
public void restoreState(@Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) return;
|
||||
|
||||
List<String> list = savedInstanceState.getStringArrayList(KEY_STATE);
|
||||
if (list != null) {
|
||||
mPendingMap.addAll(list);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveState(@Nullable Bundle outState) {
|
||||
if (outState == null) return;
|
||||
|
||||
for (Disposable disposable : mDisposableList) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
outState.putStringArrayList(KEY_STATE, new ArrayList<>(mPendingMap));
|
||||
}
|
||||
|
||||
private void showUndoDeleteSnackbar() {
|
||||
if (mPendingMap.size() < 1) return;
|
||||
|
||||
String url = mPendingMap.iterator().next();
|
||||
|
||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
||||
DownloadMission mission = mDownloadManager.getMission(i);
|
||||
if (url.equals(mission.url)) {
|
||||
showUndoDeleteSnackbar(mission);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showUndoDeleteSnackbar(@NonNull DownloadMission mission) {
|
||||
final Snackbar snackbar = Snackbar.make(mView, mission.name, Snackbar.LENGTH_INDEFINITE);
|
||||
final Disposable disposable = Observable.timer(3, TimeUnit.SECONDS)
|
||||
.subscribeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(l -> snackbar.dismiss());
|
||||
|
||||
mDisposableList.add(disposable);
|
||||
|
||||
snackbar.setAction(R.string.undo, v -> {
|
||||
mPendingMap.remove(mission.url);
|
||||
publishSubject.onNext(mission);
|
||||
disposable.dispose();
|
||||
snackbar.dismiss();
|
||||
});
|
||||
|
||||
snackbar.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
@Override
|
||||
public void onDismissed(Snackbar transientBottomBar, int event) {
|
||||
if (!disposable.isDisposed()) {
|
||||
Completable.fromAction(() -> deletePending(mission))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe();
|
||||
}
|
||||
mPendingMap.remove(mission.url);
|
||||
snackbar.removeCallback(this);
|
||||
mDisposableList.remove(disposable);
|
||||
showUndoDeleteSnackbar();
|
||||
}
|
||||
});
|
||||
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
public void deletePending() {
|
||||
if (mPendingMap.size() < 1) return;
|
||||
|
||||
HashSet<Integer> idSet = new HashSet<>();
|
||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
||||
if (contains(mDownloadManager.getMission(i))) {
|
||||
idSet.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (Integer id : idSet) {
|
||||
mDownloadManager.deleteMission(id);
|
||||
}
|
||||
|
||||
mPendingMap.clear();
|
||||
}
|
||||
|
||||
private void deletePending(@NonNull DownloadMission mission) {
|
||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
||||
if (mission.url.equals(mDownloadManager.getMission(i).url)) {
|
||||
mDownloadManager.deleteMission(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,12 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
import us.shandian.giga.ui.fragment.AllMissionsFragment;
|
||||
import us.shandian.giga.ui.fragment.MissionsFragment;
|
||||
|
||||
public class DownloadActivity extends AppCompatActivity {
|
||||
|
||||
private static final String MISSIONS_FRAGMENT_TAG = "fragment_tag";
|
||||
private DeleteDownloadManager mDeleteDownloadManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -47,32 +43,17 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
|
||||
mDeleteDownloadManager = new DeleteDownloadManager(this);
|
||||
mDeleteDownloadManager.restoreState(savedInstanceState);
|
||||
|
||||
MissionsFragment fragment = (MissionsFragment) getFragmentManager().findFragmentByTag(MISSIONS_FRAGMENT_TAG);
|
||||
if (fragment != null) {
|
||||
fragment.setDeleteManager(mDeleteDownloadManager);
|
||||
} else {
|
||||
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
updateFragments();
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
mDeleteDownloadManager.saveState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
updateFragments();
|
||||
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateFragments() {
|
||||
MissionsFragment fragment = new AllMissionsFragment();
|
||||
fragment.setDeleteManager(mDeleteDownloadManager);
|
||||
MissionsFragment fragment = new MissionsFragment();
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG)
|
||||
@@ -99,7 +80,6 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
case R.id.action_settings: {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
deletePending();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@@ -108,14 +88,7 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
deletePending();
|
||||
}
|
||||
|
||||
private void deletePending() {
|
||||
Completable.fromAction(mDeleteDownloadManager::deletePending)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe();
|
||||
public void onRestoreInstanceState(Bundle inState){
|
||||
super.onRestoreInstanceState(inState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package org.schabi.newpipe.download;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
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 android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -22,40 +26,57 @@ import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
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.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.util.FilenameUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.SecondaryStreamHelper;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import icepick.Icepick;
|
||||
import icepick.State;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import us.shandian.giga.postprocessing.Postprocessing;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
|
||||
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
|
||||
private static final String TAG = "DialogFragment";
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
@State protected StreamInfo currentInfo;
|
||||
@State protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
|
||||
@State protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
|
||||
@State protected int selectedVideoIndex = 0;
|
||||
@State protected int selectedAudioIndex = 0;
|
||||
@State
|
||||
protected StreamInfo currentInfo;
|
||||
@State
|
||||
protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
|
||||
@State
|
||||
protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
|
||||
@State
|
||||
protected StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams = StreamSizeWrapper.empty();
|
||||
@State
|
||||
protected int selectedVideoIndex = 0;
|
||||
@State
|
||||
protected int selectedAudioIndex = 0;
|
||||
@State
|
||||
protected int selectedSubtitleIndex = 0;
|
||||
|
||||
private StreamItemAdapter<AudioStream> audioStreamsAdapter;
|
||||
private StreamItemAdapter<VideoStream> videoStreamsAdapter;
|
||||
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
|
||||
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
|
||||
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private EditText nameEditText;
|
||||
private Spinner streamsSpinner;
|
||||
@@ -63,6 +84,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
private TextView threadsCountTextView;
|
||||
private SeekBar threadsSeekBar;
|
||||
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public static DownloadDialog newInstance(StreamInfo info) {
|
||||
DownloadDialog dialog = new DownloadDialog();
|
||||
dialog.setInfo(info);
|
||||
@@ -78,6 +101,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
instance.setVideoStreams(streamsList);
|
||||
instance.setSelectedVideoStream(selectedStreamIndex);
|
||||
instance.setAudioStreams(info.getAudioStreams());
|
||||
instance.setSubtitleStreams(info.getSubtitles());
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -86,7 +111,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
}
|
||||
|
||||
public void setAudioStreams(List<AudioStream> audioStreams) {
|
||||
setAudioStreams(new StreamSizeWrapper<>(audioStreams));
|
||||
setAudioStreams(new StreamSizeWrapper<>(audioStreams, getContext()));
|
||||
}
|
||||
|
||||
public void setAudioStreams(StreamSizeWrapper<AudioStream> wrappedAudioStreams) {
|
||||
@@ -94,13 +119,21 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
}
|
||||
|
||||
public void setVideoStreams(List<VideoStream> videoStreams) {
|
||||
setVideoStreams(new StreamSizeWrapper<>(videoStreams));
|
||||
setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext()));
|
||||
}
|
||||
|
||||
public void setVideoStreams(StreamSizeWrapper<VideoStream> wrappedVideoStreams) {
|
||||
this.wrappedVideoStreams = wrappedVideoStreams;
|
||||
}
|
||||
|
||||
public void setSubtitleStreams(List<SubtitlesStream> subtitleStreams) {
|
||||
setSubtitleStreams(new StreamSizeWrapper<>(subtitleStreams, getContext()));
|
||||
}
|
||||
|
||||
public void setSubtitleStreams(StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams) {
|
||||
this.wrappedSubtitleStreams = wrappedSubtitleStreams;
|
||||
}
|
||||
|
||||
public void setSelectedVideoStream(int selectedVideoIndex) {
|
||||
this.selectedVideoIndex = selectedVideoIndex;
|
||||
}
|
||||
@@ -109,6 +142,10 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
this.selectedAudioIndex = selectedAudioIndex;
|
||||
}
|
||||
|
||||
public void setSelectedSubtitleStream(int selectedSubtitleIndex) {
|
||||
this.selectedSubtitleIndex = selectedSubtitleIndex;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -116,7 +153,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
|
||||
getDialog().dismiss();
|
||||
return;
|
||||
@@ -125,13 +163,29 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext()));
|
||||
Icepick.restoreInstanceState(this, savedInstanceState);
|
||||
|
||||
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, true);
|
||||
SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
|
||||
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||
|
||||
for (int i = 0; i < videoStreams.size(); i++) {
|
||||
if (!videoStreams.get(i).isVideoOnly()) continue;
|
||||
AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
|
||||
|
||||
if (audioStream != null) {
|
||||
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
|
||||
} else if (DEBUG) {
|
||||
Log.w(TAG, "No audio stream candidates for video format " + videoStreams.get(i).getFormat().name());
|
||||
}
|
||||
}
|
||||
|
||||
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, secondaryStreams);
|
||||
this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams);
|
||||
this.subtitleStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedSubtitleStreams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
|
||||
return inflater.inflate(R.layout.download_dialog, container);
|
||||
}
|
||||
|
||||
@@ -142,6 +196,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
|
||||
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(), currentInfo.getAudioStreams());
|
||||
|
||||
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
|
||||
|
||||
streamsSpinner = view.findViewById(R.id.quality_spinner);
|
||||
streamsSpinner.setOnItemSelectedListener(this);
|
||||
|
||||
@@ -154,14 +210,18 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
initToolbar(view.findViewById(R.id.toolbar));
|
||||
setupDownloadOptions();
|
||||
|
||||
int def = 3;
|
||||
threadsCountTextView.setText(String.valueOf(def));
|
||||
threadsSeekBar.setProgress(def - 1);
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
|
||||
int threads = prefs.getInt(getString(R.string.default_download_threads), 3);
|
||||
threadsCountTextView.setText(String.valueOf(threads));
|
||||
threadsSeekBar.setProgress(threads - 1);
|
||||
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
|
||||
threadsCountTextView.setText(String.valueOf(progress + 1));
|
||||
progress++;
|
||||
prefs.edit().putInt(getString(R.string.default_download_threads), progress).apply();
|
||||
threadsCountTextView.setText(String.valueOf(progress));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -189,6 +249,11 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
setupAudioSpinner();
|
||||
}
|
||||
}));
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> {
|
||||
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
|
||||
setupSubtitleSpinner();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -216,7 +281,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
|
||||
toolbar.setOnMenuItemClickListener(item -> {
|
||||
if (item.getItemId() == R.id.okay) {
|
||||
downloadSelected();
|
||||
prepareSelectedDownload();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -239,13 +304,24 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
setRadioButtonsState(true);
|
||||
}
|
||||
|
||||
private void setupSubtitleSpinner() {
|
||||
if (getContext() == null) return;
|
||||
|
||||
streamsSpinner.setAdapter(subtitleStreamsAdapter);
|
||||
streamsSpinner.setSelection(selectedSubtitleIndex);
|
||||
setRadioButtonsState(true);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Radio group Video&Audio options - Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
|
||||
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
|
||||
boolean flag = true;
|
||||
|
||||
switch (checkedId) {
|
||||
case R.id.audio_button:
|
||||
setupAudioSpinner();
|
||||
@@ -253,7 +329,13 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
case R.id.video_button:
|
||||
setupVideoSpinner();
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
setupSubtitleSpinner();
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
|
||||
threadsSeekBar.setEnabled(flag);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -262,7 +344,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
|
||||
@Override
|
||||
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 + "]");
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
|
||||
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
selectedAudioIndex = position;
|
||||
@@ -270,6 +353,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
case R.id.video_button:
|
||||
selectedVideoIndex = position;
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
selectedSubtitleIndex = position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,11 +372,14 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
|
||||
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 boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
|
||||
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
|
||||
final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0;
|
||||
|
||||
audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE : View.GONE);
|
||||
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
|
||||
subtitleButton.setVisibility(isSubtitleStreamsAvailable ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (isVideoStreamsAvailable) {
|
||||
videoButton.setChecked(true);
|
||||
@@ -298,6 +387,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||
} else if (isAudioStreamsAvailable) {
|
||||
audioButton.setChecked(true);
|
||||
setupAudioSpinner();
|
||||
} else if (isSubtitleStreamsAvailable) {
|
||||
subtitleButton.setChecked(true);
|
||||
setupSubtitleSpinner();
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show();
|
||||
getDialog().dismiss();
|
||||
@@ -307,28 +399,144 @@ 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);
|
||||
}
|
||||
|
||||
private void downloadSelected() {
|
||||
Stream stream;
|
||||
String location;
|
||||
private int getSubtitleIndexBy(List<SubtitlesStream> streams) {
|
||||
Localization loc = NewPipe.getPreferredLocalization();
|
||||
|
||||
String fileName = nameEditText.getText().toString().trim();
|
||||
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
|
||||
|
||||
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
|
||||
if (isAudio) {
|
||||
stream = audioStreamsAdapter.getItem(selectedAudioIndex);
|
||||
location = NewPipeSettings.getAudioDownloadPath(getContext());
|
||||
} else {
|
||||
stream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
location = NewPipeSettings.getVideoDownloadPath(getContext());
|
||||
for (int i = 0; i < streams.size(); i++) {
|
||||
Locale streamLocale = streams.get(i).getLocale();
|
||||
String tag = streamLocale.getLanguage().concat("-").concat(streamLocale.getCountry());
|
||||
if (tag.equalsIgnoreCase(loc.getLanguage())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
String url = stream.getUrl();
|
||||
fileName += "." + stream.getFormat().getSuffix();
|
||||
// fallback
|
||||
// 1st loop match country & language
|
||||
// 2nd loop match language only
|
||||
int index = loc.getLanguage().indexOf("-");
|
||||
String lang = index > 0 ? loc.getLanguage().substring(0, index) : loc.getLanguage();
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
for (int i = 0; i < streams.size(); i++) {
|
||||
Locale streamLocale = streams.get(i).getLocale();
|
||||
|
||||
if (streamLocale.getLanguage().equalsIgnoreCase(lang)) {
|
||||
if (j > 0 || streamLocale.getCountry().equalsIgnoreCase(loc.getCountry())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void prepareSelectedDownload() {
|
||||
final Context context = getContext();
|
||||
Stream stream;
|
||||
String location;
|
||||
char kind;
|
||||
|
||||
String fileName = nameEditText.getText().toString().trim();
|
||||
if (fileName.isEmpty())
|
||||
fileName = FilenameUtils.createFilename(context, currentInfo.getName());
|
||||
|
||||
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
stream = audioStreamsAdapter.getItem(selectedAudioIndex);
|
||||
location = NewPipeSettings.getAudioDownloadPath(context);
|
||||
kind = 'a';
|
||||
break;
|
||||
case R.id.video_button:
|
||||
stream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
location = NewPipeSettings.getVideoDownloadPath(context);
|
||||
kind = 'v';
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video go together
|
||||
kind = 's';
|
||||
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();
|
||||
}
|
||||
|
||||
final String finalFileName = fileName;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void downloadSelected(Context context, Stream selectedStream, String location, String fileName, char kind, int threads) {
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
} 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
|
||||
};
|
||||
}
|
||||
|
||||
if (secondaryStreamUrl == null) {
|
||||
urls = new String[]{selectedStream.getUrl()};
|
||||
} else {
|
||||
urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl};
|
||||
}
|
||||
|
||||
DownloadManagerService.startMission(context, urls, location, fileName, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength);
|
||||
|
||||
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
|
||||
getDialog().dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.functions.Consumer;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
@@ -51,9 +50,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
protected Button errorButtonRetry;
|
||||
protected TextView errorTextView;
|
||||
|
||||
@State
|
||||
protected boolean useAsFrontPage = false;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View rootView, Bundle savedInstanceState) {
|
||||
super.onViewCreated(rootView, savedInstanceState);
|
||||
@@ -66,9 +62,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
wasLoading.set(isLoading.get());
|
||||
}
|
||||
|
||||
public void useAsFrontPage(boolean value) {
|
||||
useAsFrontPage = value;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
@@ -93,12 +86,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||
RxView.clicks(errorButtonRetry)
|
||||
.debounce(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Object>() {
|
||||
@Override
|
||||
public void accept(Object o) throws Exception {
|
||||
onRetryButtonClicked();
|
||||
}
|
||||
});
|
||||
.subscribe(o -> onRetryButtonClicked());
|
||||
}
|
||||
|
||||
protected void onRetryButtonClicked() {
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -14,24 +13,16 @@ public class BlankFragment extends BaseFragment {
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
||||
if(activity != null && activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar()
|
||||
.setTitle("NewPipe");
|
||||
}
|
||||
setTitle("NewPipe");
|
||||
return inflater.inflate(R.layout.fragment_blank, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if(isVisibleToUser) {
|
||||
if(activity != null && activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar()
|
||||
.setTitle("NewPipe");
|
||||
}
|
||||
// leave this inline. Will make it harder for copy cats.
|
||||
// If you are a Copy cat FUCK YOU.
|
||||
// I WILL FIND YOU, AND I WILL ...
|
||||
}
|
||||
setTitle("NewPipe");
|
||||
// leave this inline. Will make it harder for copy cats.
|
||||
// If you are a Copy cat FUCK YOU.
|
||||
// I WILL FIND YOU, AND I WILL ...
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -11,48 +10,36 @@ 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.support.v7.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
import org.schabi.newpipe.local.feed.FeedFragment;
|
||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.settings.tabs.Tab;
|
||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
|
||||
|
||||
public int currentServiceId = -1;
|
||||
private ViewPager viewPager;
|
||||
private SelectedTabsPagerAdapter pagerAdapter;
|
||||
private TabLayout tabLayout;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Constants
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private List<Tab> tabsList = new ArrayList<>();
|
||||
private TabsManager tabsManager;
|
||||
|
||||
private static final int FALLBACK_SERVICE_ID = ServiceList.YouTube.getServiceId();
|
||||
private static final String FALLBACK_CHANNEL_URL = "https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ";
|
||||
private static final String FALLBACK_CHANNEL_NAME = "Music";
|
||||
private static final String FALLBACK_KIOSK_ID = "Trending";
|
||||
private static final int KIOSK_MENU_OFFSET = 2000;
|
||||
private boolean hasTabsChanged = false;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Fragment's LifeCycle
|
||||
@@ -62,11 +49,22 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
tabsManager = TabsManager.getManager(activity);
|
||||
tabsManager.setSavedTabsListener(() -> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed());
|
||||
}
|
||||
if (isResumed()) {
|
||||
updateTabs();
|
||||
} else {
|
||||
hasTabsChanged = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
currentServiceId = ServiceHelper.getSelectedServiceId(activity);
|
||||
return inflater.inflate(R.layout.fragment_main, container, false);
|
||||
}
|
||||
|
||||
@@ -74,30 +72,34 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
||||
TabLayout tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
||||
tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
||||
viewPager = rootView.findViewById(R.id.pager);
|
||||
|
||||
/* Nested fragment, use child fragment here to maintain backstack in view pager. */
|
||||
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager());
|
||||
viewPager.setAdapter(adapter);
|
||||
viewPager.setOffscreenPageLimit(adapter.getCount());
|
||||
pagerAdapter = new SelectedTabsPagerAdapter(getChildFragmentManager());
|
||||
viewPager.setAdapter(pagerAdapter);
|
||||
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
tabLayout.addOnTabSelectedListener(this);
|
||||
updateTabs();
|
||||
}
|
||||
|
||||
int channelIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_channel);
|
||||
int whatsHotIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_hot);
|
||||
int bookmarkIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_bookmark);
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (isSubscriptionsPageOnlySelected()) {
|
||||
tabLayout.getTabAt(0).setIcon(channelIcon);
|
||||
tabLayout.getTabAt(1).setIcon(bookmarkIcon);
|
||||
} else {
|
||||
tabLayout.getTabAt(0).setIcon(whatsHotIcon);
|
||||
tabLayout.getTabAt(1).setIcon(channelIcon);
|
||||
tabLayout.getTabAt(2).setIcon(bookmarkIcon);
|
||||
if (hasTabsChanged) {
|
||||
hasTabsChanged = false;
|
||||
updateTabs();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
tabsManager.unsetSavedTabsListener();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -107,16 +109,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
|
||||
inflater.inflate(R.menu.main_fragment_menu, menu);
|
||||
SubMenu kioskMenu = menu.addSubMenu(Menu.NONE, Menu.NONE, 200, getString(R.string.kiosk));
|
||||
try {
|
||||
createKioskMenu(kioskMenu, inflater);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportError(activity, e,
|
||||
activity.getClass(),
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"none", "", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
@@ -145,9 +137,33 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
// Tabs
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void updateTabs() {
|
||||
tabsList.clear();
|
||||
tabsList.addAll(tabsManager.getTabs());
|
||||
pagerAdapter.notifyDataSetChanged();
|
||||
|
||||
viewPager.setOffscreenPageLimit(pagerAdapter.getCount());
|
||||
updateTabsIcon();
|
||||
updateCurrentTitle();
|
||||
}
|
||||
|
||||
private void updateTabsIcon() {
|
||||
for (int i = 0; i < tabsList.size(); i++) {
|
||||
final TabLayout.Tab tabToSet = tabLayout.getTabAt(i);
|
||||
if (tabToSet != null) {
|
||||
tabToSet.setIcon(tabsList.get(i).getTabIconRes(activity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCurrentTitle() {
|
||||
setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
viewPager.setCurrentItem(tab.getPosition());
|
||||
public void onTabSelected(TabLayout.Tab selectedTab) {
|
||||
if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
|
||||
updateCurrentTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,124 +172,58 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
|
||||
updateCurrentTitle();
|
||||
}
|
||||
|
||||
private class PagerAdapter extends FragmentPagerAdapter {
|
||||
PagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
private class SelectedTabsPagerAdapter extends FragmentPagerAdapter {
|
||||
private SelectedTabsPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment();
|
||||
case 1:
|
||||
if(PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
|
||||
.equals(getString(R.string.subscription_page_key))) {
|
||||
return new BookmarkFragment();
|
||||
} else {
|
||||
return new SubscriptionFragment();
|
||||
}
|
||||
case 2:
|
||||
return new BookmarkFragment();
|
||||
default:
|
||||
return new BlankFragment();
|
||||
final Tab tab = tabsList.get(position);
|
||||
|
||||
Throwable throwable = null;
|
||||
Fragment fragment = null;
|
||||
try {
|
||||
fragment = tab.getFragment();
|
||||
} catch (ExtractionException e) {
|
||||
throwable = e;
|
||||
}
|
||||
|
||||
if (throwable != null) {
|
||||
ErrorActivity.reportError(activity, throwable, activity.getClass(), null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
||||
return new BlankFragment();
|
||||
}
|
||||
|
||||
if (fragment instanceof BaseFragment) {
|
||||
((BaseFragment) fragment).useAsFrontPage(true);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
//return getString(this.tabTitles[position]);
|
||||
return "";
|
||||
public int getItemPosition(Object object) {
|
||||
// Causes adapter to reload all Fragments when
|
||||
// notifyDataSetChanged is called
|
||||
return POSITION_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return isSubscriptionsPageOnlySelected() ? 2 : 3;
|
||||
return tabsList.size();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Main page content
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private boolean isSubscriptionsPageOnlySelected() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key))
|
||||
.equals(getString(R.string.subscription_page_key));
|
||||
}
|
||||
|
||||
private Fragment getMainPageFragment() {
|
||||
if (getActivity() == null) return new BlankFragment();
|
||||
|
||||
try {
|
||||
SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
final String setMainPage = preferences.getString(getString(R.string.main_page_content_key),
|
||||
getString(R.string.main_page_selectd_kiosk_id));
|
||||
if (setMainPage.equals(getString(R.string.blank_page_key))) {
|
||||
return new BlankFragment();
|
||||
} else if (setMainPage.equals(getString(R.string.kiosk_page_key))) {
|
||||
int serviceId = preferences.getInt(getString(R.string.main_page_selected_service),
|
||||
FALLBACK_SERVICE_ID);
|
||||
String kioskId = preferences.getString(getString(R.string.main_page_selectd_kiosk_id),
|
||||
FALLBACK_KIOSK_ID);
|
||||
KioskFragment fragment = KioskFragment.getInstance(serviceId, kioskId);
|
||||
fragment.useAsFrontPage(true);
|
||||
return fragment;
|
||||
} else if (setMainPage.equals(getString(R.string.feed_page_key))) {
|
||||
FeedFragment fragment = new FeedFragment();
|
||||
fragment.useAsFrontPage(true);
|
||||
return fragment;
|
||||
} else if (setMainPage.equals(getString(R.string.channel_page_key))) {
|
||||
int serviceId = preferences.getInt(getString(R.string.main_page_selected_service),
|
||||
FALLBACK_SERVICE_ID);
|
||||
String url = preferences.getString(getString(R.string.main_page_selected_channel_url),
|
||||
FALLBACK_CHANNEL_URL);
|
||||
String name = preferences.getString(getString(R.string.main_page_selected_channel_name),
|
||||
FALLBACK_CHANNEL_NAME);
|
||||
ChannelFragment fragment = ChannelFragment.getInstance(serviceId,
|
||||
url,
|
||||
name);
|
||||
fragment.useAsFrontPage(true);
|
||||
return fragment;
|
||||
} else {
|
||||
return new BlankFragment();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportError(activity, e,
|
||||
activity.getClass(),
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"none", "", R.string.app_ui_crash));
|
||||
return new BlankFragment();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Select Kiosk
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void createKioskMenu(Menu menu, MenuInflater menuInflater)
|
||||
throws Exception {
|
||||
StreamingService service = NewPipe.getService(currentServiceId);
|
||||
KioskList kl = service.getKioskList();
|
||||
int i = 0;
|
||||
for (final String ks : kl.getAvailableKiosks()) {
|
||||
menu.add(0, KIOSK_MENU_OFFSET + i, Menu.NONE,
|
||||
KioskTranslator.getTranslatedKioskName(ks, getContext()))
|
||||
.setOnMenuItemClickListener(menuItem -> {
|
||||
try {
|
||||
NavigationHelper.openKioskFragment(getFragmentManager(), currentServiceId, ks);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
i++;
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.remove((Fragment) object)
|
||||
.commitNowAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package org.schabi.newpipe.fragments.detail;
|
||||
import java.io.Serializable;
|
||||
|
||||
class StackItem implements Serializable {
|
||||
private int serviceId;
|
||||
private final int serviceId;
|
||||
private String title;
|
||||
private String url;
|
||||
private final String url;
|
||||
|
||||
StackItem(int serviceId, String url, String title) {
|
||||
this.serviceId = serviceId;
|
||||
|
||||
@@ -33,12 +33,14 @@ import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@@ -61,22 +63,20 @@ import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.MainVideoPlayer;
|
||||
import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
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.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
@@ -87,6 +87,8 @@ import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -154,6 +156,7 @@ public class VideoDetailFragment
|
||||
|
||||
private View videoTitleRoot;
|
||||
private TextView videoTitleTextView;
|
||||
@Nullable
|
||||
private ImageView videoTitleToggleArrow;
|
||||
private TextView videoCountView;
|
||||
|
||||
@@ -368,14 +371,14 @@ public class VideoDetailFragment
|
||||
Log.w(TAG, "Can't open channel because we got no channel URL");
|
||||
} else {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(
|
||||
getFragmentManager(),
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUploaderUrl(),
|
||||
currentInfo.getUploaderName());
|
||||
NavigationHelper.openChannelFragment(
|
||||
getFragmentManager(),
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUploaderUrl(),
|
||||
currentInfo.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R.id.detail_thumbnail_root_layout:
|
||||
@@ -415,14 +418,16 @@ public class VideoDetailFragment
|
||||
}
|
||||
|
||||
private void toggleTitleAndDescription() {
|
||||
if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
|
||||
videoTitleTextView.setMaxLines(1);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
} else {
|
||||
videoTitleTextView.setMaxLines(10);
|
||||
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
|
||||
if (videoTitleToggleArrow != null) { //it is null for tablets
|
||||
if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
|
||||
videoTitleTextView.setMaxLines(1);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
} else {
|
||||
videoTitleTextView.setMaxLines(10);
|
||||
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,8 +627,11 @@ public class VideoDetailFragment
|
||||
relatedStreamsView.addView(
|
||||
infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo()));
|
||||
relatedStreamsView.addView(getSeparatorView());
|
||||
relatedStreamRootLayout.setVisibility(View.VISIBLE);
|
||||
} else nextStreamTitle.setVisibility(View.GONE);
|
||||
setRelatedStreamsVisibility(View.VISIBLE);
|
||||
} else {
|
||||
nextStreamTitle.setVisibility(View.GONE);
|
||||
setRelatedStreamsVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (info.getRelatedStreams() != null
|
||||
&& !info.getRelatedStreams().isEmpty() && showRelatedStreams) {
|
||||
@@ -639,13 +647,13 @@ public class VideoDetailFragment
|
||||
}
|
||||
//if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms");
|
||||
|
||||
relatedStreamRootLayout.setVisibility(View.VISIBLE);
|
||||
setRelatedStreamsVisibility(View.VISIBLE);
|
||||
relatedStreamExpandButton.setVisibility(View.VISIBLE);
|
||||
|
||||
relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(
|
||||
activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand)));
|
||||
} else {
|
||||
if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE);
|
||||
if (info.getNextVideo() == null) setRelatedStreamsVisibility(View.GONE);
|
||||
relatedStreamExpandButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -737,7 +745,7 @@ public class VideoDetailFragment
|
||||
sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
||||
selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams);
|
||||
|
||||
final StreamItemAdapter<VideoStream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams), isExternalPlayerEnabled);
|
||||
final StreamItemAdapter<VideoStream, Stream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams, activity), isExternalPlayerEnabled);
|
||||
spinnerToolbar.setAdapter(streamsAdapter);
|
||||
spinnerToolbar.setSelection(selectedVideoStreamIndex);
|
||||
spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@@ -760,7 +768,7 @@ public class VideoDetailFragment
|
||||
* Stack that contains the "navigation history".<br>
|
||||
* The peek is the current video.
|
||||
*/
|
||||
protected LinkedList<StackItem> stack = new LinkedList<>();
|
||||
protected final LinkedList<StackItem> stack = new LinkedList<>();
|
||||
|
||||
public void clearHistory() {
|
||||
stack.clear();
|
||||
@@ -913,7 +921,7 @@ public class VideoDetailFragment
|
||||
.getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
|
||||
startOnExternalPlayer(activity, currentInfo, selectedVideoStream);
|
||||
} else {
|
||||
openNormalPlayer(selectedVideoStream);
|
||||
openNormalPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,24 +934,13 @@ public class VideoDetailFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void openNormalPlayer(VideoStream selectedVideoStream) {
|
||||
private void openNormalPlayer() {
|
||||
Intent mIntent;
|
||||
boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16);
|
||||
if (!useOldPlayer) {
|
||||
// ExoPlayer
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
mIntent = NavigationHelper.getPlayerIntent(activity,
|
||||
MainVideoPlayer.class,
|
||||
playQueue,
|
||||
getSelectedVideoStream().getResolution());
|
||||
} else {
|
||||
// Internal Player
|
||||
mIntent = new Intent(activity, PlayVideoActivity.class)
|
||||
.putExtra(PlayVideoActivity.VIDEO_TITLE, currentInfo.getName())
|
||||
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.getUrl())
|
||||
.putExtra(PlayVideoActivity.VIDEO_URL, currentInfo.getUrl())
|
||||
.putExtra(PlayVideoActivity.START_POSITION, currentInfo.getStartPosition());
|
||||
}
|
||||
final PlayQueue playQueue = new SinglePlayQueue(currentInfo);
|
||||
mIntent = NavigationHelper.getPlayerIntent(activity,
|
||||
MainVideoPlayer.class,
|
||||
playQueue,
|
||||
getSelectedVideoStream().getResolution());
|
||||
startActivity(mIntent);
|
||||
}
|
||||
|
||||
@@ -1114,8 +1111,16 @@ public class VideoDetailFragment
|
||||
animateView(videoTitleTextView, true, 0);
|
||||
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoTitleToggleArrow.setVisibility(View.GONE);
|
||||
if (videoTitleToggleArrow != null) { //phone
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoTitleToggleArrow.setVisibility(View.GONE);
|
||||
} else { //tablet
|
||||
final View related = (View) relatedStreamRootLayout.getParent();
|
||||
//don`t need to hide it if related streams are disabled
|
||||
if (related.getVisibility() == View.VISIBLE) {
|
||||
related.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
videoTitleRoot.setClickable(false);
|
||||
|
||||
imageLoader.cancelDisplayTask(thumbnailImageView);
|
||||
@@ -1190,11 +1195,15 @@ public class VideoDetailFragment
|
||||
detailDurationView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
videoTitleRoot.setClickable(true);
|
||||
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoDescriptionView.setVisibility(View.GONE);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
if (videoTitleToggleArrow != null) {
|
||||
videoTitleRoot.setClickable(true);
|
||||
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (!TextUtils.isEmpty(info.getUploadDate())) {
|
||||
videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate()));
|
||||
}
|
||||
@@ -1227,10 +1236,10 @@ public class VideoDetailFragment
|
||||
spinnerToolbar.setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
if(info.getAudioStreams().isEmpty()) detailControlsBackground.setVisibility(View.GONE);
|
||||
if (!info.getVideoStreams().isEmpty()
|
||||
|| !info.getVideoOnlyStreams().isEmpty()) break;
|
||||
|
||||
detailControlsBackground.setVisibility(View.GONE);
|
||||
detailControlsPopup.setVisibility(View.GONE);
|
||||
spinnerToolbar.setVisibility(View.GONE);
|
||||
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
|
||||
@@ -1242,6 +1251,11 @@ public class VideoDetailFragment
|
||||
// Only auto play in the first open
|
||||
autoPlayEnabled = false;
|
||||
}
|
||||
|
||||
final ViewParent related = relatedStreamRootLayout.getParent();
|
||||
if (related instanceof ScrollView) {
|
||||
((ScrollView) related).scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1251,6 +1265,7 @@ public class VideoDetailFragment
|
||||
downloadDialog.setVideoStreams(sortedVideoStreams);
|
||||
downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
|
||||
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
|
||||
downloadDialog.setSubtitleStreams(currentInfo.getSubtitles());
|
||||
|
||||
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
||||
} catch (Exception e) {
|
||||
@@ -1299,4 +1314,13 @@ public class VideoDetailFragment
|
||||
|
||||
showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema);
|
||||
}
|
||||
}
|
||||
|
||||
private void setRelatedStreamsVisibility(int visibility) {
|
||||
final ViewParent parent = relatedStreamRootLayout.getParent();
|
||||
if (parent instanceof ScrollView) {
|
||||
((ScrollView) parent).setVisibility(visibility);
|
||||
} else {
|
||||
relatedStreamRootLayout.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@ 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 android.util.Log;
|
||||
@@ -21,9 +26,9 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
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;
|
||||
@@ -36,7 +41,7 @@ import java.util.Queue;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implements ListViewContract<I, N>, StateSaver.WriteRead {
|
||||
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implements ListViewContract<I, N>, StateSaver.WriteRead, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
@@ -44,6 +49,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
|
||||
protected InfoListAdapter infoListAdapter;
|
||||
protected RecyclerView itemsList;
|
||||
private int updateFlags = 0;
|
||||
|
||||
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
@@ -59,12 +67,31 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
StateSaver.onDestroy(savedState);
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (updateFlags != 0) {
|
||||
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
||||
final boolean useGrid = isGridLayout();
|
||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
infoListAdapter.setGridItemVariants(useGrid);
|
||||
infoListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
updateFlags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -119,13 +146,25 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
return new LinearLayoutManager(activity);
|
||||
}
|
||||
|
||||
protected RecyclerView.LayoutManager getGridLayoutManager() {
|
||||
final Resources resources = activity.getResources();
|
||||
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
||||
width += (24 * resources.getDisplayMetrics().density);
|
||||
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
|
||||
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
||||
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
|
||||
return lm;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
||||
final boolean useGrid = isGridLayout();
|
||||
itemsList = rootView.findViewById(R.id.items_list);
|
||||
itemsList.setLayoutManager(getListLayoutManager());
|
||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
|
||||
infoListAdapter.setGridItemVariants(useGrid);
|
||||
infoListAdapter.setFooter(getListFooter());
|
||||
infoListAdapter.setHeader(getListHeader());
|
||||
|
||||
@@ -156,9 +195,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
public void selected(ChannelInfoItem selectedItem) {
|
||||
try {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openChannelFragment(useAsFrontPage ?
|
||||
getParentFragment().getFragmentManager()
|
||||
: getFragmentManager(),
|
||||
NavigationHelper.openChannelFragment(getFM(),
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
@@ -173,10 +210,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
public void selected(PlaylistInfoItem selectedItem) {
|
||||
try {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(
|
||||
useAsFrontPage
|
||||
? getParentFragment().getFragmentManager()
|
||||
: getFragmentManager(),
|
||||
NavigationHelper.openPlaylistFragment(getFM(),
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
@@ -197,9 +231,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
|
||||
private void onStreamSelected(StreamInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openVideoDetailFragment(useAsFrontPage
|
||||
? getParentFragment().getFragmentManager()
|
||||
: getFragmentManager(),
|
||||
NavigationHelper.openVideoDetailFragment(getFM(),
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
}
|
||||
|
||||
@@ -315,4 +347,22 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
public void handleNextItems(N result) {
|
||||
isLoading.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals(getString(R.string.list_view_mode_key))) {
|
||||
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isGridLayout() {
|
||||
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
|
||||
if ("auto".equals(list_mode)) {
|
||||
final Configuration configuration = getResources().getConfiguration();
|
||||
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
|
||||
} else {
|
||||
return "grid".equals(list_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
|
||||
import java.util.Queue;
|
||||
@@ -19,7 +16,6 @@ import icepick.State;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
|
||||
@@ -33,15 +33,14 @@ import org.schabi.newpipe.extractor.NewPipe;
|
||||
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.extractor.linkhandler.ListLinkHandler;
|
||||
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.local.subscription.SubscriptionService;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
@@ -69,7 +68,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
private Disposable subscribeButtonMonitor;
|
||||
private SubscriptionService subscriptionService;
|
||||
|
||||
@@ -166,38 +165,35 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
context.getResources().getString(R.string.share)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -255,12 +251,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||
|
||||
private void monitorSubscription(final ChannelInfo info) {
|
||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
||||
animateView(headerSubscribeButton, false, 100);
|
||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Get subscription status", 0);
|
||||
}
|
||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
|
||||
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
||||
"Get subscription status",
|
||||
0);
|
||||
};
|
||||
|
||||
final Observable<List<SubscriptionEntity>> observable = subscriptionService.subscriptionTable()
|
||||
@@ -276,50 +272,38 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
// so only update the UI for the latest emission ("sync" the subscribe button's state)
|
||||
.debounce(100, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<List<SubscriptionEntity>>() {
|
||||
@Override
|
||||
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
|
||||
updateSubscribeButton(!subscriptionEntities.isEmpty());
|
||||
}
|
||||
}, onError));
|
||||
.subscribe((List<SubscriptionEntity> subscriptionEntities) ->
|
||||
updateSubscribeButton(!subscriptionEntities.isEmpty())
|
||||
, onError));
|
||||
|
||||
}
|
||||
|
||||
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
|
||||
return new Function<Object, Object>() {
|
||||
@Override
|
||||
public Object apply(@NonNull Object o) throws Exception {
|
||||
subscriptionService.subscriptionTable().insert(subscription);
|
||||
return o;
|
||||
}
|
||||
return (@NonNull Object o) -> {
|
||||
subscriptionService.subscriptionTable().insert(subscription);
|
||||
return o;
|
||||
};
|
||||
}
|
||||
|
||||
private Function<Object, Object> mapOnUnsubscribe(final SubscriptionEntity subscription) {
|
||||
return new Function<Object, Object>() {
|
||||
@Override
|
||||
public Object apply(@NonNull Object o) throws Exception {
|
||||
subscriptionService.subscriptionTable().delete(subscription);
|
||||
return o;
|
||||
}
|
||||
return (@NonNull Object o) -> {
|
||||
subscriptionService.subscriptionTable().delete(subscription);
|
||||
return o;
|
||||
};
|
||||
}
|
||||
|
||||
private void updateSubscription(final ChannelInfo info) {
|
||||
if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
|
||||
final Action onComplete = new Action() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
final Action onComplete = () -> {
|
||||
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
|
||||
}
|
||||
};
|
||||
|
||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.getServiceId()), "Updating Subscription for " + info.getUrl(), R.string.subscription_update_failed);
|
||||
}
|
||||
};
|
||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||
onUnrecoverableError(throwable,
|
||||
UserAction.SUBSCRIPTION,
|
||||
NewPipe.getNameOfService(info.getServiceId()),
|
||||
"Updating Subscription for " + info.getUrl(),
|
||||
R.string.subscription_update_failed);
|
||||
|
||||
disposables.add(subscriptionService.updateChannelInfo(info)
|
||||
.subscribeOn(Schedulers.io())
|
||||
@@ -328,19 +312,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
}
|
||||
|
||||
private Disposable monitorSubscribeButton(final Button subscribeButton, final Function<Object, Object> action) {
|
||||
final Consumer<Object> onNext = new Consumer<Object>() {
|
||||
@Override
|
||||
public void accept(@NonNull Object o) throws Exception {
|
||||
final Consumer<Object> onNext = (@NonNull Object o) -> {
|
||||
if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!");
|
||||
}
|
||||
};
|
||||
|
||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Subscription Change", R.string.subscription_change_failed);
|
||||
}
|
||||
};
|
||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||
onUnrecoverableError(throwable,
|
||||
UserAction.SUBSCRIPTION,
|
||||
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
||||
"Subscription Change",
|
||||
R.string.subscription_change_failed);
|
||||
|
||||
/* Emit clicks from main thread unto io thread */
|
||||
return RxView.clicks(subscribeButton)
|
||||
@@ -352,25 +333,25 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
}
|
||||
|
||||
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
|
||||
return new Consumer<List<SubscriptionEntity>>() {
|
||||
@Override
|
||||
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
|
||||
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
||||
return (List<SubscriptionEntity> subscriptionEntities) -> {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
|
||||
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
||||
|
||||
if (subscriptionEntities.isEmpty()) {
|
||||
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
|
||||
SubscriptionEntity channel = new SubscriptionEntity();
|
||||
channel.setServiceId(info.getServiceId());
|
||||
channel.setUrl(info.getUrl());
|
||||
channel.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
|
||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
|
||||
final SubscriptionEntity subscription = subscriptionEntities.get(0);
|
||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
|
||||
}
|
||||
if (subscriptionEntities.isEmpty()) {
|
||||
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
|
||||
SubscriptionEntity channel = new SubscriptionEntity();
|
||||
channel.setServiceId(info.getServiceId());
|
||||
channel.setUrl(info.getUrl());
|
||||
channel.setData(info.getName(),
|
||||
info.getAvatarUrl(),
|
||||
info.getDescription(),
|
||||
info.getSubscriberCount());
|
||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
|
||||
final SubscriptionEntity subscription = subscriptionEntities.get(0);
|
||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -437,10 +418,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
|
||||
if (result.getSubscriberCount() != -1) {
|
||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||
if (result.getSubscriberCount() >= 0) {
|
||||
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount()));
|
||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||
} else headerSubscribersTextView.setVisibility(View.GONE);
|
||||
} else {
|
||||
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
||||
}
|
||||
|
||||
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||
|
||||
@@ -488,8 +471,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
super.handleNextItems(result);
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url, R.string.general_error);
|
||||
showSnackBarError(result.getErrors(),
|
||||
UserAction.REQUESTED_CHANNEL,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"Get next page of: " + url,
|
||||
R.string.general_error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +503,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
super.setTitle(title);
|
||||
headerTitleView.setText(title);
|
||||
if (!useAsFrontPage) headerTitleView.setText(title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -19,8 +18,6 @@ import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
@@ -57,6 +54,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
protected String kioskId = "";
|
||||
protected String kioskTranslatedName;
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -130,26 +128,16 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
|
||||
@Override
|
||||
public Single<KioskInfo> loadResult(boolean forceReload) {
|
||||
String contentCountry = PreferenceManager
|
||||
.getDefaultSharedPreferences(activity)
|
||||
.getString(getString(R.string.content_country_key),
|
||||
getString(R.string.default_country_value));
|
||||
return ExtractorHelper.getKioskInfo(serviceId,
|
||||
url,
|
||||
contentCountry,
|
||||
forceReload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
String contentCountry = PreferenceManager
|
||||
.getDefaultSharedPreferences(activity)
|
||||
.getString(getString(R.string.content_country_key),
|
||||
getString(R.string.default_country_value));
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId,
|
||||
url,
|
||||
currentNextPageUrl,
|
||||
contentCountry);
|
||||
currentNextPageUrl);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -167,7 +155,9 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
super.handleResult(result);
|
||||
|
||||
name = kioskTranslatedName;
|
||||
setTitle(kioskTranslatedName);
|
||||
if(!useAsFrontPage) {
|
||||
setTitle(kioskTranslatedName);
|
||||
}
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(),
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.widget.TextView;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
@@ -30,7 +29,6 @@ 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.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
|
||||
@@ -122,12 +122,11 @@ public class SearchFragment
|
||||
private String nextPageUrl;
|
||||
private String contentCountry;
|
||||
private boolean isSuggestionsEnabled = true;
|
||||
private boolean isSearchHistoryEnabled = true;
|
||||
|
||||
private PublishSubject<String> suggestionPublisher = PublishSubject.create();
|
||||
private final PublishSubject<String> suggestionPublisher = PublishSubject.create();
|
||||
private Disposable searchDisposable;
|
||||
private Disposable suggestionDisposable;
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private SuggestionListAdapter suggestionListAdapter;
|
||||
private HistoryRecordManager historyRecordManager;
|
||||
@@ -173,7 +172,7 @@ public class SearchFragment
|
||||
|
||||
suggestionListAdapter = new SuggestionListAdapter(activity);
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||
boolean isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true);
|
||||
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
|
||||
|
||||
historyRecordManager = new HistoryRecordManager(context);
|
||||
@@ -365,7 +364,7 @@ public class SearchFragment
|
||||
int itemId = 0;
|
||||
boolean isFirstItem = true;
|
||||
final Context c = getContext();
|
||||
for(String filter : service.getSearchQIHFactory().getAvailableContentFilter()) {
|
||||
for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||
menuItemToFilterName.put(itemId, filter);
|
||||
MenuItem item = menu.add(1,
|
||||
itemId++,
|
||||
@@ -575,8 +574,7 @@ public class SearchFragment
|
||||
.onNext(searchEditText.getText().toString()),
|
||||
throwable -> showSnackBarError(throwable,
|
||||
UserAction.DELETE_FROM_HISTORY, "none",
|
||||
"Deleting item failed", R.string.general_error)
|
||||
);
|
||||
"Deleting item failed", R.string.general_error));
|
||||
disposables.add(onDelete);
|
||||
})
|
||||
.show();
|
||||
@@ -628,7 +626,7 @@ public class SearchFragment
|
||||
}
|
||||
|
||||
final Observable<List<SuggestionItem>> network = ExtractorHelper
|
||||
.suggestionsFor(serviceId, query, contentCountry)
|
||||
.suggestionsFor(serviceId, query)
|
||||
.toObservable()
|
||||
.map(strings -> {
|
||||
List<SuggestionItem> result = new ArrayList<>();
|
||||
@@ -728,8 +726,7 @@ public class SearchFragment
|
||||
searchDisposable = ExtractorHelper.searchFor(serviceId,
|
||||
searchString,
|
||||
Arrays.asList(contentFilter),
|
||||
sortFilter,
|
||||
contentCountry)
|
||||
sortFilter)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
||||
@@ -747,8 +744,7 @@ public class SearchFragment
|
||||
searchString,
|
||||
asList(contentFilter),
|
||||
sortFilter,
|
||||
nextPageUrl,
|
||||
contentCountry)
|
||||
nextPageUrl)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||
@@ -837,7 +833,10 @@ public class SearchFragment
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull SearchInfo result) {
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
final List<Throwable> exceptions = result.getErrors();
|
||||
if (!exceptions.isEmpty()
|
||||
&& !(exceptions.size() == 1
|
||||
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||
}
|
||||
@@ -864,6 +863,7 @@ public class SearchFragment
|
||||
showListFooter(false);
|
||||
currentPageUrl = result.getNextPageUrl();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
|
||||
@@ -45,7 +45,7 @@ public class InfoItemBuilder {
|
||||
private static final String TAG = InfoItemBuilder.class.toString();
|
||||
|
||||
private final Context context;
|
||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
|
||||
private OnClickGesture<StreamInfoItem> onStreamSelectedListener;
|
||||
private OnClickGesture<ChannelInfoItem> onChannelSelectedListener;
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
@@ -12,9 +13,12 @@ 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.ChannelGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
|
||||
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
|
||||
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.util.FallbackViewHolder;
|
||||
@@ -52,14 +56,18 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
|
||||
private static final int MINI_STREAM_HOLDER_TYPE = 0x100;
|
||||
private static final int STREAM_HOLDER_TYPE = 0x101;
|
||||
private static final int GRID_STREAM_HOLDER_TYPE = 0x102;
|
||||
private static final int MINI_CHANNEL_HOLDER_TYPE = 0x200;
|
||||
private static final int CHANNEL_HOLDER_TYPE = 0x201;
|
||||
private static final int GRID_CHANNEL_HOLDER_TYPE = 0x202;
|
||||
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 final InfoItemBuilder infoItemBuilder;
|
||||
private final ArrayList<InfoItem> infoItemList;
|
||||
private boolean useMiniVariant = false;
|
||||
private boolean useGridVariant = false;
|
||||
private boolean showFooter = false;
|
||||
private View header = null;
|
||||
private View footer = null;
|
||||
@@ -94,6 +102,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
this.useMiniVariant = useMiniVariant;
|
||||
}
|
||||
|
||||
public void setGridItemVariants(boolean useGridVariant) {
|
||||
this.useGridVariant = useGridVariant;
|
||||
}
|
||||
|
||||
public void addInfoItemList(List<InfoItem> data) {
|
||||
if (data != null) {
|
||||
if (DEBUG) {
|
||||
@@ -206,11 +218,11 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
final InfoItem item = infoItemList.get(position);
|
||||
switch (item.getInfoType()) {
|
||||
case STREAM:
|
||||
return useMiniVariant ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
|
||||
return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant ? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
|
||||
case CHANNEL:
|
||||
return useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
||||
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
|
||||
case PLAYLIST:
|
||||
return useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
|
||||
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return -1;
|
||||
@@ -229,14 +241,20 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
return new StreamMiniInfoItemHolder(infoItemBuilder, parent);
|
||||
case STREAM_HOLDER_TYPE:
|
||||
return new StreamInfoItemHolder(infoItemBuilder, parent);
|
||||
case GRID_STREAM_HOLDER_TYPE:
|
||||
return new StreamGridInfoItemHolder(infoItemBuilder, parent);
|
||||
case MINI_CHANNEL_HOLDER_TYPE:
|
||||
return new ChannelMiniInfoItemHolder(infoItemBuilder, parent);
|
||||
case CHANNEL_HOLDER_TYPE:
|
||||
return new ChannelInfoItemHolder(infoItemBuilder, parent);
|
||||
case GRID_CHANNEL_HOLDER_TYPE:
|
||||
return new ChannelGridInfoItemHolder(infoItemBuilder, parent);
|
||||
case MINI_PLAYLIST_HOLDER_TYPE:
|
||||
return new PlaylistMiniInfoItemHolder(infoItemBuilder, parent);
|
||||
case PLAYLIST_HOLDER_TYPE:
|
||||
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
|
||||
case GRID_PLAYLIST_HOLDER_TYPE:
|
||||
return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
|
||||
default:
|
||||
Log.e(TAG, "Trollolo");
|
||||
return new FallbackViewHolder(new View(parent.getContext()));
|
||||
@@ -257,4 +275,14 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
||||
((HFHolder) holder).view = footer;
|
||||
}
|
||||
}
|
||||
|
||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
||||
return new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
final int type = getItemViewType(position);
|
||||
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
|
||||
public class ChannelGridInfoItemHolder extends ChannelMiniInfoItemHolder {
|
||||
|
||||
public ChannelGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_channel_grid_item, parent);
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,13 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemBuilder.getOnChannelSelectedListener().selected(item);
|
||||
}
|
||||
});
|
||||
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (itemBuilder.getOnChannelSelectedListener() != null) {
|
||||
itemBuilder.getOnChannelSelectedListener().held(item);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
protected String getDetailLine(final ChannelInfoItem item) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
|
||||
public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder {
|
||||
|
||||
public PlaylistGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
|
||||
public class StreamGridInfoItemHolder extends StreamMiniInfoItemHolder {
|
||||
|
||||
public StreamGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package org.schabi.newpipe.local;
|
||||
|
||||
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.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 android.util.Log;
|
||||
@@ -25,7 +30,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
* called and is memory efficient when in backstack.
|
||||
* */
|
||||
public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||
implements ListViewContract<I, N> {
|
||||
implements ListViewContract<I, N>, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
@@ -36,6 +41,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||
|
||||
protected LocalItemListAdapter itemListAdapter;
|
||||
protected RecyclerView itemsList;
|
||||
private int updateFlags = 0;
|
||||
|
||||
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Lifecycle - Creation
|
||||
@@ -45,6 +53,29 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (updateFlags != 0) {
|
||||
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
||||
final boolean useGrid = isGridLayout();
|
||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
itemListAdapter.setGridItemVariants(useGrid);
|
||||
itemListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
updateFlags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -59,6 +90,16 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||
return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false);
|
||||
}
|
||||
|
||||
protected RecyclerView.LayoutManager getGridLayoutManager() {
|
||||
final Resources resources = activity.getResources();
|
||||
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
||||
width += (24 * resources.getDisplayMetrics().density);
|
||||
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
|
||||
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
||||
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
|
||||
return lm;
|
||||
}
|
||||
|
||||
protected RecyclerView.LayoutManager getListLayoutManager() {
|
||||
return new LinearLayoutManager(activity);
|
||||
}
|
||||
@@ -67,10 +108,13 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
||||
itemsList = rootView.findViewById(R.id.items_list);
|
||||
itemsList.setLayoutManager(getListLayoutManager());
|
||||
|
||||
itemListAdapter = new LocalItemListAdapter(activity);
|
||||
|
||||
final boolean useGrid = isGridLayout();
|
||||
itemsList = rootView.findViewById(R.id.items_list);
|
||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
|
||||
itemListAdapter.setGridItemVariants(useGrid);
|
||||
itemListAdapter.setHeader(headerRootView = getListHeader());
|
||||
itemListAdapter.setFooter(footerRootView = getListFooter());
|
||||
|
||||
@@ -174,4 +218,22 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||
resetFragment();
|
||||
return super.onError(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals(getString(R.string.list_view_mode_key))) {
|
||||
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isGridLayout() {
|
||||
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
|
||||
if ("auto".equals(list_mode)) {
|
||||
final Configuration configuration = getResources().getConfiguration();
|
||||
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
|
||||
} else {
|
||||
return "grid".equals(list_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class LocalItemBuilder {
|
||||
private static final String TAG = LocalItemBuilder.class.toString();
|
||||
|
||||
private final Context context;
|
||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
|
||||
private OnClickGesture<LocalItem> onSelectedListener;
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
package org.schabi.newpipe.local;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.local.HeaderFooterHolder;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.holder.LocalItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalStatisticStreamGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder;
|
||||
import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder;
|
||||
import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder;
|
||||
import org.schabi.newpipe.util.FallbackViewHolder;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
@@ -52,14 +55,19 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
|
||||
private static final int STREAM_STATISTICS_HOLDER_TYPE = 0x1000;
|
||||
private static final int STREAM_PLAYLIST_HOLDER_TYPE = 0x1001;
|
||||
private static final int STREAM_STATISTICS_GRID_HOLDER_TYPE = 0x1002;
|
||||
private static final int STREAM_PLAYLIST_GRID_HOLDER_TYPE = 0x1004;
|
||||
private static final int LOCAL_PLAYLIST_HOLDER_TYPE = 0x2000;
|
||||
private static final int REMOTE_PLAYLIST_HOLDER_TYPE = 0x2001;
|
||||
private static final int LOCAL_PLAYLIST_GRID_HOLDER_TYPE = 0x2002;
|
||||
private static final int REMOTE_PLAYLIST_GRID_HOLDER_TYPE = 0x2004;
|
||||
|
||||
private final LocalItemBuilder localItemBuilder;
|
||||
private final ArrayList<LocalItem> localItems;
|
||||
private final DateFormat dateFormat;
|
||||
|
||||
private boolean showFooter = false;
|
||||
private boolean useGridVariant = false;
|
||||
private View header = null;
|
||||
private View footer = null;
|
||||
|
||||
@@ -134,6 +142,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setGridItemVariants(boolean useGridVariant) {
|
||||
this.useGridVariant = useGridVariant;
|
||||
}
|
||||
|
||||
public void setHeader(View header) {
|
||||
boolean changed = header != this.header;
|
||||
this.header = header;
|
||||
@@ -195,11 +207,11 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
final LocalItem item = localItems.get(position);
|
||||
|
||||
switch (item.getLocalItemType()) {
|
||||
case PLAYLIST_LOCAL_ITEM: return LOCAL_PLAYLIST_HOLDER_TYPE;
|
||||
case PLAYLIST_REMOTE_ITEM: return REMOTE_PLAYLIST_HOLDER_TYPE;
|
||||
case PLAYLIST_LOCAL_ITEM: return useGridVariant ? LOCAL_PLAYLIST_GRID_HOLDER_TYPE : LOCAL_PLAYLIST_HOLDER_TYPE;
|
||||
case PLAYLIST_REMOTE_ITEM: return useGridVariant ? REMOTE_PLAYLIST_GRID_HOLDER_TYPE : REMOTE_PLAYLIST_HOLDER_TYPE;
|
||||
|
||||
case PLAYLIST_STREAM_ITEM: return STREAM_PLAYLIST_HOLDER_TYPE;
|
||||
case STATISTIC_STREAM_ITEM: return STREAM_STATISTICS_HOLDER_TYPE;
|
||||
case PLAYLIST_STREAM_ITEM: return useGridVariant ? STREAM_PLAYLIST_GRID_HOLDER_TYPE : STREAM_PLAYLIST_HOLDER_TYPE;
|
||||
case STATISTIC_STREAM_ITEM: return useGridVariant ? STREAM_STATISTICS_GRID_HOLDER_TYPE : STREAM_STATISTICS_HOLDER_TYPE;
|
||||
default:
|
||||
Log.e(TAG, "No holder type has been considered for item: [" +
|
||||
item.getLocalItemType() + "]");
|
||||
@@ -218,12 +230,20 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
return new HeaderFooterHolder(footer);
|
||||
case LOCAL_PLAYLIST_HOLDER_TYPE:
|
||||
return new LocalPlaylistItemHolder(localItemBuilder, parent);
|
||||
case LOCAL_PLAYLIST_GRID_HOLDER_TYPE:
|
||||
return new LocalPlaylistGridItemHolder(localItemBuilder, parent);
|
||||
case REMOTE_PLAYLIST_HOLDER_TYPE:
|
||||
return new RemotePlaylistItemHolder(localItemBuilder, parent);
|
||||
case REMOTE_PLAYLIST_GRID_HOLDER_TYPE:
|
||||
return new RemotePlaylistGridItemHolder(localItemBuilder, parent);
|
||||
case STREAM_PLAYLIST_HOLDER_TYPE:
|
||||
return new LocalPlaylistStreamItemHolder(localItemBuilder, parent);
|
||||
case STREAM_PLAYLIST_GRID_HOLDER_TYPE:
|
||||
return new LocalPlaylistStreamGridItemHolder(localItemBuilder, parent);
|
||||
case STREAM_STATISTICS_HOLDER_TYPE:
|
||||
return new LocalStatisticStreamItemHolder(localItemBuilder, parent);
|
||||
case STREAM_STATISTICS_GRID_HOLDER_TYPE:
|
||||
return new LocalStatisticStreamGridItemHolder(localItemBuilder, parent);
|
||||
default:
|
||||
Log.e(TAG, "No view type has been considered for holder: [" + type + "]");
|
||||
return new FallbackViewHolder(new View(parent.getContext()));
|
||||
@@ -247,4 +267,14 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
((HeaderFooterHolder) holder).view = footer;
|
||||
}
|
||||
}
|
||||
|
||||
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) {
|
||||
return new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
final int type = getItemViewType(position);
|
||||
return type == HEADER_TYPE || type == FOOTER_TYPE ? spanCount : 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -20,11 +19,9 @@ import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
@@ -69,11 +66,10 @@ public final class BookmarkFragment
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
if (activity != null && activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||
activity.setTitle(R.string.tab_subscriptions);
|
||||
}
|
||||
|
||||
if(!useAsFrontPage) {
|
||||
setTitle(activity.getString(R.string.tab_bookmarks));
|
||||
}
|
||||
return inflater.inflate(R.layout.fragment_bookmarks, container, false);
|
||||
}
|
||||
|
||||
@@ -102,26 +98,20 @@ public final class BookmarkFragment
|
||||
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||
@Override
|
||||
public void selected(LocalItem selectedItem) {
|
||||
try {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
if (getParentFragment() == null) return;
|
||||
final FragmentManager fragmentManager = getParentFragment().getFragmentManager();
|
||||
final FragmentManager fragmentManager = getFM();
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid,
|
||||
entry.name);
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid,
|
||||
entry.name);
|
||||
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(
|
||||
fragmentManager,
|
||||
entry.getServiceId(),
|
||||
entry.getUrl(),
|
||||
entry.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(
|
||||
fragmentManager,
|
||||
entry.getServiceId(),
|
||||
entry.getUrl(),
|
||||
entry.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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 android.view.Window;
|
||||
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
@@ -41,6 +43,18 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
||||
StateSaver.onDestroy(savedState);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
//remove title
|
||||
final Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.requestFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -58,7 +72,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) {
|
||||
streamEntities = (List<StreamEntity>) savedObjects.poll();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@ import io.reactivex.MaybeObserver;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Predicate;
|
||||
|
||||
public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Void> {
|
||||
|
||||
@@ -71,6 +69,10 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
if(!useAsFrontPage) {
|
||||
setTitle(activity.getString(R.string.fragment_whats_new));
|
||||
}
|
||||
return inflater.inflate(R.layout.fragment_feed, container, false);
|
||||
}
|
||||
|
||||
@@ -105,20 +107,19 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
/*@Override
|
||||
protected RecyclerView.LayoutManager getListLayoutManager() {
|
||||
boolean isPortrait = getResources().getDisplayMetrics().heightPixels > getResources().getDisplayMetrics().widthPixels;
|
||||
return new GridLayoutManager(activity, isPortrait ? 1 : 2);
|
||||
}*/
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (activity != null && isVisibleToUser) {
|
||||
setTitle(activity.getString(R.string.fragment_whats_new));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
ActionBar supportActionBar = activity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setTitle(R.string.fragment_whats_new);
|
||||
}
|
||||
|
||||
if(useAsFrontPage) {
|
||||
supportActionBar.setDisplayShowTitleEnabled(true);
|
||||
@@ -176,19 +177,9 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
showLoading();
|
||||
showListFooter(true);
|
||||
subscriptionObserver = subscriptionService.getSubscription()
|
||||
.onErrorReturnItem(Collections.<SubscriptionEntity>emptyList())
|
||||
.onErrorReturnItem(Collections.emptyList())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<List<SubscriptionEntity>>() {
|
||||
@Override
|
||||
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
|
||||
handleResult(subscriptionEntities);
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
onError(throwable);
|
||||
}
|
||||
});
|
||||
.subscribe(this::handleResult, this::onError);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,13 +230,12 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
if (!itemsLoaded.contains(subscriptionEntity.getServiceId() + subscriptionEntity.getUrl())) {
|
||||
subscriptionService.getChannelInfo(subscriptionEntity)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.onErrorComplete(new Predicate<Throwable>() {
|
||||
@Override
|
||||
public boolean test(@io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
|
||||
return FeedFragment.super.onError(throwable);
|
||||
}
|
||||
})
|
||||
.subscribe(getChannelInfoObserver(subscriptionEntity.getServiceId(), subscriptionEntity.getUrl()));
|
||||
.onErrorComplete(
|
||||
(@io.reactivex.annotations.NonNull Throwable throwable) ->
|
||||
FeedFragment.super.onError(throwable))
|
||||
.subscribe(
|
||||
getChannelInfoObserver(subscriptionEntity.getServiceId(),
|
||||
subscriptionEntity.getUrl()));
|
||||
} else {
|
||||
requestFeed(1);
|
||||
}
|
||||
@@ -316,7 +306,10 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
|
||||
@Override
|
||||
public void onError(Throwable exception) {
|
||||
showSnackBarError(exception, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(serviceId), url, 0);
|
||||
showSnackBarError(exception,
|
||||
UserAction.SUBSCRIPTION,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
url, 0);
|
||||
requestFeed(1);
|
||||
onDone();
|
||||
}
|
||||
@@ -361,12 +354,7 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
delayHandler.removeCallbacksAndMessages(null);
|
||||
// Add a little of a delay when requesting more items because the cache is so fast,
|
||||
// that the view seems stuck to the user when he scroll to the bottom
|
||||
delayHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
requestFeed(FEED_LOAD_COUNT);
|
||||
}
|
||||
}, 300);
|
||||
delayHandler.postDelayed(() -> requestFeed(FEED_LOAD_COUNT), 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -423,7 +411,9 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
int heightPixels = getResources().getDisplayMetrics().heightPixels;
|
||||
int itemHeightPixels = activity.getResources().getDimensionPixelSize(R.dimen.video_item_search_height);
|
||||
|
||||
int items = itemHeightPixels > 0 ? heightPixels / itemHeightPixels + OFF_SCREEN_ITEMS_COUNT : MIN_ITEMS_INITIAL_LOAD;
|
||||
int items = itemHeightPixels > 0
|
||||
? heightPixels / itemHeightPixels + OFF_SCREEN_ITEMS_COUNT
|
||||
: MIN_ITEMS_INITIAL_LOAD;
|
||||
return Math.max(MIN_ITEMS_INITIAL_LOAD, items);
|
||||
}
|
||||
|
||||
@@ -441,8 +431,14 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Requesting feed", errorId);
|
||||
int errorId = exception instanceof ExtractionException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception,
|
||||
UserAction.SOMETHING_ELSE,
|
||||
"none",
|
||||
"Requesting feed",
|
||||
errorId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.schabi.newpipe.local.history;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
@@ -45,7 +45,6 @@ import java.util.List;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class StatisticsPlaylistFragment
|
||||
/* Used for independent events */
|
||||
private Subscription databaseSubscription;
|
||||
private HistoryRecordManager recordManager;
|
||||
private CompositeDisposable disposables = new CompositeDisposable();
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private enum StatisticSortMode {
|
||||
LAST_PLAYED,
|
||||
@@ -73,7 +73,7 @@ public class StatisticsPlaylistFragment
|
||||
return results;
|
||||
case MOST_PLAYED:
|
||||
Collections.sort(results, (left, right) ->
|
||||
((Long) right.watchCount).compareTo(left.watchCount));
|
||||
Long.compare(right.watchCount, left.watchCount));
|
||||
return results;
|
||||
default: return null;
|
||||
}
|
||||
@@ -96,6 +96,14 @@ public class StatisticsPlaylistFragment
|
||||
return inflater.inflate(R.layout.fragment_playlist, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
if (activity != null && isVisibleToUser) {
|
||||
setTitle(activity.getString(R.string.title_activity_history));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Views
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -103,7 +111,9 @@ public class StatisticsPlaylistFragment
|
||||
@Override
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
setTitle(getString(R.string.title_last_played));
|
||||
if(!useAsFrontPage) {
|
||||
setTitle(getString(R.string.title_last_played));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,8 +139,10 @@ public class StatisticsPlaylistFragment
|
||||
public void selected(LocalItem selectedItem) {
|
||||
if (selectedItem instanceof StreamStatisticsEntry) {
|
||||
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
|
||||
NavigationHelper.openVideoDetailFragment(getFragmentManager(),
|
||||
item.serviceId, item.url, item.title);
|
||||
NavigationHelper.openVideoDetailFragment(getFM(),
|
||||
item.serviceId,
|
||||
item.url,
|
||||
item.title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +353,7 @@ public class StatisticsPlaylistFragment
|
||||
final Disposable onDelete = recordManager.deleteStreamHistory(entry.streamId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
howManyDelted -> {
|
||||
howManyDeleted -> {
|
||||
if(getView() != null) {
|
||||
Snackbar.make(getView(), R.string.one_item_deleted,
|
||||
Snackbar.LENGTH_SHORT).show();
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
public class LocalPlaylistGridItemHolder extends LocalPlaylistItemHolder {
|
||||
|
||||
public LocalPlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||
super(infoItemBuilder, parent);
|
||||
}
|
||||
|
||||
LocalPlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof PlaylistMetadataEntry)) return;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
public class LocalPlaylistStreamGridItemHolder extends LocalPlaylistStreamItemHolder {
|
||||
|
||||
public LocalPlaylistStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_playlist_grid_item, parent); //TODO
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
public class LocalStatisticStreamGridItemHolder extends LocalStatisticStreamItemHolder {
|
||||
|
||||
public LocalStatisticStreamGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -42,10 +43,15 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
public final TextView itemVideoTitleView;
|
||||
public final TextView itemUploaderView;
|
||||
public final TextView itemDurationView;
|
||||
@Nullable
|
||||
public final TextView itemAdditionalDetails;
|
||||
|
||||
public LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_item, parent);
|
||||
public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) {
|
||||
this(itemBuilder, R.layout.list_stream_item, parent);
|
||||
}
|
||||
|
||||
LocalStatisticStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
|
||||
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
||||
itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView);
|
||||
@@ -80,7 +86,9 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
itemDurationView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat));
|
||||
if (itemAdditionalDetails != null) {
|
||||
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat));
|
||||
}
|
||||
|
||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.local.holder;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
|
||||
public class RemotePlaylistGridItemHolder extends RemotePlaylistItemHolder {
|
||||
|
||||
public RemotePlaylistGridItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_playlist_grid_item, parent);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||
super(infoItemBuilder, parent);
|
||||
}
|
||||
|
||||
RemotePlaylistItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) {
|
||||
if (!(localItem instanceof PlaylistRemoteEntity)) return;
|
||||
|
||||
@@ -459,7 +459,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
|
||||
|
||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
||||
int directions = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
|
||||
if (isGridLayout()) {
|
||||
directions |= ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
|
||||
}
|
||||
return new ItemTouchHelper.SimpleCallback(directions,
|
||||
ItemTouchHelper.ACTION_STATE_IDLE) {
|
||||
@Override
|
||||
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
package org.schabi.newpipe.local.subscription;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
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 android.view.LayoutInflater;
|
||||
@@ -39,10 +48,11 @@ 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.ErrorActivity;
|
||||
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;
|
||||
@@ -71,7 +81,7 @@ import static org.schabi.newpipe.local.subscription.services.SubscriptionsImport
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEntity>> {
|
||||
public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEntity>> implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final int REQUEST_EXPORT_CODE = 666;
|
||||
private static final int REQUEST_IMPORT_CODE = 667;
|
||||
|
||||
@@ -79,8 +89,10 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
@State
|
||||
protected Parcelable itemsListState;
|
||||
private InfoListAdapter infoListAdapter;
|
||||
private int updateFlags = 0;
|
||||
|
||||
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
|
||||
|
||||
private View headerRootLayout;
|
||||
private View whatsNewItemListHeader;
|
||||
private View importExportListHeader;
|
||||
|
||||
@@ -99,6 +111,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,6 +140,15 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setupBroadcastReceiver();
|
||||
if (updateFlags != 0) {
|
||||
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
|
||||
final boolean useGrid = isGridLayout();
|
||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
infoListAdapter.setGridItemVariants(useGrid);
|
||||
infoListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
updateFlags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,9 +175,25 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
disposables = null;
|
||||
subscriptionService = null;
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected RecyclerView.LayoutManager getListLayoutManager() {
|
||||
return new LinearLayoutManager(activity);
|
||||
}
|
||||
|
||||
protected RecyclerView.LayoutManager getGridLayoutManager() {
|
||||
final Resources resources = activity.getResources();
|
||||
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
||||
width += (24 * resources.getDisplayMetrics().density);
|
||||
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width);
|
||||
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
||||
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
|
||||
return lm;
|
||||
}
|
||||
|
||||
/*/////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
/////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -209,7 +248,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
}
|
||||
|
||||
private void setupImportFromItems(final ViewGroup listHolder) {
|
||||
final View previousBackupItem = addItemView(getString(R.string.previous_export), ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_backup), listHolder);
|
||||
final View previousBackupItem = addItemView(getString(R.string.previous_export),
|
||||
ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_backup), listHolder);
|
||||
previousBackupItem.setOnClickListener(item -> onImportPreviousSelected());
|
||||
|
||||
final int iconColor = ThemeHelper.isLightThemeSelected(getContext()) ? Color.BLACK : Color.WHITE;
|
||||
@@ -241,8 +281,8 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
}
|
||||
|
||||
private void onImportFromServiceSelected(int serviceId) {
|
||||
if (getParentFragment() == null) return;
|
||||
NavigationHelper.openSubscriptionsImportFragment(getParentFragment().getFragmentManager(), serviceId);
|
||||
FragmentManager fragmentManager = getFM();
|
||||
NavigationHelper.openSubscriptionsImportFragment(fragmentManager, serviceId);
|
||||
}
|
||||
|
||||
private void onImportPreviousSelected() {
|
||||
@@ -285,16 +325,19 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
protected void initViews(View rootView, Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
||||
final boolean useGrid = isGridLayout();
|
||||
infoListAdapter = new InfoListAdapter(getActivity());
|
||||
itemsList = rootView.findViewById(R.id.items_list);
|
||||
itemsList.setLayoutManager(new LinearLayoutManager(activity));
|
||||
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
|
||||
|
||||
View headerRootLayout;
|
||||
infoListAdapter.setHeader(headerRootLayout = activity.getLayoutInflater().inflate(R.layout.subscription_header, itemsList, false));
|
||||
whatsNewItemListHeader = headerRootLayout.findViewById(R.id.whats_new);
|
||||
importExportListHeader = headerRootLayout.findViewById(R.id.import_export);
|
||||
importExportOptions = headerRootLayout.findViewById(R.id.import_export_options);
|
||||
|
||||
infoListAdapter.useMiniItemVariants(true);
|
||||
infoListAdapter.setGridItemVariants(useGrid);
|
||||
itemsList.setAdapter(infoListAdapter);
|
||||
|
||||
setupImportFromItems(headerRootLayout.findViewById(R.id.import_from_options));
|
||||
@@ -318,26 +361,108 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
super.initListeners();
|
||||
|
||||
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
|
||||
@Override
|
||||
|
||||
public void selected(ChannelInfoItem selectedItem) {
|
||||
try {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(),
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
final FragmentManager fragmentManager = getFM();
|
||||
NavigationHelper.openChannelFragment(fragmentManager,
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
}
|
||||
|
||||
public void held(ChannelInfoItem selectedItem) {
|
||||
showLongTapDialog(selectedItem);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//noinspection ConstantConditions
|
||||
whatsNewItemListHeader.setOnClickListener(v ->
|
||||
NavigationHelper.openWhatsNewFragment(getParentFragment().getFragmentManager()));
|
||||
whatsNewItemListHeader.setOnClickListener(v -> {
|
||||
FragmentManager fragmentManager = getFM();
|
||||
NavigationHelper.openWhatsNewFragment(fragmentManager);
|
||||
});
|
||||
importExportListHeader.setOnClickListener(v -> importExportOptions.switchState());
|
||||
}
|
||||
|
||||
private void showLongTapDialog(ChannelInfoItem selectedItem) {
|
||||
final Context context = getContext();
|
||||
final Activity activity = getActivity();
|
||||
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)
|
||||
};
|
||||
|
||||
final DialogInterface.OnClickListener actions = (dialogInterface, i) -> {
|
||||
switch (i) {
|
||||
case 0:
|
||||
shareChannel(selectedItem);
|
||||
break;
|
||||
case 1:
|
||||
deleteChannel(selectedItem);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
|
||||
bannerView.setSelected(true);
|
||||
|
||||
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
||||
titleView.setText(selectedItem.getName());
|
||||
|
||||
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
||||
detailsView.setVisibility(View.GONE);
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
.setCustomTitle(bannerView)
|
||||
.setItems(commands, actions)
|
||||
.create()
|
||||
.show();
|
||||
|
||||
}
|
||||
|
||||
private void shareChannel (ChannelInfoItem selectedItem) {
|
||||
shareUrl(selectedItem.getName(), selectedItem.getUrl());
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void deleteChannel (ChannelInfoItem selectedItem) {
|
||||
subscriptionService.subscriptionTable()
|
||||
.getSubscription(selectedItem.getServiceId(), selectedItem.getUrl())
|
||||
.toObservable()
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(getDeleteObserver());
|
||||
|
||||
Toast.makeText(activity, getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Observer<List<SubscriptionEntity>> getDeleteObserver(){
|
||||
return new Observer<List<SubscriptionEntity>>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
disposables.add(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(List<SubscriptionEntity> subscriptionEntities) {
|
||||
subscriptionService.subscriptionTable().delete(subscriptionEntities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable exception) {
|
||||
SubscriptionFragment.this.onError(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() { }
|
||||
};
|
||||
}
|
||||
|
||||
private void resetFragment() {
|
||||
if (disposables != null) disposables.clear();
|
||||
if (infoListAdapter != null) infoListAdapter.clearStreamItemList();
|
||||
@@ -405,10 +530,13 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
|
||||
private List<InfoItem> getSubscriptionItems(List<SubscriptionEntity> subscriptions) {
|
||||
List<InfoItem> items = new ArrayList<>();
|
||||
for (final SubscriptionEntity subscription : subscriptions) items.add(subscription.toChannelInfoItem());
|
||||
for (final SubscriptionEntity subscription : subscriptions) {
|
||||
items.add(subscription.toChannelInfoItem());
|
||||
}
|
||||
|
||||
Collections.sort(items,
|
||||
(InfoItem o1, InfoItem o2) -> o1.getName().compareToIgnoreCase(o2.getName()));
|
||||
(InfoItem o1, InfoItem o2) ->
|
||||
o1.getName().compareToIgnoreCase(o2.getName()));
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -437,7 +565,29 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
resetFragment();
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Subscriptions", R.string.general_error);
|
||||
onUnrecoverableError(exception,
|
||||
UserAction.SOMETHING_ELSE,
|
||||
"none",
|
||||
"Subscriptions",
|
||||
R.string.general_error);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (key.equals(getString(R.string.list_view_mode_key))) {
|
||||
updateFlags |= LIST_MODE_UPDATE_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isGridLayout() {
|
||||
final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value));
|
||||
if ("auto".equals(list_mode)) {
|
||||
final Configuration configuration = getResources().getConfiguration();
|
||||
return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE);
|
||||
} else {
|
||||
return "grid".equals(list_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,10 @@ public class SubscriptionService {
|
||||
private static final int SUBSCRIPTION_DEBOUNCE_INTERVAL = 500;
|
||||
private static final int SUBSCRIPTION_THREAD_POOL_SIZE = 4;
|
||||
|
||||
private AppDatabase db;
|
||||
private Flowable<List<SubscriptionEntity>> subscription;
|
||||
private final AppDatabase db;
|
||||
private final Flowable<List<SubscriptionEntity>> subscription;
|
||||
|
||||
private Scheduler subscriptionScheduler;
|
||||
private final Scheduler subscriptionScheduler;
|
||||
|
||||
private SubscriptionService(Context context) {
|
||||
db = NewPipeDatabase.getInstance(context.getApplicationContext());
|
||||
@@ -116,7 +116,7 @@ public class SubscriptionService {
|
||||
public Completable updateChannelInfo(final ChannelInfo info) {
|
||||
final Function<List<SubscriptionEntity>, CompletableSource> update = new Function<List<SubscriptionEntity>, CompletableSource>() {
|
||||
@Override
|
||||
public CompletableSource apply(@NonNull List<SubscriptionEntity> subscriptionEntities) throws Exception {
|
||||
public CompletableSource apply(@NonNull List<SubscriptionEntity> subscriptionEntities) {
|
||||
if (DEBUG) Log.d(TAG, "updateChannelInfo() called with: subscriptionEntities = [" + subscriptionEntities + "]");
|
||||
if (subscriptionEntities.size() == 1) {
|
||||
SubscriptionEntity subscription = subscriptionEntities.get(0);
|
||||
|
||||
@@ -58,8 +58,8 @@ public abstract class BaseImportExportService extends Service {
|
||||
protected NotificationCompat.Builder notificationBuilder;
|
||||
|
||||
protected SubscriptionService subscriptionService;
|
||||
protected CompositeDisposable disposables = new CompositeDisposable();
|
||||
protected PublishProcessor<String> notificationUpdater = PublishProcessor.create();
|
||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||
protected final PublishProcessor<String> notificationUpdater = PublishProcessor.create();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -90,9 +90,9 @@ public abstract class BaseImportExportService extends Service {
|
||||
|
||||
private static final int NOTIFICATION_SAMPLING_PERIOD = 2500;
|
||||
|
||||
protected AtomicInteger currentProgress = new AtomicInteger(-1);
|
||||
protected AtomicInteger maxProgress = new AtomicInteger(-1);
|
||||
protected ImportExportEventListener eventListener = new ImportExportEventListener() {
|
||||
protected final AtomicInteger currentProgress = new AtomicInteger(-1);
|
||||
protected final AtomicInteger maxProgress = new AtomicInteger(-1);
|
||||
protected final ImportExportEventListener eventListener = new ImportExportEventListener() {
|
||||
@Override
|
||||
public void onSizeReceived(int size) {
|
||||
maxProgress.set(size);
|
||||
@@ -187,13 +187,13 @@ public abstract class BaseImportExportService extends Service {
|
||||
protected Toast toast;
|
||||
|
||||
protected void showToast(@StringRes int message) {
|
||||
showToast(getString(message), Toast.LENGTH_SHORT);
|
||||
showToast(getString(message));
|
||||
}
|
||||
|
||||
protected void showToast(String message, int duration) {
|
||||
protected void showToast(String message) {
|
||||
if (toast != null) toast.cancel();
|
||||
|
||||
toast = Toast.makeText(this, message, duration);
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -144,12 +144,16 @@ public class SubscriptionsImportService extends BaseImportExportService {
|
||||
showToast(R.string.import_ongoing);
|
||||
|
||||
Flowable<List<SubscriptionItem>> flowable = null;
|
||||
if (currentMode == CHANNEL_URL_MODE) {
|
||||
flowable = importFromChannelUrl();
|
||||
} else if (currentMode == INPUT_STREAM_MODE) {
|
||||
flowable = importFromInputStream();
|
||||
} else if (currentMode == PREVIOUS_EXPORT_MODE) {
|
||||
flowable = importFromPreviousExport();
|
||||
switch (currentMode) {
|
||||
case CHANNEL_URL_MODE:
|
||||
flowable = importFromChannelUrl();
|
||||
break;
|
||||
case INPUT_STREAM_MODE:
|
||||
flowable = importFromInputStream();
|
||||
break;
|
||||
case PREVIOUS_EXPORT_MODE:
|
||||
flowable = importFromPreviousExport();
|
||||
break;
|
||||
}
|
||||
|
||||
if (flowable == null) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -343,6 +344,7 @@ public final class BackgroundPlayer extends Service {
|
||||
|
||||
if (!shouldUpdateOnProgress) return;
|
||||
resetNotification();
|
||||
if(Build.VERSION.SDK_INT >= 26 /*Oreo*/) updateNotificationThumbnail();
|
||||
if (bigNotRemoteView != null) {
|
||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
|
||||
|
||||
@@ -51,6 +51,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.Downloader;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
@@ -96,36 +97,55 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJ
|
||||
public abstract class BasePlayer implements
|
||||
Player.EventListener, PlaybackListener, ImageLoadingListener {
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
@NonNull public static final String TAG = "BasePlayer";
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
@NonNull
|
||||
public static final String TAG = "BasePlayer";
|
||||
|
||||
@NonNull final protected Context context;
|
||||
@NonNull
|
||||
final protected Context context;
|
||||
|
||||
@NonNull final protected BroadcastReceiver broadcastReceiver;
|
||||
@NonNull final protected IntentFilter intentFilter;
|
||||
@NonNull
|
||||
final protected BroadcastReceiver broadcastReceiver;
|
||||
@NonNull
|
||||
final protected IntentFilter intentFilter;
|
||||
|
||||
@NonNull final protected HistoryRecordManager recordManager;
|
||||
@NonNull
|
||||
final protected HistoryRecordManager recordManager;
|
||||
|
||||
@NonNull final protected CustomTrackSelector trackSelector;
|
||||
@NonNull final protected PlayerDataSource dataSource;
|
||||
@NonNull
|
||||
final protected CustomTrackSelector trackSelector;
|
||||
@NonNull
|
||||
final protected PlayerDataSource dataSource;
|
||||
|
||||
@NonNull final private LoadControl loadControl;
|
||||
@NonNull final private RenderersFactory renderFactory;
|
||||
@NonNull
|
||||
final private LoadControl loadControl;
|
||||
@NonNull
|
||||
final private RenderersFactory renderFactory;
|
||||
|
||||
@NonNull final private SerialDisposable progressUpdateReactor;
|
||||
@NonNull final private CompositeDisposable databaseUpdateReactor;
|
||||
@NonNull
|
||||
final private SerialDisposable progressUpdateReactor;
|
||||
@NonNull
|
||||
final private CompositeDisposable databaseUpdateReactor;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Intent
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@NonNull public static final String REPEAT_MODE = "repeat_mode";
|
||||
@NonNull public static final String PLAYBACK_PITCH = "playback_pitch";
|
||||
@NonNull public static final String PLAYBACK_SPEED = "playback_speed";
|
||||
@NonNull public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
|
||||
@NonNull public static final String PLAYBACK_QUALITY = "playback_quality";
|
||||
@NonNull public static final String PLAY_QUEUE_KEY = "play_queue_key";
|
||||
@NonNull public static final String APPEND_ONLY = "append_only";
|
||||
@NonNull public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
@NonNull
|
||||
public static final String REPEAT_MODE = "repeat_mode";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_PITCH = "playback_pitch";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_SPEED = "playback_speed";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
|
||||
@NonNull
|
||||
public static final String PLAYBACK_QUALITY = "playback_quality";
|
||||
@NonNull
|
||||
public static final String PLAY_QUEUE_KEY = "play_queue_key";
|
||||
@NonNull
|
||||
public static final String APPEND_ONLY = "append_only";
|
||||
@NonNull
|
||||
public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback
|
||||
@@ -136,13 +156,18 @@ public abstract class BasePlayer implements
|
||||
protected PlayQueue playQueue;
|
||||
protected PlayQueueAdapter playQueueAdapter;
|
||||
|
||||
@Nullable protected MediaSourceManager playbackManager;
|
||||
@Nullable
|
||||
protected MediaSourceManager playbackManager;
|
||||
|
||||
@Nullable private PlayQueueItem currentItem;
|
||||
@Nullable private MediaSourceTag currentMetadata;
|
||||
@Nullable private Bitmap currentThumbnail;
|
||||
@Nullable
|
||||
private PlayQueueItem currentItem;
|
||||
@Nullable
|
||||
private MediaSourceTag currentMetadata;
|
||||
@Nullable
|
||||
private Bitmap currentThumbnail;
|
||||
|
||||
@Nullable protected Toast errorToast;
|
||||
@Nullable
|
||||
protected Toast errorToast;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
@@ -172,7 +197,6 @@ public abstract class BasePlayer implements
|
||||
};
|
||||
this.intentFilter = new IntentFilter();
|
||||
setupBroadcastReceiver(intentFilter);
|
||||
context.registerReceiver(broadcastReceiver, intentFilter);
|
||||
|
||||
this.recordManager = new HistoryRecordManager(context);
|
||||
|
||||
@@ -209,9 +233,12 @@ public abstract class BasePlayer implements
|
||||
audioReactor = new AudioReactor(context, simpleExoPlayer);
|
||||
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
|
||||
new BasePlayerMediaSession(this));
|
||||
|
||||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
public void initListeners() {}
|
||||
public void initListeners() {
|
||||
}
|
||||
|
||||
public void handleIntent(Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||
@@ -228,7 +255,8 @@ public abstract class BasePlayer implements
|
||||
int sizeBeforeAppend = playQueue.size();
|
||||
playQueue.append(queue.getStreams());
|
||||
|
||||
if (intent.getBooleanExtra(SELECT_ON_APPEND, false) &&
|
||||
if ((intent.getBooleanExtra(SELECT_ON_APPEND, false) ||
|
||||
getCurrentState() == STATE_COMPLETED) &&
|
||||
queue.getStreams().size() > 0) {
|
||||
playQueue.setIndex(sizeBeforeAppend);
|
||||
}
|
||||
@@ -294,7 +322,6 @@ public abstract class BasePlayer implements
|
||||
databaseUpdateReactor.clear();
|
||||
progressUpdateReactor.set(null);
|
||||
|
||||
simpleExoPlayer = null;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -359,11 +386,17 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterBroadcastReceiver() {
|
||||
protected void registerBroadcastReceiver() {
|
||||
// Try to unregister current first
|
||||
unregisterBroadcastReceiver();
|
||||
context.registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
|
||||
protected void unregisterBroadcastReceiver() {
|
||||
try {
|
||||
context.unregisterReceiver(broadcastReceiver);
|
||||
} catch (final IllegalArgumentException unregisteredException) {
|
||||
Log.e(TAG, "Broadcast receiver already unregistered.", unregisteredException);
|
||||
Log.w(TAG, "Broadcast receiver already unregistered (" + unregisteredException.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,13 +449,15 @@ public abstract class BasePlayer implements
|
||||
if (!isProgressLoopRunning()) startProgressLoop();
|
||||
}
|
||||
|
||||
public void onBuffering() {}
|
||||
public void onBuffering() {
|
||||
}
|
||||
|
||||
public void onPaused() {
|
||||
if (isProgressLoopRunning()) stopProgressLoop();
|
||||
}
|
||||
|
||||
public void onPausedSeek() {}
|
||||
public void onPausedSeek() {
|
||||
}
|
||||
|
||||
public void onCompleted() {
|
||||
if (DEBUG) Log.d(TAG, "onCompleted() called");
|
||||
@@ -593,19 +628,19 @@ public abstract class BasePlayer implements
|
||||
/**
|
||||
* Processes the exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
|
||||
* There are multiple types of errors: <br><br>
|
||||
*
|
||||
* <p>
|
||||
* {@link ExoPlaybackException#TYPE_SOURCE TYPE_SOURCE}: <br><br>
|
||||
*
|
||||
* <p>
|
||||
* {@link ExoPlaybackException#TYPE_UNEXPECTED TYPE_UNEXPECTED}: <br><br>
|
||||
* If a runtime error occurred, then we can try to recover it by restarting the playback
|
||||
* after setting the timestamp recovery. <br><br>
|
||||
*
|
||||
* <p>
|
||||
* {@link ExoPlaybackException#TYPE_RENDERER TYPE_RENDERER}: <br><br>
|
||||
* If the renderer failed, treat the error as unrecoverable.
|
||||
*
|
||||
* @see #processSourceError(IOException)
|
||||
* @see Player.EventListener#onPlayerError(ExoPlaybackException)
|
||||
* */
|
||||
*/
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
if (DEBUG) Log.d(TAG, "ExoPlayer - onPlayerError() called with: " +
|
||||
@@ -891,8 +926,8 @@ public abstract class BasePlayer implements
|
||||
if (DEBUG) Log.d(TAG, "onPlayPrevious() called");
|
||||
|
||||
/* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT_MILLIS milliseconds,
|
||||
* restart current track. Also restart the track if the current track
|
||||
* is the first in a queue.*/
|
||||
* restart current track. Also restart the track if the current track
|
||||
* is the first in a queue.*/
|
||||
if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT_MILLIS ||
|
||||
playQueue.getIndex() == 0) {
|
||||
seekToDefault();
|
||||
@@ -1001,6 +1036,8 @@ public abstract class BasePlayer implements
|
||||
try {
|
||||
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
|
||||
} catch (IndexOutOfBoundsException | ClassCastException error) {
|
||||
if (DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage());
|
||||
if (DEBUG) error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1064,7 +1101,9 @@ public abstract class BasePlayer implements
|
||||
currentThumbnail;
|
||||
}
|
||||
|
||||
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
|
||||
/**
|
||||
* Checks if the current playback is a livestream AND is playing at or beyond the live edge
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isLiveEdge() {
|
||||
if (simpleExoPlayer == null || !isLive()) return false;
|
||||
@@ -1087,11 +1126,15 @@ public abstract class BasePlayer implements
|
||||
return simpleExoPlayer.isCurrentWindowDynamic();
|
||||
} catch (@NonNull IndexOutOfBoundsException ignored) {
|
||||
// Why would this even happen =(
|
||||
// But lets log it anyway. Save is save
|
||||
if (DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage());
|
||||
if (DEBUG) ignored.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
if (simpleExoPlayer == null) return false;
|
||||
final int state = simpleExoPlayer.getPlaybackState();
|
||||
return (state == Player.STATE_READY || state == Player.STATE_BUFFERING)
|
||||
&& simpleExoPlayer.getPlayWhenReady();
|
||||
@@ -1099,7 +1142,9 @@ public abstract class BasePlayer implements
|
||||
|
||||
@Player.RepeatMode
|
||||
public int getRepeatMode() {
|
||||
return simpleExoPlayer == null ? Player.REPEAT_MODE_OFF : simpleExoPlayer.getRepeatMode();
|
||||
return simpleExoPlayer == null
|
||||
? Player.REPEAT_MODE_OFF
|
||||
: simpleExoPlayer.getRepeatMode();
|
||||
}
|
||||
|
||||
public void setRepeatMode(@Player.RepeatMode final int repeatMode) {
|
||||
@@ -1165,4 +1210,8 @@ public abstract class BasePlayer implements
|
||||
if (DEBUG) Log.d(TAG, "Setting recovery, queue: " + queuePos + ", pos: " + windowPos);
|
||||
playQueue.setRecovery(queuePos, windowPos);
|
||||
}
|
||||
|
||||
public boolean gotDestroyed() {
|
||||
return simpleExoPlayer == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.content.res.AppCompatResources;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.util.DisplayMetrics;
|
||||
@@ -46,7 +47,9 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
@@ -82,6 +85,7 @@ import java.util.UUID;
|
||||
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
|
||||
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
|
||||
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateRotation;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
@@ -171,6 +175,10 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
setLandscape(lastOrientationWasLandscape);
|
||||
}
|
||||
|
||||
final int lastResizeMode = defaultPreferences.getInt(
|
||||
getString(R.string.last_resize_mode), AspectRatioFrameLayout.RESIZE_MODE_FIT);
|
||||
playerImpl.setResizeMode(lastResizeMode);
|
||||
|
||||
// Upon going in or out of multiwindow mode, isInMultiWindow will always be false,
|
||||
// since the first onResume needs to restore the player.
|
||||
// Subsequent onResume calls while multiwindow mode remains the same and the player is
|
||||
@@ -209,10 +217,9 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
if (playerImpl == null) return;
|
||||
|
||||
playerImpl.setRecovery();
|
||||
playerState = new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(),
|
||||
playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(),
|
||||
playerImpl.getPlaybackQuality(), playerImpl.getPlaybackSkipSilence(),
|
||||
playerImpl.isPlaying());
|
||||
if(!playerImpl.gotDestroyed()) {
|
||||
playerState = createPlayerState();
|
||||
}
|
||||
StateSaver.tryToSave(isChangingConfigurations(), null, outState, this);
|
||||
}
|
||||
|
||||
@@ -227,6 +234,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
if (!isBackPressed) {
|
||||
playerImpl.minimize();
|
||||
}
|
||||
playerState = createPlayerState();
|
||||
playerImpl.destroy();
|
||||
|
||||
isInMultiWindow = false;
|
||||
@@ -237,6 +245,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
// State Saving
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private PlayerState createPlayerState() {
|
||||
return new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(),
|
||||
playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(),
|
||||
playerImpl.getPlaybackQuality(), playerImpl.getPlaybackSkipSilence(),
|
||||
playerImpl.isPlaying());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateSuffix() {
|
||||
return "." + UUID.randomUUID().toString() + ".player";
|
||||
@@ -365,10 +380,16 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
private class VideoPlayerImpl extends VideoPlayer {
|
||||
private final float MAX_GESTURE_LENGTH = 0.75f;
|
||||
|
||||
private TextView titleTextView;
|
||||
private TextView channelTextView;
|
||||
private TextView volumeTextView;
|
||||
private TextView brightnessTextView;
|
||||
private RelativeLayout volumeRelativeLayout;
|
||||
private ProgressBar volumeProgressBar;
|
||||
private ImageView volumeImageView;
|
||||
private RelativeLayout brightnessRelativeLayout;
|
||||
private ProgressBar brightnessProgressBar;
|
||||
private ImageView brightnessImageView;
|
||||
private ImageButton queueButton;
|
||||
private ImageButton repeatButton;
|
||||
private ImageButton shuffleButton;
|
||||
@@ -392,6 +413,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
private RelativeLayout windowRootLayout;
|
||||
private View secondaryControls;
|
||||
|
||||
private int maxGestureLength;
|
||||
|
||||
VideoPlayerImpl(final Context context) {
|
||||
super("VideoPlayerImpl" + MainVideoPlayer.TAG, context);
|
||||
}
|
||||
@@ -401,8 +424,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
super.initViews(rootView);
|
||||
this.titleTextView = rootView.findViewById(R.id.titleTextView);
|
||||
this.channelTextView = rootView.findViewById(R.id.channelTextView);
|
||||
this.volumeTextView = rootView.findViewById(R.id.volumeTextView);
|
||||
this.brightnessTextView = rootView.findViewById(R.id.brightnessTextView);
|
||||
this.volumeRelativeLayout = rootView.findViewById(R.id.volumeRelativeLayout);
|
||||
this.volumeProgressBar = rootView.findViewById(R.id.volumeProgressBar);
|
||||
this.volumeImageView = rootView.findViewById(R.id.volumeImageView);
|
||||
this.brightnessRelativeLayout = rootView.findViewById(R.id.brightnessRelativeLayout);
|
||||
this.brightnessProgressBar = rootView.findViewById(R.id.brightnessProgressBar);
|
||||
this.brightnessImageView = rootView.findViewById(R.id.brightnessImageView);
|
||||
this.queueButton = rootView.findViewById(R.id.queueButton);
|
||||
this.repeatButton = rootView.findViewById(R.id.repeatButton);
|
||||
this.shuffleButton = rootView.findViewById(R.id.shuffleButton);
|
||||
@@ -444,7 +471,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
public void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
|
||||
PlayerGestureListener listener = new PlayerGestureListener();
|
||||
gestureDetector = new GestureDetector(context, listener);
|
||||
gestureDetector.setIsLongpressEnabled(false);
|
||||
getRootView().setOnTouchListener(listener);
|
||||
@@ -461,6 +488,22 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
toggleOrientationButton.setOnClickListener(this);
|
||||
switchBackgroundButton.setOnClickListener(this);
|
||||
switchPopupButton.setOnClickListener(this);
|
||||
|
||||
getRootView().addOnLayoutChangeListener((view, l, t, r, b, ol, ot, or, ob) -> {
|
||||
if (l != ol || t != ot || r != or || b != ob) {
|
||||
// Use smaller value to be consistent between screen orientations
|
||||
// (and to make usage easier)
|
||||
int width = r - l, height = b - t;
|
||||
maxGestureLength = (int) (Math.min(width, height) * MAX_GESTURE_LENGTH);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "maxGestureLength = " + maxGestureLength);
|
||||
|
||||
volumeProgressBar.setMax(maxGestureLength);
|
||||
brightnessProgressBar.setMax(maxGestureLength);
|
||||
|
||||
setInitialGestureValues();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void minimize() {
|
||||
@@ -673,14 +716,27 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
protected int nextResizeMode(int currentResizeMode) {
|
||||
final int newResizeMode;
|
||||
switch (currentResizeMode) {
|
||||
case AspectRatioFrameLayout.RESIZE_MODE_FIT:
|
||||
return AspectRatioFrameLayout.RESIZE_MODE_FILL;
|
||||
newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL;
|
||||
break;
|
||||
case AspectRatioFrameLayout.RESIZE_MODE_FILL:
|
||||
return AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||
newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||
break;
|
||||
default:
|
||||
return AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||
newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||
break;
|
||||
}
|
||||
|
||||
storeResizeMode(newResizeMode);
|
||||
return newResizeMode;
|
||||
}
|
||||
|
||||
private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) {
|
||||
defaultPreferences.edit()
|
||||
.putInt(getString(R.string.last_resize_mode), resizeMode)
|
||||
.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -769,6 +825,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void setInitialGestureValues() {
|
||||
if (getAudioReactor() != null) {
|
||||
final float currentVolumeNormalized = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume();
|
||||
volumeProgressBar.setProgress((int) (volumeProgressBar.getMax() * currentVolumeNormalized));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showControlsThenHide() {
|
||||
if (queueVisible) return;
|
||||
@@ -837,6 +900,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
public void onMove(int sourceIndex, int targetIndex) {
|
||||
if (playQueue != null) playQueue.move(sourceIndex, targetIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(int index) {
|
||||
if(index != -1) playQueue.remove(index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -872,12 +940,28 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
return channelTextView;
|
||||
}
|
||||
|
||||
public TextView getVolumeTextView() {
|
||||
return volumeTextView;
|
||||
public RelativeLayout getVolumeRelativeLayout() {
|
||||
return volumeRelativeLayout;
|
||||
}
|
||||
|
||||
public TextView getBrightnessTextView() {
|
||||
return brightnessTextView;
|
||||
public ProgressBar getVolumeProgressBar() {
|
||||
return volumeProgressBar;
|
||||
}
|
||||
|
||||
public ImageView getVolumeImageView() {
|
||||
return volumeImageView;
|
||||
}
|
||||
|
||||
public RelativeLayout getBrightnessRelativeLayout() {
|
||||
return brightnessRelativeLayout;
|
||||
}
|
||||
|
||||
public ProgressBar getBrightnessProgressBar() {
|
||||
return brightnessProgressBar;
|
||||
}
|
||||
|
||||
public ImageView getBrightnessImageView() {
|
||||
return brightnessImageView;
|
||||
}
|
||||
|
||||
public ImageButton getRepeatButton() {
|
||||
@@ -887,9 +971,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
public ImageButton getPlayPauseButton() {
|
||||
return playPauseButton;
|
||||
}
|
||||
|
||||
public int getMaxGestureLength() {
|
||||
return maxGestureLength;
|
||||
}
|
||||
}
|
||||
|
||||
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
||||
private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
||||
private boolean isMoving;
|
||||
|
||||
@Override
|
||||
@@ -928,91 +1016,97 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
return super.onDown(e);
|
||||
}
|
||||
|
||||
private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext());
|
||||
private static final int MOVEMENT_THRESHOLD = 40;
|
||||
|
||||
private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
|
||||
private float currentBrightness = getWindow().getAttributes().screenBrightness > 0
|
||||
? getWindow().getAttributes().screenBrightness
|
||||
: 0.5f;
|
||||
private final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(getApplicationContext());
|
||||
private final boolean isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(getApplicationContext());
|
||||
|
||||
private int currentVolume, maxVolume = playerImpl.getAudioReactor().getMaxVolume();
|
||||
private final float stepsVolume = 15, stepVolume = (float) Math.ceil(maxVolume / stepsVolume), minVolume = 0;
|
||||
private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
|
||||
|
||||
private final String brightnessUnicode = new String(Character.toChars(0x2600));
|
||||
private final String volumeUnicode = new String(Character.toChars(0x1F508));
|
||||
|
||||
private final int MOVEMENT_THRESHOLD = 40;
|
||||
private final int eventsThreshold = 8;
|
||||
private boolean triggered = false;
|
||||
private int eventsNum;
|
||||
|
||||
// TODO: Improve video gesture controls
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (!isPlayerGestureEnabled) return false;
|
||||
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
|
||||
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) return false;
|
||||
|
||||
//noinspection PointlessBooleanExpression
|
||||
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
||||
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
|
||||
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
|
||||
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
|
||||
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
|
||||
", distanceXy = [" + distanceX + ", " + distanceY + "]");
|
||||
float abs = Math.abs(e2.getY() - e1.getY());
|
||||
if (!triggered) {
|
||||
triggered = abs > MOVEMENT_THRESHOLD;
|
||||
|
||||
final boolean insideThreshold = Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
|
||||
if (!isMoving && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|
||||
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) return false;
|
||||
isMoving = true;
|
||||
// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top
|
||||
boolean up = distanceY > 0;
|
||||
|
||||
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
|
||||
boolean acceptVolumeArea = acceptAnyArea || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2;
|
||||
boolean acceptBrightnessArea = acceptAnyArea || !acceptVolumeArea;
|
||||
|
||||
if (e1.getX() > playerImpl.getRootView().getWidth() / 2) {
|
||||
double floor = Math.floor(up ? stepVolume : -stepVolume);
|
||||
currentVolume = (int) (playerImpl.getAudioReactor().getVolume() + floor);
|
||||
if (currentVolume >= maxVolume) currentVolume = maxVolume;
|
||||
if (currentVolume <= minVolume) currentVolume = (int) minVolume;
|
||||
if (isVolumeGestureEnabled && acceptVolumeArea) {
|
||||
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
|
||||
float currentProgressPercent =
|
||||
(float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
|
||||
int currentVolume = (int) (maxVolume * currentProgressPercent);
|
||||
playerImpl.getAudioReactor().setVolume(currentVolume);
|
||||
|
||||
currentVolume = playerImpl.getAudioReactor().getVolume();
|
||||
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
|
||||
final String volumeText = volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%";
|
||||
playerImpl.getVolumeTextView().setText(volumeText);
|
||||
|
||||
if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getVolumeTextView(), true, 200);
|
||||
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);
|
||||
} else {
|
||||
WindowManager.LayoutParams lp = getWindow().getAttributes();
|
||||
currentBrightness += up ? stepBrightness : -stepBrightness;
|
||||
if (currentBrightness >= 1f) currentBrightness = 1f;
|
||||
if (currentBrightness <= minBrightness) currentBrightness = minBrightness;
|
||||
final int resId =
|
||||
currentProgressPercent <= 0 ? R.drawable.ic_volume_off_white_72dp
|
||||
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_72dp
|
||||
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_72dp
|
||||
: R.drawable.ic_volume_up_white_72dp;
|
||||
|
||||
lp.screenBrightness = currentBrightness;
|
||||
getWindow().setAttributes(lp);
|
||||
if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentBrightness);
|
||||
int brightnessNormalized = Math.round(currentBrightness * 100);
|
||||
playerImpl.getVolumeImageView().setImageDrawable(
|
||||
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
||||
);
|
||||
|
||||
final String brightnessText = brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%";
|
||||
playerImpl.getBrightnessTextView().setText(brightnessText);
|
||||
if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
|
||||
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200);
|
||||
}
|
||||
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
|
||||
}
|
||||
} else if (isBrightnessGestureEnabled && acceptBrightnessArea) {
|
||||
playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY);
|
||||
float currentProgressPercent =
|
||||
(float) playerImpl.getBrightnessProgressBar().getProgress() / playerImpl.getMaxGestureLength();
|
||||
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
|
||||
layoutParams.screenBrightness = currentProgressPercent;
|
||||
getWindow().setAttributes(layoutParams);
|
||||
|
||||
if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), true, 200);
|
||||
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
|
||||
if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentProgressPercent);
|
||||
|
||||
final int resId =
|
||||
currentProgressPercent < 0.25 ? R.drawable.ic_brightness_low_white_72dp
|
||||
: currentProgressPercent < 0.75 ? R.drawable.ic_brightness_medium_white_72dp
|
||||
: R.drawable.ic_brightness_high_white_72dp;
|
||||
|
||||
playerImpl.getBrightnessImageView().setImageDrawable(
|
||||
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
||||
);
|
||||
|
||||
if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
|
||||
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200);
|
||||
}
|
||||
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onScrollEnd() {
|
||||
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
|
||||
triggered = false;
|
||||
eventsNum = 0;
|
||||
/* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
|
||||
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/
|
||||
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) {
|
||||
animateView(playerImpl.getVolumeTextView(), false, 200, 200);
|
||||
|
||||
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
|
||||
}
|
||||
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) {
|
||||
animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
|
||||
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
|
||||
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
|
||||
}
|
||||
|
||||
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -34,6 +36,7 @@ import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
@@ -41,7 +44,9 @@ import android.view.GestureDetector;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AnticipateInterpolator;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
@@ -63,7 +68,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
@@ -75,7 +79,6 @@ import java.util.List;
|
||||
import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING;
|
||||
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION;
|
||||
import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.isUsingOldPlayer;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
/**
|
||||
@@ -104,10 +107,12 @@ public final class PopupVideoPlayer extends Service {
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
|
||||
private WindowManager windowManager;
|
||||
private WindowManager.LayoutParams windowLayoutParams;
|
||||
private GestureDetector gestureDetector;
|
||||
private WindowManager.LayoutParams popupLayoutParams;
|
||||
private GestureDetector popupGestureDetector;
|
||||
|
||||
private View closeOverlayView;
|
||||
private FloatingActionButton closeOverlayButton;
|
||||
|
||||
private int shutdownFlingVelocity;
|
||||
private int tossFlingVelocity;
|
||||
|
||||
private float screenWidth, screenHeight;
|
||||
@@ -122,6 +127,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
private VideoPlayerImpl playerImpl;
|
||||
private LockManager lockManager;
|
||||
private boolean isPopupClosing = false;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service-Activity Binder
|
||||
@@ -150,7 +156,10 @@ public final class PopupVideoPlayer extends Service {
|
||||
public int onStartCommand(final Intent intent, int flags, int startId) {
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
|
||||
if (playerImpl.getPlayer() == null) initPopup();
|
||||
if (playerImpl.getPlayer() == null) {
|
||||
initPopup();
|
||||
initPopupCloseOverlay();
|
||||
}
|
||||
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
|
||||
|
||||
playerImpl.handleIntent(intent);
|
||||
@@ -160,15 +169,16 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (DEBUG) Log.d(TAG, "onConfigurationChanged() called with: newConfig = [" + newConfig + "]");
|
||||
updateScreenSize();
|
||||
updatePopupSize(windowLayoutParams.width, -1);
|
||||
checkPositionBounds();
|
||||
updatePopupSize(popupLayoutParams.width, -1);
|
||||
checkPopupPositionBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (DEBUG) Log.d(TAG, "onDestroy() called");
|
||||
onClose();
|
||||
closePopup();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,7 +196,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
View rootView = View.inflate(this, R.layout.player_popup, null);
|
||||
playerImpl.setup(rootView);
|
||||
|
||||
shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this);
|
||||
tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this);
|
||||
|
||||
updateScreenSize();
|
||||
@@ -200,27 +209,52 @@ public final class PopupVideoPlayer extends Service {
|
||||
WindowManager.LayoutParams.TYPE_PHONE :
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
|
||||
windowLayoutParams = new WindowManager.LayoutParams(
|
||||
popupLayoutParams = new WindowManager.LayoutParams(
|
||||
(int) popupWidth, (int) getMinimumVideoHeight(popupWidth),
|
||||
layoutParamType,
|
||||
IDLE_WINDOW_FLAGS,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
|
||||
windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
|
||||
popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
|
||||
popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
|
||||
|
||||
int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
|
||||
int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
|
||||
windowLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
|
||||
windowLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
|
||||
popupLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
|
||||
popupLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
|
||||
|
||||
checkPositionBounds();
|
||||
checkPopupPositionBounds();
|
||||
|
||||
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
|
||||
gestureDetector = new GestureDetector(this, listener);
|
||||
PopupWindowGestureListener listener = new PopupWindowGestureListener();
|
||||
popupGestureDetector = new GestureDetector(this, listener);
|
||||
rootView.setOnTouchListener(listener);
|
||||
playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width);
|
||||
playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height);
|
||||
windowManager.addView(rootView, windowLayoutParams);
|
||||
|
||||
playerImpl.getLoadingPanel().setMinimumWidth(popupLayoutParams.width);
|
||||
playerImpl.getLoadingPanel().setMinimumHeight(popupLayoutParams.height);
|
||||
windowManager.addView(rootView, popupLayoutParams);
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
private void initPopupCloseOverlay() {
|
||||
if (DEBUG) Log.d(TAG, "initPopupCloseOverlay() called");
|
||||
closeOverlayView = View.inflate(this, R.layout.player_popup_close_overlay, null);
|
||||
closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton);
|
||||
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ?
|
||||
WindowManager.LayoutParams.TYPE_PHONE :
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
||||
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||
|
||||
WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
layoutParamType,
|
||||
flags,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
|
||||
closeOverlayLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
|
||||
|
||||
closeOverlayButton.setVisibility(View.GONE);
|
||||
windowManager.addView(closeOverlayView, closeOverlayLayoutParams);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -280,44 +314,105 @@ public final class PopupVideoPlayer extends Service {
|
||||
// Misc
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void onClose() {
|
||||
if (DEBUG) Log.d(TAG, "onClose() called");
|
||||
public void closePopup() {
|
||||
if (DEBUG) Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing);
|
||||
if (isPopupClosing) return;
|
||||
isPopupClosing = true;
|
||||
|
||||
if (playerImpl != null) {
|
||||
if (playerImpl.getRootView() != null) {
|
||||
windowManager.removeView(playerImpl.getRootView());
|
||||
playerImpl.setRootView(null);
|
||||
}
|
||||
playerImpl.setRootView(null);
|
||||
playerImpl.stopActivityBinding();
|
||||
playerImpl.destroy();
|
||||
playerImpl = null;
|
||||
}
|
||||
|
||||
mBinder = null;
|
||||
if (lockManager != null) lockManager.releaseWifiAndCpu();
|
||||
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
|
||||
mBinder = null;
|
||||
playerImpl = null;
|
||||
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
animateOverlayAndFinishService();
|
||||
}
|
||||
|
||||
private void animateOverlayAndFinishService() {
|
||||
final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() - closeOverlayButton.getY());
|
||||
|
||||
closeOverlayButton.animate().setListener(null).cancel();
|
||||
closeOverlayButton.animate()
|
||||
.setInterpolator(new AnticipateInterpolator())
|
||||
.translationY(targetTranslationY)
|
||||
.setDuration(400)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
end();
|
||||
}
|
||||
|
||||
private void end() {
|
||||
windowManager.removeView(closeOverlayView);
|
||||
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void checkPositionBounds() {
|
||||
if (windowLayoutParams.x > screenWidth - windowLayoutParams.width)
|
||||
windowLayoutParams.x = (int) (screenWidth - windowLayoutParams.width);
|
||||
if (windowLayoutParams.x < 0) windowLayoutParams.x = 0;
|
||||
if (windowLayoutParams.y > screenHeight - windowLayoutParams.height)
|
||||
windowLayoutParams.y = (int) (screenHeight - windowLayoutParams.height);
|
||||
if (windowLayoutParams.y < 0) windowLayoutParams.y = 0;
|
||||
/**
|
||||
* @see #checkPopupPositionBounds(float, float)
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
private boolean checkPopupPositionBounds() {
|
||||
return checkPopupPositionBounds(screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if {@link #popupLayoutParams}' position is within a arbitrary boundary that goes from (0,0) to (boundaryWidth,boundaryHeight).
|
||||
* <p>
|
||||
* If it's out of these boundaries, {@link #popupLayoutParams}' position is changed and {@code true} is returned
|
||||
* to represent this change.
|
||||
*
|
||||
* @return if the popup was out of bounds and have been moved back to it
|
||||
*/
|
||||
private boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "checkPopupPositionBounds() called with: boundaryWidth = [" + boundaryWidth + "], boundaryHeight = [" + boundaryHeight + "]");
|
||||
}
|
||||
|
||||
if (popupLayoutParams.x < 0) {
|
||||
popupLayoutParams.x = 0;
|
||||
return true;
|
||||
} else if (popupLayoutParams.x > boundaryWidth - popupLayoutParams.width) {
|
||||
popupLayoutParams.x = (int) (boundaryWidth - popupLayoutParams.width);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (popupLayoutParams.y < 0) {
|
||||
popupLayoutParams.y = 0;
|
||||
return true;
|
||||
} else if (popupLayoutParams.y > boundaryHeight - popupLayoutParams.height) {
|
||||
popupLayoutParams.y = (int) (boundaryHeight - popupLayoutParams.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void savePositionAndSize() {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this);
|
||||
sharedPreferences.edit().putInt(POPUP_SAVED_X, windowLayoutParams.x).apply();
|
||||
sharedPreferences.edit().putInt(POPUP_SAVED_Y, windowLayoutParams.y).apply();
|
||||
sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, windowLayoutParams.width).apply();
|
||||
sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply();
|
||||
sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply();
|
||||
sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply();
|
||||
}
|
||||
|
||||
private float getMinimumVideoHeight(float width) {
|
||||
@@ -352,13 +447,13 @@ public final class PopupVideoPlayer extends Service {
|
||||
if (height == -1) height = (int) getMinimumVideoHeight(width);
|
||||
else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height);
|
||||
|
||||
windowLayoutParams.width = width;
|
||||
windowLayoutParams.height = height;
|
||||
popupLayoutParams.width = width;
|
||||
popupLayoutParams.height = height;
|
||||
popupWidth = width;
|
||||
popupHeight = height;
|
||||
|
||||
if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]");
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
}
|
||||
|
||||
protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) {
|
||||
@@ -380,10 +475,10 @@ public final class PopupVideoPlayer extends Service {
|
||||
}
|
||||
|
||||
private void updateWindowFlags(final int flags) {
|
||||
if (windowLayoutParams == null || windowManager == null || playerImpl == null) return;
|
||||
if (popupLayoutParams == null || windowManager == null || playerImpl == null) return;
|
||||
|
||||
windowLayoutParams.flags = flags;
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
popupLayoutParams.flags = flags;
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -393,6 +488,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
private ImageView videoPlayPause;
|
||||
|
||||
private View extraOptionsView;
|
||||
private View closingOverlayView;
|
||||
|
||||
@Override
|
||||
public void handleIntent(Intent intent) {
|
||||
@@ -413,12 +509,18 @@ public final class PopupVideoPlayer extends Service {
|
||||
fullScreenButton = rootView.findViewById(R.id.fullScreenButton);
|
||||
fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked());
|
||||
videoPlayPause = rootView.findViewById(R.id.videoPlayPause);
|
||||
videoPlayPause.setOnClickListener(this::onPlayPauseButtonPressed);
|
||||
|
||||
extraOptionsView = rootView.findViewById(R.id.extraOptionsView);
|
||||
closingOverlayView = rootView.findViewById(R.id.closingOverlay);
|
||||
rootView.addOnLayoutChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initListeners() {
|
||||
super.initListeners();
|
||||
videoPlayPause.setOnClickListener(v -> onPlayPause());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupSubtitleView(@NonNull SubtitleView view,
|
||||
final float captionScale,
|
||||
@@ -429,10 +531,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
view.setStyle(captionStyle);
|
||||
}
|
||||
|
||||
private void onPlayPauseButtonPressed(View ib) {
|
||||
onPlayPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChange(final View view, int left, int top, int right, int bottom,
|
||||
int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||
@@ -454,29 +552,19 @@ public final class PopupVideoPlayer extends Service {
|
||||
if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called");
|
||||
|
||||
setRecovery();
|
||||
Intent intent;
|
||||
if (!isUsingOldPlayer(getApplicationContext())) {
|
||||
intent = NavigationHelper.getPlayerIntent(
|
||||
context,
|
||||
MainVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackSkipSilence(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
} else {
|
||||
intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class)
|
||||
.putExtra(PlayVideoActivity.VIDEO_TITLE, getVideoTitle())
|
||||
.putExtra(PlayVideoActivity.STREAM_URL, getSelectedVideoStream().getUrl())
|
||||
.putExtra(PlayVideoActivity.VIDEO_URL, getVideoUrl())
|
||||
.putExtra(PlayVideoActivity.START_POSITION, Math.round(getPlayer().getCurrentPosition() / 1000f));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
final Intent intent = NavigationHelper.getPlayerIntent(
|
||||
context,
|
||||
MainVideoPlayer.class,
|
||||
this.getPlayQueue(),
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackSkipSilence(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
onClose();
|
||||
closePopup();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -634,7 +722,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
@Override
|
||||
public void onPlaybackShutdown() {
|
||||
super.onPlaybackShutdown();
|
||||
onClose();
|
||||
closePopup();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -660,7 +748,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]");
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_CLOSE:
|
||||
onClose();
|
||||
closePopup();
|
||||
break;
|
||||
case ACTION_PLAY_PAUSE:
|
||||
onPlayPause();
|
||||
@@ -791,12 +879,15 @@ public final class PopupVideoPlayer extends Service {
|
||||
public TextView getResizingIndicator() {
|
||||
return resizingIndicator;
|
||||
}
|
||||
|
||||
public View getClosingOverlayView() {
|
||||
return closingOverlayView;
|
||||
}
|
||||
}
|
||||
|
||||
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
||||
private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
||||
private int initialPopupX, initialPopupY;
|
||||
private boolean isMoving;
|
||||
|
||||
private boolean isResizing;
|
||||
|
||||
@Override
|
||||
@@ -832,10 +923,15 @@ public final class PopupVideoPlayer extends Service {
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
|
||||
initialPopupX = windowLayoutParams.x;
|
||||
initialPopupY = windowLayoutParams.y;
|
||||
popupWidth = windowLayoutParams.width;
|
||||
popupHeight = windowLayoutParams.height;
|
||||
|
||||
// Fix popup position when the user touch it, it may have the wrong one
|
||||
// because the soft input is visible (the draggable area is currently resized).
|
||||
checkPopupPositionBounds(closeOverlayView.getWidth(), closeOverlayView.getHeight());
|
||||
|
||||
initialPopupX = popupLayoutParams.x;
|
||||
initialPopupY = popupLayoutParams.y;
|
||||
popupWidth = popupLayoutParams.width;
|
||||
popupHeight = popupLayoutParams.height;
|
||||
return super.onDown(e);
|
||||
}
|
||||
|
||||
@@ -843,20 +939,22 @@ public final class PopupVideoPlayer extends Service {
|
||||
public void onLongPress(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
|
||||
updateScreenSize();
|
||||
checkPositionBounds();
|
||||
checkPopupPositionBounds();
|
||||
updatePopupSize((int) screenWidth, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (isResizing || playerImpl == null) return super.onScroll(e1, e2, distanceX, distanceY);
|
||||
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
|
||||
if (isResizing || playerImpl == null) return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
|
||||
|
||||
if (!isMoving) {
|
||||
animateView(closeOverlayButton, true, 200);
|
||||
}
|
||||
|
||||
if (playerImpl.getCurrentState() != BasePlayer.STATE_BUFFERING
|
||||
&& (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f)) playerImpl.showControls(0);
|
||||
isMoving = true;
|
||||
|
||||
float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX);
|
||||
float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY);
|
||||
float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX()), posX = (int) (initialPopupX + diffX);
|
||||
float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY()), posY = (int) (initialPopupY + diffY);
|
||||
|
||||
if (posX > (screenWidth - popupWidth)) posX = (int) (screenWidth - popupWidth);
|
||||
else if (posX < 0) posX = 0;
|
||||
@@ -864,26 +962,49 @@ public final class PopupVideoPlayer extends Service {
|
||||
if (posY > (screenHeight - popupHeight)) posY = (int) (screenHeight - popupHeight);
|
||||
else if (posY < 0) posY = 0;
|
||||
|
||||
windowLayoutParams.x = (int) posX;
|
||||
windowLayoutParams.y = (int) posY;
|
||||
popupLayoutParams.x = (int) posX;
|
||||
popupLayoutParams.y = (int) posY;
|
||||
|
||||
final View closingOverlayView = playerImpl.getClosingOverlayView();
|
||||
if (isInsideClosingRadius(movingEvent)) {
|
||||
if (closingOverlayView.getVisibility() == View.GONE) {
|
||||
animateView(closingOverlayView, true, 250);
|
||||
}
|
||||
} else {
|
||||
if (closingOverlayView.getVisibility() == View.VISIBLE) {
|
||||
animateView(closingOverlayView, false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection PointlessBooleanExpression
|
||||
if (DEBUG && false) Log.d(TAG, "PopupVideoPlayer.onScroll = " +
|
||||
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
|
||||
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
|
||||
", distanceXy = [" + distanceX + ", " + distanceY + "]" +
|
||||
", posXy = [" + posX + ", " + posY + "]" +
|
||||
", popupWh = [" + popupWidth + " x " + popupHeight + "]");
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
if (DEBUG && false) {
|
||||
Log.d(TAG, "PopupVideoPlayer.onScroll = " +
|
||||
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + ", e1.getX,Y = [" + initialEvent.getX() + ", " + initialEvent.getY() + "]" +
|
||||
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" + ", e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "]" +
|
||||
", distanceX,Y = [" + distanceX + ", " + distanceY + "]" +
|
||||
", posX,Y = [" + posX + ", " + posY + "]" +
|
||||
", popupW,H = [" + popupWidth + " x " + popupHeight + "]");
|
||||
}
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onScrollEnd() {
|
||||
private void onScrollEnd(MotionEvent event) {
|
||||
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
|
||||
if (playerImpl == null) return;
|
||||
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
|
||||
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||
}
|
||||
|
||||
if (isInsideClosingRadius(event)) {
|
||||
closePopup();
|
||||
} else {
|
||||
animateView(playerImpl.getClosingOverlayView(), false, 0);
|
||||
|
||||
if (!isPopupClosing) {
|
||||
animateView(closeOverlayButton, false, 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -893,14 +1014,11 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
final float absVelocityX = Math.abs(velocityX);
|
||||
final float absVelocityY = Math.abs(velocityY);
|
||||
if (absVelocityX > shutdownFlingVelocity) {
|
||||
onClose();
|
||||
return true;
|
||||
} else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
|
||||
if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX;
|
||||
if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY;
|
||||
checkPositionBounds();
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
|
||||
if (absVelocityX > tossFlingVelocity) popupLayoutParams.x = (int) velocityX;
|
||||
if (absVelocityY > tossFlingVelocity) popupLayoutParams.y = (int) velocityY;
|
||||
checkPopupPositionBounds();
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -908,7 +1026,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
gestureDetector.onTouchEvent(event);
|
||||
popupGestureDetector.onTouchEvent(event);
|
||||
if (playerImpl == null) return false;
|
||||
if (event.getPointerCount() == 2 && !isResizing) {
|
||||
if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
|
||||
@@ -931,7 +1049,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
|
||||
if (isMoving) {
|
||||
isMoving = false;
|
||||
onScrollEnd();
|
||||
onScrollEnd(event);
|
||||
}
|
||||
|
||||
if (isResizing) {
|
||||
@@ -939,7 +1057,10 @@ public final class PopupVideoPlayer extends Service {
|
||||
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
|
||||
playerImpl.changeState(playerImpl.getCurrentState());
|
||||
}
|
||||
savePositionAndSize();
|
||||
|
||||
if (!isPopupClosing) {
|
||||
savePositionAndSize();
|
||||
}
|
||||
}
|
||||
|
||||
v.performClick();
|
||||
@@ -955,13 +1076,13 @@ public final class PopupVideoPlayer extends Service {
|
||||
final float diff = Math.abs(firstPointerX - secondPointerX);
|
||||
if (firstPointerX > secondPointerX) {
|
||||
// second pointer is the anchor (the leftmost pointer)
|
||||
windowLayoutParams.x = (int) (event.getRawX() - diff);
|
||||
popupLayoutParams.x = (int) (event.getRawX() - diff);
|
||||
} else {
|
||||
// first pointer is the anchor
|
||||
windowLayoutParams.x = (int) event.getRawX();
|
||||
popupLayoutParams.x = (int) event.getRawX();
|
||||
}
|
||||
|
||||
checkPositionBounds();
|
||||
checkPopupPositionBounds();
|
||||
updateScreenSize();
|
||||
|
||||
final int width = (int) Math.min(screenWidth, diff);
|
||||
@@ -969,5 +1090,29 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private int distanceFromCloseButton(MotionEvent popupMotionEvent) {
|
||||
final int closeOverlayButtonX = closeOverlayButton.getLeft() + closeOverlayButton.getWidth() / 2;
|
||||
final int closeOverlayButtonY = closeOverlayButton.getTop() + closeOverlayButton.getHeight() / 2;
|
||||
|
||||
float fingerX = popupLayoutParams.x + popupMotionEvent.getX();
|
||||
float fingerY = popupLayoutParams.y + popupMotionEvent.getY();
|
||||
|
||||
return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + Math.pow(closeOverlayButtonY - fingerY, 2));
|
||||
}
|
||||
|
||||
private float getClosingRadius() {
|
||||
final int buttonRadius = closeOverlayButton.getWidth() / 2;
|
||||
// 20% wider than the button itself
|
||||
return buttonRadius * 1.2f;
|
||||
}
|
||||
|
||||
private boolean isInsideClosingRadius(MotionEvent popupMotionEvent) {
|
||||
return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
@@ -374,6 +375,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
public void onMove(int sourceIndex, int targetIndex) {
|
||||
if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(int index) {
|
||||
if (index != -1) player.getPlayQueue().remove(index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -562,6 +568,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
if (player != null) {
|
||||
progressLiveSync.setClickable(!player.isLiveEdge());
|
||||
}
|
||||
|
||||
// this will make shure progressCurrentTime has the same width as progressEndTime
|
||||
final ViewGroup.LayoutParams endTimeParams = progressEndTime.getLayoutParams();
|
||||
final ViewGroup.LayoutParams currentTimeParams = progressCurrentTime.getLayoutParams();
|
||||
currentTimeParams.width = progressEndTime.getWidth();
|
||||
progressCurrentTime.setLayoutParams(currentTimeParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -137,16 +137,16 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
private TextView captionTextView;
|
||||
|
||||
private ValueAnimator controlViewAnimator;
|
||||
private Handler controlsVisibilityHandler = new Handler();
|
||||
private final Handler controlsVisibilityHandler = new Handler();
|
||||
|
||||
boolean isSomePopupMenuVisible = false;
|
||||
private int qualityPopupMenuGroupId = 69;
|
||||
private final int qualityPopupMenuGroupId = 69;
|
||||
private PopupMenu qualityPopupMenu;
|
||||
|
||||
private int playbackSpeedPopupMenuGroupId = 79;
|
||||
private final int playbackSpeedPopupMenuGroupId = 79;
|
||||
private PopupMenu playbackSpeedPopupMenu;
|
||||
|
||||
private int captionPopupMenuGroupId = 89;
|
||||
private final int captionPopupMenuGroupId = 89;
|
||||
private PopupMenu captionPopupMenu;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -683,12 +683,17 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
if (getAspectRatioFrameLayout() != null) {
|
||||
final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode();
|
||||
final int newResizeMode = nextResizeMode(currentResizeMode);
|
||||
getAspectRatioFrameLayout().setResizeMode(newResizeMode);
|
||||
getResizeView().setText(PlayerHelper.resizeTypeOf(context, newResizeMode));
|
||||
setResizeMode(newResizeMode);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
|
||||
getAspectRatioFrameLayout().setResizeMode(resizeMode);
|
||||
getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode));
|
||||
}
|
||||
|
||||
protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode);
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// SeekBar Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@@ -116,7 +116,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
private void onAudioFocusGain() {
|
||||
Log.d(TAG, "onAudioFocusGain() called");
|
||||
player.setVolume(DUCK_AUDIO_TO);
|
||||
animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION);
|
||||
animateAudio(DUCK_AUDIO_TO, 1f);
|
||||
|
||||
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
|
||||
player.setPlayWhenReady(true);
|
||||
@@ -131,13 +131,13 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
|
||||
private void onAudioFocusLossCanDuck() {
|
||||
Log.d(TAG, "onAudioFocusLossCanDuck() called");
|
||||
// Set the volume to 1/10 on ducking
|
||||
animateAudio(player.getVolume(), DUCK_AUDIO_TO, DUCK_DURATION);
|
||||
animateAudio(player.getVolume(), DUCK_AUDIO_TO);
|
||||
}
|
||||
|
||||
private void animateAudio(final float from, final float to, int duration) {
|
||||
private void animateAudio(final float from, final float to) {
|
||||
ValueAnimator valueAnimator = new ValueAnimator();
|
||||
valueAnimator.setFloatValues(from, to);
|
||||
valueAnimator.setDuration(duration);
|
||||
valueAnimator.setDuration(AudioReactor.DUCK_DURATION);
|
||||
valueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
|
||||
@@ -4,12 +4,9 @@ import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.FileDataSource;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
|
||||
@@ -17,8 +14,6 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
|
||||
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
|
||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
||||
|
||||
import org.schabi.newpipe.Downloader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/* package-private */ class CacheFactory implements DataSource.Factory {
|
||||
|
||||
@@ -66,25 +66,15 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
private double stepSize = DEFAULT_STEP;
|
||||
|
||||
@Nullable private SeekBar tempoSlider;
|
||||
@Nullable private TextView tempoMinimumText;
|
||||
@Nullable private TextView tempoMaximumText;
|
||||
@Nullable private TextView tempoCurrentText;
|
||||
@Nullable private TextView tempoStepDownText;
|
||||
@Nullable private TextView tempoStepUpText;
|
||||
|
||||
@Nullable private SeekBar pitchSlider;
|
||||
@Nullable private TextView pitchMinimumText;
|
||||
@Nullable private TextView pitchMaximumText;
|
||||
@Nullable private TextView pitchCurrentText;
|
||||
@Nullable private TextView pitchStepDownText;
|
||||
@Nullable private TextView pitchStepUpText;
|
||||
|
||||
@Nullable private TextView stepSizeOnePercentText;
|
||||
@Nullable private TextView stepSizeFivePercentText;
|
||||
@Nullable private TextView stepSizeTenPercentText;
|
||||
@Nullable private TextView stepSizeTwentyFivePercentText;
|
||||
@Nullable private TextView stepSizeOneHundredPercentText;
|
||||
|
||||
@Nullable private CheckBox unhookingCheckbox;
|
||||
@Nullable private CheckBox skipSilenceCheckbox;
|
||||
|
||||
@@ -181,8 +171,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
|
||||
private void setupTempoControl(@NonNull View rootView) {
|
||||
tempoSlider = rootView.findViewById(R.id.tempoSeekbar);
|
||||
tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
||||
tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
||||
TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText);
|
||||
TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText);
|
||||
tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText);
|
||||
tempoStepUpText = rootView.findViewById(R.id.tempoStepUp);
|
||||
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
||||
@@ -203,8 +193,8 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
|
||||
private void setupPitchControl(@NonNull View rootView) {
|
||||
pitchSlider = rootView.findViewById(R.id.pitchSeekbar);
|
||||
pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
||||
pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
||||
TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText);
|
||||
TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText);
|
||||
pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText);
|
||||
pitchStepDownText = rootView.findViewById(R.id.pitchStepDown);
|
||||
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
||||
@@ -247,11 +237,11 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
}
|
||||
|
||||
private void setupStepSizeSelector(@NonNull final View rootView) {
|
||||
stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
||||
stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
||||
stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
||||
stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
|
||||
stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
|
||||
TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
||||
TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
||||
TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
||||
TextView stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
|
||||
TextView stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
|
||||
|
||||
if (stepSizeOnePercentText != null) {
|
||||
stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));
|
||||
|
||||
@@ -19,11 +19,11 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.Subtitles;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesFormat;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
@@ -87,7 +87,7 @@ public class PlayerHelper {
|
||||
return pitchFormatter.format(pitch);
|
||||
}
|
||||
|
||||
public static String mimeTypesOf(final SubtitlesFormat format) {
|
||||
public static String subtitleMimeTypesOf(final MediaFormat format) {
|
||||
switch (format) {
|
||||
case VTT: return MimeTypes.TEXT_VTT;
|
||||
case TTML: return MimeTypes.APPLICATION_TTML;
|
||||
@@ -97,8 +97,8 @@ public class PlayerHelper {
|
||||
|
||||
@NonNull
|
||||
public static String captionLanguageOf(@NonNull final Context context,
|
||||
@NonNull final Subtitles subtitles) {
|
||||
final String displayName = subtitles.getLocale().getDisplayName(subtitles.getLocale());
|
||||
@NonNull final SubtitlesStream subtitles) {
|
||||
final String displayName = subtitles.getDisplayLanguageName();
|
||||
return displayName + (subtitles.isAutoGenerated() ? " (" + context.getString(R.string.caption_auto_generated)+ ")" : "");
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ public class PlayerHelper {
|
||||
|
||||
final StreamInfoItem nextVideo = info.getNextVideo();
|
||||
if (nextVideo != null && !urls.contains(nextVideo.getUrl())) {
|
||||
return new SinglePlayQueue(nextVideo);
|
||||
return getAutoQueuedSinglePlayQueue(nextVideo);
|
||||
}
|
||||
|
||||
final List<InfoItem> relatedItems = info.getRelatedStreams();
|
||||
@@ -158,7 +158,7 @@ public class PlayerHelper {
|
||||
}
|
||||
}
|
||||
Collections.shuffle(autoQueueItems);
|
||||
return autoQueueItems.isEmpty() ? null : new SinglePlayQueue(autoQueueItems.get(0));
|
||||
return autoQueueItems.isEmpty() ? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -169,12 +169,12 @@ public class PlayerHelper {
|
||||
return isResumeAfterAudioFocusGain(context, false);
|
||||
}
|
||||
|
||||
public static boolean isPlayerGestureEnabled(@NonNull final Context context) {
|
||||
return isPlayerGestureEnabled(context, true);
|
||||
public static boolean isVolumeGestureEnabled(@NonNull final Context context) {
|
||||
return isVolumeGestureEnabled(context, true);
|
||||
}
|
||||
|
||||
public static boolean isUsingOldPlayer(@NonNull final Context context) {
|
||||
return isUsingOldPlayer(context, false);
|
||||
public static boolean isBrightnessGestureEnabled(@NonNull final Context context) {
|
||||
return isBrightnessGestureEnabled(context, true);
|
||||
}
|
||||
|
||||
public static boolean isRememberingPopupDimensions(@NonNull final Context context) {
|
||||
@@ -203,7 +203,7 @@ public class PlayerHelper {
|
||||
|
||||
@NonNull
|
||||
public static SeekParameters getSeekParameters(@NonNull final Context context) {
|
||||
return isUsingInexactSeek(context, false) ?
|
||||
return isUsingInexactSeek(context) ?
|
||||
SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
|
||||
}
|
||||
|
||||
@@ -251,10 +251,6 @@ public class PlayerHelper {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int getShutdownFlingVelocity(@NonNull final Context context) {
|
||||
return 6000;
|
||||
}
|
||||
|
||||
public static int getTossFlingVelocity(@NonNull final Context context) {
|
||||
return 2500;
|
||||
}
|
||||
@@ -310,20 +306,20 @@ public class PlayerHelper {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.resume_on_audio_focus_gain_key), b);
|
||||
}
|
||||
|
||||
private static boolean isPlayerGestureEnabled(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.player_gesture_controls_key), b);
|
||||
private static boolean isVolumeGestureEnabled(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.volume_gesture_control_key), b);
|
||||
}
|
||||
|
||||
private static boolean isUsingOldPlayer(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_old_player_key), b);
|
||||
private static boolean isBrightnessGestureEnabled(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
|
||||
}
|
||||
|
||||
private static boolean isRememberingPopupDimensions(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
|
||||
}
|
||||
|
||||
private static boolean isUsingInexactSeek(@NonNull final Context context, final boolean b) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), b);
|
||||
private static boolean isUsingInexactSeek(@NonNull final Context context) {
|
||||
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), false);
|
||||
}
|
||||
|
||||
private static boolean isAutoQueueEnabled(@NonNull final Context context, final boolean b) {
|
||||
@@ -354,4 +350,10 @@ public class PlayerHelper {
|
||||
return getPreferences(context).getString(context.getString(R.string.minimize_on_exit_key),
|
||||
key);
|
||||
}
|
||||
|
||||
private static SinglePlayQueue getAutoQueuedSinglePlayQueue(StreamInfoItem streamInfoItem) {
|
||||
SinglePlayQueue singlePlayQueue = new SinglePlayQueue(streamInfoItem);
|
||||
singlePlayQueue.getItem().setAutoQueued(true);
|
||||
return singlePlayQueue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
|
||||
|
||||
private void publishFloatingQueueWindow() {
|
||||
if (callback.getQueueSize() == 0) {
|
||||
mediaSession.setQueue(Collections.<MediaSessionCompat.QueueItem>emptyList());
|
||||
mediaSession.setQueue(Collections.emptyList());
|
||||
activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
package org.schabi.newpipe.player.old;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
/*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* PlayVideoActivity.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 PlayVideoActivity extends AppCompatActivity {
|
||||
|
||||
//// TODO: 11.09.15 add "choose stream" menu
|
||||
|
||||
private static final String TAG = PlayVideoActivity.class.toString();
|
||||
public static final String VIDEO_URL = "video_url";
|
||||
public static final String STREAM_URL = "stream_url";
|
||||
public static final String VIDEO_TITLE = "video_title";
|
||||
private static final String POSITION = "position";
|
||||
public static final String START_POSITION = "start_position";
|
||||
|
||||
private static final long HIDING_DELAY = 3000;
|
||||
|
||||
private String videoUrl = "";
|
||||
|
||||
private ActionBar actionBar;
|
||||
private VideoView videoView;
|
||||
private int position;
|
||||
private MediaController mediaController;
|
||||
private ProgressBar progressBar;
|
||||
private View decorView;
|
||||
private boolean uiIsHidden;
|
||||
private static long lastUiShowTime;
|
||||
private boolean isLandscape = true;
|
||||
private boolean hasSoftKeys;
|
||||
|
||||
private SharedPreferences prefs;
|
||||
private static final String PREF_IS_LANDSCAPE = "is_landscape";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_play_video);
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
//set background arrow style
|
||||
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back_white_24dp);
|
||||
|
||||
isLandscape = checkIfLandscape();
|
||||
hasSoftKeys = checkIfHasSoftKeys();
|
||||
|
||||
actionBar = getSupportActionBar();
|
||||
assert actionBar != null;
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
Intent intent = getIntent();
|
||||
if(mediaController == null) {
|
||||
//prevents back button hiding media controller controls (after showing them)
|
||||
//instead of exiting video
|
||||
//see http://stackoverflow.com/questions/6051825
|
||||
//also solves https://github.com/theScrabi/NewPipe/issues/99
|
||||
mediaController = new MediaController(this) {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
int keyCode = event.getKeyCode();
|
||||
final boolean uniqueDown = event.getRepeatCount() == 0
|
||||
&& event.getAction() == KeyEvent.ACTION_DOWN;
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (uniqueDown)
|
||||
{
|
||||
if (isShowing()) {
|
||||
finish();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
position = intent.getIntExtra(START_POSITION, 0)*1000;//convert from seconds to milliseconds
|
||||
|
||||
videoView = findViewById(R.id.video_view);
|
||||
progressBar = findViewById(R.id.play_video_progress_bar);
|
||||
try {
|
||||
videoView.setMediaController(mediaController);
|
||||
videoView.setVideoURI(Uri.parse(intent.getStringExtra(STREAM_URL)));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
videoView.requestFocus();
|
||||
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
videoView.seekTo(position);
|
||||
if (position <= 0) {
|
||||
videoView.start();
|
||||
showUi();
|
||||
} else {
|
||||
videoView.pause();
|
||||
}
|
||||
}
|
||||
});
|
||||
videoUrl = intent.getStringExtra(VIDEO_URL);
|
||||
|
||||
Button button = findViewById(R.id.content_button);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if(uiIsHidden) {
|
||||
showUi();
|
||||
} else {
|
||||
hideUi();
|
||||
}
|
||||
}
|
||||
});
|
||||
decorView = getWindow().getDecorView();
|
||||
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int visibility) {
|
||||
if (visibility == View.VISIBLE && uiIsHidden) {
|
||||
showUi();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 17) {
|
||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
|
||||
prefs = getPreferences(Context.MODE_PRIVATE);
|
||||
if(prefs.getBoolean(PREF_IS_LANDSCAPE, false) && !isLandscape) {
|
||||
toggleOrientation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreatePanelMenu(int featured, Menu menu) {
|
||||
super.onCreatePanelMenu(featured, menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.video_player, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
videoView.pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
prefs = getPreferences(Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch(id) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, videoUrl);
|
||||
intent.setType("text/plain");
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
|
||||
break;
|
||||
case R.id.menu_item_screen_rotation:
|
||||
toggleOrientation();
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Error: MenuItem not known");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration config) {
|
||||
super.onConfigurationChanged(config);
|
||||
|
||||
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
isLandscape = true;
|
||||
adjustMediaControlMetrics();
|
||||
} else if (config.orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
isLandscape = false;
|
||||
adjustMediaControlMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
//savedInstanceState.putInt(POSITION, videoView.getCurrentPosition());
|
||||
//videoView.pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
position = savedInstanceState.getInt(POSITION);
|
||||
//videoView.seekTo(position);
|
||||
}
|
||||
|
||||
private void showUi() {
|
||||
try {
|
||||
uiIsHidden = false;
|
||||
mediaController.show(100000);
|
||||
actionBar.show();
|
||||
adjustMediaControlMetrics();
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if ((System.currentTimeMillis() - lastUiShowTime) >= HIDING_DELAY) {
|
||||
hideUi();
|
||||
}
|
||||
}
|
||||
}, HIDING_DELAY);
|
||||
lastUiShowTime = System.currentTimeMillis();
|
||||
}catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideUi() {
|
||||
uiIsHidden = true;
|
||||
actionBar.hide();
|
||||
mediaController.hide();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 17) {
|
||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
private void adjustMediaControlMetrics() {
|
||||
MediaController.LayoutParams mediaControllerLayout
|
||||
= new MediaController.LayoutParams(MediaController.LayoutParams.MATCH_PARENT,
|
||||
MediaController.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
if(!hasSoftKeys) {
|
||||
mediaControllerLayout.setMargins(20, 0, 20, 20);
|
||||
} else {
|
||||
int width = getNavigationBarWidth();
|
||||
int height = getNavigationBarHeight();
|
||||
mediaControllerLayout.setMargins(width + 20, 0, width + 20, height + 20);
|
||||
}
|
||||
mediaController.setLayoutParams(mediaControllerLayout);
|
||||
}
|
||||
|
||||
private boolean checkIfHasSoftKeys(){
|
||||
return Build.VERSION.SDK_INT >= 17 ||
|
||||
getNavigationBarHeight() != 0 ||
|
||||
getNavigationBarWidth() != 0;
|
||||
}
|
||||
|
||||
private int getNavigationBarHeight() {
|
||||
if(Build.VERSION.SDK_INT >= 17) {
|
||||
Display d = getWindowManager().getDefaultDisplay();
|
||||
|
||||
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
|
||||
d.getRealMetrics(realDisplayMetrics);
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
d.getMetrics(displayMetrics);
|
||||
|
||||
int realHeight = realDisplayMetrics.heightPixels;
|
||||
int displayHeight = displayMetrics.heightPixels;
|
||||
return realHeight - displayHeight;
|
||||
} else {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
|
||||
private int getNavigationBarWidth() {
|
||||
if(Build.VERSION.SDK_INT >= 17) {
|
||||
Display d = getWindowManager().getDefaultDisplay();
|
||||
|
||||
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
|
||||
d.getRealMetrics(realDisplayMetrics);
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
d.getMetrics(displayMetrics);
|
||||
|
||||
int realWidth = realDisplayMetrics.widthPixels;
|
||||
int displayWidth = displayMetrics.widthPixels;
|
||||
return realWidth - displayWidth;
|
||||
} else {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkIfLandscape() {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||
return displayMetrics.heightPixels < displayMetrics.widthPixels;
|
||||
}
|
||||
|
||||
private void toggleOrientation() {
|
||||
if(isLandscape) {
|
||||
isLandscape = false;
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
} else {
|
||||
isLandscape = true;
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
}
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(PREF_IS_LANDSCAPE, isLandscape);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
public class BasePlayerMediaSession implements MediaSessionCallback {
|
||||
private BasePlayer player;
|
||||
private final BasePlayer player;
|
||||
|
||||
public BasePlayerMediaSession(final BasePlayer player) {
|
||||
this.player = player;
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
@@ -12,7 +11,6 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
* This class allows irregular text language labels for use when selecting text captions and
|
||||
@@ -55,7 +53,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
/** @see DefaultTrackSelector#selectTextTrack(TrackGroupArray, int[][], Parameters) */
|
||||
@Override
|
||||
protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport,
|
||||
Parameters params) throws ExoPlaybackException {
|
||||
Parameters params) {
|
||||
TrackGroup selectedGroup = null;
|
||||
int selectedTrackIndex = 0;
|
||||
int selectedTrackScore = 0;
|
||||
|
||||
@@ -335,7 +335,7 @@ public class MediaSourceManager {
|
||||
|
||||
private void loadImmediate() {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called");
|
||||
final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue, WINDOW_SIZE);
|
||||
final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue);
|
||||
if (itemsToLoad == null) return;
|
||||
|
||||
// Evict the previous items being loaded to free up memory, before start loading new ones
|
||||
@@ -472,8 +472,7 @@ public class MediaSourceManager {
|
||||
// Manager Helpers
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@Nullable
|
||||
private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue,
|
||||
final int windowSize) {
|
||||
private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) {
|
||||
// The current item has higher priority
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
|
||||
@@ -482,8 +481,8 @@ public class MediaSourceManager {
|
||||
// The rest are just for seamless playback
|
||||
// Although timeline is not updated prior to the current index, these sources are still
|
||||
// loaded into the cache for faster retrieval at a potentially later time.
|
||||
final int leftBound = Math.max(0, currentIndex - windowSize);
|
||||
final int rightLimit = currentIndex + windowSize + 1;
|
||||
final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE);
|
||||
final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1;
|
||||
final int rightBound = Math.min(playQueue.size(), rightLimit);
|
||||
final Set<PlayQueueItem> neighbors = new ArraySet<>(
|
||||
playQueue.getStreams().subList(leftBound,rightBound));
|
||||
|
||||
@@ -8,8 +8,6 @@ import com.google.android.exoplayer2.source.MediaSource;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PlaybackListener {
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,14 +19,14 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
boolean isInitial;
|
||||
boolean isComplete;
|
||||
|
||||
int serviceId;
|
||||
String baseUrl;
|
||||
final int serviceId;
|
||||
final String baseUrl;
|
||||
String nextUrl;
|
||||
|
||||
transient Disposable fetchReactor;
|
||||
|
||||
AbstractInfoPlayQueue(final U item) {
|
||||
this(item.getServiceId(), item.getUrl(), null, Collections.<StreamInfoItem>emptyList(), 0);
|
||||
this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId,
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.util.Log;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
|
||||
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
|
||||
import org.schabi.newpipe.player.playqueue.events.InitEvent;
|
||||
@@ -41,7 +42,7 @@ import io.reactivex.subjects.BehaviorSubject;
|
||||
public abstract class PlayQueue implements Serializable {
|
||||
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
|
||||
private ArrayList<PlayQueueItem> backup;
|
||||
private ArrayList<PlayQueueItem> streams;
|
||||
@@ -232,6 +233,9 @@ public abstract class PlayQueue implements Serializable {
|
||||
backup.addAll(itemList);
|
||||
Collections.shuffle(itemList);
|
||||
}
|
||||
if (!streams.isEmpty() && streams.get(streams.size() - 1).isAutoQueued() && !itemList.get(0).isAutoQueued()) {
|
||||
streams.remove(streams.size() - 1);
|
||||
}
|
||||
streams.addAll(itemList);
|
||||
|
||||
broadcast(new AppendEvent(itemList.size()));
|
||||
@@ -313,7 +317,9 @@ public abstract class PlayQueue implements Serializable {
|
||||
queueIndex.incrementAndGet();
|
||||
}
|
||||
|
||||
streams.add(target, streams.remove(source));
|
||||
PlayQueueItem playQueueItem = streams.remove(source);
|
||||
playQueueItem.setAutoQueued(false);
|
||||
streams.add(target, playQueueItem);
|
||||
broadcast(new MoveEvent(source, target));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,10 @@ public class PlayQueueItem implements Serializable {
|
||||
@NonNull final private String uploader;
|
||||
@NonNull final private StreamType streamType;
|
||||
|
||||
private boolean isAutoQueued;
|
||||
|
||||
private long recoveryPosition;
|
||||
private Throwable error;
|
||||
|
||||
PlayQueueItem(@NonNull final StreamInfo info) {
|
||||
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
|
||||
info.getThumbnailUrl(), info.getUploaderName(), info.getStreamType());
|
||||
@@ -105,6 +106,14 @@ public class PlayQueueItem implements Serializable {
|
||||
.doOnError(throwable -> error = throwable);
|
||||
}
|
||||
|
||||
public boolean isAutoQueued() {
|
||||
return isAutoQueued;
|
||||
}
|
||||
|
||||
public void setAutoQueued(boolean autoQueued) {
|
||||
isAutoQueued = autoQueued;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Item States, keep external access out
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -5,10 +5,8 @@ import android.text.TextUtils;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
@@ -6,8 +6,6 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
|
||||
@@ -8,11 +8,13 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
|
||||
private static final int MAXIMUM_INITIAL_DRAG_VELOCITY = 25;
|
||||
|
||||
public PlayQueueItemTouchCallback() {
|
||||
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
|
||||
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT);
|
||||
}
|
||||
|
||||
public abstract void onMove(final int sourceIndex, final int targetIndex);
|
||||
|
||||
public abstract void onSwiped(int index);
|
||||
|
||||
@Override
|
||||
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
|
||||
int viewSizeOutOfBounds, int totalSize,
|
||||
@@ -44,9 +46,11 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC
|
||||
|
||||
@Override
|
||||
public boolean isItemViewSwipeEnabled() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {}
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
||||
onSwiped(viewHolder.getAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.Subtitles;
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
@@ -93,8 +93,8 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
// Below are auxiliary media sources
|
||||
|
||||
// Create subtitle sources
|
||||
for (final Subtitles subtitle : info.getSubtitles()) {
|
||||
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
|
||||
for (final SubtitlesStream subtitle : info.getSubtitles()) {
|
||||
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
|
||||
if (mimeType == null) continue;
|
||||
|
||||
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.support.annotation.NonNull;
|
||||
|
||||
import org.acra.collector.CrashReportData;
|
||||
import org.acra.sender.ReportSender;
|
||||
import org.acra.sender.ReportSenderException;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
/*
|
||||
@@ -31,7 +30,7 @@ import org.schabi.newpipe.R;
|
||||
public class AcraReportSender implements ReportSender {
|
||||
|
||||
@Override
|
||||
public void send(@NonNull Context context, @NonNull CrashReportData report) throws ReportSenderException {
|
||||
public void send(@NonNull Context context, @NonNull CrashReportData report) {
|
||||
ErrorActivity.reportError(context, report,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,"none",
|
||||
"App crash, UI failure", R.string.app_ui_crash));
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.schabi.newpipe.report;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
@@ -46,7 +45,6 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 24.10.15.
|
||||
@@ -81,12 +79,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
private ErrorInfo errorInfo;
|
||||
private Class returnActivity;
|
||||
private String currentTimeStamp;
|
||||
// views
|
||||
private TextView errorView;
|
||||
private EditText userCommentBox;
|
||||
private Button reportButton;
|
||||
private TextView infoView;
|
||||
private TextView errorMessageView;
|
||||
|
||||
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
|
||||
reportError(activity, el, activity.getClass(), null,
|
||||
@@ -194,11 +187,11 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
|
||||
reportButton = findViewById(R.id.errorReportButton);
|
||||
Button reportButton = findViewById(R.id.errorReportButton);
|
||||
userCommentBox = findViewById(R.id.errorCommentBox);
|
||||
errorView = findViewById(R.id.errorView);
|
||||
infoView = findViewById(R.id.errorInfosView);
|
||||
errorMessageView = findViewById(R.id.errorMessageView);
|
||||
TextView errorView = findViewById(R.id.errorView);
|
||||
TextView infoView = findViewById(R.id.errorInfosView);
|
||||
TextView errorMessageView = findViewById(R.id.errorMessageView);
|
||||
|
||||
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
||||
returnActivity = ac.returnActivity;
|
||||
@@ -281,15 +274,14 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private String formErrorText(String[] el) {
|
||||
String text = "";
|
||||
StringBuilder text = new StringBuilder();
|
||||
if (el != null) {
|
||||
for (String e : el) {
|
||||
text += "-------------------------------------\n"
|
||||
+ e;
|
||||
text.append("-------------------------------------\n").append(e);
|
||||
}
|
||||
}
|
||||
text += "-------------------------------------";
|
||||
return text;
|
||||
text.append("-------------------------------------");
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,8 @@ public enum UserAction {
|
||||
REQUESTED_CHANNEL("requested channel"),
|
||||
REQUESTED_PLAYLIST("requested playlist"),
|
||||
REQUESTED_KIOSK("requested kiosk"),
|
||||
DELETE_FROM_HISTORY("delete from history");
|
||||
DELETE_FROM_HISTORY("delete from history"),
|
||||
PLAY_STREAM("Play stream");
|
||||
|
||||
|
||||
private final String message;
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
@@ -49,7 +48,7 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
private Preference.OnPreferenceChangeListener themePreferenceChange = new Preference.OnPreferenceChangeListener() {
|
||||
private final Preference.OnPreferenceChangeListener themePreferenceChange = new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.schabi.newpipe.MainActivity;
|
||||
|
||||
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
|
||||
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||
protected boolean DEBUG = MainActivity.DEBUG;
|
||||
protected final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
protected SharedPreferences defaultPreferences;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
@@ -19,16 +18,12 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.utils.Localization;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.ZipHelper;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -42,17 +37,13 @@ import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
private static final int REQUEST_IMPORT_PATH = 8945;
|
||||
private static final int REQUEST_EXPORT_PATH = 30945;
|
||||
|
||||
private String homeDir;
|
||||
private File databasesDir;
|
||||
private File newpipe_db;
|
||||
private File newpipe_db_journal;
|
||||
@@ -86,7 +77,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
|
||||
homeDir = getActivity().getApplicationInfo().dataDir;
|
||||
String homeDir = getActivity().getApplicationInfo().dataDir;
|
||||
databasesDir = new File(homeDir + "/databases");
|
||||
newpipe_db = new File(homeDir + "/databases/newpipe.db");
|
||||
newpipe_db_journal = new File(homeDir + "/databases/newpipe.db-journal");
|
||||
@@ -98,68 +89,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
addPreferencesFromResource(R.xml.content_settings);
|
||||
|
||||
final ListPreference mainPageContentPref = (ListPreference) findPreference(getString(R.string.main_page_content_key));
|
||||
mainPageContentPref.setOnPreferenceChangeListener((Preference preference, Object newValueO) -> {
|
||||
final String newValue = newValueO.toString();
|
||||
|
||||
final String mainPrefOldValue =
|
||||
defaultPreferences.getString(getString(R.string.main_page_content_key), "blank_page");
|
||||
final String mainPrefOldSummary = getMainPagePrefSummery(mainPrefOldValue, mainPageContentPref);
|
||||
|
||||
if(newValue.equals(getString(R.string.kiosk_page_key))) {
|
||||
SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
|
||||
selectKioskFragment.setOnSelectedLisener((String kioskId, int service_id) -> {
|
||||
defaultPreferences.edit()
|
||||
.putInt(getString(R.string.main_page_selected_service), service_id).apply();
|
||||
defaultPreferences.edit()
|
||||
.putString(getString(R.string.main_page_selectd_kiosk_id), kioskId).apply();
|
||||
String serviceName = "";
|
||||
try {
|
||||
serviceName = NewPipe.getService(service_id).getServiceInfo().getName();
|
||||
} catch (ExtractionException e) {
|
||||
onError(e);
|
||||
}
|
||||
String kioskName = KioskTranslator.getTranslatedKioskName(kioskId,
|
||||
getContext());
|
||||
|
||||
String summary =
|
||||
String.format(getString(R.string.service_kiosk_string),
|
||||
serviceName,
|
||||
kioskName);
|
||||
|
||||
mainPageContentPref.setSummary(summary);
|
||||
});
|
||||
selectKioskFragment.setOnCancelListener(() -> {
|
||||
mainPageContentPref.setSummary(mainPrefOldSummary);
|
||||
mainPageContentPref.setValue(mainPrefOldValue);
|
||||
});
|
||||
selectKioskFragment.show(getFragmentManager(), "select_kiosk");
|
||||
} else if(newValue.equals(getString(R.string.channel_page_key))) {
|
||||
SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
|
||||
selectChannelFragment.setOnSelectedLisener((String url, String name, int service) -> {
|
||||
defaultPreferences.edit()
|
||||
.putInt(getString(R.string.main_page_selected_service), service).apply();
|
||||
defaultPreferences.edit()
|
||||
.putString(getString(R.string.main_page_selected_channel_url), url).apply();
|
||||
defaultPreferences.edit()
|
||||
.putString(getString(R.string.main_page_selected_channel_name), name).apply();
|
||||
|
||||
mainPageContentPref.setSummary(name);
|
||||
});
|
||||
selectChannelFragment.setOnCancelListener(() -> {
|
||||
mainPageContentPref.setSummary(mainPrefOldSummary);
|
||||
mainPageContentPref.setValue(mainPrefOldValue);
|
||||
});
|
||||
selectChannelFragment.show(getFragmentManager(), "select_channel");
|
||||
} else {
|
||||
mainPageContentPref.setSummary(getMainPageSummeryByKey(newValue));
|
||||
}
|
||||
|
||||
defaultPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference importDataPreference = findPreference(getString(R.string.import_data));
|
||||
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||
Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
|
||||
@@ -179,6 +108,20 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
startActivityForResult(i, REQUEST_EXPORT_PATH);
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference setPreferredLanguage = findPreference(getString(R.string.content_language_key));
|
||||
setPreferredLanguage.setOnPreferenceChangeListener((Preference p, Object newLanguage) -> {
|
||||
Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity());
|
||||
NewPipe.setLocalization(new Localization(oldLocal.getCountry(), (String) newLanguage));
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference setPreferredCountry = findPreference(getString(R.string.content_country_key));
|
||||
setPreferredCountry.setOnPreferenceChangeListener((Preference p, Object newCountry) -> {
|
||||
Localization oldLocal = org.schabi.newpipe.util.Localization.getPreferredExtractorLocal(getActivity());
|
||||
NewPipe.setLocalization(new Localization((String) newCountry, oldLocal.getLanguage()));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -260,7 +203,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
} finally {
|
||||
try {
|
||||
zipFile.close();
|
||||
} catch (Exception e){}
|
||||
} catch (Exception ignored){}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -321,17 +264,17 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
String key = entry.getKey();
|
||||
|
||||
if (v instanceof Boolean)
|
||||
prefEdit.putBoolean(key, ((Boolean) v).booleanValue());
|
||||
prefEdit.putBoolean(key, (Boolean) v);
|
||||
else if (v instanceof Float)
|
||||
prefEdit.putFloat(key, ((Float) v).floatValue());
|
||||
prefEdit.putFloat(key, (Float) v);
|
||||
else if (v instanceof Integer)
|
||||
prefEdit.putInt(key, ((Integer) v).intValue());
|
||||
prefEdit.putInt(key, (Integer) v);
|
||||
else if (v instanceof Long)
|
||||
prefEdit.putLong(key, ((Long) v).longValue());
|
||||
prefEdit.putLong(key, (Long) v);
|
||||
else if (v instanceof String)
|
||||
prefEdit.putString(key, ((String) v));
|
||||
}
|
||||
prefEdit.commit();
|
||||
prefEdit.apply();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
@@ -349,77 +292,16 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
final String mainPageContentKey = getString(R.string.main_page_content_key);
|
||||
final Preference mainPagePref = findPreference(getString(R.string.main_page_content_key));
|
||||
final String bpk = getString(R.string.blank_page_key);
|
||||
if(defaultPreferences.getString(mainPageContentKey, bpk)
|
||||
.equals(getString(R.string.channel_page_key))) {
|
||||
mainPagePref.setSummary(defaultPreferences.getString(getString(R.string.main_page_selected_channel_name), "error"));
|
||||
} else if(defaultPreferences.getString(mainPageContentKey, bpk)
|
||||
.equals(getString(R.string.kiosk_page_key))) {
|
||||
try {
|
||||
StreamingService service = NewPipe.getService(
|
||||
defaultPreferences.getInt(
|
||||
getString(R.string.main_page_selected_service), 0));
|
||||
|
||||
String kioskName = KioskTranslator.getTranslatedKioskName(
|
||||
defaultPreferences.getString(
|
||||
getString(R.string.main_page_selectd_kiosk_id), "Trending"),
|
||||
getContext());
|
||||
|
||||
String summary =
|
||||
String.format(getString(R.string.service_kiosk_string),
|
||||
service.getServiceInfo().getName(),
|
||||
kioskName);
|
||||
|
||||
mainPagePref.setSummary(summary);
|
||||
} catch (Exception e) {
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private String getMainPagePrefSummery(final String mainPrefOldValue, final ListPreference mainPageContentPref) {
|
||||
if(mainPrefOldValue.equals(getString(R.string.channel_page_key))) {
|
||||
return defaultPreferences.getString(getString(R.string.main_page_selected_channel_name), "error");
|
||||
} else {
|
||||
return mainPageContentPref.getSummary().toString();
|
||||
}
|
||||
}
|
||||
|
||||
private int getMainPageSummeryByKey(final String key) {
|
||||
if(key.equals(getString(R.string.blank_page_key))) {
|
||||
return R.string.blank_page_summary;
|
||||
} else if(key.equals(getString(R.string.kiosk_page_key))) {
|
||||
return R.string.kiosk_page_summary;
|
||||
} else if(key.equals(getString(R.string.feed_page_key))) {
|
||||
return R.string.feed_page_summary;
|
||||
} else if(key.equals(getString(R.string.subscription_page_key))) {
|
||||
return R.string.subscription_page_summary;
|
||||
} else if(key.equals(getString(R.string.channel_page_key))) {
|
||||
return R.string.channel_page_summary;
|
||||
}
|
||||
return R.string.blank_page_summary;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Error
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected boolean onError(Throwable e) {
|
||||
protected void onError(Throwable e) {
|
||||
final Activity activity = getActivity();
|
||||
ErrorActivity.reportError(activity, e,
|
||||
activity.getClass(),
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"none", "", R.string.app_ui_crash));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.InfoCache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.Disposables;
|
||||
|
||||
public class HistorySettingsFragment extends BasePreferenceFragment {
|
||||
private String cacheWipeKey;
|
||||
|
||||
@@ -71,7 +71,7 @@ public class NewPipeSettings {
|
||||
}
|
||||
|
||||
public static File getVideoDownloadFolder(Context context) {
|
||||
return getFolder(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES);
|
||||
return getDir(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES);
|
||||
}
|
||||
|
||||
public static String getVideoDownloadPath(Context context) {
|
||||
@@ -81,7 +81,7 @@ public class NewPipeSettings {
|
||||
}
|
||||
|
||||
public static File getAudioDownloadFolder(Context context) {
|
||||
return getFolder(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC);
|
||||
return getDir(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC);
|
||||
}
|
||||
|
||||
public static String getAudioDownloadPath(Context context) {
|
||||
@@ -90,21 +90,37 @@ public class NewPipeSettings {
|
||||
return prefs.getString(key, Environment.DIRECTORY_MUSIC);
|
||||
}
|
||||
|
||||
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
|
||||
private static File getDir(Context context, int keyID, String defaultDirectoryName) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final String key = context.getString(keyID);
|
||||
String downloadPath = prefs.getString(key, null);
|
||||
if ((downloadPath != null) && (!downloadPath.isEmpty())) return new File(downloadPath.trim());
|
||||
|
||||
final File folder = getFolder(defaultDirectoryName);
|
||||
final File dir = getDir(defaultDirectoryName);
|
||||
SharedPreferences.Editor spEditor = prefs.edit();
|
||||
spEditor.putString(key, new File(folder, "NewPipe").getAbsolutePath());
|
||||
spEditor.putString(key, getNewPipeChildFolderPathForDir(dir));
|
||||
spEditor.apply();
|
||||
return folder;
|
||||
return dir;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static File getFolder(String defaultDirectoryName) {
|
||||
private static File getDir(String defaultDirectoryName) {
|
||||
return new File(Environment.getExternalStorageDirectory(), defaultDirectoryName);
|
||||
}
|
||||
|
||||
public static void resetDownloadFolders(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
resetDownloadFolder(prefs, context.getString(R.string.download_path_audio_key), Environment.DIRECTORY_MUSIC);
|
||||
resetDownloadFolder(prefs, context.getString(R.string.download_path_key), Environment.DIRECTORY_MOVIES);
|
||||
}
|
||||
|
||||
private static void resetDownloadFolder(SharedPreferences prefs, String key, String defaultDirectoryName) {
|
||||
SharedPreferences.Editor spEditor = prefs.edit();
|
||||
spEditor.putString(key, getNewPipeChildFolderPathForDir(getDir(defaultDirectoryName)));
|
||||
spEditor.apply();
|
||||
}
|
||||
|
||||
private static String getNewPipeChildFolderPathForDir(File dir) {
|
||||
return new File(dir, "NewPipe").getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,7 @@ import io.reactivex.schedulers.Schedulers;
|
||||
*/
|
||||
|
||||
public class SelectChannelFragment extends DialogFragment {
|
||||
private SelectChannelAdapter channelAdapter;
|
||||
private SubscriptionService subscriptionService;
|
||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private TextView emptyView;
|
||||
@@ -66,7 +64,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public interface OnSelectedLisener {
|
||||
void onChannelSelected(String url, String name, int service);
|
||||
void onChannelSelected(int serviceId, String url, String name);
|
||||
}
|
||||
OnSelectedLisener onSelectedLisener = null;
|
||||
public void setOnSelectedLisener(OnSelectedLisener listener) {
|
||||
@@ -89,9 +87,9 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.select_channel_fragment, container, false);
|
||||
recyclerView = (RecyclerView) v.findViewById(R.id.items_list);
|
||||
recyclerView = v.findViewById(R.id.items_list);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
channelAdapter = new SelectChannelAdapter();
|
||||
SelectChannelAdapter channelAdapter = new SelectChannelAdapter();
|
||||
recyclerView.setAdapter(channelAdapter);
|
||||
|
||||
progressBar = v.findViewById(R.id.progressBar);
|
||||
@@ -101,7 +99,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
emptyView.setVisibility(View.GONE);
|
||||
|
||||
|
||||
subscriptionService = SubscriptionService.getInstance(getContext());
|
||||
SubscriptionService subscriptionService = SubscriptionService.getInstance(getContext());
|
||||
subscriptionService.getSubscription().toObservable()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
@@ -126,7 +124,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
private void clickedItem(int position) {
|
||||
if(onSelectedLisener != null) {
|
||||
SubscriptionEntity entry = subscriptions.get(position);
|
||||
onSelectedLisener.onChannelSelected(entry.getUrl(), entry.getName(), entry.getServiceId());
|
||||
onSelectedLisener.onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName());
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
@@ -203,9 +201,9 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
thumbnailView = v.findViewById(R.id.itemThumbnailView);
|
||||
titleView = v.findViewById(R.id.itemTitleView);
|
||||
}
|
||||
public View view;
|
||||
public CircleImageView thumbnailView;
|
||||
public TextView titleView;
|
||||
public final View view;
|
||||
public final CircleImageView thumbnailView;
|
||||
public final TextView titleView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,14 +211,13 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
// Error
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected boolean onError(Throwable e) {
|
||||
protected void onError(Throwable e) {
|
||||
final Activity activity = getActivity();
|
||||
ErrorActivity.reportError(activity, e,
|
||||
activity.getClass(),
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"none", "", R.string.app_ui_crash));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public interface OnSelectedLisener {
|
||||
void onKioskSelected(String kioskId, int service_id);
|
||||
void onKioskSelected(int serviceId, String kioskId, String kioskName);
|
||||
}
|
||||
|
||||
OnSelectedLisener onSelectedLisener = null;
|
||||
@@ -75,7 +75,7 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false);
|
||||
recyclerView = (RecyclerView) v.findViewById(R.id.items_list);
|
||||
recyclerView = v.findViewById(R.id.items_list);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
try {
|
||||
selectKioskAdapter = new SelectKioskAdapter();
|
||||
@@ -101,7 +101,7 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
|
||||
private void clickedItem(SelectKioskAdapter.Entry entry) {
|
||||
if(onSelectedLisener != null) {
|
||||
onSelectedLisener.onKioskSelected(entry.kioskId, entry.serviceId);
|
||||
onSelectedLisener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
@@ -112,13 +112,13 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
public Entry (int i, int si, String ki, String kn){
|
||||
icon = i; serviceId=si; kioskId=ki; kioskName = kn;
|
||||
}
|
||||
int icon;
|
||||
int serviceId;
|
||||
String kioskId;
|
||||
String kioskName;
|
||||
final int icon;
|
||||
final int serviceId;
|
||||
final String kioskId;
|
||||
final String kioskName;
|
||||
}
|
||||
|
||||
private List<Entry> kioskList = new Vector<>();
|
||||
private final List<Entry> kioskList = new Vector<>();
|
||||
|
||||
public SelectKioskAdapter()
|
||||
throws Exception {
|
||||
@@ -157,9 +157,9 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
thumbnailView = v.findViewById(R.id.itemThumbnailView);
|
||||
titleView = v.findViewById(R.id.itemTitleView);
|
||||
}
|
||||
public View view;
|
||||
public ImageView thumbnailView;
|
||||
public TextView titleView;
|
||||
public final View view;
|
||||
public final ImageView thumbnailView;
|
||||
public final TextView titleView;
|
||||
}
|
||||
|
||||
public void onBindViewHolder(SelectKioskItemHolder holder, final int position) {
|
||||
@@ -179,13 +179,12 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
// Error
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected boolean onError(Throwable e) {
|
||||
protected void onError(Throwable e) {
|
||||
final Activity activity = getActivity();
|
||||
ErrorActivity.reportError(activity, e,
|
||||
activity.getClass(),
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"none", "", R.string.app_ui_crash));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc
|
||||
finish();
|
||||
} else getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
return true;
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.schabi.newpipe.settings.tabs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.AppCompatImageView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public class AddTabDialog {
|
||||
private final AlertDialog dialog;
|
||||
|
||||
AddTabDialog(@NonNull final Context context,
|
||||
@NonNull final ChooseTabListItem[] items,
|
||||
@NonNull final DialogInterface.OnClickListener actions) {
|
||||
|
||||
dialog = new AlertDialog.Builder(context)
|
||||
.setTitle(context.getString(R.string.tab_choose))
|
||||
.setAdapter(new DialogListAdapter(context, items), actions)
|
||||
.create();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public static final class ChooseTabListItem {
|
||||
final int tabId;
|
||||
final String itemName;
|
||||
@DrawableRes final int itemIcon;
|
||||
|
||||
ChooseTabListItem(Context context, Tab tab) {
|
||||
this(tab.getTabId(), tab.getTabName(context), tab.getTabIconRes(context));
|
||||
}
|
||||
|
||||
ChooseTabListItem(int tabId, String itemName, @DrawableRes int itemIcon) {
|
||||
this.tabId = tabId;
|
||||
this.itemName = itemName;
|
||||
this.itemIcon = itemIcon;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DialogListAdapter extends BaseAdapter {
|
||||
private final LayoutInflater inflater;
|
||||
private final ChooseTabListItem[] items;
|
||||
|
||||
@DrawableRes private final int fallbackIcon;
|
||||
|
||||
private DialogListAdapter(Context context, ChooseTabListItem[] items) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.items = items;
|
||||
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChooseTabListItem getItem(int position) {
|
||||
return items[position];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).tabId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = inflater.inflate(R.layout.list_choose_tabs_dialog, parent, false);
|
||||
}
|
||||
|
||||
final ChooseTabListItem item = getItem(position);
|
||||
final AppCompatImageView tabIconView = convertView.findViewById(R.id.tabIcon);
|
||||
final TextView tabNameView = convertView.findViewById(R.id.tabName);
|
||||
|
||||
tabIconView.setImageResource(item.itemIcon > 0 ? item.itemIcon : fallbackIcon);
|
||||
tabNameView.setText(item.itemName);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package org.schabi.newpipe.settings.tabs;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.content.res.AppCompatResources;
|
||||
import android.support.v7.widget.AppCompatImageView;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
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.extractor.NewPipe;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SelectChannelFragment;
|
||||
import org.schabi.newpipe.settings.SelectKioskFragment;
|
||||
import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
|
||||
|
||||
public class ChooseTabsFragment extends Fragment {
|
||||
|
||||
private TabsManager tabsManager;
|
||||
private List<Tab> tabList = new ArrayList<>();
|
||||
public ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Lifecycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
tabsManager = TabsManager.getManager(requireContext());
|
||||
updateTabList();
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_choose_tabs, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(rootView, savedInstanceState);
|
||||
|
||||
initButton(rootView);
|
||||
|
||||
RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs);
|
||||
listSelectedTabs.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
itemTouchHelper.attachToRecyclerView(listSelectedTabs);
|
||||
|
||||
selectedTabsAdapter = new SelectedTabsAdapter(requireContext(), itemTouchHelper);
|
||||
listSelectedTabs.setAdapter(selectedTabsAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
saveChanges();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private final int MENU_ITEM_RESTORE_ID = 123456;
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults);
|
||||
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
|
||||
final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults);
|
||||
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == MENU_ITEM_RESTORE_ID) {
|
||||
restoreDefaults();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void updateTabList() {
|
||||
tabList.clear();
|
||||
tabList.addAll(tabsManager.getTabs());
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
if (getActivity() instanceof AppCompatActivity) {
|
||||
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
|
||||
if (actionBar != null) actionBar.setTitle(R.string.main_page_content);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveChanges() {
|
||||
tabsManager.saveTabs(tabList);
|
||||
}
|
||||
|
||||
private void restoreDefaults() {
|
||||
new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext()))
|
||||
.setTitle(R.string.restore_defaults)
|
||||
.setMessage(R.string.restore_defaults_confirmation)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
tabsManager.resetTabs();
|
||||
updateTabList();
|
||||
selectedTabsAdapter.notifyDataSetChanged();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void initButton(View rootView) {
|
||||
final FloatingActionButton fab = rootView.findViewById(R.id.addTabsButton);
|
||||
fab.setOnClickListener(v -> {
|
||||
final ChooseTabListItem[] availableTabs = getAvailableTabs(requireContext());
|
||||
|
||||
if (availableTabs.length == 0) {
|
||||
//Toast.makeText(requireContext(), "No available tabs", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Dialog.OnClickListener actionListener = (dialog, which) -> {
|
||||
final ChooseTabListItem selected = availableTabs[which];
|
||||
addTab(selected.tabId);
|
||||
};
|
||||
|
||||
new AddTabDialog(requireContext(), availableTabs, actionListener)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void addTab(final Tab tab) {
|
||||
tabList.add(tab);
|
||||
selectedTabsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void addTab(int tabId) {
|
||||
final Tab.Type type = typeFrom(tabId);
|
||||
|
||||
if (type == null) {
|
||||
ErrorActivity.reportError(requireContext(), new IllegalStateException("Tab id not found: " + tabId), null, null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", "Choosing tabs on settings", 0));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case KIOSK: {
|
||||
SelectKioskFragment selectFragment = new SelectKioskFragment();
|
||||
selectFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) ->
|
||||
addTab(new Tab.KioskTab(serviceId, kioskId)));
|
||||
selectFragment.show(requireFragmentManager(), "select_kiosk");
|
||||
return;
|
||||
}
|
||||
case CHANNEL: {
|
||||
SelectChannelFragment selectFragment = new SelectChannelFragment();
|
||||
selectFragment.setOnSelectedLisener((serviceId, url, name) ->
|
||||
addTab(new Tab.ChannelTab(serviceId, url, name)));
|
||||
selectFragment.show(requireFragmentManager(), "select_channel");
|
||||
return;
|
||||
}
|
||||
default:
|
||||
addTab(type.getTab());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ChooseTabListItem[] getAvailableTabs(Context context) {
|
||||
final ArrayList<ChooseTabListItem> returnList = new ArrayList<>();
|
||||
|
||||
for (Tab.Type type : Tab.Type.values()) {
|
||||
final Tab tab = type.getTab();
|
||||
switch (type) {
|
||||
case BLANK:
|
||||
if (!tabList.contains(tab)) {
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.blank_page_summary),
|
||||
tab.getTabIconRes(context)));
|
||||
}
|
||||
break;
|
||||
case KIOSK:
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.kiosk_page_summary),
|
||||
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
|
||||
break;
|
||||
case CHANNEL:
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.channel_page_summary),
|
||||
tab.getTabIconRes(context)));
|
||||
break;
|
||||
default:
|
||||
if (!tabList.contains(tab)) {
|
||||
returnList.add(new ChooseTabListItem(context, tab));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return returnList.toArray(new ChooseTabListItem[0]);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// List Handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private class SelectedTabsAdapter extends RecyclerView.Adapter<ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder> {
|
||||
private ItemTouchHelper itemTouchHelper;
|
||||
private final LayoutInflater inflater;
|
||||
|
||||
SelectedTabsAdapter(Context context, ItemTouchHelper itemTouchHelper) {
|
||||
this.itemTouchHelper = itemTouchHelper;
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
public void swapItems(int fromPosition, int toPosition) {
|
||||
Collections.swap(tabList, fromPosition, toPosition);
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = inflater.inflate(R.layout.list_choose_tabs, parent, false);
|
||||
return new ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, int position) {
|
||||
holder.bind(position, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return tabList.size();
|
||||
}
|
||||
|
||||
class TabViewHolder extends RecyclerView.ViewHolder {
|
||||
private AppCompatImageView tabIconView;
|
||||
private TextView tabNameView;
|
||||
private ImageView handle;
|
||||
|
||||
TabViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
tabNameView = itemView.findViewById(R.id.tabName);
|
||||
tabIconView = itemView.findViewById(R.id.tabIcon);
|
||||
handle = itemView.findViewById(R.id.handle);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
void bind(int position, TabViewHolder holder) {
|
||||
handle.setOnTouchListener(getOnTouchListener(holder));
|
||||
|
||||
final Tab tab = tabList.get(position);
|
||||
final Tab.Type type = Tab.typeFrom(tab.getTabId());
|
||||
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String tabName = tab.getTabName(requireContext());
|
||||
switch (type) {
|
||||
case BLANK:
|
||||
tabName = requireContext().getString(R.string.blank_page_summary);
|
||||
break;
|
||||
case KIOSK:
|
||||
tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab).getKioskServiceId()) + "/" + tabName;
|
||||
break;
|
||||
case CHANNEL:
|
||||
tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab).getChannelServiceId()) + "/" + tabName;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
tabNameView.setText(tabName);
|
||||
tabIconView.setImageResource(tab.getTabIconRes(requireContext()));
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) {
|
||||
return (view, motionEvent) -> {
|
||||
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
if (itemTouchHelper != null && getItemCount() > 1) {
|
||||
itemTouchHelper.startDrag(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
||||
ItemTouchHelper.START | ItemTouchHelper.END) {
|
||||
@Override
|
||||
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
|
||||
int viewSizeOutOfBounds, int totalSize,
|
||||
long msSinceStartScroll) {
|
||||
final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize,
|
||||
viewSizeOutOfBounds, totalSize, msSinceStartScroll);
|
||||
final int minimumAbsVelocity = Math.max(12,
|
||||
Math.abs(standardSpeed));
|
||||
return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
|
||||
RecyclerView.ViewHolder target) {
|
||||
if (source.getItemViewType() != target.getItemViewType() ||
|
||||
selectedTabsAdapter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int sourceIndex = source.getAdapterPosition();
|
||||
final int targetIndex = target.getAdapterPosition();
|
||||
selectedTabsAdapter.swapItems(sourceIndex, targetIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLongPressDragEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemViewSwipeEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
||||
int position = viewHolder.getAdapterPosition();
|
||||
tabList.remove(position);
|
||||
selectedTabsAdapter.notifyItemRemoved(position);
|
||||
|
||||
if (tabList.isEmpty()) {
|
||||
tabList.add(Tab.Type.BLANK.getTab());
|
||||
selectedTabsAdapter.notifyItemInserted(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
416
app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
Normal file
416
app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
Normal file
@@ -0,0 +1,416 @@
|
||||
package org.schabi.newpipe.settings.tabs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonSink;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.fragments.BlankFragment;
|
||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
||||
import org.schabi.newpipe.local.feed.FeedFragment;
|
||||
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public abstract class Tab {
|
||||
Tab() {
|
||||
}
|
||||
|
||||
Tab(@NonNull JsonObject jsonObject) {
|
||||
readDataFromJson(jsonObject);
|
||||
}
|
||||
|
||||
public abstract int getTabId();
|
||||
public abstract String getTabName(Context context);
|
||||
@DrawableRes public abstract int getTabIconRes(Context context);
|
||||
|
||||
/**
|
||||
* Return a instance of the fragment that this tab represent.
|
||||
*/
|
||||
public abstract Fragment getFragment() throws ExtractionException;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Tab && obj.getClass().equals(this.getClass())
|
||||
&& ((Tab) obj).getTabId() == this.getTabId();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// JSON Handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private static final String JSON_TAB_ID_KEY = "tab_id";
|
||||
|
||||
public void writeJsonOn(JsonSink jsonSink) {
|
||||
jsonSink.object();
|
||||
|
||||
jsonSink.value(JSON_TAB_ID_KEY, getTabId());
|
||||
writeDataToJson(jsonSink);
|
||||
|
||||
jsonSink.end();
|
||||
}
|
||||
|
||||
protected void writeDataToJson(JsonSink writerSink) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
protected void readDataFromJson(JsonObject jsonObject) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Tab Handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Nullable
|
||||
public static Tab from(@NonNull JsonObject jsonObject) {
|
||||
final int tabId = jsonObject.getInt(Tab.JSON_TAB_ID_KEY, -1);
|
||||
|
||||
if (tabId == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return from(tabId, jsonObject);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Tab from(final int tabId) {
|
||||
return from(tabId, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Type typeFrom(int tabId) {
|
||||
for (Type available : Type.values()) {
|
||||
if (available.getTabId() == tabId) {
|
||||
return available;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Tab from(final int tabId, @Nullable JsonObject jsonObject) {
|
||||
final Type type = typeFrom(tabId);
|
||||
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (jsonObject != null) {
|
||||
switch (type) {
|
||||
case KIOSK:
|
||||
return new KioskTab(jsonObject);
|
||||
case CHANNEL:
|
||||
return new ChannelTab(jsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
return type.getTab();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Implementations
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public enum Type {
|
||||
BLANK(new BlankTab()),
|
||||
SUBSCRIPTIONS(new SubscriptionsTab()),
|
||||
FEED(new FeedTab()),
|
||||
BOOKMARKS(new BookmarksTab()),
|
||||
HISTORY(new HistoryTab()),
|
||||
KIOSK(new KioskTab()),
|
||||
CHANNEL(new ChannelTab());
|
||||
|
||||
private Tab tab;
|
||||
|
||||
Type(Tab tab) {
|
||||
this.tab = tab;
|
||||
}
|
||||
|
||||
public int getTabId() {
|
||||
return tab.getTabId();
|
||||
}
|
||||
|
||||
public Tab getTab() {
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlankTab extends Tab {
|
||||
public static final int ID = 0;
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(Context context) {
|
||||
return "NewPipe"; //context.getString(R.string.blank_page_summary);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_blank_page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlankFragment getFragment() {
|
||||
return new BlankFragment();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubscriptionsTab extends Tab {
|
||||
public static final int ID = 1;
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(Context context) {
|
||||
return context.getString(R.string.tab_subscriptions);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionFragment getFragment() {
|
||||
return new SubscriptionFragment();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class FeedTab extends Tab {
|
||||
public static final int ID = 2;
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(Context context) {
|
||||
return context.getString(R.string.fragment_whats_new);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeedFragment getFragment() {
|
||||
return new FeedFragment();
|
||||
}
|
||||
}
|
||||
|
||||
public static class BookmarksTab extends Tab {
|
||||
public static final int ID = 3;
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(Context context) {
|
||||
return context.getString(R.string.tab_bookmarks);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BookmarkFragment getFragment() {
|
||||
return new BookmarkFragment();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HistoryTab extends Tab {
|
||||
public static final int ID = 4;
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(Context context) {
|
||||
return context.getString(R.string.title_activity_history);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.history);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatisticsPlaylistFragment getFragment() {
|
||||
return new StatisticsPlaylistFragment();
|
||||
}
|
||||
}
|
||||
|
||||
public static class KioskTab extends Tab {
|
||||
public static final int ID = 5;
|
||||
|
||||
private int kioskServiceId;
|
||||
private String kioskId;
|
||||
|
||||
private static final String JSON_KIOSK_SERVICE_ID_KEY = "service_id";
|
||||
private static final String JSON_KIOSK_ID_KEY = "kiosk_id";
|
||||
|
||||
private KioskTab() {
|
||||
this(-1, "<no-id>");
|
||||
}
|
||||
|
||||
public KioskTab(int kioskServiceId, String kioskId) {
|
||||
this.kioskServiceId = kioskServiceId;
|
||||
this.kioskId = kioskId;
|
||||
}
|
||||
|
||||
public KioskTab(JsonObject jsonObject) {
|
||||
super(jsonObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(Context context) {
|
||||
return KioskTranslator.getTranslatedKioskName(kioskId, context);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(Context context) {
|
||||
final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context);
|
||||
|
||||
if (kioskIcon <= 0) {
|
||||
throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\"");
|
||||
}
|
||||
|
||||
return kioskIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KioskFragment getFragment() throws ExtractionException {
|
||||
return KioskFragment.getInstance(kioskServiceId, kioskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDataToJson(JsonSink writerSink) {
|
||||
writerSink.value(JSON_KIOSK_SERVICE_ID_KEY, kioskServiceId)
|
||||
.value(JSON_KIOSK_ID_KEY, kioskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readDataFromJson(JsonObject jsonObject) {
|
||||
kioskServiceId = jsonObject.getInt(JSON_KIOSK_SERVICE_ID_KEY, -1);
|
||||
kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, "<no-id>");
|
||||
}
|
||||
|
||||
public int getKioskServiceId() {
|
||||
return kioskServiceId;
|
||||
}
|
||||
|
||||
public String getKioskId() {
|
||||
return kioskId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChannelTab extends Tab {
|
||||
public static final int ID = 6;
|
||||
|
||||
private int channelServiceId;
|
||||
private String channelUrl;
|
||||
private String channelName;
|
||||
|
||||
private static final String JSON_CHANNEL_SERVICE_ID_KEY = "channel_service_id";
|
||||
private static final String JSON_CHANNEL_URL_KEY = "channel_url";
|
||||
private static final String JSON_CHANNEL_NAME_KEY = "channel_name";
|
||||
|
||||
private ChannelTab() {
|
||||
this(-1, "<no-url>", "<no-name>");
|
||||
}
|
||||
|
||||
public ChannelTab(int channelServiceId, String channelUrl, String channelName) {
|
||||
this.channelServiceId = channelServiceId;
|
||||
this.channelUrl = channelUrl;
|
||||
this.channelName = channelName;
|
||||
}
|
||||
|
||||
public ChannelTab(JsonObject jsonObject) {
|
||||
super(jsonObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(Context context) {
|
||||
return channelName;
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFragment getFragment() {
|
||||
return ChannelFragment.getInstance(channelServiceId, channelUrl, channelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDataToJson(JsonSink writerSink) {
|
||||
writerSink.value(JSON_CHANNEL_SERVICE_ID_KEY, channelServiceId)
|
||||
.value(JSON_CHANNEL_URL_KEY, channelUrl)
|
||||
.value(JSON_CHANNEL_NAME_KEY, channelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readDataFromJson(JsonObject jsonObject) {
|
||||
channelServiceId = jsonObject.getInt(JSON_CHANNEL_SERVICE_ID_KEY, -1);
|
||||
channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, "<no-url>");
|
||||
channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, "<no-name>");
|
||||
}
|
||||
|
||||
public int getChannelServiceId() {
|
||||
return channelServiceId;
|
||||
}
|
||||
|
||||
public String getChannelUrl() {
|
||||
return channelUrl;
|
||||
}
|
||||
|
||||
public String getChannelName() {
|
||||
return channelName;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user