1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-01-15 05:58:02 +00:00

Compare commits

..

186 Commits

Author SHA1 Message Date
TobiGr
a54bc96eab Update extractor to 0.20.5 2020-12-01 08:22:17 +01:00
Ajeje Brazorf
a2a8e4b965 Translated using Weblate (Sardinian)
Currently translated at 12.5% (5 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sc/
2020-12-01 08:20:11 +01:00
ssantos
81ad2c61d9 Translated using Weblate (Portuguese (Portugal))
Currently translated at 62.5% (25 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
2020-12-01 08:20:10 +01:00
Bopol
32616493b3 Translated using Weblate (French)
Currently translated at 70.0% (28 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
2020-12-01 08:20:10 +01:00
Stypox
05183ffd0f Translated using Weblate (Italian)
Currently translated at 25.0% (10 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
2020-12-01 08:20:09 +01:00
Yaron Shahrabani
e72ddc9439 Translated using Weblate (Hebrew)
Currently translated at 27.5% (11 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
2020-12-01 08:20:09 +01:00
ssantos
32e3caecac Translated using Weblate (Portuguese)
Currently translated at 62.5% (25 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
2020-12-01 08:20:08 +01:00
minsk21
df43389183 Translated using Weblate (Belarusian)
Currently translated at 74.5% (454 of 609 strings)
2020-12-01 08:20:07 +01:00
Aarjav Parashar
19b77809ec Translated using Weblate (Hindi)
Currently translated at 82.2% (501 of 609 strings)
2020-12-01 08:20:07 +01:00
Milo Ivir
be05b827f3 Translated using Weblate (Croatian)
Currently translated at 99.1% (604 of 609 strings)
2020-12-01 08:20:06 +01:00
Vũ Hoàng
5dfc6f822d Translated using Weblate (Vietnamese)
Currently translated at 100.0% (609 of 609 strings)
2020-12-01 08:20:06 +01:00
Emin Tufan Çetin
c3e004da03 Translated using Weblate (Turkish)
Currently translated at 100.0% (609 of 609 strings)
2020-12-01 08:20:05 +01:00
zmni
8bae73b6ea Translated using Weblate (Indonesian)
Currently translated at 100.0% (609 of 609 strings)
2020-12-01 08:20:04 +01:00
Mohammed Anas
d1e19d3b63 Translated using Weblate (Arabic)
Currently translated at 100.0% (609 of 609 strings)
2020-12-01 08:20:04 +01:00
Francesco Saltori
ffca897ddf Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)
2020-12-01 08:20:03 +01:00
Mohammed Anas
4a1213c081 Translated using Weblate (Arabic)
Currently translated at 62.5% (25 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
2020-11-28 14:21:26 +01:00
Oğuz Ersen
8b7609255c Translated using Weblate (Turkish)
Currently translated at 17.5% (7 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
2020-11-28 14:21:25 +01:00
TobiGr
ef78fe0653 Translated using Weblate (German)
Currently translated at 27.5% (11 of 40 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
2020-11-28 14:21:25 +01:00
TobiGr
70b3ccb422 Release NewPipe 0.20.5 (959) 2020-11-28 12:56:45 +01:00
TobiGr
81d6b367fe Add changelog for 0.20.5 (959) 2020-11-28 12:54:21 +01:00
TobiGr
0a78ae60be Add missing import for @throws annotation 2020-11-28 12:53:22 +01:00
TobiGr
a61830a860 Merge remote-tracking branch 'Weblate/dev' into dev 2020-11-28 12:47:46 +01:00
Mukhamadjonov
86bae9ddc9 Translated using Weblate (Uzbek (latin))
Currently translated at 58.9% (23 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uz_Latn/
2020-11-28 12:46:10 +01:00
Issac
033780862a Translated using Weblate (Spanish)
Currently translated at 23.0% (9 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
2020-11-28 12:46:10 +01:00
pjammo
6094d8a74e Translated using Weblate (Italian)
Currently translated at 23.0% (9 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
2020-11-28 12:46:09 +01:00
yn
356ca3d177 Translated using Weblate (Japanese)
Currently translated at 15.3% (6 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
2020-11-28 12:46:09 +01:00
Igor Nedoboy
d69806faa9 Translated using Weblate (Russian)
Currently translated at 100.0% (39 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
2020-11-28 12:46:08 +01:00
David Schneider
ab67635dcb Translated using Weblate (German)
Currently translated at 25.6% (10 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
2020-11-28 12:46:07 +01:00
Igor Nedoboy
cee3d49458 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:06 +01:00
Igor Nedoboy
5b53a7aef7 Translated using Weblate (Sardinian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:06 +01:00
Igor Nedoboy
9b29665cc0 Translated using Weblate (Dutch (Belgium))
Currently translated at 99.6% (607 of 609 strings)
2020-11-28 12:46:06 +01:00
Igor Nedoboy
f447c87b45 Translated using Weblate (Finnish)
Currently translated at 99.6% (607 of 609 strings)
2020-11-28 12:46:06 +01:00
Igor Nedoboy
e3eea45d86 Translated using Weblate (Hebrew)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:06 +01:00
Jeff Huang
f61a06ce0a Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:05 +01:00
Eric
539842aa99 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:05 +01:00
Igor Nedoboy
5925f1d2aa Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:05 +01:00
Enol P
61eb150825 Translated using Weblate (Asturian)
Currently translated at 47.9% (292 of 609 strings)
2020-11-28 12:46:04 +01:00
Igor Nedoboy
cf95de4d27 Translated using Weblate (Polish)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:04 +01:00
Oğuz Ersen
fdad7ec1ba Translated using Weblate (Turkish)
Currently translated at 99.8% (608 of 609 strings)
2020-11-28 12:46:04 +01:00
Igor Nedoboy
850efb4237 Translated using Weblate (Indonesian)
Currently translated at 99.6% (607 of 609 strings)
2020-11-28 12:46:04 +01:00
Igor Nedoboy
853cb3887f Translated using Weblate (Arabic)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:03 +01:00
Igor Nedoboy
412f2c1664 Translated using Weblate (Czech)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:03 +01:00
zeritti
2810a69bd4 Translated using Weblate (Czech)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:03 +01:00
Igor Nedoboy
5347f95f50 Translated using Weblate (Slovak)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:03 +01:00
Igor Nedoboy
6b469f0621 Translated using Weblate (Greek)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:03 +01:00
Igor Nedoboy
0021562c93 Translated using Weblate (Basque)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:02 +01:00
Gontzal Manuel Pujana Onaindia
f2bd2b0a59 Translated using Weblate (Basque)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:02 +01:00
Igor Nedoboy
647eb8bbf5 Translated using Weblate (Portuguese)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:02 +01:00
Igor Nedoboy
816d13ae3f Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:02 +01:00
pjammo
578fea4a9c Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:02 +01:00
Y. Sakamoto
1a660d9a4a Translated using Weblate (Japanese)
Currently translated at 99.8% (608 of 609 strings)
2020-11-28 12:46:01 +01:00
Igor Nedoboy
227ac6d9e3 Translated using Weblate (English)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:01 +01:00
Igor Nedoboy
bb57407733 Translated using Weblate (Dutch)
Currently translated at 99.6% (607 of 609 strings)
2020-11-28 12:46:01 +01:00
Éfrit
13ddcce0a2 Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:00 +01:00
Igor Nedoboy
53767a78d1 Translated using Weblate (Spanish)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:00 +01:00
Issac
5600e8a2ad Translated using Weblate (Spanish)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:00 +01:00
Igor Nedoboy
c6ed52c592 Translated using Weblate (German)
Currently translated at 100.0% (609 of 609 strings)
2020-11-28 12:46:00 +01:00
Allan Nordhøy
3ad14e4adf Contributing guidelines reworked (#4318)
* Contributing guidelines reworked

Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
Co-authored-by: bopol <bopol@e.email>
Co-authored-by: Tobias Groza <TobiGr@users.noreply.github.com>
2020-11-28 12:00:00 +01:00
Igor Nedoboy
8a22bdea5d Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)
2020-11-27 22:53:42 +01:00
Éfrit
6135a3c3e2 Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)
2020-11-27 22:53:42 +01:00
Tobias Groza
1e3c979303 Merge pull request #5005 from Chenkail/grammar-fixes
Minor grammar fixes in README.md
2020-11-27 22:31:17 +01:00
Tobias Groza
d0228406b6 Merge pull request #4995 from XiangRongLin/hotfix
Make ErrorInfo contructor public
2020-11-27 22:29:12 +01:00
XiangRongLin
507a2237b7 Make ErrorInfo constructor public
Allows the library behind Parceable to crete an object of the class.
2020-11-25 17:05:52 +01:00
Chenkail
c15c597d99 Update README.md 2020-11-25 11:12:53 +00:00
Igor Nedoboy
7c26cd3270 Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-24 02:14:43 +01:00
Igor Nedoboy
938af73059 Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-24 02:06:09 +01:00
Jeff Huang
1c047366d2 Translated using Weblate (Chinese (Traditional))
Currently translated at 56.4% (22 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
2020-11-23 23:11:07 +01:00
Michal L
cb20f0cbb0 Translated using Weblate (Polish)
Currently translated at 46.1% (18 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
2020-11-23 23:11:06 +01:00
Yaron Shahrabani
468251c84e Translated using Weblate (Hebrew)
Currently translated at 25.6% (10 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
2020-11-23 23:11:06 +01:00
Gontzal Manuel Pujana Onaindia
ca86ae0c9a Translated using Weblate (Basque)
Currently translated at 23.0% (9 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/eu/
2020-11-23 23:11:05 +01:00
Sérgio Marques
59221b0b4e Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:05 +01:00
Marcos Chavarría Teijeiro
d3e0640400 Translated using Weblate (Galician)
Currently translated at 94.4% (575 of 609 strings)
2020-11-23 23:11:04 +01:00
Davit Mayilyan
bcb72321f5 Translated using Weblate (Armenian)
Currently translated at 23.9% (146 of 609 strings)
2020-11-23 23:11:04 +01:00
Jeff Huang
4060af715d Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:03 +01:00
Eric
2ec0237e83 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:03 +01:00
Michal L
c5593880f2 Translated using Weblate (Polish)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:03 +01:00
Marian Hanzel
3673cbce4f Translated using Weblate (Slovak)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:02 +01:00
Vasilis K
1f6f7be4b2 Translated using Weblate (Greek)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:02 +01:00
Gontzal Manuel Pujana Onaindia
580cce3506 Translated using Weblate (Basque)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:02 +01:00
Sérgio Marques
36ba546fc6 Translated using Weblate (Portuguese)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:01 +01:00
Francesco Saltori
7f37799cbe Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 23:11:01 +01:00
Sérgio Marques
5570eeeff9 Translated using Weblate (Portuguese)
Currently translated at 99.6% (607 of 609 strings)
2020-11-23 15:16:35 +01:00
pjammo
2b186ce6e0 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 08:20:34 +01:00
Francesco Saltori
72938fed69 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-23 08:20:33 +01:00
Tobias Groza
d54c806e03 Merge pull request #4969 from B0pol/peertube_instances
[PeerTube] update instances list
2020-11-22 23:07:29 +01:00
Hosted Weblate
7eb3551485 Merge branch 'origin/dev' into Weblate. 2020-11-22 22:50:12 +01:00
David Braz
57abe27895 Translated using Weblate (Portuguese (Brazil))
Currently translated at 23.0% (9 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
2020-11-22 22:30:43 +01:00
David Braz
a628a36082 Translated using Weblate (Portuguese (Brazil))
Currently translated at 23.0% (9 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
2020-11-22 22:29:56 +01:00
Ajeje Brazorf
0d3e04ff25 Translated using Weblate (Sardinian)
Currently translated at 100.0% (609 of 609 strings)
2020-11-22 22:29:56 +01:00
Yaron Shahrabani
0c78a3f7b0 Translated using Weblate (Hebrew)
Currently translated at 100.0% (609 of 609 strings)
2020-11-22 22:29:55 +01:00
simo
fb1f574c26 Translated using Weblate (Arabic)
Currently translated at 100.0% (609 of 609 strings)
2020-11-22 22:29:55 +01:00
bopol
7f15c18fca [PeerTube] update instance list 2020-11-22 22:10:41 +01:00
TobiGr
e274650956 Release NewPipe 0.20.4 2020-11-22 18:18:14 +01:00
Oğuz Ersen
2a1db4a338 Revert "Translated using Weblate (Turkish)"
This reverts commit 6d15389da8.
2020-11-22 18:04:04 +01:00
TobiGr
0b6ea9ec61 Translated using Weblate (German)
Currently translated at 25.6% (10 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
2020-11-22 18:03:23 +01:00
Bopol
257a826d45 Translated using Weblate (French)
Currently translated at 69.2% (27 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
2020-11-22 17:53:49 +01:00
Mohammed Anas
e9f48f5134 Translated using Weblate (Arabic)
Currently translated at 61.5% (24 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
2020-11-22 17:53:48 +01:00
Stypox
fcf04624d4 Translated using Weblate (Italian)
Currently translated at 23.0% (9 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
2020-11-22 17:53:47 +01:00
mmagian
16218d6dc5 Translated using Weblate (Portuguese (Brazil))
Currently translated at 20.5% (8 of 39 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
2020-11-22 17:53:47 +01:00
mmagian
071f33e3cd Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (609 of 609 strings)
2020-11-22 17:53:46 +01:00
Oğuz Ersen
6d15389da8 Translated using Weblate (Turkish)
Currently translated at 99.1% (604 of 609 strings)
2020-11-22 17:53:46 +01:00
Ldm Public
2e8530ec00 Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)
2020-11-22 17:53:46 +01:00
nautilusx
4edd1c5497 Translated using Weblate (German)
Currently translated at 100.0% (609 of 609 strings)
2020-11-22 17:53:46 +01:00
TobiGr
8e693b8b42 Add changelog for version 0.20.4 (958) 2020-11-22 14:53:36 +01:00
Tobias Groza
29376066e8 Merge pull request #4768 from Stypox/update-dependencies
Update most dependencies
2020-11-22 14:28:57 +01:00
Stypox
a44f3071bf Update checkstyle (8.36.2 -> 8.37) and mockito-core (3.5.13 -> 3.6.0) 2020-11-22 14:03:11 +01:00
Stypox
b66047e084 Fix ktlint errors 2020-11-22 14:03:10 +01:00
Stypox
f0ca916432 Update most dependencies 2020-11-22 14:03:10 +01:00
Tobias Groza
c88b4032ef Merge pull request #4948 from Stypox/fix-crash-startup
Fix crash on startup when there is no internet connection
2020-11-22 14:00:36 +01:00
Stypox
6f5e99be6f Handle CheckForNewAppVersion exceptions in one place 2020-11-22 13:27:51 +01:00
TobiGr
fd4c37e9b3 Fix crash on startup caused by no implementation of onError() method 2020-11-22 11:46:24 +01:00
TobiGr
18fb0a13d7 Merge remote-tracking branch 'Weblate/dev' into dev 2020-11-22 10:10:41 +01:00
Rui Martins
e88f9ae03b Translated using Weblate (Portuguese)
Currently translated at 60.5% (23 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
2020-11-21 22:09:26 +01:00
Éfrit
30c010ad3f Translated using Weblate (French)
Currently translated at 68.4% (26 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
2020-11-21 18:06:19 +01:00
Azizov Aga
e84e70bdc6 Translated using Weblate (Azerbaijani)
Currently translated at 5.2% (2 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/
2020-11-21 18:06:19 +01:00
Sebuktegin Khan
1e6b6165ae Translated using Weblate (Hindi)
Currently translated at 2.6% (1 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
2020-11-21 18:06:18 +01:00
Gontzal Manuel Pujana Onaindia
c6d149d091 Translated using Weblate (Basque)
Currently translated at 21.0% (8 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/eu/
2020-11-21 18:06:18 +01:00
Deactivated Account
d55d8d78de Translated using Weblate (Greek)
Currently translated at 36.8% (14 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/el/
2020-11-21 18:06:18 +01:00
Azizov Aga
eff59f7b5e Translated using Weblate (Azerbaijani)
Currently translated at 43.1% (261 of 605 strings)
2020-11-21 18:06:17 +01:00
Davit Mayilyan
a7a5437245 Translated using Weblate (Armenian)
Currently translated at 18.3% (111 of 605 strings)
2020-11-21 18:06:15 +01:00
Gontzal Manuel Pujana Onaindia
6671b9e55b Translated using Weblate (Basque)
Currently translated at 100.0% (605 of 605 strings)
2020-11-21 18:06:14 +01:00
Éfrit
2dde1cc589 Translated using Weblate (French)
Currently translated at 100.0% (605 of 605 strings)
2020-11-21 18:06:14 +01:00
Stypox
17866c29ae Refactor CheckForNewAppVersion 2020-11-21 12:02:08 +01:00
Stypox
8dc4e6dc2a Fix crash on startup without internet: Cbservable callable returning null
Use Maybe instead
2020-11-21 12:02:03 +01:00
Stypox
1197f44262 Merge pull request #4944 from Isira-Seneviratne/Dispose_RxView_disposable
Dispose of RxViews disposable in BaseStateFragment.
2020-11-21 10:52:00 +01:00
Stypox
a86ed1f801 Merge pull request #4815 from Isira-Seneviratne/Use_try-with-resources
Use try-with-resources.
2020-11-21 10:20:49 +01:00
Isira Seneviratne
e98d3423e4 Dispose of RxViews disposable in BaseStateFragment. 2020-11-21 14:24:21 +05:30
Isira Seneviratne
95333d37c8 Use try-with-resources. 2020-11-21 13:47:13 +05:30
Stypox
8bcf0c6498 Merge pull request #4377 from Isira-Seneviratne/Use_Parcelize_annotation
Use the Kotlin Parcelize annotation.
2020-11-21 08:39:55 +01:00
Isira Seneviratne
340b92e32b Convert ErrorInfo to Kotlin and use the Parcelize annotation. 2020-11-21 12:47:35 +05:30
Isira Seneviratne
6e68ab19f9 Convert SavedState to Kotlin and use the Parcelize annotation. 2020-11-21 12:47:32 +05:30
Isira Seneviratne
15fed32d92 Convert SoftwareComponent to Kotlin and use the Parcelize annotation. 2020-11-21 12:47:30 +05:30
Isira Seneviratne
897c754dd4 Convert MissionRecoveryInfo to Kotlin and use the Parcelize annotation. 2020-11-21 12:47:29 +05:30
Isira Seneviratne
ec1e746a22 Convert License to Kotlin and use the Parcelize annotation. 2020-11-21 12:47:26 +05:30
Stypox
001f078ba9 Merge pull request #4937 from vkay94/fix-player-brightness-volume-setting
Fix respecting brightness-volume-gesture settings
2020-11-20 22:53:14 +01:00
vkay94
b5321152fd Player gestures: Fix respecting brightness-volume-gesture settings 2020-11-20 21:08:02 +01:00
Stypox
66d15ea635 Merge pull request #4893 from okan35/whatsNewSwipeRefresh
Pull to Refresh Feed
2020-11-20 18:13:32 +01:00
Stypox
72177033d2 Merge pull request #4891 from vkay94/locallist-entry-progressTime
Improve performance for some updateState calls (local lists)
2020-11-20 17:12:16 +01:00
Stypox
06b7072240 Merge pull request #4642 from XiangRongLin/hide_thumbnail
Add option to hide thumbnail on lock screen
2020-11-20 16:35:10 +01:00
Tobias Groza
4700f35739 Merge pull request #4921 from Isira-Seneviratne/Call_offsetDateTime_instead_of_date
Call DateWrapper's offsetDateTime() instead of date().
2020-11-20 15:58:22 +01:00
Tobias Groza
bbfa280e86 Merge pull request #4929 from TacoTheDank/murder-all-the-annoying-lints
Various code improvements
2020-11-20 15:53:37 +01:00
TacoTheDank
2669ba944d Correct some other small lints 2020-11-19 18:54:27 -05:00
Michal L
c8788dbfbe Translated using Weblate (Polish)
Currently translated at 44.7% (17 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
2020-11-20 00:21:29 +01:00
Anxhelo Lushka
5e7c3b53f8 Translated using Weblate (Albanian)
Currently translated at 98.8% (598 of 605 strings)
2020-11-20 00:21:28 +01:00
Vasilis K
99348c2300 Translated using Weblate (Greek)
Currently translated at 100.0% (605 of 605 strings)
2020-11-20 00:21:28 +01:00
randomtroller
ab2b9797fd Translated using Weblate (Slovenian)
Currently translated at 54.7% (331 of 605 strings)
2020-11-20 00:21:28 +01:00
Alvaro
637653ea11 Translated using Weblate (Spanish)
Currently translated at 99.8% (604 of 605 strings)
2020-11-20 00:21:27 +01:00
TacoTheDank
04cb6ba3d0 Update Android Studio 2020-11-19 15:25:07 -05:00
Isira Seneviratne
eb1cddd85a Call DateWrapper's offsetDateTime() instead of date(). 2020-11-19 18:37:07 +05:30
Prasanta-Hembram
723b230093 Translated using Weblate (Santali)
Currently translated at 10.5% (64 of 605 strings)
2020-11-19 09:33:47 +01:00
nautilusx
7185fae491 Translated using Weblate (German)
Currently translated at 23.6% (9 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
2020-11-19 09:33:46 +01:00
TacoTheDank
0274cd6beb Lint: Inner class may be static 2020-11-18 18:02:33 -05:00
TacoTheDank
ad2ea0b807 Lint: 'if' replaceable with 'switch' 2020-11-18 17:58:41 -05:00
TacoTheDank
c24999075d Lint: Lambda fix 2020-11-18 17:57:30 -05:00
TacoTheDank
773bde14ab Lint: 'size() == 0' replaceable with 'isEmpty()' 2020-11-18 17:54:16 -05:00
TacoTheDank
00b08318a5 Lint: Redundant 'new' expression in constant array creation 2020-11-18 17:52:30 -05:00
TacoTheDank
39e5d8ccc2 Lint: Make a bunch of stuff final 2020-11-18 17:50:00 -05:00
TacoTheDank
e25622df4b Lint: Move declarations into assignments 2020-11-18 17:48:01 -05:00
TacoTheDank
ea5939c1b7 Kotlin lint fixing 2020-11-18 17:45:19 -05:00
TacoTheDank
4734d04d4f Use two more KTX extensions 2020-11-18 17:29:58 -05:00
Mukhamadjonov
ef2b32eb05 Translated using Weblate (Uzbek (latin))
Currently translated at 13.1% (5 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uz_Latn/
2020-11-18 18:44:59 +01:00
Mukhamadjonov
1da91d44e1 Translated using Weblate (Uzbek (latin))
Currently translated at 99.1% (600 of 605 strings)
2020-11-18 18:44:58 +01:00
Mario Rossi
ebd7ab3e46 Translated using Weblate (Neapolitan)
Currently translated at 10.7% (65 of 605 strings)
2020-11-18 18:44:54 +01:00
Allan Nordhøy
99bddfdf0a Translated using Weblate (Norwegian Bokmål)
Currently translated at 10.5% (4 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nb_NO/
2020-11-18 18:44:53 +01:00
Nicky Db
144f48c9a6 Translated using Weblate (Dutch (Belgium))
Currently translated at 36.8% (14 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl_BE/
2020-11-18 18:44:53 +01:00
Mark
8d0d2ba07b Translated using Weblate (Dutch)
Currently translated at 10.5% (4 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl/
2020-11-18 18:44:52 +01:00
Yaron Shahrabani
a6ad334dc0 Translated using Weblate (Hebrew)
Currently translated at 23.6% (9 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
2020-11-18 18:44:52 +01:00
Naimul Kabir
468ca30070 Translated using Weblate (Bengali)
Currently translated at 10.5% (4 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/
2020-11-18 18:44:52 +01:00
UserX
7b2d2d9338 Translated using Weblate (German)
Currently translated at 23.6% (9 of 38 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
2020-11-18 18:44:51 +01:00
Naimul Kabir
0e08819cf3 Translated using Weblate (Bengali)
Currently translated at 78.6% (476 of 605 strings)
2020-11-18 18:44:51 +01:00
vachan-maker
1d3f7b49dc Translated using Weblate (Malayalam)
Currently translated at 92.7% (561 of 605 strings)
2020-11-18 18:44:51 +01:00
Allan Nordhøy
3566ec7012 Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.3% (565 of 605 strings)
2020-11-18 18:44:50 +01:00
Azizov Aga
f37a36efa4 Translated using Weblate (Azerbaijani)
Currently translated at 36.5% (221 of 605 strings)
2020-11-18 18:44:50 +01:00
षिखर्
2ea069cd8c Translated using Weblate (Hindi)
Currently translated at 78.5% (475 of 605 strings)
2020-11-18 18:44:48 +01:00
Naimul Kabir
08e111f6dc Translated using Weblate (Bengali (Bangladesh))
Currently translated at 64.9% (393 of 605 strings)
2020-11-18 18:44:48 +01:00
Krysa Czech
6339881684 Translated using Weblate (Czech)
Currently translated at 100.0% (605 of 605 strings)
2020-11-18 18:44:47 +01:00
Stypox
e222538575 Translated using Weblate (Italian)
Currently translated at 100.0% (605 of 605 strings)
2020-11-18 18:44:47 +01:00
Francesco Saltori
c4a67ce420 Translated using Weblate (Italian)
Currently translated at 100.0% (605 of 605 strings)
2020-11-18 18:44:47 +01:00
xxkfqz
77e348ba62 Translated using Weblate (Russian)
Currently translated at 100.0% (605 of 605 strings)
2020-11-18 18:44:46 +01:00
Pera Petrovic
bde39d8c37 Translated using Weblate (Serbian)
Currently translated at 40.9% (248 of 605 strings)
2020-11-18 18:44:45 +01:00
okan35
7a432b38e9 gradle change 2020-11-17 19:25:49 +01:00
okan35
9b6a201bbb removed unnecessary spaces - code change 2020-11-17 19:23:29 +01:00
okan35
745773b207 swipe to refresh added 2020-11-15 17:54:40 +01:00
vkay94
ebe9f518d0 Replace some loadStateStream calls with progressTime field 2020-11-15 14:12:05 +01:00
vkay94
37ceddd11b Add progressTime field to some stream entries (database) 2020-11-15 14:08:41 +01:00
XiangRongLin
7805f8a9b1 Add option to hide thumbnail on lock screen and inside notification 2020-11-14 10:01:07 +01:00
351 changed files with 3313 additions and 2161 deletions

View File

@@ -1,65 +1,46 @@
NewPipe contribution guidelines
===============================
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.
Report crashes through the automated crash report system of NewPipe.
This way all the data needed for debugging is included in your bugreport for GitHub.
You'll see exactly what is sent, be able to add your comments, and then send it.
## 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.
* 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 an issue or implement 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.
* Follow the template! Issues or feature requests not matching the template might be closed.
* **Already reported**? Browse the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) to make sure your issue/feature hasn't been reported/requested.
* **Already fixed**? Check whether your issue/feature is already fixed/implemented.
* **Still relevant**? Check if the issue still exists in the latest release/beta version.
* **Can you fix it**? If you are an Android/Java developer, you are always welcome to fix an issue or implement a feature yourself. PRs welcome! See [Code contribution](#code-contribution) for more info.
* **Is it in English**? Issues in other languages will be ignored unless someone translates them.
* **Is it one issue**? Multiple issues require multiple reports, that can be linked to track their statuses.
* **The template**: Fill it out, everyone wins. Your issue has a chance of getting fixed.
## 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
<a href="mailto:tnp@newpipe.schabi.org">tnp@newpipe.schabi.org</a> to let us know that you intend to help. We'll send you further instructions. You may, on request,
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information).
## Translation
* NewPipe is translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
with your GitHub account.
* If the language you want to translate is not on Weblate, you can add it: see [How to add a new language](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-add-a-new-language-to-NewPipe) in the wiki.
* NewPipe is translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). Log in there with your GitHub account, or register.
* Add the language you want to translate if it is not there already: see [How to add a new language](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-add-a-new-language-to-NewPipe) in the wiki.
## Code contribution
* If you want to add a feature or change one, please open an issue describing your change. This gives the team and community a chance to give feedback before you spend any time on something that could be done differently or not done at all. It also prevents two contributors from working on the same thing and one being disappointed when only one user's code can be added.
* Stick to NewPipe's style conventions: follow [checkStyle](https://github.com/checkstyle/checkstyle). It will run each time you build the project.
* Do not bring non-free software (e.g. binary blobs) into the project. Also, make sure you do not introduce Google
* If you want to help out with an existing bug report or feature request, leave a comment on that issue saying you want to try your hand at it.
* If there is no existing issue for what you want to work on, open a new one describing your changes. This gives the team and the community a chance to give feedback before you spend time on something that is already in development, should be done differently, or should be avoided completely.
* Stick to NewPipe's style conventions of [checkStyle](https://github.com/checkstyle/checkstyle). It runs each time you build the project.
* Do not bring non-free software (e.g. binary blobs) into the project. 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 with a meaningful name, not on the master neither dev branch. This is commonly known as *feature branch workflow*. You
may then send your changes as a pull request (PR) on GitHub.
* 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 with a meaningful name, not on the _master_ branch or the _dev_ branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request (PR) on GitHub.
* Please test (compile and run) your code before submitting changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
* 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 must rebase the dev branch manually and resolve the problems on your own. You can find help [on the wiki](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-merge-a-PR). That makes the maintainers' jobs way easier.
* Please show intention to maintain your features and code after you contribute a PR. Unmaintained code is a hassle for core developers. If you do not intend to maintain features you plan to contribute, please rethink your submission, or clearly state that in the PR description.
* Respond if someone requests changes or otherwise raises issues about your PRs.
* Send PRs that only cover one specific issue/solution/bug. Do not send PRs that are huge and consist of multiple independent solutions.
* 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 rebase the dev branch manually and resolve the problems on your own. You can find help [on the wiki](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-merge-a-PR). That will make the
maintainers' jobs way easier.
* 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.
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
independent solutions.
## Communication
* 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
<a href="mailto:tnp@newpipe.schabi.org">tnp@newpipe.schabi.org</a>. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
tracker described above!
* Feel free to post suggestions, changes, ideas etc. on GitHub or IRC!
* The [#newpipe](irc:irc.freenode.net/newpipe) channel on freenode has the core team and other developers in it. [Click here for webchat](https://webchat.freenode.net/?channels=newpipe)!
* You can also use a Matrix account to join the Newpipe channel at [#freenode_#newpipe:matrix.org](https://matrix.to/#/#freenode_#newpipe:matrix.org).
* Post suggestions, changes, ideas etc. on GitHub or IRC.

View File

@@ -20,7 +20,7 @@
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
<b>PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
<b>PUTTING NEWPIPE OR ANY FORK OF IT INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS.</b>
## Screenshots
@@ -87,7 +87,7 @@ When a change to the NewPipe code occurs (due to either adding features or bug f
3. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it as soon as we publish a release.
4. Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
We recommend method 2 for most users. APKs installed using method 2 or 3 are compatible with each other, but not with those installed using method 4. This is due to the same signing key (ours) being using for 2 and 3, but a different signing key (F-Droid's) being used for 4. Building a debug APK using method 1 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app.
We recommend method 2 for most users. APKs installed using method 2 or 3 are compatible with each other, but not with those installed using method 4. This is due to the same signing key (ours) being used for 2 and 3, but a different signing key (F-Droid's) being used for 4. Building a debug APK using method 1 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app.
In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the update yet), we recommend following this procedure:
1. Back up your data via Settings > Content > Export Database so you keep your history, subscriptions, and playlists

View File

@@ -13,8 +13,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 29
versionCode 957
versionName "0.20.3"
versionCode 959
versionName "0.20.5"
multiDexEnabled true
@@ -89,20 +89,20 @@ android {
ext {
icepickVersion = '3.2.0'
checkstyleVersion = '8.36.2'
checkstyleVersion = '8.37'
stethoVersion = '1.5.1'
leakCanaryVersion = '2.2'
leakCanaryVersion = '2.5'
exoPlayerVersion = '2.11.8'
androidxLifecycleVersion = '2.2.0'
androidxRoomVersion = '2.2.5'
groupieVersion = '2.8.0'
markwonVersion = '4.3.1'
androidxRoomVersion = '2.3.0-alpha03'
groupieVersion = '2.8.1'
markwonVersion = '4.6.0'
googleAutoServiceVersion = '1.0-rc7'
}
configurations {
checkstyle
// ktlint
ktlint
}
checkstyle {
@@ -130,81 +130,85 @@ task runCheckstyle(type: Checkstyle) {
}
}
//task runKtlint(type: JavaExec) {
// main = "com.pinterest.ktlint.Main"
// classpath = configurations.ktlint
// args "src/**/*.kt"
//}
//
//task formatKtlint(type: JavaExec) {
// main = "com.pinterest.ktlint.Main"
// classpath = configurations.ktlint
// args "-F", "src/**/*.kt"
//}
def outputDir = "${project.buildDir}/reports/ktlint/"
def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
task runKtlint(type: JavaExec) {
inputs.files(inputFiles)
outputs.dir(outputDir)
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "src/**/*.kt"
}
task formatKtlint(type: JavaExec) {
inputs.files(inputFiles)
outputs.dir(outputDir)
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "-F", "src/**/*.kt"
}
afterEvaluate {
preDebugBuild.dependsOn runCheckstyle //, runKtlint
preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
implementation "frankiesardo:icepick:${icepickVersion}"
kapt "frankiesardo:icepick-processor:${icepickVersion}"
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
// ktlint "com.pinterest:ktlint:0.35.0"
ktlint "com.pinterest:ktlint:0.39.0"
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}"
implementation "androidx.multidex:multidex:2.0.1"
testImplementation 'junit:junit:4.13.1'
testImplementation 'org.mockito:mockito-core:3.3.3'
androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", {
exclude module: 'support-annotations'
}
// NewPipe dependencies
// You can use a local version by uncommenting a few lines in settings.gradle
implementation 'com.github.TeamNewPipe:NewPipeExtractor:6701b0fe718f6bdc385221341fa473e8aaab560e'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:175df679e05b24b6094570d719cc11f8dfc17c68'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1"
//noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users
implementation "com.squareup.okhttp3:okhttp:3.12.12"
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
implementation "com.google.android.material:material:1.1.0"
implementation "com.google.android.material:material:1.2.1"
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation 'androidx.core:core-ktx:1.3.1'
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "com.xwray:groupie:${groupieVersion}"
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
@@ -216,13 +220,22 @@ dependencies {
implementation "com.nononsenseapps:filepicker:4.2.1"
implementation "ch.acra:acra-core:5.5.0"
implementation "ch.acra:acra-core:5.7.0"
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
implementation "io.reactivex.rxjava3:rxjava:3.0.7"
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
implementation "org.ocpsoft.prettytime:prettytime:4.0.6.Final"
testImplementation 'junit:junit:4.13.1'
testImplementation 'org.mockito:mockito-core:3.6.0'
androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0", {
exclude module: 'support-annotations'
}
}
static String getGitWorkingBranch() {

View File

@@ -31,49 +31,62 @@ class AppDatabaseTest {
}
@get:Rule
val testHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory())
val testHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory()
)
@Test
fun migrateDatabaseFrom2to3() {
val databaseInV2 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_2)
databaseInV2.run {
insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SERVICE_ID)
put("url", DEFAULT_URL)
put("title", DEFAULT_TITLE)
put("stream_type", DEFAULT_TYPE.name)
put("duration", DEFAULT_DURATION)
put("uploader", DEFAULT_UPLOADER_NAME)
put("thumbnail_url", DEFAULT_THUMBNAIL)
})
insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("url", DEFAULT_SECOND_URL)
// put("title", null)
// put("stream_type", null)
// put("duration", null)
// put("uploader", null)
// put("thumbnail_url", null)
})
insert("streams", SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SERVICE_ID)
// put("url", null)
// put("title", null)
// put("stream_type", null)
// put("duration", null)
// put("uploader", null)
// put("thumbnail_url", null)
})
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SERVICE_ID)
put("url", DEFAULT_URL)
put("title", DEFAULT_TITLE)
put("stream_type", DEFAULT_TYPE.name)
put("duration", DEFAULT_DURATION)
put("uploader", DEFAULT_UPLOADER_NAME)
put("thumbnail_url", DEFAULT_THUMBNAIL)
}
)
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("url", DEFAULT_SECOND_URL)
// put("title", null)
// put("stream_type", null)
// put("duration", null)
// put("uploader", null)
// put("thumbnail_url", null)
}
)
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
// put("uid", null)
put("service_id", DEFAULT_SERVICE_ID)
// put("url", null)
// put("title", null)
// put("stream_type", null)
// put("duration", null)
// put("uploader", null)
// put("thumbnail_url", null)
}
)
close()
}
testHelper.runMigrationsAndValidate(AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
true, Migrations.MIGRATION_2_3)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
true, Migrations.MIGRATION_2_3
)
val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
@@ -110,9 +123,11 @@ class AppDatabaseTest {
}
private fun getMigratedDatabase(): AppDatabase {
val database: AppDatabase = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, AppDatabase.DATABASE_NAME)
.build()
val database: AppDatabase = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, AppDatabase.DATABASE_NAME
)
.build()
testHelper.closeWhenFinished(database)
return database
}

View File

@@ -8,7 +8,6 @@ import androidx.test.filters.LargeTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.schabi.newpipe.R;
import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
import static org.junit.Assert.assertEquals;
@@ -29,10 +28,10 @@ public class ErrorInfoTest {
parcel.setDataPosition(0);
final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
assertEquals("youtube", infoFromParcel.serviceName);
assertEquals("request", infoFromParcel.request);
assertEquals(R.string.general_error, infoFromParcel.message);
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
assertEquals("youtube", infoFromParcel.getServiceName());
assertEquals("request", infoFromParcel.getRequest());
assertEquals(R.string.general_error, infoFromParcel.getMessage());
parcel.recycle();
}

View File

@@ -15,14 +15,22 @@ class DebugApp : App() {
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
LeakCanary.config = LeakCanary.config.copy(dumpHeap = PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(getString(
R.string.allow_heap_dumping_key), false))
LeakCanary.config = LeakCanary.config.copy(
dumpHeap = PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(
getString(
R.string.allow_heap_dumping_key
),
false
)
)
}
override fun getDownloader(): Downloader {
val downloader = DownloaderImpl.init(OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor()))
val downloader = DownloaderImpl.init(
OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor())
)
setCookiesToDownloader(downloader)
return downloader
}
@@ -36,7 +44,8 @@ class DebugApp : App() {
// Enable command line interface
initializerBuilder.enableDumpapp(
Stetho.defaultDumperPluginsProvider(applicationContext))
Stetho.defaultDumperPluginsProvider(applicationContext)
)
// Use the InitializerBuilder to generate an Initializer
val initializer = initializerBuilder.build()
@@ -47,6 +56,6 @@ class DebugApp : App() {
override fun isDisposedRxExceptionsReported(): Boolean {
return PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
}
}

View File

@@ -305,8 +305,7 @@
<data android:host="peertube.cpy.re" />
<data android:host="peertube.mastodon.host" />
<data android:host="peertube.fr" />
<data android:host="peertube.live" />
<data android:host="peertube.video" />
<data android:host="tilvids.com" />
<data android:host="tube.privacytools.io" />
<data android:host="video.ploud.fr" />
<data android:host="video.lqdn.fr" />

View File

@@ -86,8 +86,8 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
private final int mBehavior;
private FragmentTransaction mCurTransaction = null;
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
/**

View File

@@ -8,6 +8,7 @@ import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
@@ -22,6 +23,7 @@ import org.acra.config.CoreConfigurationBuilder;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ExceptionUtils;
@@ -36,13 +38,13 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.CompositeException;
import io.reactivex.exceptions.MissingBackpressureException;
import io.reactivex.exceptions.OnErrorNotImplementedException;
import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.functions.Consumer;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.CompositeException;
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
/*
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
@@ -66,7 +68,7 @@ public class App extends MultiDexApplication {
protected static final String TAG = App.class.toString();
private static App app;
private Disposable disposable = null;
@Nullable private Disposable disposable = null;
@NonNull
public static App getApp() {
@@ -226,7 +228,7 @@ public class App extends MultiDexApplication {
ace,
null,
null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not initialize ACRA crash report", R.string.app_ui_crash));
}
}

View File

@@ -12,6 +12,7 @@ import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
@@ -21,12 +22,11 @@ import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -35,11 +35,10 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class CheckForNewAppVersion {
private CheckForNewAppVersion() { }
@@ -57,48 +56,43 @@ public final class CheckForNewAppVersion {
* @param application The application
* @return String with the apk's SHA1 fingeprint in hexadecimal
*/
@NonNull
private static String getCertificateSHA1Fingerprint(@NonNull final Application application) {
final PackageManager pm = application.getPackageManager();
final String packageName = application.getPackageName();
final int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
final PackageInfo packageInfo;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
packageInfo = application.getPackageManager().getPackageInfo(
application.getPackageName(), PackageManager.GET_SIGNATURES);
} catch (final PackageManager.NameNotFoundException e) {
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
return "";
}
final Signature[] signatures = packageInfo.signatures;
final byte[] cert = signatures[0].toByteArray();
final InputStream input = new ByteArrayInputStream(cert);
X509Certificate c = null;
final X509Certificate c;
try {
final Signature[] signatures = packageInfo.signatures;
final byte[] cert = signatures[0].toByteArray();
final InputStream input = new ByteArrayInputStream(cert);
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (final CertificateException e) {
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
return "";
}
String hexString = null;
try {
final MessageDigest md = MessageDigest.getInstance("SHA1");
final byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
return byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
return "";
}
return hexString;
}
private static String byte2HexFormatted(final byte[] arr) {
@@ -163,66 +157,66 @@ public final class CheckForNewAppVersion {
}
private static boolean isConnected(@NonNull final App app) {
final ConnectivityManager cm = ContextCompat.getSystemService(app,
ConnectivityManager.class);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected();
final ConnectivityManager connectivityManager =
ContextCompat.getSystemService(app, ConnectivityManager.class);
return connectivityManager != null && connectivityManager.getActiveNetworkInfo() != null
&& connectivityManager.getActiveNetworkInfo().isConnected();
}
public static boolean isGithubApk(@NonNull final App app) {
return getCertificateSHA1Fingerprint(app).equals(GITHUB_APK_SHA1);
}
@NonNull
@Nullable
public static Disposable checkNewVersion(@NonNull final App app) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
// Check if user has enabled/disabled update checking
// and if the current apk is a github one or not.
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true)
|| !isGithubApk(app)) {
return Disposables.empty();
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
return null;
}
return Observable.fromCallable(() -> {
if (!isConnected(app)) {
return null;
}
return Maybe
.fromCallable(() -> {
if (!isConnected(app)) {
return null;
}
// Make a network request to get latest NewPipe data.
try {
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
} catch (IOException | ReCaptchaException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
}
}
return null;
})
// Make a network request to get latest NewPipe data.
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
// Parse the json from the response.
if (response != null) {
try {
final JsonObject githubStableObject = JsonParser.object().from(response)
.getObject("flavors").getObject("github").getObject("stable");
.subscribe(
response -> {
// Parse the json from the response.
try {
final JsonObject githubStableObject = JsonParser.object()
.from(response).getObject("flavors").getObject("github")
.getObject("stable");
final String versionName = githubStableObject.getString("version");
final int versionCode = githubStableObject.getInt("version_code");
final String apkLocationUrl = githubStableObject.getString("apk");
final String versionName = githubStableObject
.getString("version");
final int versionCode = githubStableObject
.getInt("version_code");
final String apkLocationUrl = githubStableObject
.getString("apk");
compareAppVersionAndShowNotification(app, versionName, apkLocationUrl,
versionCode);
} catch (final JsonParserException e) {
compareAppVersionAndShowNotification(app, versionName,
apkLocationUrl, versionCode);
} catch (final JsonParserException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, "Could not get NewPipe API: invalid json", e);
}
}
},
e -> {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
Log.w(TAG, "Could not get NewPipe API: network problem", e);
}
}
}
});
});
}
}

View File

@@ -50,8 +50,8 @@ public final class DownloaderImpl extends Downloader {
public static final String YOUTUBE_DOMAIN = "youtube.com";
private static DownloaderImpl instance;
private Map<String, String> mCookies;
private OkHttpClient client;
private final Map<String, String> mCookies;
private final OkHttpClient client;
private DownloaderImpl(final OkHttpClient.Builder builder) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {

View File

@@ -139,8 +139,7 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (getSupportFragmentManager() != null
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}

View File

@@ -66,13 +66,13 @@ import java.util.List;
import icepick.Icepick;
import icepick.State;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
@@ -519,7 +519,7 @@ public class RouterActivity extends AppCompatActivity {
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
.subscribe(result -> {
final List<VideoStream> sortedVideoStreams = ListHelper
.getSortedStreamVideosList(this, result.getVideoStreams(),
result.getVideoOnlyStreams(), false);
@@ -534,9 +534,8 @@ public class RouterActivity extends AppCompatActivity {
downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions();
downloadDialog.requireDialog().setOnDismissListener(dialog -> finish());
}, (@NonNull Throwable throwable) -> {
showUnsupportedUrlDialog(currentUrl);
}));
}, throwable ->
showUnsupportedUrlDialog(currentUrl)));
}
@Override

View File

@@ -31,7 +31,7 @@ public class AboutActivity extends AppCompatActivity {
/**
* List of all software components.
*/
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = {
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3),
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",

View File

@@ -1,80 +0,0 @@
package org.schabi.newpipe.about;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
/**
* Class for storing information about a software license.
*/
public class License implements Parcelable, Serializable {
public static final Creator<License> CREATOR = new Creator<License>() {
@Override
public License createFromParcel(final Parcel source) {
return new License(source);
}
@Override
public License[] newArray(final int size) {
return new License[size];
}
};
private final String abbreviation;
private final String name;
private String filename;
public License(final String name, final String abbreviation, final String filename) {
if (name == null) {
throw new NullPointerException("name is null");
}
if (abbreviation == null) {
throw new NullPointerException("abbreviation is null");
}
if (filename == null) {
throw new NullPointerException("filename is null");
}
this.name = name;
this.filename = filename;
this.abbreviation = abbreviation;
}
protected License(final Parcel in) {
this.filename = in.readString();
this.abbreviation = in.readString();
this.name = in.readString();
}
public Uri getContentUri() {
return new Uri.Builder()
.scheme("file")
.path("/android_asset")
.appendPath(filename)
.build();
}
public String getAbbreviation() {
return abbreviation;
}
public String getFilename() {
return filename;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(this.filename);
dest.writeString(this.abbreviation);
dest.writeString(this.name);
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,19 @@
package org.schabi.newpipe.about
import android.net.Uri
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import java.io.Serializable
/**
* Class for storing information about a software license.
*/
@Parcelize
class License(val name: String, val abbreviation: String, val filename: String) : Parcelable, Serializable {
val contentUri: Uri
get() = Uri.Builder()
.scheme("file")
.path("/android_asset")
.appendPath(filename)
.build()
}

View File

@@ -20,7 +20,7 @@ import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
/**
* Fragment containing the software licenses.

View File

@@ -16,11 +16,10 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -37,14 +36,12 @@ public final class LicenseFragmentHelper {
@NonNull final License license) {
final StringBuilder licenseContent = new StringBuilder();
final String webViewData;
try {
final BufferedReader in = new BufferedReader(new InputStreamReader(
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
try (BufferedReader in = new BufferedReader(new InputStreamReader(
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8))) {
String str;
while ((str = in.readLine()) != null) {
licenseContent.append(str);
}
in.close();
// split the HTML file and insert the stylesheet into the HEAD of the file
webViewData = licenseContent.toString().replace("</head>",
@@ -57,7 +54,7 @@ public final class LicenseFragmentHelper {
}
/**
* @param context
* @param context the Android context
* @return String which is a CSS stylesheet according to the context's theme
*/
private static String getLicenseStylesheet(@NonNull final Context context) {
@@ -88,7 +85,7 @@ public final class LicenseFragmentHelper {
static Disposable showLicense(@Nullable final Context context, @NonNull final License license) {
if (context == null) {
return Disposables.empty();
return Disposable.empty();
}
return Observable.fromCallable(() -> getFormattedLicense(context, license))

View File

@@ -1,83 +0,0 @@
package org.schabi.newpipe.about;
import android.os.Parcel;
import android.os.Parcelable;
public class SoftwareComponent implements Parcelable {
public static final Creator<SoftwareComponent> CREATOR = new Creator<SoftwareComponent>() {
@Override
public SoftwareComponent createFromParcel(final Parcel source) {
return new SoftwareComponent(source);
}
@Override
public SoftwareComponent[] newArray(final int size) {
return new SoftwareComponent[size];
}
};
private final License license;
private final String name;
private final String years;
private final String copyrightOwner;
private final String link;
private final String version;
public SoftwareComponent(final String name, final String years, final String copyrightOwner,
final String link, final License license) {
this.name = name;
this.years = years;
this.copyrightOwner = copyrightOwner;
this.link = link;
this.license = license;
this.version = null;
}
protected SoftwareComponent(final Parcel in) {
this.name = in.readString();
this.license = in.readParcelable(License.class.getClassLoader());
this.copyrightOwner = in.readString();
this.link = in.readString();
this.years = in.readString();
this.version = in.readString();
}
public String getName() {
return name;
}
public String getYears() {
return years;
}
public String getCopyrightOwner() {
return copyrightOwner;
}
public String getLink() {
return link;
}
public String getVersion() {
return version;
}
public License getLicense() {
return license;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(name);
dest.writeParcelable(license, flags);
dest.writeString(copyrightOwner);
dest.writeString(link);
dest.writeString(years);
dest.writeString(version);
}
}

View File

@@ -0,0 +1,16 @@
package org.schabi.newpipe.about
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@Parcelize
class SoftwareComponent
@JvmOverloads
constructor(
val name: String,
val years: String,
val copyrightOwner: String,
val link: String,
val license: License,
val version: String? = null
) : Parcelable

View File

@@ -9,7 +9,7 @@ import androidx.room.Update;
import java.util.Collection;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Flowable;
@Dao
public interface BasicDAO<Entity> {

View File

@@ -6,19 +6,20 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
import java.time.OffsetDateTime
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
@Dao
abstract class FeedDAO {
@Query("DELETE FROM feed")
abstract fun deleteAll(): Int
@Query("""
@Query(
"""
SELECT s.* FROM streams s
INNER JOIN feed f
@@ -27,10 +28,12 @@ abstract class FeedDAO {
ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC
LIMIT 500
""")
"""
)
abstract fun getAllStreams(): Flowable<List<StreamEntity>>
@Query("""
@Query(
"""
SELECT s.* FROM streams s
INNER JOIN feed f
@@ -46,10 +49,12 @@ abstract class FeedDAO {
ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC
LIMIT 500
""")
"""
)
abstract fun getAllStreamsFromGroup(groupId: Long): Flowable<List<StreamEntity>>
@Query("""
@Query(
"""
DELETE FROM feed WHERE
feed.stream_id IN (
@@ -60,10 +65,12 @@ abstract class FeedDAO {
WHERE s.upload_date < :offsetDateTime
)
""")
"""
)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
@Query("""
@Query(
"""
DELETE FROM feed
WHERE feed.subscription_id = :subscriptionId
@@ -76,7 +83,8 @@ abstract class FeedDAO {
WHERE s.stream_type = "LIVE_STREAM" OR s.stream_type = "AUDIO_LIVE_STREAM"
)
""")
"""
)
abstract fun unlinkOldLivestreams(subscriptionId: Long)
@Insert(onConflict = OnConflictStrategy.IGNORE)
@@ -100,12 +108,14 @@ abstract class FeedDAO {
}
}
@Query("""
@Query(
"""
SELECT MIN(lu.last_updated) FROM feed_last_updated lu
INNER JOIN feed_group_subscription_join fgs
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
""")
"""
)
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
@@ -114,7 +124,8 @@ abstract class FeedDAO {
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
abstract fun notLoadedCount(): Flowable<Long>
@Query("""
@Query(
"""
SELECT COUNT(*) FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs
@@ -124,20 +135,24 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL
""")
"""
)
abstract fun notLoadedCountForGroup(groupId: Long): Flowable<Long>
@Query("""
@Query(
"""
SELECT s.* FROM subscriptions s
LEFT JOIN feed_last_updated lu
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""")
"""
)
abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
@Query("""
@Query(
"""
SELECT s.* FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs
@@ -147,6 +162,7 @@ abstract class FeedDAO {
ON s.uid = lu.subscription_id
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""")
"""
)
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
}

View File

@@ -6,8 +6,8 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity

View File

@@ -10,21 +10,24 @@ import org.schabi.newpipe.database.feed.model.FeedEntity.Companion.SUBSCRIPTION_
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
@Entity(tableName = FEED_TABLE,
primaryKeys = [STREAM_ID, SUBSCRIPTION_ID],
indices = [Index(SUBSCRIPTION_ID)],
foreignKeys = [
ForeignKey(
entity = StreamEntity::class,
parentColumns = [StreamEntity.STREAM_ID],
childColumns = [STREAM_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true),
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true)
]
@Entity(
tableName = FEED_TABLE,
primaryKeys = [STREAM_ID, SUBSCRIPTION_ID],
indices = [Index(SUBSCRIPTION_ID)],
foreignKeys = [
ForeignKey(
entity = StreamEntity::class,
parentColumns = [StreamEntity.STREAM_ID],
childColumns = [STREAM_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
),
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
)
]
)
data class FeedEntity(
@ColumnInfo(name = STREAM_ID)

View File

@@ -9,8 +9,8 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.SORT_ORD
import org.schabi.newpipe.local.subscription.FeedGroupIcon
@Entity(
tableName = FEED_GROUP_TABLE,
indices = [Index(SORT_ORDER)]
tableName = FEED_GROUP_TABLE,
indices = [Index(SORT_ORDER)]
)
data class FeedGroupEntity(
@PrimaryKey(autoGenerate = true)

View File

@@ -11,22 +11,24 @@ import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity.Compan
import org.schabi.newpipe.database.subscription.SubscriptionEntity
@Entity(
tableName = FEED_GROUP_SUBSCRIPTION_TABLE,
primaryKeys = [GROUP_ID, SUBSCRIPTION_ID],
indices = [Index(SUBSCRIPTION_ID)],
foreignKeys = [
ForeignKey(
entity = FeedGroupEntity::class,
parentColumns = [FeedGroupEntity.ID],
childColumns = [GROUP_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true),
tableName = FEED_GROUP_SUBSCRIPTION_TABLE,
primaryKeys = [GROUP_ID, SUBSCRIPTION_ID],
indices = [Index(SUBSCRIPTION_ID)],
foreignKeys = [
ForeignKey(
entity = FeedGroupEntity::class,
parentColumns = [FeedGroupEntity.ID],
childColumns = [GROUP_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true
),
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true)
]
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true
)
]
)
data class FeedGroupSubscriptionEntity(
@ColumnInfo(name = GROUP_ID)

View File

@@ -4,20 +4,21 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.time.OffsetDateTime
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
@Entity(
tableName = FEED_LAST_UPDATED_TABLE,
foreignKeys = [
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true)
]
tableName = FEED_LAST_UPDATED_TABLE,
foreignKeys = [
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
)
]
)
data class FeedLastUpdatedEntity(
@PrimaryKey

View File

@@ -8,7 +8,7 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID;

View File

@@ -10,7 +10,7 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
@@ -20,6 +20,9 @@ import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LA
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
@@ -73,6 +76,12 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
+ " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID)
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_TIME
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS)
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
}

View File

@@ -2,8 +2,8 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity
import java.time.OffsetDateTime
data class StreamHistoryEntry(
@Embedded
@@ -25,6 +25,6 @@ data class StreamHistoryEntry(
fun hasEqualValues(other: StreamHistoryEntry): Boolean {
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
accessDate.isEqual(other.accessDate)
accessDate.isEqual(other.accessDate)
}
}

View File

@@ -5,12 +5,17 @@ import androidx.room.Embedded
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import kotlin.jvm.Throws
class PlaylistStreamEntry(
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamStateEntity.STREAM_PROGRESS_TIME, defaultValue = "0")
val progressTime: Long,
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
val streamId: Long,

View File

@@ -8,7 +8,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;

View File

@@ -9,7 +9,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;

View File

@@ -11,7 +11,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
@@ -24,6 +24,9 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JO
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity> {
@@ -58,6 +61,13 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
// then merge with the stream metadata
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_TIME
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS
+ " ORDER BY " + JOIN_INDEX + " ASC")
public abstract Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);

View File

@@ -2,23 +2,27 @@ package org.schabi.newpipe.database.stream
import androidx.room.ColumnInfo
import androidx.room.Embedded
import java.time.OffsetDateTime
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_TIME
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import java.time.OffsetDateTime
class StreamStatisticsEntry(
@Embedded
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
@ColumnInfo(name = STREAM_PROGRESS_TIME, defaultValue = "0")
val progressTime: Long,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = STREAM_LATEST_DATE)
@ColumnInfo(name = STREAM_LATEST_DATE)
val latestAccessDate: OffsetDateTime,
@ColumnInfo(name = STREAM_WATCH_COUNT)
@ColumnInfo(name = STREAM_WATCH_COUNT)
val watchCount: Long
) : LocalItem {
fun toStreamInfoItem(): StreamInfoItem {

View File

@@ -6,14 +6,14 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
import java.time.OffsetDateTime
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import java.time.OffsetDateTime
@Dao
abstract class StreamDAO : BasicDAO<StreamEntity> {
@@ -35,10 +35,12 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
@Insert(onConflict = OnConflictStrategy.IGNORE)
internal abstract fun silentInsertAllInternal(streams: List<StreamEntity>): List<Long>
@Query("""
@Query(
"""
SELECT uid, stream_type, textual_upload_date, upload_date, is_upload_date_approximation, duration
FROM streams WHERE url = :url AND service_id = :serviceId
""")
"""
)
internal abstract fun getMinimalStreamForCompare(serviceId: Int, url: String): StreamCompareFeed?
@Transaction
@@ -79,7 +81,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
private fun compareAndUpdateStream(newerStream: StreamEntity) {
val existentMinimalStream = getMinimalStreamForCompare(newerStream.serviceId, newerStream.url)
?: throw IllegalStateException("Stream cannot be null just after insertion.")
?: throw IllegalStateException("Stream cannot be null just after insertion.")
newerStream.uid = existentMinimalStream.uid
val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM
@@ -88,7 +90,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
// Use the existent upload date if the newer stream does not have a better precision
// (i.e. is an approximation). This is done to prevent unnecessary changes.
val hasBetterPrecision =
newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
if (existentMinimalStream.uploadDate != null && !hasBetterPrecision) {
newerStream.uploadDate = existentMinimalStream.uploadDate
newerStream.textualUploadDate = existentMinimalStream.textualUploadDate
@@ -101,7 +103,8 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
}
}
@Query("""
@Query(
"""
DELETE FROM streams WHERE
NOT EXISTS (SELECT 1 FROM stream_history sh
@@ -112,7 +115,8 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
AND NOT EXISTS (SELECT 1 FROM feed f
WHERE f.stream_id = streams.uid)
""")
"""
)
abstract fun deleteOrphans(): Int
/**

View File

@@ -11,7 +11,7 @@ import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;

View File

@@ -5,8 +5,6 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
@@ -15,11 +13,14 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem
import java.io.Serializable
import java.time.OffsetDateTime
@Entity(tableName = STREAM_TABLE,
indices = [
Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true)
]
@Entity(
tableName = STREAM_TABLE,
indices = [
Index(value = [STREAM_SERVICE_ID, STREAM_URL], unique = true)
]
)
data class StreamEntity(
@PrimaryKey(autoGenerate = true)
@@ -61,27 +62,27 @@ data class StreamEntity(
) : Serializable {
@Ignore
constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation
serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation
)
@Ignore
constructor(info: StreamInfo) : this(
serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation
serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation
)
@Ignore
constructor(item: PlayQueueItem) : this(
serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader,
thumbnailUrl = item.thumbnailUrl
serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader,
thumbnailUrl = item.thumbnailUrl
)
fun toStreamInfoItem(): StreamInfoItem {

View File

@@ -22,6 +22,9 @@ import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_
public class StreamStateEntity {
public static final String STREAM_STATE_TABLE = "stream_state";
public static final String JOIN_STREAM_ID = "stream_id";
// This additional field is required for the SQL query because 'stream_id' is used
// for some other joins already
public static final String JOIN_STREAM_ID_ALIAS = "stream_id_alias";
public static final String STREAM_PROGRESS_TIME = "progress_time";
/**

View File

@@ -5,8 +5,8 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import org.schabi.newpipe.database.BasicDAO
@Dao
@@ -20,16 +20,19 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
@Query("""
@Query(
"""
SELECT * FROM subscriptions
WHERE name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
""")
"""
)
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
@Query("""
@Query(
"""
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
@@ -38,12 +41,14 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
ORDER BY name COLLATE NOCASE ASC
""")
"""
)
abstract fun getSubscriptionsOnlyUngrouped(
currentGroupId: Long
): Flowable<List<SubscriptionEntity>>
@Query("""
@Query(
"""
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
@@ -53,7 +58,8 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
AND s.name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
""")
"""
)
abstract fun getSubscriptionsOnlyUngroupedFiltered(
currentGroupId: Long,
filter: String

View File

@@ -10,7 +10,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -34,6 +33,7 @@ import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.appcompat.widget.Toolbar;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import com.nononsenseapps.filepicker.Utils;
@@ -49,6 +49,7 @@ 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.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilePickerActivityHelper;
@@ -69,7 +70,7 @@ import java.util.Locale;
import icepick.Icepick;
import icepick.State;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper;
@@ -602,7 +603,7 @@ public class DownloadDialog extends DialogFragment
Collections.singletonList(e),
null,
null,
ErrorActivity.ErrorInfo
ErrorInfo
.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
);
}

View File

@@ -13,7 +13,7 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.MainActivity;
@@ -23,6 +23,7 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.InfoCache;
@@ -33,7 +34,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -47,6 +49,8 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
@Nullable
private ProgressBar loadingProgressBar;
private Disposable errorDisposable;
protected View errorPanelRoot;
private Button errorButtonRetry;
private TextView errorTextView;
@@ -63,6 +67,14 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
wasLoading.set(isLoading.get());
}
@Override
public void onDestroy() {
super.onDestroy();
if (errorDisposable != null) {
errorDisposable.dispose();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@@ -82,7 +94,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
@Override
protected void initListeners() {
super.initListeners();
RxView.clicks(errorButtonRetry)
errorDisposable = RxView.clicks(errorButtonRetry)
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(o -> onRetryButtonClicked());
@@ -252,7 +264,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
}
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null,
ErrorActivity.ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
request == null ? "none" : request, errorId));
}
@@ -265,7 +277,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
/**
* Show a SnackBar and only call
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorActivity.ErrorInfo)}
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorInfo)}
* IF we a find a valid view (otherwise the error screen appears).
*
* @param exception List of the exceptions to show
@@ -291,6 +303,6 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
}
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId));
ErrorInfo.make(userAction, serviceName, request, errorId));
}
}

View File

@@ -27,6 +27,7 @@ import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
@@ -43,7 +44,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
private SelectedTabsPagerAdapter pagerAdapter;
private ScrollableTabLayout tabLayout;
private List<Tab> tabsList = new ArrayList<>();
private final List<Tab> tabsList = new ArrayList<>();
private TabsManager tabsManager;
private boolean hasTabsChanged = false;
@@ -242,7 +243,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
if (throwable != null) {
ErrorActivity.reportError(context, throwable, null, null, ErrorActivity.ErrorInfo
ErrorActivity.reportError(context, throwable, null, null, ErrorInfo
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
return new BlankFragment();
}

View File

@@ -14,12 +14,10 @@ public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollLi
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
int pastVisibleItems = 0;
final int visibleItemCount;
final int totalItemCount;
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
final int visibleItemCount = layoutManager.getChildCount();
final int totalItemCount = layoutManager.getItemCount();
// Already covers the GridLayoutManager case
if (layoutManager instanceof LinearLayoutManager) {

View File

@@ -15,11 +15,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.ViewTreeObserver;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import android.provider.Settings;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.util.Linkify;
import android.util.DisplayMetrics;
@@ -28,6 +24,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
@@ -46,7 +43,9 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -93,6 +92,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
@@ -116,11 +116,11 @@ import java.util.concurrent.TimeUnit;
import icepick.State;
import io.noties.markwon.Markwon;
import io.noties.markwon.linkify.LinkifyPlugin;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
@@ -731,7 +731,7 @@ public final class VideoDetailFragment
}
private View.OnTouchListener getOnControlsTouchListener() {
return (View view, MotionEvent motionEvent) -> {
return (view, motionEvent) -> {
if (!PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_hold_to_append_key), true)) {
return false;
@@ -948,7 +948,7 @@ public final class VideoDetailFragment
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull final StreamInfo result) -> {
.subscribe(result -> {
isLoading.set(false);
hideMainPlayer();
if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean(
@@ -969,7 +969,7 @@ public final class VideoDetailFragment
openVideoPlayer();
}
}
}, (@NonNull final Throwable throwable) -> {
}, throwable -> {
isLoading.set(false);
onError(throwable);
});
@@ -1140,7 +1140,7 @@ public final class VideoDetailFragment
PlayQueue queue = playQueue;
// Size can be 0 because queue removes bad stream automatically when error occurs
if (queue == null || queue.size() == 0) {
if (queue == null || queue.isEmpty()) {
queue = new SinglePlayQueue(currentInfo);
}
@@ -1224,12 +1224,12 @@ public final class VideoDetailFragment
if (description.getType() == Description.HTML) {
disposables.add(Single.just(description.getContent())
.map((@NonNull final String descriptionText) ->
.map(descriptionText ->
HtmlCompat.fromHtml(descriptionText,
HtmlCompat.FROM_HTML_MODE_LEGACY))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull final Spanned spanned) -> {
.subscribe(spanned -> {
videoDescriptionView.setText(spanned);
videoDescriptionView.setVisibility(View.VISIBLE);
}));
@@ -1346,19 +1346,24 @@ public final class VideoDetailFragment
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(ACTION_SHOW_MAIN_PLAYER)) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else if (intent.getAction().equals(ACTION_HIDE_MAIN_PLAYER)) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
} else if (intent.getAction().equals(ACTION_PLAYER_STARTED)) {
// If the state is not hidden we don't need to show the mini player
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
// Rebound to the service if it was closed via notification or mini player
if (!PlayerHolder.bound) {
PlayerHolder.startService(App.getApp(), false, VideoDetailFragment.this);
}
switch (intent.getAction()) {
case ACTION_SHOW_MAIN_PLAYER:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
break;
case ACTION_HIDE_MAIN_PLAYER:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
break;
case ACTION_PLAYER_STARTED:
// If the state is not hidden we don't need to show the mini player
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
// Rebound to the service if it was closed via notification or mini player
if (!PlayerHolder.bound) {
PlayerHolder.startService(
App.getApp(), false, VideoDetailFragment.this);
}
break;
}
}
};
@@ -1626,7 +1631,7 @@ public final class VideoDetailFragment
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (final Exception e) {
final ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
final ErrorInfo info = ErrorInfo.make(UserAction.UI_ERROR,
ServiceList.all()
.get(currentInfo
.getServiceId())

View File

@@ -48,7 +48,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
implements ListViewContract<I, N>, StateSaver.WriteRead,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final int LIST_MODE_UPDATE_FLAG = 0x32;
protected StateSaver.SavedState savedState;
protected org.schabi.newpipe.util.SavedState savedState;
private boolean useDefaultStateSaving = true;
private int updateFlags = 0;

View File

@@ -16,10 +16,10 @@ import org.schabi.newpipe.views.NewPipeRecyclerView;
import java.util.Queue;
import icepick.State;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public abstract class BaseListInfoFragment<I extends ListInfo>
extends BaseListFragment<I, ListExtractor.InfoItemsPage> {
@@ -204,7 +204,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
name = result.getName();
setTitle(name);
if (infoListAdapter.getItemsList().size() == 0) {
if (infoListAdapter.getItemsList().isEmpty()) {
if (result.getRelatedItems().size() > 0) {
infoListAdapter.addInfoItemList(result.getRelatedItems());
showListFooter(hasMoreItems());

View File

@@ -24,7 +24,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
@@ -53,15 +53,15 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Action;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;

View File

@@ -20,11 +20,11 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
private CompositeDisposable disposables = new CompositeDisposable();
private final CompositeDisposable disposables = new CompositeDisposable();
public static CommentsFragment getInstance(final int serviceId, final String url,
final String name) {

View File

@@ -26,7 +26,7 @@ import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization;
import icepick.State;
import io.reactivex.Single;
import io.reactivex.rxjava3.core.Single;
import static org.schabi.newpipe.util.AnimationUtils.animateView;

View File

@@ -51,12 +51,11 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import io.reactivex.Flowable;
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;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -460,7 +459,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
.doFinally(() -> playlistEntity = null)
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
} else {
action = Disposables.empty();
action = Disposable.empty();
}
disposables.add(action);

View File

@@ -49,6 +49,7 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
@@ -67,13 +68,13 @@ import java.util.Queue;
import java.util.concurrent.TimeUnit;
import icepick.State;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
@@ -248,7 +249,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} catch (final Exception e) {
ErrorActivity.reportError(getActivity(), e, requireActivity().getClass(),
requireActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
ErrorInfo.make(UserAction.UI_ERROR,
"",
"", R.string.general_error));
}
@@ -256,7 +257,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (!TextUtils.isEmpty(searchString)) {
if (wasLoading.getAndSet(false)) {
search(searchString, contentFilter, sortFilter);
} else if (infoListAdapter.getItemsList().size() == 0) {
} else if (infoListAdapter.getItemsList().isEmpty()) {
if (savedState == null) {
search(searchString, contentFilter, sortFilter);
} else if (!isLoading.get() && !wasSearchFocused) {
@@ -708,7 +709,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
final Observable<String> observable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWith(searchString != null
.startWithItem(searchString != null
? searchString
: "")
.filter(ss -> isSuggestionsEnabled);
@@ -977,7 +978,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
lastSearchedString = searchString;
nextPage = result.getNextPage();
if (infoListAdapter.getItemsList().size() == 0) {
if (infoListAdapter.getItemsList().isEmpty()) {
if (!result.getRelatedItems().isEmpty()) {
infoListAdapter.addInfoItemList(result.getRelatedItems());
} else {

View File

@@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments.list.videos;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -13,6 +12,7 @@ import android.widget.Switch;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
@@ -25,13 +25,13 @@ import org.schabi.newpipe.util.RelatedStreamInfo;
import java.io.Serializable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo>
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String INFO_KEY = "related_info_key";
private CompositeDisposable disposables = new CompositeDisposable();
private final CompositeDisposable disposables = new CompositeDisposable();
private RelatedStreamInfo relatedStreamInfo;
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -371,7 +371,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
};
}
public class HFHolder extends RecyclerView.ViewHolder {
public static class HFHolder extends RecyclerView.ViewHolder {
public View view;
HFHolder(final View v) {

View File

@@ -144,7 +144,8 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
}
if (item.getUploadDate() != null) {
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate().date()));
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate()
.offsetDateTime()));
} else {
itemPublishedTime.setText(item.getTextualUploadDate());
}

View File

@@ -95,7 +95,7 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
if (infoItem.getUploadDate() != null) {
String formattedRelativeTime = Localization
.relativeTime(infoItem.getUploadDate().date());
.relativeTime(infoItem.getUploadDate().offsetDateTime());
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
.getBoolean(itemBuilder.getContext()

View File

@@ -33,11 +33,11 @@ import org.schabi.newpipe.util.OnClickGesture;
import java.util.List;
import icepick.State;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
@State

View File

@@ -28,9 +28,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
public final class PlaylistAppendDialog extends PlaylistDialog {
private static final String TAG = PlaylistAppendDialog.class.getCanonicalName();
@@ -38,7 +38,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
private RecyclerView playlistRecyclerView;
private LocalItemListAdapter playlistAdapter;
private CompositeDisposable playlistDisposables = new CompositeDisposable();
private final CompositeDisposable playlistDisposables = new CompositeDisposable();
public static Disposable onPlaylistFound(
final Context context, final Runnable onSuccess, final Runnable onFailed
@@ -98,7 +98,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
super.onViewCreated(view, savedInstanceState);
final LocalPlaylistManager playlistManager =
new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
playlistAdapter = new LocalItemListAdapter(getActivity());
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@@ -113,7 +113,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
});
playlistRecyclerView = view.findViewById(R.id.playlist_list);
playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
playlistRecyclerView.setAdapter(playlistAdapter);
final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
@@ -146,12 +146,12 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
//////////////////////////////////////////////////////////////////////////*/
public void openCreatePlaylistDialog() {
if (getStreams() == null || getFragmentManager() == null) {
if (getStreams() == null || !isAdded()) {
return;
}
PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG);
getDialog().dismiss();
PlaylistCreationDialog.newInstance(getStreams()).show(getParentFragmentManager(), TAG);
requireDialog().dismiss();
}
private void onPlaylistsReceived(@NonNull final List<PlaylistMetadataEntry> playlists) {
@@ -183,6 +183,6 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> successToast.show()));
getDialog().dismiss();
requireDialog().dismiss();
}
}

View File

@@ -17,7 +17,7 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
public final class PlaylistCreationDialog extends PlaylistDialog {
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {

View File

@@ -17,7 +17,7 @@ import java.util.Queue;
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
private List<StreamEntity> streamEntities;
private StateSaver.SavedState savedState;
private org.schabi.newpipe.util.SavedState savedState;
protected void setInfo(final List<StreamEntity> entities) {
this.streamEntities = entities;

View File

@@ -2,14 +2,11 @@ package org.schabi.newpipe.local.feed
import android.content.Context
import android.util.Log
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneOffset
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedEntity
@@ -19,6 +16,9 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneOffset
class FeedDatabaseManager(context: Context) {
private val database = NewPipeDatabase.getInstance(context)
@@ -31,7 +31,7 @@ class FeedDatabaseManager(context: Context) {
* Only items that are newer than this will be saved.
*/
val FEED_OLDEST_ALLOWED_DATE: OffsetDateTime = LocalDate.now().minusWeeks(13)
.atStartOfDay().atOffset(ZoneOffset.UTC)
.atStartOfDay().atOffset(ZoneOffset.UTC)
}
fun groups() = feedGroupTable.getAll()
@@ -44,9 +44,9 @@ class FeedDatabaseManager(context: Context) {
else -> feedTable.getAllStreamsFromGroup(groupId)
}
return streams.map<List<StreamInfoItem>> {
return streams.map {
val items = ArrayList<StreamInfoItem>(it.size)
it.mapTo(items) { it.toStreamInfoItem() }
it.mapTo(items) { stream -> stream.toStreamInfoItem() }
return@map items
}
}
@@ -61,10 +61,10 @@ class FeedDatabaseManager(context: Context) {
}
fun outdatedSubscriptionsForGroup(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, outdatedThreshold: OffsetDateTime) =
feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
fun markAsOutdated(subscriptionId: Long) = feedTable
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
fun upsertAll(
subscriptionId: Long,
@@ -92,8 +92,12 @@ class FeedDatabaseManager(context: Context) {
feedTable.insertAll(feedEntities)
}
feedTable.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId,
OffsetDateTime.now(ZoneOffset.UTC)))
feedTable.setLastUpdatedForSubscription(
FeedLastUpdatedEntity(
subscriptionId,
OffsetDateTime.now(ZoneOffset.UTC)
)
)
}
fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
@@ -113,38 +117,38 @@ class FeedDatabaseManager(context: Context) {
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
return feedGroupTable.getSubscriptionIdsFor(groupId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun updateSubscriptionsForGroup(groupId: Long, subscriptionIds: List<Long>): Completable {
return Completable.fromCallable { feedGroupTable.updateSubscriptionsForGroup(groupId, subscriptionIds) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun createGroup(name: String, icon: FeedGroupIcon): Maybe<Long> {
return Maybe.fromCallable { feedGroupTable.insert(FeedGroupEntity(0, name, icon)) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun getGroup(groupId: Long): Maybe<FeedGroupEntity> {
return feedGroupTable.getGroup(groupId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun updateGroup(feedGroupEntity: FeedGroupEntity): Completable {
return Completable.fromCallable { feedGroupTable.update(feedGroupEntity) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun deleteGroup(groupId: Long): Completable {
return Completable.fromCallable { feedGroupTable.delete(groupId) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun updateGroupsOrder(groupIdList: List<Long>): Completable {
@@ -152,8 +156,8 @@ class FeedDatabaseManager(context: Context) {
val orderMap = groupIdList.associateBy({ it }, { index++ })
return Completable.fromCallable { feedGroupTable.updateOrder(orderMap) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> {

View File

@@ -29,13 +29,14 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import icepick.State
import java.util.Calendar
import kotlinx.android.synthetic.main.error_retry.error_button_retry
import kotlinx.android.synthetic.main.error_retry.error_message_view
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
@@ -53,9 +54,11 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.report.UserAction
import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.Localization
import java.util.Calendar
class FeedFragment : BaseListFragment<FeedState, Unit>() {
private lateinit var viewModel: FeedViewModel
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@State
@JvmField
var listState: Parcelable? = null
@@ -73,7 +76,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
super.onCreate(savedInstanceState)
groupId = arguments?.getLong(KEY_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
?: FeedGroupEntity.GROUP_ALL_ID
?: FeedGroupEntity.GROUP_ALL_ID
groupName = arguments?.getString(KEY_GROUP_NAME) ?: ""
}
@@ -83,7 +86,8 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
super.onViewCreated(rootView, savedInstanceState)
swipeRefreshLayout = requireView().findViewById(R.id.swiperefresh)
swipeRefreshLayout.setOnRefreshListener { reloadContent() }
viewModel = ViewModelProvider(this, FeedViewModel.Factory(requireContext(), groupId)).get(FeedViewModel::class.java)
viewModel.stateLiveData.observe(viewLifecycleOwner, Observer { it?.let(::handleResult) })
}
@@ -140,15 +144,15 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
}
AlertDialog.Builder(requireContext())
.setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
.setNeutralButton(enableDisableButtonText) { _, _ ->
sharedPreferences.edit()
.putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
.apply()
.setMessage(R.string.feed_use_dedicated_fetch_method_help_text)
.setNeutralButton(enableDisableButtonText) { _, _ ->
sharedPreferences.edit {
putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
}
.setPositiveButton(resources.getString(R.string.finish), null)
.create()
.show()
}
.setPositiveButton(resources.getString(R.string.finish), null)
.create()
.show()
return true
}
@@ -189,6 +193,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
empty_state_view?.let { animateView(it, false, 0) }
animateView(error_panel, false, 0)
swipeRefreshLayout.isRefreshing = false
}
override fun showEmptyState() {
@@ -229,7 +234,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
showLoading()
val isIndeterminate = progressState.currentProgress == -1 &&
progressState.maxProgress == -1
progressState.maxProgress == -1
if (!isIndeterminate) {
loading_progress_text.text = "${progressState.currentProgress}/${progressState.maxProgress}"
@@ -240,7 +245,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
}
loading_progress_bar.isIndeterminate = isIndeterminate ||
(progressState.maxProgress > 0 && progressState.currentProgress == 0)
(progressState.maxProgress > 0 && progressState.currentProgress == 0)
loading_progress_bar.progress = progressState.currentProgress
loading_progress_bar.max = progressState.maxProgress
@@ -255,14 +260,17 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
oldestSubscriptionUpdate = loadedState.oldestUpdate
refresh_subtitle_text.isVisible = loadedState.notLoadedCount > 0
if (loadedState.notLoadedCount > 0) {
val loadedCount = loadedState.notLoadedCount > 0
refresh_subtitle_text.isVisible = loadedCount
if (loadedCount) {
refresh_subtitle_text.text = getString(R.string.feed_subscription_not_loaded_count, loadedState.notLoadedCount)
}
if (loadedState.itemsErrors.isNotEmpty()) {
showSnackBarError(loadedState.itemsErrors, UserAction.REQUESTED_FEED,
"none", "Loading feed", R.string.general_error)
showSnackBarError(
loadedState.itemsErrors, UserAction.REQUESTED_FEED,
"none", "Loading feed", R.string.general_error
)
}
if (loadedState.items.isEmpty()) {
@@ -305,9 +313,11 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
override fun hasMoreItems() = false
private fun triggerUpdate() {
getActivity()?.startService(Intent(requireContext(), FeedLoadService::class.java).apply {
putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
})
getActivity()?.startService(
Intent(requireContext(), FeedLoadService::class.java).apply {
putExtra(FeedLoadService.EXTRA_GROUP_ID, groupId)
}
)
listState = null
}

View File

@@ -1,8 +1,8 @@
package org.schabi.newpipe.local.feed
import androidx.annotation.StringRes
import java.util.Calendar
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import java.util.Calendar
sealed class FeedState {
data class ProgressState(

View File

@@ -5,12 +5,10 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Function4
import io.reactivex.schedulers.Schedulers
import java.time.OffsetDateTime
import java.util.concurrent.TimeUnit
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.functions.Function4
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.ktx.toCalendar
@@ -20,6 +18,8 @@ import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
import java.time.OffsetDateTime
import java.util.concurrent.TimeUnit
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
@@ -35,35 +35,34 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
val stateLiveData: LiveData<FeedState> = mutableStateLiveData
private var combineDisposable = Flowable
.combineLatest(
FeedEventManager.events(),
feedDatabaseManager.asStreamItems(groupId),
feedDatabaseManager.notLoadedCount(groupId),
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
.combineLatest(
FeedEventManager.events(),
feedDatabaseManager.asStreamItems(groupId),
feedDatabaseManager.notLoadedCount(groupId),
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
Function4 { t1: FeedEventManager.Event, t2: List<StreamInfoItem>, t3: Long, t4: List<OffsetDateTime> ->
return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
}
)
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
val oldestUpdateCalendar = oldestUpdate?.toCalendar()
Function4 { t1: FeedEventManager.Event, t2: List<StreamInfoItem>, t3: Long, t4: List<OffsetDateTime> ->
return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
}
)
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
val (event, listFromDB, notLoadedCount, oldestUpdate) = it
val oldestUpdateCalendar = oldestUpdate?.toCalendar()
mutableStateLiveData.postValue(when (event) {
mutableStateLiveData.postValue(
when (event) {
is IdleEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount)
is ProgressEvent -> FeedState.ProgressState(event.currentProgress, event.maxProgress, event.progressMessage)
is SuccessResultEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount, event.itemsErrors)
is ErrorResultEvent -> FeedState.ErrorState(event.error)
})
if (event is ErrorResultEvent || event is SuccessResultEvent) {
FeedEventManager.reset()
}
)
if (event is ErrorResultEvent || event is SuccessResultEvent) {
FeedEventManager.reset()
}
}
override fun onCleared() {
super.onCleared()

View File

@@ -1,15 +1,15 @@
package org.schabi.newpipe.local.feed.service
import androidx.annotation.StringRes
import io.reactivex.Flowable
import io.reactivex.processors.BehaviorProcessor
import java.util.concurrent.atomic.AtomicBoolean
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.processors.BehaviorProcessor
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import java.util.concurrent.atomic.AtomicBoolean
object FeedEventManager {
private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create()
private var ignoreUpstream = AtomicBoolean()
private var eventsFlowable = processor.startWith(IdleEvent)
private var eventsFlowable = processor.startWithItem(IdleEvent)
fun postEvent(event: Event) {
processor.onNext(event)

View File

@@ -31,21 +31,15 @@ import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager
import io.reactivex.Flowable
import io.reactivex.Notification
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.Consumer
import io.reactivex.functions.Function
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import java.io.IOException
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Notification
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.functions.Function
import io.reactivex.rxjava3.processors.PublishProcessor
import io.reactivex.rxjava3.schedulers.Schedulers
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import org.schabi.newpipe.MainActivity.DEBUG
@@ -56,13 +50,18 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.ExceptionUtils
import org.schabi.newpipe.util.ExtractorHelper
import java.io.IOException
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
class FeedLoadService : Service() {
companion object {
@@ -109,8 +108,11 @@ class FeedLoadService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "]," +
" flags = [" + flags + "], startId = [" + startId + "]")
Log.d(
TAG,
"onStartCommand() called with: intent = [" + intent + "]," +
" flags = [" + flags + "], startId = [" + startId + "]"
)
}
if (intent == null || loadingSubscription != null) {
@@ -123,10 +125,10 @@ class FeedLoadService : Service() {
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
val useFeedExtractor = defaultSharedPreferences
.getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false)
.getBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), false)
val thresholdOutdatedSecondsString = defaultSharedPreferences
.getString(getString(R.string.feed_update_threshold_key), getString(R.string.feed_update_threshold_default_value))
.getString(getString(R.string.feed_update_threshold_key), getString(R.string.feed_update_threshold_default_value))
val thresholdOutdatedSeconds = thresholdOutdatedSecondsString!!.toInt()
startLoading(groupId, useFeedExtractor, thresholdOutdatedSeconds)
@@ -181,63 +183,63 @@ class FeedLoadService : Service() {
}
subscriptions
.limit(1)
.take(1)
.doOnNext {
currentProgress.set(0)
maxProgress.set(it.size)
.doOnNext {
currentProgress.set(0)
maxProgress.set(it.size)
}
.filter { it.isNotEmpty() }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
startForeground(NOTIFICATION_ID, notificationBuilder.build())
updateNotificationProgress(null)
broadcastProgress()
}
.observeOn(Schedulers.io())
.flatMap { Flowable.fromIterable(it) }
.takeWhile { !cancelSignal.get() }
.parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
.runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
.filter { !cancelSignal.get() }
.map { subscriptionEntity ->
try {
val listInfo = if (useFeedExtractor) {
ExtractorHelper
.getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
.blockingGet()
} else {
ExtractorHelper
.getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
.blockingGet()
} as ListInfo<StreamInfoItem>
return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
} catch (e: Throwable) {
val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
val wrapper = RequestException(subscriptionEntity.uid, request, e)
return@map Notification.createOnError<Pair<Long, ListInfo<StreamInfoItem>>>(wrapper)
}
.filter { it.isNotEmpty() }
}
.sequential()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
startForeground(NOTIFICATION_ID, notificationBuilder.build())
updateNotificationProgress(null)
broadcastProgress()
}
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(errorHandlingConsumer)
.observeOn(Schedulers.io())
.flatMap { Flowable.fromIterable(it) }
.takeWhile { !cancelSignal.get() }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(notificationsConsumer)
.parallel(PARALLEL_EXTRACTIONS, PARALLEL_EXTRACTIONS * 2)
.runOn(Schedulers.io(), PARALLEL_EXTRACTIONS * 2)
.filter { !cancelSignal.get() }
.observeOn(Schedulers.io())
.buffer(BUFFER_COUNT_BEFORE_INSERT)
.doOnNext(databaseConsumer)
.map { subscriptionEntity ->
try {
val listInfo = if (useFeedExtractor) {
ExtractorHelper
.getFeedInfoFallbackToChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url)
.blockingGet()
} else {
ExtractorHelper
.getChannelInfo(subscriptionEntity.serviceId, subscriptionEntity.url, true)
.blockingGet()
} as ListInfo<StreamInfoItem>
return@map Notification.createOnNext(Pair(subscriptionEntity.uid, listInfo))
} catch (e: Throwable) {
val request = "${subscriptionEntity.serviceId}:${subscriptionEntity.url}"
val wrapper = RequestException(subscriptionEntity.uid, request, e)
return@map Notification.createOnError<Pair<Long, ListInfo<StreamInfoItem>>>(wrapper)
}
}
.sequential()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(errorHandlingConsumer)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(notificationsConsumer)
.observeOn(Schedulers.io())
.buffer(BUFFER_COUNT_BEFORE_INSERT)
.doOnNext(databaseConsumer)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(resultSubscriber)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(resultSubscriber)
}
private fun broadcastProgress() {
@@ -262,7 +264,7 @@ class FeedLoadService : Service() {
override fun onComplete() {
if (maxProgress.get() == 0) {
postEvent(IdleEvent)
postEvent(FeedEventManager.Event.IdleEvent)
stopService()
return
@@ -274,7 +276,8 @@ class FeedLoadService : Service() {
notificationUpdater.onNext(getString(R.string.feed_processing_message))
postEvent(ProgressEvent(R.string.feed_processing_message))
disposables.add(Single
disposables.add(
Single
.fromCallable {
feedResultsHolder.ready()
@@ -293,7 +296,8 @@ class FeedLoadService : Service() {
return@subscribe
}
stopService()
})
}
)
}
}
@@ -364,16 +368,18 @@ class FeedLoadService : Service() {
private var maxProgress = AtomicInteger(-1)
private fun createNotification(): NotificationCompat.Builder {
val cancelActionIntent = PendingIntent.getBroadcast(this,
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0)
val cancelActionIntent = PendingIntent.getBroadcast(
this,
NOTIFICATION_ID, Intent(ACTION_CANCEL), 0
)
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
.setOngoing(true)
.setProgress(-1, -1, true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.addAction(0, getString(R.string.cancel), cancelActionIntent)
.setContentTitle(getString(R.string.feed_notification_loading))
.setOngoing(true)
.setProgress(-1, -1, true)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.addAction(0, getString(R.string.cancel), cancelActionIntent)
.setContentTitle(getString(R.string.feed_notification_loading))
}
private fun setupNotification() {
@@ -381,13 +387,15 @@ class FeedLoadService : Service() {
notificationBuilder = createNotification()
val throttleAfterFirstEmission = Function { flow: Flowable<String> ->
flow.limit(1).concatWith(flow.skip(1).throttleLatest(NOTIFICATION_SAMPLING_PERIOD.toLong(), TimeUnit.MILLISECONDS))
flow.take(1).concatWith(flow.skip(1).throttleLatest(NOTIFICATION_SAMPLING_PERIOD.toLong(), TimeUnit.MILLISECONDS))
}
disposables.add(notificationUpdater
disposables.add(
notificationUpdater
.publish(throttleAfterFirstEmission)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateNotificationProgress))
.subscribe(this::updateNotificationProgress)
)
}
private fun updateNotificationProgress(updateDescription: String?) {

View File

@@ -50,11 +50,11 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class HistoryRecordManager {
private final AppDatabase database;

View File

@@ -34,6 +34,7 @@ import org.schabi.newpipe.player.helper.PlayerHolder;
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.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.NavigationHelper;
@@ -48,9 +49,9 @@ import java.util.Comparator;
import java.util.List;
import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
@@ -183,7 +184,7 @@ public class StatisticsPlaylistFragment
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete view history",
@@ -197,7 +198,7 @@ public class StatisticsPlaylistFragment
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete search history",

View File

@@ -11,7 +11,6 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -21,7 +20,6 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
@@ -70,15 +68,11 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null) {
if (item.getProgressTime() > 0) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
@@ -115,18 +109,14 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
}
final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem;
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null && item.getStreamEntity().getDuration() > 0) {
if (item.getProgressTime() > 0 && item.getStreamEntity().getDuration() > 0) {
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {

View File

@@ -11,7 +11,6 @@ import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -21,7 +20,6 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/*
@@ -98,15 +96,11 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null) {
if (item.getProgressTime() > 0) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setVisibility(View.GONE);
}
@@ -146,18 +140,14 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
}
final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem;
final StreamStateEntity state = historyRecordManager
.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{
add(localItem);
}}).blockingGet().get(0);
if (state != null && item.getStreamEntity().getDuration() > 0) {
if (item.getProgressTime() > 0 && item.getStreamEntity().getDuration() > 0) {
itemProgressView.setMax((int) item.getStreamEntity().getDuration());
if (itemProgressView.getVisibility() == View.VISIBLE) {
itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
} else {
itemProgressView.setProgress((int) TimeUnit.MILLISECONDS
.toSeconds(state.getProgressTime()));
.toSeconds(item.getProgressTime()));
AnimationUtils.animateView(itemProgressView, true, 500);
}
} else if (itemProgressView.getVisibility() == View.VISIBLE) {

View File

@@ -55,13 +55,12 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -641,7 +640,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private Disposable getDebouncedSaver() {
if (debouncedSaveSignal == null) {
return Disposables.empty();
return Disposable.empty();
}
return debouncedSaveSignal

View File

@@ -15,11 +15,11 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class LocalPlaylistManager {
private final AppDatabase database;

View File

@@ -7,9 +7,9 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class RemotePlaylistManager {

View File

@@ -28,13 +28,7 @@ import com.xwray.groupie.Item
import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.State
import io.reactivex.disposables.CompositeDisposable
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.floor
import kotlin.math.max
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
import kotlinx.android.synthetic.main.fragment_subscription.items_list
@@ -68,6 +62,12 @@ import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.floor
import kotlin.math.max
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private lateinit var viewModel: SubscriptionViewModel
@@ -208,14 +208,19 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
if (!exportFile.parentFile.canWrite() || !exportFile.parentFile.canRead()) {
Toast.makeText(activity, R.string.invalid_directory, Toast.LENGTH_SHORT).show()
} else {
activity.startService(Intent(activity, SubscriptionsExportService::class.java)
.putExtra(KEY_FILE_PATH, exportFile.absolutePath))
activity.startService(
Intent(activity, SubscriptionsExportService::class.java)
.putExtra(KEY_FILE_PATH, exportFile.absolutePath)
)
}
} else if (requestCode == REQUEST_IMPORT_CODE) {
val path = Utils.getFileForUri(data.data!!).absolutePath
ImportConfirmationDialog.show(this, Intent(activity, SubscriptionsImportService::class.java)
ImportConfirmationDialog.show(
this,
Intent(activity, SubscriptionsImportService::class.java)
.putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE)
.putExtra(KEY_VALUE, path))
.putExtra(KEY_VALUE, path)
)
}
}
}
@@ -247,9 +252,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter)
feedGroupsSortMenuItem = HeaderWithMenuItem(
getString(R.string.feed_groups_header_title),
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
menuItemOnClickListener = ::openReorderDialog
getString(R.string.feed_groups_header_title),
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
menuItemOnClickListener = ::openReorderDialog
)
add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel)))
@@ -260,10 +265,11 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
subscriptionsSection.setHideWhenEmpty(true)
importExportItem = FeedImportExportItem(
{ onImportPreviousSelected() },
{ onImportFromServiceSelected(it) },
{ onExportSelected() },
importExportItemExpandedState ?: false)
{ onImportPreviousSelected() },
{ onImportFromServiceSelected(it) },
{ onExportSelected() },
importExportItemExpandedState ?: false
)
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
}
@@ -284,8 +290,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
val commands = arrayOf(
getString(R.string.share),
getString(R.string.unsubscribe)
getString(R.string.share),
getString(R.string.unsubscribe)
)
val actions = DialogInterface.OnClickListener { _, i ->
@@ -301,16 +307,18 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
bannerView.itemAdditionalDetails.visibility = View.GONE
AlertDialog.Builder(requireContext())
.setCustomTitle(bannerView)
.setItems(commands, actions)
.create()
.show()
.setCustomTitle(bannerView)
.setItems(commands, actions)
.create()
.show()
}
private fun deleteChannel(selectedItem: ChannelInfoItem) {
disposables.add(subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe {
Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show()
})
disposables.add(
subscriptionManager.deleteSubscription(selectedItem.serviceId, selectedItem.url).subscribe {
Toast.makeText(requireContext(), getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show()
}
)
}
override fun doInitialLoadLogic() = Unit
@@ -332,8 +340,10 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
private val listenerChannelItem = object : OnClickGesture<ChannelInfoItem>() {
override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(fm,
selectedItem.serviceId, selectedItem.url, selectedItem.name)
override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment(
fm,
selectedItem.serviceId, selectedItem.url, selectedItem.name
)
override fun held(selectedItem: ChannelInfoItem) = showLongTapDialog(selectedItem)
}
@@ -420,14 +430,16 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private fun shouldUseGridLayout(): Boolean {
val listMode = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value))
.getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value))
return when (listMode) {
getString(R.string.list_view_mode_auto_key) -> {
val configuration = resources.configuration
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
(
configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
)
}
getString(R.string.list_view_mode_grid_key) -> true
else -> false

View File

@@ -1,10 +1,10 @@
package org.schabi.newpipe.local.subscription
import android.content.Context
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionDAO
@@ -32,7 +32,8 @@ class SubscriptionManager(context: Context) {
filterQuery.isNotEmpty() -> {
return if (showOnlyUngrouped) {
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
currentGroupId, filterQuery)
currentGroupId, filterQuery
)
} else {
subscriptionTable.getSubscriptionsFiltered(filterQuery)
}
@@ -44,7 +45,8 @@ class SubscriptionManager(context: Context) {
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
val listEntities = subscriptionTable.upsertAll(
infoList.map { SubscriptionEntity.from(it) })
infoList.map { SubscriptionEntity.from(it) }
)
database.runInTransaction {
infoList.forEachIndexed { index, info ->

View File

@@ -5,12 +5,12 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.xwray.groupie.Group
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.item.ChannelItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
import java.util.concurrent.TimeUnit
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
@@ -22,22 +22,22 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
val feedGroupsLiveData: LiveData<List<Group>> = mutableFeedGroupsLiveData
private var feedGroupItemsDisposable = feedDatabaseManager.groups()
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.map { it.map(::FeedGroupCardItem) }
.subscribeOn(Schedulers.io())
.subscribe(
{ mutableFeedGroupsLiveData.postValue(it) },
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
)
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.map { it.map(::FeedGroupCardItem) }
.subscribeOn(Schedulers.io())
.subscribe(
{ mutableFeedGroupsLiveData.postValue(it) },
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
)
private var stateItemsDisposable = subscriptionManager.subscriptions()
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } }
.subscribeOn(Schedulers.io())
.subscribe(
{ mutableStateLiveData.postValue(SubscriptionState.LoadedState(it)) },
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
)
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } }
.subscribeOn(Schedulers.io())
.subscribe(
{ mutableStateLiveData.postValue(SubscriptionState.LoadedState(it)) },
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
)
override fun onCleared() {
super.onCleared()

View File

@@ -27,6 +27,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.FilePickerActivityHelper;
@@ -84,7 +85,7 @@ public class SubscriptionsImportFragment extends BaseFragment {
setupServiceVariables();
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
ErrorActivity.reportError(activity, Collections.emptyList(), null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE,
ErrorInfo.make(UserAction.SOMETHING_ELSE,
NewPipe.getNameOfService(currentServiceId),
"Service don't support importing", R.string.general_error));
activity.finish();

View File

@@ -3,7 +3,6 @@ package org.schabi.newpipe.local.subscription.dialog
import android.app.Dialog
import android.os.Bundle
import android.os.Parcelable
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -25,8 +24,6 @@ import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
import java.io.Serializable
import kotlin.collections.contains
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
import kotlinx.android.synthetic.main.toolbar_search_layout.*
import org.schabi.newpipe.R
@@ -44,6 +41,8 @@ import org.schabi.newpipe.local.subscription.item.PickerIconItem
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
import org.schabi.newpipe.util.DeviceUtils
import org.schabi.newpipe.util.ThemeHelper
import java.io.Serializable
import kotlin.collections.contains
class FeedGroupDialog : DialogFragment(), BackPressable {
private lateinit var viewModel: FeedGroupDialogViewModel
@@ -117,21 +116,30 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this,
FeedGroupDialogViewModel.Factory(requireContext(),
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
viewModel = ViewModelProvider(
this,
FeedGroupDialogViewModel.Factory(
requireContext(),
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped
)
).get(FeedGroupDialogViewModel::class.java)
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
setupSubscriptionPicker(it.first, it.second)
})
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
viewModel.subscriptionsLiveData.observe(
viewLifecycleOwner,
Observer {
setupSubscriptionPicker(it.first, it.second)
}
})
)
viewModel.dialogEventLiveData.observe(
viewLifecycleOwner,
Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
}
}
)
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
add(subscriptionMainSection)
@@ -142,8 +150,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
// Disable animations, too distracting.
itemAnimator = null
adapter = subscriptionGroupAdapter
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
RecyclerView.VERTICAL, false).apply {
layoutManager = GridLayoutManager(
requireContext(), subscriptionGroupAdapter.spanCount,
RecyclerView.VERTICAL, false
).apply {
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
}
}
@@ -225,7 +235,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
toolbar_search_clear.setOnClickListener {
if (TextUtils.isEmpty(toolbar_search_edit_text.text)) {
if (toolbar_search_edit_text.text.isEmpty()) {
hideSearch()
return@setOnClickListener
}
@@ -347,7 +357,8 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
val selectedCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString(
R.plurals.feed_group_dialog_selection_count,
selectedCount, selectedCount)
selectedCount, selectedCount
)
selected_subscription_count_view.text = selectedCountText
subscriptions_header_info.text = selectedCountText
}
@@ -402,10 +413,12 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
separator.onlyVisibleIn(SubscriptionsPickerScreen, IconPickerScreen)
cancel_button.onlyVisibleIn(InitialScreen, DeleteScreen)
confirm_button.setText(when {
currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
else -> android.R.string.ok
})
confirm_button.setText(
when {
currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED -> R.string.create
else -> android.R.string.ok
}
)
delete_button.isGone = currentScreen != InitialScreen || groupId == NO_GROUP_SELECTED
@@ -455,8 +468,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun hideKeyboardSearch() {
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN)
inputMethodManager.hideSoftInputFromWindow(
toolbar_search_edit_text.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
toolbar_search_edit_text.clearFocus()
}
@@ -467,8 +482,10 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
}
private fun hideKeyboard() {
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN)
inputMethodManager.hideSoftInputFromWindow(
group_name_input.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
group_name_input.clearFocus()
}

View File

@@ -5,12 +5,12 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.disposables.Disposable
import io.reactivex.functions.BiFunction
import io.reactivex.processors.BehaviorProcessor
import io.reactivex.schedulers.Schedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.functions.BiFunction
import io.reactivex.rxjava3.processors.BehaviorProcessor
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.FeedGroupIcon
@@ -32,9 +32,9 @@ class FeedGroupDialogViewModel(
private var subscriptionsFlowable = Flowable
.combineLatest(
filterSubscriptions.startWith(initialQuery),
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
filterSubscriptions.startWithItem(initialQuery),
toggleShowOnlyUngrouped.startWithItem(initialShowOnlyUngrouped),
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
)
.distinctUntilChanged()
.switchMap { (query, showOnlyUngrouped) ->
@@ -55,8 +55,10 @@ class FeedGroupDialogViewModel(
.subscribe(mutableGroupLiveData::postValue)
private var subscriptionsDisposable = Flowable
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
.combineLatest(
subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() }
)
.subscribeOn(Schedulers.io())
.subscribe(mutableSubscriptionsLiveData::postValue)
@@ -68,15 +70,19 @@ class FeedGroupDialogViewModel(
}
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
.flatMapCompletable {
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
})
doAction(
feedDatabaseManager.createGroup(name, selectedIcon)
.flatMapCompletable {
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
}
)
}
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
doAction(
feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder)))
)
}
fun deleteGroup() {
@@ -120,8 +126,10 @@ class FeedGroupDialogViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FeedGroupDialogViewModel(context.applicationContext,
groupId, initialQuery, initialShowOnlyUngrouped) as T
return FeedGroupDialogViewModel(
context.applicationContext,
groupId, initialQuery, initialShowOnlyUngrouped
) as T
}
}
}

View File

@@ -16,7 +16,6 @@ import com.xwray.groupie.TouchCallback
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
import java.util.Collections
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
import org.schabi.newpipe.R
@@ -25,6 +24,7 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
import org.schabi.newpipe.util.ThemeHelper
import java.util.Collections
class FeedGroupReorderDialog : DialogFragment() {
private lateinit var viewModel: FeedGroupReorderDialogViewModel
@@ -51,12 +51,15 @@ class FeedGroupReorderDialog : DialogFragment() {
viewModel = ViewModelProvider(this).get(FeedGroupReorderDialogViewModel::class.java)
viewModel.groupsLiveData.observe(viewLifecycleOwner, Observer(::handleGroups))
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
viewModel.dialogEventLiveData.observe(
viewLifecycleOwner,
Observer {
when (it) {
ProcessingEvent -> disableInput()
SuccessEvent -> dismiss()
}
}
})
)
feed_groups_list.layoutManager = LinearLayoutManager(requireContext())
feed_groups_list.adapter = groupAdapter

View File

@@ -4,9 +4,9 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import io.reactivex.Completable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.local.feed.FeedDatabaseManager
@@ -21,9 +21,9 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
private var actionProcessingDisposable: Disposable? = null
private var groupsDisposable = feedDatabaseManager.groups()
.limit(1)
.subscribeOn(Schedulers.io())
.subscribe(mutableGroupsLiveData::postValue)
.take(1)
.subscribeOn(Schedulers.io())
.subscribe(mutableGroupsLiveData::postValue)
override fun onCleared() {
super.onCleared()
@@ -40,8 +40,8 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
actionProcessingDisposable = completable
.subscribeOn(Schedulers.io())
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
.subscribeOn(Schedulers.io())
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
}
}

View File

@@ -36,8 +36,10 @@ class ChannelItem(
viewHolder.itemAdditionalDetails.text = getDetailLine(viewHolder.root.context)
if (itemVersion == ItemVersion.NORMAL) viewHolder.itemChannelDescriptionView.text = infoItem.description
ImageLoader.getInstance().displayImage(infoItem.thumbnailUrl, viewHolder.itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS)
ImageLoader.getInstance().displayImage(
infoItem.thumbnailUrl, viewHolder.itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS
)
gesturesListener?.run {
viewHolder.containerView.setOnClickListener { selected(infoItem) }

View File

@@ -20,7 +20,7 @@ data class FeedGroupReorderItem(
val dragCallback: ItemTouchHelper
) : Item() {
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) :
this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
override fun getId(): Long {
return when (groupId) {

View File

@@ -49,8 +49,10 @@ class FeedImportExportItem(
expandIconListener?.let { viewHolder.import_export_options.removeListener(it) }
expandIconListener = CollapsibleView.StateListener { newState ->
AnimationUtils.animateRotation(viewHolder.import_export_expand_icon,
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180)
AnimationUtils.animateRotation(
viewHolder.import_export_expand_icon,
250, if (newState == CollapsibleView.COLLAPSED) 0 else 180
)
}
viewHolder.import_export_options.currentState = if (isExpanded) CollapsibleView.EXPANDED else CollapsibleView.COLLAPSED
@@ -85,8 +87,10 @@ class FeedImportExportItem(
}
private fun setupImportFromItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(listHolder.context.getString(R.string.previous_export),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder)
val previousBackupItem = addItemView(
listHolder.context.getString(R.string.previous_export),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder
)
previousBackupItem.setOnClickListener { onImportPreviousSelected() }
val iconColor = if (ThemeHelper.isLightThemeSelected(listHolder.context)) Color.BLACK else Color.WHITE
@@ -112,8 +116,10 @@ class FeedImportExportItem(
}
private fun setupExportToItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView(listHolder.context.getString(R.string.file),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder)
val previousBackupItem = addItemView(
listHolder.context.getString(R.string.file),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder
)
previousBackupItem.setOnClickListener { onExportSelected() }
}
}

View File

@@ -36,11 +36,11 @@ class HeaderWithMenuItem(
viewHolder.header_menu_item.setImageResource(itemIcon)
val listener: OnClickListener? =
onClickListener?.let { OnClickListener { onClickListener.invoke() } }
onClickListener?.let { OnClickListener { onClickListener.invoke() } }
viewHolder.root.setOnClickListener(listener)
val menuItemListener: OnClickListener? =
menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
viewHolder.header_menu_item.setOnClickListener(menuItemListener)
updateMenuItemVisibility(viewHolder)
}

View File

@@ -22,8 +22,10 @@ data class PickerSubscriptionItem(
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
ImageLoader.getInstance().displayImage(
subscriptionEntity.avatarUrl,
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
)
viewHolder.title_view.text = subscriptionEntity.name
viewHolder.selected_highlight.isVisible = isSelected
@@ -39,7 +41,9 @@ data class PickerSubscriptionItem(
fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected
animateView(containerView.selected_highlight,
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
animateView(
containerView.selected_highlight,
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150
)
}
}

View File

@@ -37,6 +37,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.ErrorInfo;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExceptionUtils;
@@ -45,11 +46,11 @@ import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Function;
import io.reactivex.processors.PublishProcessor;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.processors.PublishProcessor;
public abstract class BaseImportExportService extends Service {
protected final String TAG = this.getClass().getSimpleName();
@@ -119,7 +120,7 @@ public abstract class BaseImportExportService extends Service {
startForeground(getNotificationId(), notificationBuilder.build());
final Function<Flowable<String>, Publisher<String>> throttleAfterFirstEmission = flow ->
flow.limit(1).concatWith(flow.skip(1)
flow.take(1).concatWith(flow.skip(1)
.throttleLast(NOTIFICATION_SAMPLING_PERIOD, TimeUnit.MILLISECONDS));
disposables.add(notificationUpdater
@@ -153,7 +154,7 @@ public abstract class BaseImportExportService extends Service {
protected void stopAndReportError(@Nullable final Throwable error, final String request) {
stopService();
final ErrorActivity.ErrorInfo errorInfo = ErrorActivity.ErrorInfo
final ErrorInfo errorInfo = ErrorInfo
.make(UserAction.SUBSCRIPTION, "unknown", request, R.string.general_error);
ErrorActivity.reportError(this, error != null ? Collections.singletonList(error)
: Collections.emptyList(), null, null, errorInfo);

View File

@@ -37,9 +37,9 @@ import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.MainActivity.DEBUG;

View File

@@ -46,12 +46,12 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Flowable;
import io.reactivex.Notification;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Notification;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.MainActivity.DEBUG;

View File

@@ -27,13 +27,13 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
@@ -77,17 +77,16 @@ import org.schabi.newpipe.util.SerializedCache;
import java.io.IOException;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.SerialDisposable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.disposables.SerialDisposable;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
import static io.reactivex.android.schedulers.AndroidSchedulers.mainThread;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
@@ -720,8 +719,9 @@ public abstract class BasePlayer implements
}
private Disposable getProgressReactor() {
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS, mainThread())
.observeOn(mainThread())
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, MILLISECONDS,
AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> triggerProgressUpdate(),
error -> Log.e(TAG, "Progress update failure: ", error));
}
@@ -1319,7 +1319,7 @@ public abstract class BasePlayer implements
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
final Disposable stateSaver = recordManager.saveStreamState(info, progress)
.observeOn(mainThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> {
if (DEBUG) {
e.printStackTrace();
@@ -1339,7 +1339,7 @@ public abstract class BasePlayer implements
if (prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) {
final Disposable stateSaver = queueItem.getStream()
.flatMapCompletable(info -> recordManager.saveStreamState(info, 0))
.observeOn(mainThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError((e) -> {
if (DEBUG) {
e.printStackTrace();
@@ -1545,8 +1545,7 @@ public abstract class BasePlayer implements
if (simpleExoPlayer == null) {
return PlaybackParameters.DEFAULT;
}
final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters();
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
return simpleExoPlayer.getPlaybackParameters();
}
/**

View File

@@ -48,7 +48,7 @@ public final class NotificationUtil {
@Nullable private static NotificationUtil instance = null;
@NotificationConstants.Action
private int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone();
private final int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone();
private NotificationManagerCompat notificationManager;
private NotificationCompat.Builder notificationBuilder;
@@ -146,7 +146,11 @@ public final class NotificationUtil {
notificationBuilder.setContentText(player.getUploaderName());
notificationBuilder.setTicker(player.getVideoTitle());
updateActions(notificationBuilder, player);
setLargeIcon(notificationBuilder, player);
final boolean showThumbnail = player.sharedPreferences.getBoolean(
player.context.getString(R.string.show_thumbnail_key), true);
if (showThumbnail) {
setLargeIcon(notificationBuilder, player);
}
}

View File

@@ -337,7 +337,7 @@ public class VideoPlayerImpl extends VideoPlayer
view.setFixedTextSize(TypedValue.COMPLEX_UNIT_PX,
(float) minimumLength / captionRatioInverse);
}
view.setApplyEmbeddedStyles(captionStyle.equals(CaptionStyleCompat.DEFAULT));
view.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT);
view.setStyle(captionStyle);
}
@@ -671,9 +671,13 @@ public class VideoPlayerImpl extends VideoPlayer
super.onUpdateProgress(currentProgress, duration, bufferPercent);
updateProgress(currentProgress, duration, bufferPercent);
final boolean showThumbnail =
sharedPreferences.getBoolean(
context.getString(R.string.show_thumbnail_key),
true);
// setMetadata only updates the metadata when any of the metadata keys are null
mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(), getThumbnail(),
duration);
mediaSessionManager.setMetadata(getVideoTitle(), getUploaderName(),
showThumbnail ? getThumbnail() : null, duration);
}
@Override

View File

@@ -15,6 +15,7 @@ import org.schabi.newpipe.util.AnimationUtils
import kotlin.math.abs
import kotlin.math.hypot
import kotlin.math.max
import kotlin.math.min
/**
* Base gesture handling for [VideoPlayerImpl]
@@ -117,22 +118,30 @@ abstract class BasePlayerGestureListener(
initSecPointerX = event.getX(1)
initSecPointerY = event.getY(1)
// record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX.toDouble(),
initFirstPointerY - initSecPointerY.toDouble())
initPointerDistance = hypot(
initFirstPointerX - initSecPointerX.toDouble(),
initFirstPointerY - initSecPointerY.toDouble()
)
isResizing = true
}
if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
Log.d(
TAG,
"onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" +
"[${event.rawX}, ${event.rawY}]"
)
}
return handleMultiDrag(event)
}
if (event.action == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
Log.d(
TAG,
"onTouch() ACTION_UP > v = [$v], e1.getRaw =" +
" [${event.rawX}, ${event.rawY}]"
)
}
if (isMovingInPopup) {
isMovingInPopup = false
@@ -162,18 +171,24 @@ abstract class BasePlayerGestureListener(
private fun handleMultiDrag(event: MotionEvent): Boolean {
if (initPointerDistance != -1.0 && event.pointerCount == 2) {
// get the movements of the fingers
val firstPointerMove = hypot(event.getX(0) - initFirstPointerX.toDouble(),
event.getY(0) - initFirstPointerY.toDouble())
val secPointerMove = hypot(event.getX(1) - initSecPointerX.toDouble(),
event.getY(1) - initSecPointerY.toDouble())
val firstPointerMove = hypot(
event.getX(0) - initFirstPointerX.toDouble(),
event.getY(0) - initFirstPointerY.toDouble()
)
val secPointerMove = hypot(
event.getX(1) - initSecPointerX.toDouble(),
event.getY(1) - initSecPointerY.toDouble()
)
// minimum threshold beyond which pinch gesture will work
val minimumMove = ViewConfiguration.get(service).scaledTouchSlop
if (max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
val currentPointerDistance = hypot(event.getX(0) - event.getX(1).toDouble(),
event.getY(0) - event.getY(1).toDouble())
val currentPointerDistance = hypot(
event.getX(0) - event.getX(1).toDouble(),
event.getY(0) - event.getY(1).toDouble()
)
val popupWidth = playerImpl.popupWidth.toDouble()
// change co-ordinates of popup so the center stays at the same position
@@ -185,8 +200,9 @@ abstract class BasePlayerGestureListener(
playerImpl.updateScreenSize()
playerImpl.updatePopupSize(
Math.min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
-1)
min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
-1
)
return true
}
}
@@ -315,22 +331,30 @@ abstract class BasePlayerGestureListener(
}
val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service)
val isTouchingNavigationBar: Boolean = (initialEvent.y
> playerImpl.rootView.height - getNavigationBarHeight(service))
val isTouchingNavigationBar: Boolean =
initialEvent.y > (playerImpl.rootView.height - getNavigationBarHeight(service))
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false
}
val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD
if (!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
playerImpl.currentState == BasePlayer.STATE_COMPLETED) {
if (
!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
playerImpl.currentState == BasePlayer.STATE_COMPLETED
) {
return false
}
isMovingInMain = true
onScroll(MainPlayer.PlayerType.VIDEO, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
onScroll(
MainPlayer.PlayerType.VIDEO,
getDisplayHalfPortion(initialEvent),
initialEvent,
movingEvent,
distanceX,
distanceY
)
return true
}
@@ -372,8 +396,14 @@ abstract class BasePlayerGestureListener(
playerImpl.popupLayoutParams.x = posX.toInt()
playerImpl.popupLayoutParams.y = posY.toInt()
onScroll(MainPlayer.PlayerType.POPUP, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
onScroll(
MainPlayer.PlayerType.POPUP,
getDisplayHalfPortion(initialEvent),
initialEvent,
movingEvent,
distanceX,
distanceY
)
playerImpl.windowManager
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)

View File

@@ -36,15 +36,10 @@ public class PlayerGestureListener
private static final String TAG = ".PlayerGestureListener";
private static final boolean DEBUG = BasePlayer.DEBUG;
private final boolean isVolumeGestureEnabled;
private final boolean isBrightnessGestureEnabled;
private final int maxVolume;
public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
super(playerImpl, service);
isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
maxVolume = playerImpl.getAudioReactor().getMaxVolume();
}
@@ -110,10 +105,20 @@ public class PlayerGestureListener
+ portion + "]");
}
if (playerType == MainPlayer.PlayerType.VIDEO) {
if (portion == DisplayPortion.LEFT_HALF) {
onScrollMainBrightness(distanceX, distanceY);
final boolean isBrightnessGestureEnabled =
PlayerHelper.isBrightnessGestureEnabled(service);
final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
} else /* DisplayPortion.RIGHT_HALF */ {
if (isBrightnessGestureEnabled && isVolumeGestureEnabled) {
if (portion == DisplayPortion.LEFT_HALF) {
onScrollMainBrightness(distanceX, distanceY);
} else /* DisplayPortion.RIGHT_HALF */ {
onScrollMainVolume(distanceX, distanceY);
}
} else if (isBrightnessGestureEnabled) {
onScrollMainBrightness(distanceX, distanceY);
} else if (isVolumeGestureEnabled) {
onScrollMainVolume(distanceX, distanceY);
}
@@ -132,75 +137,71 @@ public class PlayerGestureListener
}
private void onScrollMainVolume(final float distanceX, final float distanceY) {
if (isVolumeGestureEnabled) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
final int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
final int currentVolume = (int) (maxVolume * currentProgressPercent);
playerImpl.getAudioReactor().setVolume(currentVolume);
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
}
if (DEBUG) {
Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
}
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
: R.drawable.ic_volume_up_white_24dp)
);
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
: R.drawable.ic_volume_up_white_24dp)
);
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);
}
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);
}
}
private void onScrollMainBrightness(final float distanceX, final float distanceY) {
if (isBrightnessGestureEnabled) {
final Activity parent = playerImpl.getParentActivity();
if (parent == null) {
return;
}
final Activity parent = playerImpl.getParentActivity();
if (parent == null) {
return;
}
final Window window = parent.getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
final ProgressBar bar = playerImpl.getBrightnessProgressBar();
final float oldBrightness = layoutParams.screenBrightness;
bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness))));
bar.incrementProgressBy((int) distanceY);
final Window window = parent.getWindow();
final WindowManager.LayoutParams layoutParams = window.getAttributes();
final ProgressBar bar = playerImpl.getBrightnessProgressBar();
final float oldBrightness = layoutParams.screenBrightness;
bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness))));
bar.incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) bar.getProgress() / bar.getMax();
layoutParams.screenBrightness = currentProgressPercent;
window.setAttributes(layoutParams);
final float currentProgressPercent = (float) bar.getProgress() / bar.getMax();
layoutParams.screenBrightness = currentProgressPercent;
window.setAttributes(layoutParams);
// Save current brightness level
PlayerHelper.setScreenBrightness(parent, currentProgressPercent);
// Save current brightness level
PlayerHelper.setScreenBrightness(parent, currentProgressPercent);
if (DEBUG) {
Log.d(TAG, "onScroll().brightnessControl, "
+ "currentBrightness = " + currentProgressPercent);
}
if (DEBUG) {
Log.d(TAG, "onScroll().brightnessControl, "
+ "currentBrightness = " + currentProgressPercent);
}
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_24dp
: R.drawable.ic_brightness_high_white_24dp)
);
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_24dp
: R.drawable.ic_brightness_high_white_24dp)
);
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);
}
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);
}
}

View File

@@ -30,14 +30,14 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.internal.subscriptions.EmptySubscription;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.internal.subscriptions.EmptySubscription;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException;
@@ -123,7 +123,7 @@ public class MediaSourceManager {
@NonNull
private ManagedMediaSourcePlaylist playlist;
private Handler removeMediaSourceHandler = new Handler();
private final Handler removeMediaSourceHandler = new Handler();
public MediaSourceManager(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue) {

View File

@@ -14,8 +14,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.SingleObserver;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjava3.core.SingleObserver;
import io.reactivex.rxjava3.disposables.Disposable;
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
boolean isInitial;

View File

@@ -9,8 +9,8 @@ import org.schabi.newpipe.util.ExtractorHelper;
import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, ChannelInfoItem> {
public ChannelPlayQueue(final ChannelInfoItem item) {

View File

@@ -21,10 +21,10 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
/**
* PlayQueue is responsible for keeping track of a list of streams and the index of
@@ -80,7 +80,7 @@ public abstract class PlayQueue implements Serializable {
broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(AndroidSchedulers.mainThread())
.startWith(new InitEvent());
.startWithItem(new InitEvent());
}
/**

View File

@@ -20,8 +20,8 @@ import org.schabi.newpipe.util.FallbackViewHolder;
import java.util.List;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* Created by Christian Schabesberger on 01.08.16.
@@ -215,7 +215,7 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
}
public class HFHolder extends RecyclerView.ViewHolder {
public static class HFHolder extends RecyclerView.ViewHolder {
public View view;
public HFHolder(final View v) {

View File

@@ -10,8 +10,8 @@ import org.schabi.newpipe.util.ExtractorHelper;
import java.io.Serializable;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class PlayQueueItem implements Serializable {
public static final long RECOVERY_UNSET = Long.MIN_VALUE;

Some files were not shown because too many files have changed in this diff Show More