1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-01-14 19:17:55 +00:00

Compare commits

..

345 Commits

Author SHA1 Message Date
Tobias Groza
c900ef036c Merge pull request #3863 from TeamNewPipe/release_0.19.6
Release 0.19.6
2020-07-12 20:25:05 +02:00
TobiGr
d088d432c5 Release NewPipe 0.19.6 (951) 2020-07-11 21:47:19 +02:00
TobiGr
c9fafbe198 Add changelog 2020-07-11 21:47:19 +02:00
TobiGr
2d909b0514 Remove untranslatable translations 2020-07-11 21:47:19 +02:00
TobiGr
c2a012553d Use %1$d instead of %1$s in deleted_downloads string
We don't pass a String, but an Integer
2020-07-11 21:47:19 +02:00
TobiGr
085f0266ac Fix Linter (translations) 2020-07-11 20:52:20 +02:00
Tobias Groza
7ede2daa3c Merge pull request #3861 from TeamNewPipe/languages
add the new languages translated in NewPipe
2020-07-08 22:45:16 +02:00
bopol
713c53d170 add bengali (india) and arabic (lybia) 2020-07-08 21:34:34 +02:00
TobiGr
110b3a6a8f Add some new languages to the language selector
ckb - Central Kurdish
jv - Javanese
kab - Kabyle
2020-07-07 22:32:14 +02:00
Tobias Groza
e12e6dd7a7 Merge pull request #3441 from wb9688/nextpage
Next page stuff
2020-07-07 21:19:27 +02:00
wb9688
e183fc6118 Bump NPE version 2020-07-07 21:04:08 +02:00
wb9688
dd57e246b8 Use getNextPage() instead of getNextPageUrl() 2020-07-07 21:03:24 +02:00
Tobias Groza
f4a4172369 Merge pull request #3471 from Royosef/DisplaySearchSuggestion
Display search suggestion: did you mean & showing result for
2020-07-07 20:57:02 +02:00
TobiGr
b96d1714b5 Highlight search suggestion 2020-07-07 20:23:41 +02:00
TobiGr
dbd809b040 Merge remote-tracking branch 'Weblate/dev' into dev 2020-07-07 18:41:55 +02:00
Tobias Groza
ff4e6b139d Merge pull request #3579 from TobiGr/error_md_export
Add Markdown export of crash logs
2020-07-07 00:01:12 +02:00
Tobias Groza
af098aaac8 Merge pull request #3843 from kapodamy/drop-writting-application-metadata
Drop writting application metadata in muxed files
2020-07-06 23:55:06 +02:00
Digiwizkid
5d7e62c736 Translated using Weblate (Bengali (India))
Currently translated at 61.0% (352 of 577 strings)
2020-07-06 12:56:56 +02:00
Prajwol Pradhan
e2ead011f5 Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-07-06 12:56:54 +02:00
Allan Nordhøy
a067c950e1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.0% (514 of 577 strings)
2020-07-06 12:56:54 +02:00
Yaron Shahrabani
3369618cfa Translated using Weblate (Hebrew)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:54 +02:00
Jeff Huang
4e9b6520e5 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
Eric
a074203fae Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
Samuel Carvalho de Araújo
42092e3f28 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
WaldiS
c07b34e298 Translated using Weblate (Polish)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:53 +02:00
Emin Tufan Çetin
ef5f181328 Translated using Weblate (Turkish)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
ssantos
a7ea2fcf92 Translated using Weblate (Portuguese)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
AioiLight
e81730715c Translated using Weblate (Japanese)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
Lesya Novaselskaya
c09f2ad482 Translated using Weblate (Russian)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:52 +02:00
B0pol
84aef8b5b5 Translated using Weblate (French)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:51 +02:00
nautilusx
8fac3e8221 Translated using Weblate (German)
Currently translated at 100.0% (577 of 577 strings)
2020-07-06 12:56:51 +02:00
Prajwol Pradhan
5874ed781d Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-07-06 12:56:49 +02:00
Ishwor Ghimire
d8f29bd7a7 Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-07-06 12:56:48 +02:00
kapodamy
8120b6aaaa checkstyle's amend 2020-07-05 23:55:40 -03:00
Stypox
13a0d1de70 Replace search query without searching on suggestion panel long click 2020-07-05 22:01:35 +02:00
Stypox
20e828be51 Improve suggestion panel 2020-07-05 22:01:35 +02:00
wb9688
ccd82fb8b8 Improve search suggestion code 2020-07-05 22:01:35 +02:00
Roy Yosef
0711650ff8 Fix search suggestions not working on resume
* add searchSuggestionString, isCorrectedSearch fields to state and load onResume
2020-07-05 22:01:35 +02:00
Roy Yosef
4194ac2226 Display search suggestion: did you mean & showing result for 2020-07-05 22:01:35 +02:00
Stypox
248e2d7ee0 Merge pull request #3506 from Royosef/PlaylistTab
Add playlist tab to main page
2020-07-05 21:06:23 +02:00
Tobias Groza
1daa654051 Merge pull request #3845 from B0pol/peertube_instance
add privacytools.io peertube instance to manifest
2020-07-05 14:37:11 +02:00
bopol
d751434979 add privacytools.io peertube instance to manifest 2020-07-04 11:39:00 +02:00
Stypox
8cc21920b7 Move local/remote playlist merge() to PlaylistLocalItem class
In order not to have a utils class just for one function
2020-07-04 11:38:22 +02:00
Stypox
248212588d Fix style issues 2020-07-04 11:38:22 +02:00
Roy Yosef
13c0fdef08 Final declarations, naming & redundant code
* add final declarations where missing
* fix typo "onSelectedLisener" to "onSelectedListener"
* rename "baseEqual" to "baseEquals"
* replace getPlaylistsObserver code with functions pointers
* remove duplicate code in constructors
* remove useless null checks
2020-07-04 11:37:38 +02:00
Roy Yosef
dfc27b2480 Add playlist tab to main page
Add bookmarked playlist as tab in the main page (by Settings > Content > Content of main page)
2020-07-04 11:35:45 +02:00
kapodamy
b2d78d380b update WebMWriter.java 2020-07-03 20:51:45 -03:00
Tobias Groza
faa6cb5c7d Merge pull request #3841 from B0pol/update_inv_instances
update invidious instances
2020-07-03 15:38:10 +02:00
kapodamy
452977abdf Drop "writing/muxed by" metadata
* All muxers (mp4, webm and ogg) are affected
* solve some checkstyle's errors (building was blocked)

Mp4FromDashWriter:
* drop "writing application"
* drop "handler name"

OggFromWebMWriter:
* drop "writing application" for OPUS and VORBIS header

WebMWriter:
* Drop "Muxing application"
* Drop "Writing application"
2020-07-03 02:07:42 -03:00
Hosted Weblate
93570b2f59 Merge branch 'origin/dev' into Weblate. 2020-07-03 00:18:10 +02:00
Ville Rantanen
073f5c2c8c Translated using Weblate (Finnish)
Currently translated at 100.0% (576 of 576 strings)
2020-07-03 00:18:06 +02:00
bopol
1b4313f847 update extractor version 2020-07-02 23:17:38 +02:00
Tobias Groza
07cead7e99 Merge pull request #3404 from mauriciocolli/feed-add-filter-sub-list
Add filter to the feed group dialog to show only ungrouped subscriptions
2020-07-02 22:53:11 +02:00
bopol
f128751aba update invidious instances 2020-07-02 21:41:23 +02:00
Tobias Groza
9516d9da17 Merge pull request #3837 from Stypox/audio-sync
Fix audio/video desync caused by floating point cumulative errors
2020-07-02 21:29:18 +02:00
Hosted Weblate
e9aafc2a56 Merge branch 'origin/dev' into Weblate. 2020-07-02 17:34:57 +02:00
Digiwizkid
a91a6575e0 Translated using Weblate (Bengali (India))
Currently translated at 50.3% (290 of 576 strings)
2020-07-02 17:34:56 +02:00
Milo Ivir
5bd4093dfb Translated using Weblate (Croatian)
Currently translated at 86.6% (499 of 576 strings)
2020-07-02 17:34:54 +02:00
WaldiS
f9890e2016 Translated using Weblate (Polish)
Currently translated at 100.0% (576 of 576 strings)
2020-07-02 17:34:54 +02:00
BenjaminForster
00529fe134 Added translation using Weblate (Afrikaans) 2020-07-02 17:34:51 +02:00
Stypox
5e9dce7d39 Merge pull request #3774 from eames-palmer/status-bar-color
Update status color to match toolbar color
2020-07-01 08:39:32 +02:00
Tobias Groza
734680b9f0 Merge pull request #3373 from mauriciocolli/feed-add-search-sub-list
Add search for subscription picker in the feed group dialog
2020-07-01 00:16:01 +02:00
Tobias Groza
d0b5345252 Merge pull request #3822 from Stypox/fix-queue-colors
Fix queue channel name color for some devices
2020-07-01 00:13:29 +02:00
Tobias Groza
c2b4a44a59 Merge pull request #3828 from wb9688/checkstyle-fix
Do not include Checkstyle in any APK
2020-06-30 23:19:33 +02:00
Stypox
38c79bbc11 Fix audio/video desync caused by floating point cumulative errors 2020-06-30 22:41:09 +02:00
Digiwizkid
0d7028a36c Translated using Weblate (Bengali (India))
Currently translated at 36.1% (208 of 576 strings)
2020-06-30 19:32:06 +02:00
Digiwizkid
b63f687491 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 30.9% (178 of 576 strings)
2020-06-30 19:32:02 +02:00
Digiwizkid
952b636569 Added translation using Weblate (Bengali (India)) 2020-06-30 12:20:19 +02:00
Jasper Eames Palmer
e14ba48244 Update non-service status bar colors to match toolbar colors 2020-06-29 22:30:09 -07:00
Hosted Weblate
720c8c31ec Merge branch 'origin/dev' into Weblate. 2020-06-29 13:30:45 +02:00
Allan Nordhøy
4e8407ed8f Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.2% (514 of 576 strings)
2020-06-29 13:30:43 +02:00
Enol P
26c5f69161 Translated using Weblate (Asturian)
Currently translated at 51.0% (294 of 576 strings)
2020-06-29 13:30:43 +02:00
Anonim Qwerty
078ae15794 Translated using Weblate (Ukrainian)
Currently translated at 98.9% (570 of 576 strings)
2020-06-29 13:30:42 +02:00
ssantos
16ae90dc9f Translated using Weblate (German)
Currently translated at 100.0% (576 of 576 strings)
2020-06-29 13:30:42 +02:00
TobiGr
3de5afc68e Add Markdown export of crash logs
Add app language as additional debug information to reports
2020-06-28 16:07:22 +02:00
wb9688
a7e8f5087e Do not include Checkstyle in any APK 2020-06-28 14:59:44 +02:00
Mauricio Colli
2e6e75cd4e Add filter to the feed group dialog to show only ungrouped subscriptions 2020-06-27 11:58:40 -03:00
Mauricio Colli
9f3b35634a Fix subscription picker items flickering in the feed group dialog
The adapter could not tell the items were the same because the
subscription class was missing some methods (i.e. equals and hashcode),
so a full rebind was being done.
2020-06-27 11:25:31 -03:00
Mauricio Colli
c24dfc63dc Add search for subscription picker in the feed group dialog 2020-06-27 11:25:25 -03:00
Stypox
c029929850 Fix queue channel name color for some devices 2020-06-27 16:11:25 +02:00
Tobias Groza
d9100913d5 Merge pull request #3787 from budde25/fix-popup-queue
Fixes enqueue resuming paused videos
2020-06-27 11:56:08 +02:00
Tobias Groza
e03d970bc2 Merge pull request #3406 from B0pol/playlist_peertube
Add possibility to open PeerTube links in NP for some instances (without needing to share to NP)
2020-06-26 18:53:49 +02:00
Tobias Groza
fe4516ea23 Merge pull request #3752 from Redirion/exoupdate
Update to ExoPlayer 2.11.6
2020-06-26 18:33:53 +02:00
Jwtiyar Nariman
039b47b872 Translated using Weblate (Central Kurdish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-25 14:41:53 +02:00
Robin
0a57a8a7f3 2.11.6 2020-06-25 09:26:59 +02:00
Jwtiyar Nariman
8c823f3a2d Added translation using Weblate (Central Kurdish) 2020-06-24 11:12:52 +02:00
Éfrit
33f3a4f455 Translated using Weblate (French)
Currently translated at 100.0% (576 of 576 strings)
2020-06-24 04:41:44 +02:00
Zackz
2ecf8044d7 Translated using Weblate (Javanese)
Currently translated at 10.9% (63 of 576 strings)
2020-06-22 19:41:47 +02:00
Zackz
c9a1eb55b5 Added translation using Weblate (Javanese) 2020-06-21 18:02:36 +02:00
Ayoub Rejal
76bb1fd61e Translated using Weblate (Arabic (Libya))
Currently translated at 6.7% (39 of 576 strings)
2020-06-19 17:41:46 +02:00
ButterflyOfFire
1275b26ba0 Translated using Weblate (Kabyle)
Currently translated at 4.3% (25 of 576 strings)
2020-06-19 17:41:45 +02:00
Andreas Westrell
a470a4af9b Translated using Weblate (Swedish)
Currently translated at 99.1% (571 of 576 strings)
2020-06-19 17:41:45 +02:00
Marian Hanzel
487c9ebbd4 Translated using Weblate (Slovak)
Currently translated at 96.1% (554 of 576 strings)
2020-06-19 17:41:43 +02:00
Ayoub Rejal
746cab92f0 Added translation using Weblate (Arabic (Libya)) 2020-06-18 17:17:41 +02:00
Ethan Budd
33266a96ff fixes enqueue resuming paused videos 2020-06-17 22:15:50 -05:00
Hosted Weblate
2816889d8d Merge branch 'origin/dev' into Weblate. 2020-06-17 14:25:36 +02:00
ButterflyOfFire
58e177b3e4 Added translation using Weblate (Kabyle) 2020-06-17 14:25:34 +02:00
wb9688
5cfd8bbb56 Merge pull request #3704 from Stypox/keep-failed-streams
Do not remove items generating errors form queue
2020-06-15 15:16:26 +02:00
wb9688
e6fe6fd645 Merge pull request #3437 from TheLastGimbus/fast-rewind-forward-in-background-activity
Fast rewind forward in background activity
2020-06-15 15:06:58 +02:00
TheLastGimbus
63e167b38e Add buttons also in landscape mode 2020-06-14 23:22:31 +02:00
TheLastGimbus
abe77c4783 Change to final 2020-06-14 19:52:58 +02:00
TheLastGimbus
72af51fe9d Add speed button to top bar 2020-06-14 15:16:04 +02:00
TheLastGimbus
36b4134838 Remove speed buttons from bottom menu 2020-06-14 15:15:38 +02:00
Hosted Weblate
cf6ee26fdb Merge branch 'origin/dev' into Weblate. 2020-06-14 12:41:48 +02:00
Oymate
858111e623 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 30.5% (176 of 576 strings)
2020-06-14 12:41:42 +02:00
Oğuz Ersen
367e625804 Translated using Weblate (Turkish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-14 12:41:42 +02:00
Freddy Morán Jr
ae4d9c7f80 Translated using Weblate (Spanish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-14 12:41:42 +02:00
nautilusx
6c6ee41346 Translated using Weblate (German)
Currently translated at 99.8% (575 of 576 strings)
2020-06-14 12:41:41 +02:00
Tobias Groza
9ef7688f9e Merge pull request #3772 from adinilfeld/copy-video-title
Copy video title
2020-06-13 16:38:14 +02:00
Jasper Eames Palmer
7d6e226c2b Update status color to match toolbar color 2020-06-11 19:29:15 -07:00
adinilfeld
17d1346a8a made ClipboardManager final 2020-06-11 09:36:57 -07:00
adinilfeld
59e0c10c42 inverted if-else statement 2020-06-11 09:36:05 -07:00
adinilfeld
0d29e66092 removed unnecessary setLongClickable 2020-06-11 09:33:05 -07:00
wb9688
caa000f447 Merge pull request #3759 from Stypox/fix-search
Fix search crash: adapter array index out of bounds
2020-06-11 13:06:30 +02:00
adinilfeld
267e114354 added a copyToClipboard method to ShareUtils, and modified CommentsMiniInfoItemHolder and VideoDetailFragment to use the new method. 2020-06-10 15:14:08 -07:00
adinilfeld
b5375396d2 allowed user to copy video title to clipboard (from detail screen) 2020-06-10 14:17:43 -07:00
adinilfeld
e34f666b70 set an OnLongClickListener 2020-06-10 14:11:06 -07:00
Theophine Savio Theodore
19334b4f96 Translated using Weblate (Malayalam)
Currently translated at 100.0% (576 of 576 strings)
2020-06-10 21:41:40 +02:00
Theophine Savio Theodore
1fa609e539 Translated using Weblate (Malayalam)
Currently translated at 100.0% (576 of 576 strings)
2020-06-09 21:03:02 +02:00
Ville Rantanen
d9ce25a721 Translated using Weblate (Finnish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-09 16:22:19 +02:00
thami simo
5b8fc25da6 Translated using Weblate (Arabic)
Currently translated at 100.0% (576 of 576 strings)
2020-06-09 16:22:16 +02:00
Hosted Weblate
c70968dcf1 Merge branch 'origin/dev' into Weblate. 2020-06-08 14:14:35 +02:00
Ville Rantanen
77147510fb Translated using Weblate (Finnish)
Currently translated at 83.5% (481 of 576 strings)
2020-06-08 14:14:33 +02:00
Freddy Morán Jr
7092577482 Translated using Weblate (Spanish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-08 14:14:29 +02:00
Stypox
3e70050056 Fix search crash: adapter array index out of bounds 2020-06-07 21:28:54 +02:00
Tobias Groza
1f23c814e5 Merge pull request #3698 from B0pol/openWebsite
avoid duplicate: use openUrlInBrowser instead of openWebsite
2020-06-06 18:44:24 +02:00
Robin
145e0a0b7b Update to ExoPlayer 2.11.5 2020-06-06 15:29:52 +02:00
pitachips
e68e787e7a Translated using Weblate (Korean)
Currently translated at 84.3% (486 of 576 strings)
2020-06-05 19:41:39 +02:00
WaldiS
903308d285 Translated using Weblate (Polish)
Currently translated at 100.0% (576 of 576 strings)
2020-06-04 02:41:47 +02:00
zmni
a4d8388b2e Translated using Weblate (Indonesian)
Currently translated at 100.0% (576 of 576 strings)
2020-06-04 02:41:46 +02:00
Hosted Weblate
3e83f9f956 Merge branch 'origin/dev' into Weblate. 2020-06-01 19:41:43 +02:00
Anxhelo Lushka
4063221313 Translated using Weblate (Albanian)
Currently translated at 100.0% (577 of 577 strings)
2020-06-01 19:41:39 +02:00
zeritti
c3c8d80919 Translated using Weblate (Czech)
Currently translated at 100.0% (577 of 577 strings)
2020-06-01 19:41:39 +02:00
notramo
3ae71c73c4 Translated using Weblate (Hungarian)
Currently translated at 60.6% (350 of 577 strings)
2020-06-01 19:41:38 +02:00
Stypox
b3db8c9549 Do not remove items generating errors form queue 2020-05-31 14:06:22 +02:00
bopol
596eb4a0f9 Merge branch 'dev' into playlist_peertube 2020-05-31 12:26:46 +02:00
bopol
6b0381b903 avoid duplicate: use openUrlInBrowser instead of openWebsite 2020-05-31 12:17:54 +02:00
TobiGr
049c8f70cd Release version 0.19.5 (950) 2020-05-30 10:43:10 +02:00
TobiGr
353bf69550 Add changelog for 0.19.5 (950) 2020-05-30 10:42:39 +02:00
Tobias Groza
cdb989ede3 Merge pull request #3679 from wb9688/fix-android-10
Enable requestLegacyExternalStorage
2020-05-30 10:32:46 +02:00
Tobias Groza
74079e4238 Merge pull request #3680 from wb9688/fix-kiosks
Open the correct kiosk
2020-05-30 10:28:24 +02:00
wb9688
c89746214c Open the correct kiosk 2020-05-30 08:14:54 +02:00
wb9688
1d97db3ef9 Enable requestLegacyExternalStorage 2020-05-30 08:05:40 +02:00
Tobias Groza
b2a5ff5f9d Merge pull request #3669 from TeamNewPipe/release_0.19.4
Release 0.19.4
2020-05-29 19:13:09 +02:00
Stypox
f47ef2b5ea Add 0.19.4 (940) fastlane changelog 2020-05-29 18:55:01 +02:00
Stypox
bd7ec3b692 Bump to 0.19.4 (940) 2020-05-29 18:55:01 +02:00
TobiGr
52895e7b6b Increase JVM memory to fix release build 2020-05-29 18:55:01 +02:00
Stypox
a6a82c6477 Regression: fix icon size in main player 2020-05-29 15:32:44 +02:00
Tobias Groza
af66ed94b2 Merge pull request #3662 from B0pol/localisation-fixes
Localisation fixes (Fix crashes, lint)
2020-05-29 08:02:19 +02:00
Hosted Weblate
e43fdf5ef9 Merge branch 'origin/dev' into Weblate. 2020-05-29 07:29:53 +02:00
Ishwor Ghimire
3984fc075f Translated using Weblate (Nepali)
Currently translated at 99.8% (576 of 577 strings)
2020-05-29 07:29:53 +02:00
Allan Nordhøy
ee2a159374 Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.0% (514 of 577 strings)
2020-05-29 07:29:52 +02:00
Eric
57c9b29ba3 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:52 +02:00
Samuel Carvalho de Araújo
927ea72337 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:51 +02:00
Mostafa Ahangarha
123bdbd13b Translated using Weblate (Persian)
Currently translated at 96.1% (555 of 577 strings)
2020-05-29 07:29:51 +02:00
WaldiS
0baa5a2f04 Translated using Weblate (Polish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:46 +02:00
Emin Tufan Çetin
7d98a70028 Translated using Weblate (Turkish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:45 +02:00
Rex_sa
73b72ab01c Translated using Weblate (Arabic)
Currently translated at 99.8% (576 of 577 strings)
2020-05-29 07:29:45 +02:00
B0pol
e8cf71f41c Translated using Weblate (Esperanto)
Currently translated at 97.5% (563 of 577 strings)
2020-05-29 07:29:44 +02:00
ssantos
5f2d2a64d2 Translated using Weblate (Portuguese)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:44 +02:00
AioiLight
27bf3901de Translated using Weblate (Japanese)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:44 +02:00
TotalCaesar659
16d4fa03a5 Translated using Weblate (English)
Currently translated at 99.8% (576 of 577 strings)
2020-05-29 07:29:43 +02:00
Terry Louwers
bef579ec26 Translated using Weblate (Dutch)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:43 +02:00
Edoardo Regni
0cff10e02e Translated using Weblate (Dutch)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:41 +02:00
Terry Louwers
4c6f7238dd Translated using Weblate (Dutch)
Currently translated at 100.0% (577 of 577 strings)
2020-05-29 07:29:41 +02:00
bopol
583f1476d6 oops 2020-05-28 23:32:44 +02:00
bopol
b42bef32fd remove totally untranslated languages 2020-05-28 23:21:28 +02:00
bopol
8bb85ccf19 other translation linting stuff 2020-05-28 23:14:57 +02:00
bopol
3d88c2a5fa fix crash in hindi and pa languages 2020-05-28 22:59:14 +02:00
bopol
e350acaf08 remove unused plural string «comments»
on top of being unused, there were MANY problems for some languages: %s missing, only «one» quantity is translated…
2020-05-28 22:49:41 +02:00
bopol
172f70bef9 fix crash in polish, lint: remove translated translatable=false string 2020-05-28 22:22:29 +02:00
Tobias Groza
9d25c0bf8a Merge pull request #3509 from wb9688/upgrade-dependencies
Upgrade some dependencies
2020-05-28 22:07:04 +02:00
TobiGr
75b377aab3 Updatethe extractor version 2020-05-28 21:56:54 +02:00
TobiGr
3706f30b44 Merge remote-tracking branch 'Weblate/dev' into dev 2020-05-28 21:46:45 +02:00
wb9688
a9697a61ad Fix viewing licenses 2020-05-28 11:39:17 +02:00
wb9688
e16a2d7cb6 Upgrade jsoup 2020-05-28 11:39:17 +02:00
Arnis Jaundzeikars
f106e2945b Added translation using Weblate (Latvian) 2020-05-27 18:06:40 +02:00
pjammo
1ad7deddb1 Translated using Weblate (Italian)
Currently translated at 99.6% (575 of 577 strings)
2020-05-26 09:52:46 +02:00
wb9688
7b81e98581 Upgrade ACRA 2020-05-25 11:03:07 +02:00
wb9688
0cae58ce8e Upgrade LeakCanary 2020-05-25 11:03:07 +02:00
wb9688
7231150115 Upgrade some dependencies 2020-05-22 15:40:28 +02:00
Hosted Weblate
071986a4c9 Merge branch 'origin/dev' into Weblate. 2020-05-22 11:41:39 +02:00
Rahul Bali
39e7d43f10 Translated using Weblate (Hindi)
Currently translated at 82.4% (476 of 577 strings)
2020-05-22 11:41:37 +02:00
Yaron Shahrabani
aa1b17ae66 Translated using Weblate (Hebrew)
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:37 +02:00
Anonymous
92bae88355 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 29.8% (172 of 577 strings)
2020-05-22 11:41:36 +02:00
Jeff Huang
067eaf363e Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:36 +02:00
Samuel Carvalho de Araújo
00efc266d9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:36 +02:00
Oğuz Ersen
0b014185e3 Translated using Weblate (Turkish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:36 +02:00
zmni
3eee7378de Translated using Weblate (Indonesian)
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:36 +02:00
zeritti
c31428f6bc Translated using Weblate (Czech)
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:35 +02:00
B0pol
163e561cf9 Translated using Weblate (French)
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:35 +02:00
JoC
a32ded2829 Translated using Weblate (Spanish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-22 11:41:35 +02:00
nautilusx
a5b7517fbd Translated using Weblate (German)
Currently translated at 99.8% (576 of 577 strings)
2020-05-22 11:41:34 +02:00
Tobias Groza
176d57b35a Merge pull request #3271 from Stypox/icons
Use vector drawables instead of PNGs for material icons
2020-05-21 19:32:43 +02:00
Stypox
927a1d58e2 Use drop down/up instead of expand icons in drawer
As per the material guidelines
2020-05-21 15:39:36 +02:00
Stypox
bbd0df08d3 Add shadow behind play icon in video detail fragment 2020-05-21 15:39:36 +02:00
Stypox
9e57195e14 Fix checkstyle issues
Also replace all tabs with 4 spaces
2020-05-21 15:39:36 +02:00
Stypox
05ab54c30d Fix invisible fullscreen button in popup player on API 19 2020-05-21 15:39:36 +02:00
Stypox
e3e2028153 Use AppCompatResources instead of ContextCompat 2020-05-21 15:39:36 +02:00
Stypox
883bcc735d Fix pause used instead of play in paused popup player when seeking
Also use `setBackgroundResource` to automatically obtain PNG drawables (from exoplayer)
2020-05-21 15:39:36 +02:00
Stypox
158727e2f2 Replace hardcoded white drawable with themed one 2020-05-21 15:39:36 +02:00
Stypox
899f69d120 Fix additional empty title on tab selection fragments in API 19 2020-05-21 15:39:36 +02:00
Stypox
b575046c05 Fix choice dialogs on API 19 by manually getting drawable 2020-05-21 15:39:36 +02:00
Stypox
b5c60d2be2 Update AndroidX to fix icon crashes in preferences on API 19
Also remove legacy libraries
Use `androidx.preference:preference` instead of `androidx.legacy:legacy-preference-v14` and remove `androidx.legacy:legacy-support-v4
2020-05-21 15:39:36 +02:00
Stypox
631dfee763 Readd ic_close and ic_replay PNGs: needed in notifications
The other icons used in notifications are taken from exoplayer's ones: `@drawable/exo_controls_*`
2020-05-21 15:39:36 +02:00
Stypox
d7f610113e Fix background player queue crashing on opening 2020-05-21 15:39:36 +02:00
Stypox
e0e4f6db2b Fix MainFragment tab icons did not follow theme color 2020-05-21 15:39:36 +02:00
Stypox
c27a26c0aa Rename ic_hot in ic_kiosk_hot and improve getKioskIcon() 2020-05-21 15:39:36 +02:00
Stypox
3dcd2468a2 Use app:srcCompat and derivatives intead of android: ones
To fix crashes on API 19
2020-05-21 15:39:36 +02:00
Stypox
ea43b28f74 Use vector drawables instead of PNGs for material icons
For all manually-created images PNG have been kept.
- rename all icon attrs to have a `ic_` prefix
- always use `_24dp` icons, because there is no real difference, since they are vector drawables
- always use the original name found on material.io for icon drawables, as to not create confusion and possibly duplicates. Icon names can still be different from real drawable names, though I have made some of them compliant to this or maybe more meaningul.
- remove duplicate `getIconByAttr()` in ThemeHelper (use `resolveResourceIdFromAttr()`
- use standard icons for `expand_more` and `expand_less` instead of triangles
- use `play_button_outline` instead of custom PNG as play button in VideoDetailFragment (questionable, as there is no shadow anymore)
2020-05-21 15:39:35 +02:00
bopol
a3e2a085b6 Merge pull request #3501 from B0pol/openInBrowser
Open in browser button now really opens in browser
2020-05-21 09:24:57 +02:00
Anonymous
635d51b60d Translated using Weblate (Hindi)
Currently translated at 82.4% (476 of 577 strings)
2020-05-20 12:31:02 +02:00
Rahul Bali
95eb1c0d95 Translated using Weblate (Hindi)
Currently translated at 82.4% (476 of 577 strings)
2020-05-20 12:31:01 +02:00
Rahul Bali
8aca43a7e6 Translated using Weblate (Hindi)
Currently translated at 81.9% (473 of 577 strings)
2020-05-20 12:25:58 +02:00
Anonymous
f6afe59788 Translated using Weblate (Hindi)
Currently translated at 81.9% (473 of 577 strings)
2020-05-20 12:25:57 +02:00
bopol
8e13161f64 fix checkstyle 2020-05-19 21:57:46 +02:00
bopol
97437b8af3 apply @stypox suggestions 2020-05-19 21:52:30 +02:00
bopol
9a938093e2 Open in browser button now really opens in browser 2020-05-19 21:51:47 +02:00
Hosted Weblate
3edcc9f9fd Merge branch 'origin/dev' into Weblate. 2020-05-19 17:02:04 +02:00
Oymate
55db408720 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 29.6% (171 of 577 strings)
2020-05-19 17:02:04 +02:00
Samuel Carvalho de Araújo
caef874814 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-19 17:02:04 +02:00
Anonymous
1622639eca Translated using Weblate (Bengali (Bangladesh))
Currently translated at 29.6% (171 of 577 strings)
2020-05-19 17:02:00 +02:00
Oymate
4df89f4217 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 29.6% (171 of 577 strings)
2020-05-19 17:02:00 +02:00
Tobias Groza
079b98ed3f Merge pull request #3618 from Stypox/long-press-strings
Improve long-press menu strings
2020-05-19 08:38:00 +02:00
Stypox
a0526d2c9c Improve long-press menu strings 2020-05-19 08:24:04 +02:00
Stypox
169b1cbd32 Merge pull request #3613 from wb9688/fix-download-dialog-freeze
Fix download dialog freeze
2020-05-18 14:11:17 +02:00
wb9688
8968081e77 Remove not needed Checkstyle stuff 2020-05-18 13:40:01 +02:00
wb9688
93ba7510e1 Fix ListHelper ANR 2020-05-18 13:40:01 +02:00
Hosted Weblate
579bb743bb Merge branch 'origin/dev' into Weblate. 2020-05-18 13:17:56 +02:00
Rai Tsa
9e5e9ea612 Translated using Weblate (Finnish)
Currently translated at 58.0% (335 of 577 strings)
2020-05-18 13:17:54 +02:00
Thien Bui
2241a13cba Translated using Weblate (Vietnamese)
Currently translated at 99.8% (576 of 577 strings)
2020-05-18 13:17:54 +02:00
Isak Holmström
9c1fb0cb92 Translated using Weblate (Swedish)
Currently translated at 89.4% (516 of 577 strings)
2020-05-18 13:17:52 +02:00
Anonymous
4904514257 Translated using Weblate (Swedish)
Currently translated at 89.4% (516 of 577 strings)
2020-05-18 13:17:52 +02:00
Samuel Carvalho de Araújo
6d829c26a1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-18 13:17:52 +02:00
Stypox
c05467fb92 Merge pull request #3513 from Stypox/exoplayer
Update to ExoPlayer 2.11.4
2020-05-17 22:02:19 +02:00
Anonymous
d47f7f3348 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-17 00:52:28 +02:00
Samuel Carvalho de Araújo
5931a84651 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-16 04:41:32 +02:00
zeritti
5771783d11 Translated using Weblate (Czech)
Currently translated at 100.0% (577 of 577 strings)
2020-05-16 04:41:32 +02:00
Hosted Weblate
aae07a60bd Merge branch 'origin/dev' into Weblate. 2020-05-14 11:38:10 +02:00
MohammedSR Vevo
462d418ee0 Translated using Weblate (Kurdish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-14 11:38:08 +02:00
Isak Holmström
ce63c2e1db Translated using Weblate (Swedish)
Currently translated at 89.4% (516 of 577 strings)
2020-05-14 11:38:07 +02:00
Isak Holmström
52baf8cbe5 Translated using Weblate (Swedish)
Currently translated at 89.4% (516 of 577 strings)
2020-05-14 11:38:07 +02:00
ssantos
1779b9ee1a Translated using Weblate (Portuguese)
Currently translated at 100.0% (577 of 577 strings)
2020-05-14 11:38:06 +02:00
B0pol
eae169236c Merge pull request #3581 from B0pol/gitignore
Add vscode / eclipse files to gitignore
2020-05-13 18:11:51 +00:00
bopol
ce0efba0d2 gitignore update 2020-05-13 19:54:02 +02:00
Tobias Groza
87c7ac3970 Merge pull request #3580 from wb9688/fix-email
Fix sending e-mail from crash reporter
2020-05-13 17:10:22 +02:00
bopol
0f8d196a52 Add vscode / eclipse files to gitignore 2020-05-13 11:07:02 +02:00
Stypox
6dc7dab154 Merge pull request #3565 from B0pol/retry-button-color
Change retry & subscribe buttons background color based on service color
2020-05-13 11:01:40 +02:00
wb9688
dd4cb23005 Fix sending e-mail from crash reporter 2020-05-13 09:42:08 +02:00
ButterflyOfFire
0589017f8c Translated using Weblate (Arabic)
Currently translated at 100.0% (577 of 577 strings)
2020-05-12 15:41:32 +02:00
bopol
55027a9b2b bump extractor version 2020-05-11 21:41:56 +02:00
bopol
0a984ca8c8 remove duplicate host in manifest 2020-05-11 21:41:56 +02:00
bopol
929d13bfea add support for some peertube instances
The one used for tests, popular instances and user wanted (e.g. la quadrature du net, video.lqdn.fr, or @TheAssassin instance, media-assassinate-you.net)
2020-05-11 21:41:56 +02:00
bopol
375e18bec8 subscribe button now match service's main color 2020-05-11 19:29:34 +02:00
WaldiS
b9de74f183 Translated using Weblate (Polish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-11 08:54:43 +02:00
Oğuz Ersen
08ca69507f Translated using Weblate (Turkish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-11 08:54:42 +02:00
JoC
65ca982342 Translated using Weblate (Spanish)
Currently translated at 100.0% (577 of 577 strings)
2020-05-11 08:54:42 +02:00
Igor Nedoboy
658281a92c Translated using Weblate (Russian)
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 22:59:48 +02:00
Igor Nedoboy
a959f61367 Translated using Weblate (Russian)
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 22:50:30 +02:00
Yaron Shahrabani
8e61f744ec Translated using Weblate (Hebrew)
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:56 +02:00
Jeff Huang
73d3e52e29 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:55 +02:00
Xiang Xu
49a134845c Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:55 +02:00
Samuel Carvalho de Araújo
29807f3d39 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:55 +02:00
WaldiS
29b79b7725 Translated using Weblate (Polish)
Currently translated at 99.8% (576 of 577 strings)
2020-05-10 14:34:54 +02:00
zmni
1cdb10a040 Translated using Weblate (Indonesian)
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:54 +02:00
AioiLight
5f7851df72 Translated using Weblate (Japanese)
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:54 +02:00
winqooq
3d9bc05d7a Translated using Weblate (Russian)
Currently translated at 99.4% (574 of 577 strings)
2020-05-10 14:34:53 +02:00
B0pol
c127428c59 Translated using Weblate (French)
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:53 +02:00
nautilusx
7966d8403a Translated using Weblate (German)
Currently translated at 100.0% (577 of 577 strings)
2020-05-10 14:34:53 +02:00
bopol
80cc8a8e02 color retry button based on service color 2020-05-10 10:54:12 +02:00
Igor Nedoboy
ee4e205fef Translated using Weblate (Russian)
Currently translated at 99.4% (574 of 577 strings)
2020-05-10 02:07:05 +02:00
Hosted Weblate
ea443dc80c Merge branch 'origin/dev' into Weblate. 2020-05-09 16:13:01 +02:00
Senthil Kumar G
283645513d Translated using Weblate (Tamil)
Currently translated at 35.8% (206 of 574 strings)
2020-05-09 16:12:57 +02:00
Anonymous
81b99382b8 Translated using Weblate (Italian)
Currently translated at 100.0% (574 of 574 strings)
2020-05-09 16:12:57 +02:00
random r
ab74465e6c Translated using Weblate (Italian)
Currently translated at 100.0% (574 of 574 strings)
2020-05-09 16:12:57 +02:00
Tobias Groza
b3eadb557b Merge pull request #3337 from AioiLight/blocking-gesture-when-touch-from-navbar
Block the gesture when touch it from NavigationBar or StatusBar.
2020-05-09 10:43:15 +02:00
AioiLight
0abd2bcba6 Clean up code
Follow Checkstyle

Move to PlayerGestureListener from VideoPlayerImpl

Update app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java

Co-authored-by: wb9688 <46277131+wb9688@users.noreply.github.com>
2020-05-09 10:11:46 +02:00
Tobias Groza
9cf76a918e Merge pull request #3430 from Royosef/DisplayParentChannelDetails
Display parent channel details
2020-05-08 23:29:28 +02:00
wb9688
ae437b1510 Bump NewPipeExtractor 2020-05-08 18:07:52 +02:00
wb9688
1096ec1c09 Adjust sub-channel thumbnail size 2020-05-08 15:51:21 +02:00
wb9688
235394d96c Don't show sub-channel thumbnail by default 2020-05-08 15:51:21 +02:00
Anonymous
d25e1d801c Translated using Weblate (Italian)
Currently translated at 99.6% (572 of 574 strings)
2020-05-08 12:35:52 +02:00
random r
2dca5ab966 Translated using Weblate (Italian)
Currently translated at 99.6% (572 of 574 strings)
2020-05-08 12:35:52 +02:00
caltaojihun
89ab57b1c1 Translated using Weblate (Vietnamese)
Currently translated at 94.4% (542 of 574 strings)
2020-05-08 09:56:46 +02:00
caltaojihun
dc66e6a4bf Translated using Weblate (Vietnamese)
Currently translated at 79.7% (458 of 574 strings)
2020-05-08 06:49:55 +02:00
Hosted Weblate
5c2f2fd882 Merge branch 'origin/dev' into Weblate. 2020-05-07 23:24:06 +02:00
MohammedSR Vevo
5c3ddefbf9 Translated using Weblate (Kurdish)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 23:24:04 +02:00
thami simo
a8d3f45ea1 Translated using Weblate (Arabic)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 23:24:03 +02:00
ssantos
cc2c41ddc8 Translated using Weblate (Portuguese)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 23:24:03 +02:00
wb9688
b990f30a09 Merge pull request #3545 from Stypox/kore
Fix Kodi button showing up with unsupported services
2020-05-07 21:44:09 +02:00
Stypox
5c711322d4 In player hide kodi button if service unsupported 2020-05-07 21:11:34 +02:00
Roy Yosef
b7d4a4f604 Navigate to parent(uploader) channel from the uploader section on long click 2020-05-07 20:40:17 +02:00
Roy Yosef
cc8874b687 Fix PR review
Make all of the uploader section on stream page navigate to the channel page
Extract hard coded strings
Remove redundant spaces
Fix open streams from a channel
Rename "ParentChannel" to "SubChannel"
Config royosef:NewPipeExtractor in app/build.gradle
2020-05-07 20:40:17 +02:00
Roy Yosef
2d0bc05488 Add sub-channel details to channel fragment 2020-05-07 20:39:32 +02:00
Roy Yosef
1429774487 Add sub-channel details to video detail fragment 2020-05-07 20:39:32 +02:00
wb9688
2060312dc1 Merge pull request #3466 from B0pol/soundCloudComments
adapt CommentsInfoItemExtractorRefactoring
2020-05-07 16:06:15 +02:00
bopol
8b6728480f bump extractor version 2020-05-07 15:46:41 +02:00
Anxhelo Lushka
cecafdee29 Translated using Weblate (Albanian)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:28 +02:00
Senthil Kumar G
05f2af25af Translated using Weblate (Tamil)
Currently translated at 35.7% (205 of 574 strings)
2020-05-07 13:58:27 +02:00
Yaron Shahrabani
e3d826f6c4 Translated using Weblate (Hebrew)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:27 +02:00
Jeff Huang
02430bed90 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:27 +02:00
Samuel Carvalho de Araújo
3f7005ed9a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:26 +02:00
WaldiS
586ee75833 Translated using Weblate (Polish)
Currently translated at 99.8% (573 of 574 strings)
2020-05-07 13:58:26 +02:00
Emin Tufan Çetin
1d903f11a8 Translated using Weblate (Turkish)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:25 +02:00
Muhammad Mauli Mubassari
578159b95c Translated using Weblate (Indonesian)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:25 +02:00
zeritti
5c95587284 Translated using Weblate (Czech)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:25 +02:00
Marian Hanzel
f7e9227ad2 Translated using Weblate (Slovak)
Currently translated at 94.0% (540 of 574 strings)
2020-05-07 13:58:24 +02:00
AioiLight
c11a4d6867 Translated using Weblate (Japanese)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:23 +02:00
Anonymous
6c2b0448a4 Translated using Weblate (Dutch)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:23 +02:00
Terry Louwers
bd0eb8cccf Translated using Weblate (Dutch)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:23 +02:00
nautilusx
09bb043952 Translated using Weblate (German)
Currently translated at 100.0% (574 of 574 strings)
2020-05-07 13:58:22 +02:00
Stypox
9ca6cfd637 Fix Kodi button showing up in unsupported services 2020-05-06 20:55:53 +02:00
Igor Nedoboy
3869a66fcc Translated using Weblate (Russian)
Currently translated at 100.0% (574 of 574 strings)
2020-05-06 13:33:14 +02:00
bopol
d1c94f5120 adapt CommentsInfoItemExtractorRefactoring 2020-05-05 15:03:59 +02:00
Igor Nedoboy
c55e9941ec Translated using Weblate (Russian)
Currently translated at 100.0% (574 of 574 strings)
2020-05-05 08:21:44 +02:00
Hosted Weblate
fa9a419d73 Merge branch 'origin/dev' into Weblate. 2020-05-04 17:49:25 +02:00
Software In Interlingua
ab4e0da6b4 Translated using Weblate (Interlingua)
Currently translated at 39.7% (228 of 573 strings)
2020-05-04 17:49:22 +02:00
Igor Nedoboy
073572681e Translated using Weblate (Russian)
Currently translated at 100.0% (573 of 573 strings)
2020-05-04 17:49:21 +02:00
wb9688
b630f269c4 Merge pull request #3511 from wb9688/ktlint
Ktlint
2020-05-04 15:13:07 +02:00
wb9688
40b1cd82b1 Merge pull request #2727 from vnagel/ageRestrictedContent
Restricted mode setting for youtube
2020-05-04 15:05:11 +02:00
Tobias Groza
abcbdef63b Merge pull request #3502 from wb9688/allow-translation
Allow time ago debug setting translation
2020-05-03 23:25:54 +02:00
Hosted Weblate
56d53d8805 Merge branch 'origin/dev' into Weblate. 2020-05-03 15:40:40 +02:00
Oymate
7433fe049c Translated using Weblate (Bengali (Bangladesh))
Currently translated at 29.6% (170 of 573 strings)
2020-05-03 15:40:40 +02:00
postsorino
bb2be49d3b Translated using Weblate (Greek)
Currently translated at 85.1% (488 of 573 strings)
2020-05-03 15:40:40 +02:00
Bruno Guerreiro
cd1b578e84 Translated using Weblate (Portuguese)
Currently translated at 100.0% (573 of 573 strings)
2020-05-03 15:40:40 +02:00
winqooq
c3df9b4105 Translated using Weblate (Russian)
Currently translated at 99.4% (570 of 573 strings)
2020-05-03 15:40:37 +02:00
wb9688
b0415a5289 Auto-format using Ktlint 2020-05-01 20:13:21 +02:00
wb9688
ff7344438b Optimize imports 2020-05-01 20:13:01 +02:00
wb9688
f5f8e5d279 Add Ktlint 2020-05-01 20:09:52 +02:00
Stypox
b4ddc8f96c Update to ExoPlayer 2.11.4 2020-05-01 15:03:54 +02:00
wb9688
8ebb1e29fa Allow time ago debug setting translation 2020-04-29 14:47:06 +02:00
Vincent Nagel
72c9845174 removed unnecessary method call 2020-04-25 21:39:53 -05:00
TheLastGimbus
1975973ff2 Update progress bar on fast forward/rewind 2020-04-25 15:46:56 +02:00
Vincent Nagel
f60cce54ea rename setting to "YouTube restricted mode" 2020-04-20 21:47:32 -05:00
Vincent Nagel
63087a4311 renamed to "restricted mode" 2020-04-20 21:46:40 -05:00
Vincent Nagel
5a193d50f6 remove duplicate line 2020-04-20 21:45:32 -05:00
Vincent Nagel
08a6e999b9 fix checkstyle errors 2020-04-20 21:45:32 -05:00
Vincent Nagel
e33cdca1ef added logging when context null in onPrefTreeClick 2020-04-20 21:45:32 -05:00
Vincent Nagel
9ede7a3c42 setupTabs() if ageRestrictedContent pref changed 2020-04-20 21:45:32 -05:00
Vincent Nagel
430d4e1ccd ageRestrictedContent cookie only sent for youtube
Now the age restricted content cookie is only sent when sending a
request to youtube. There's no need to remove the cookie when the
service changes because whether to add the cookie is determined by
looking at the url the request is being sent to.
2020-04-20 21:45:32 -05:00
Vincent Nagel
de4d6037d3 ageRestrictedContent first draft
Cookie updated whenever ageRestrictedContent setting is changed or
service is changed. Right now there is only a cookie for youtube, but
cookies for other services could be added in the future.

Problems with this approach: Even when the service is set to youtube,
the downloader doesn't only request youtube urls e.g. it also sends
reqeusts to i.ytimg.com, suggestqueries.google.com, and yt3.ggpht.com.
The ageRestrictedContent cookie is not normally sent when sending
requests to these other urls, so doing so might have unknown effects.
2020-04-20 21:45:32 -05:00
TheLastGimbus
63afacc067 Add listeners in activity 2020-04-14 22:06:32 +02:00
TheLastGimbus
3175199787 Add fast-rewind/forward buttons in layout 2020-04-14 22:05:51 +02:00
AioiLight
562754c0b9 Merge branch 'dev' into blocking-gesture-when-touch-from-navbar 2020-04-04 23:52:06 +09:00
AioiLight
d9c6f7acb6 Block the gesture when touch it from NavigationBar or StatusBar. 2020-04-03 05:11:36 +09:00
962 changed files with 7825 additions and 4405 deletions

8
.gitignore vendored
View File

@@ -10,3 +10,11 @@
*~
.weblate
*.class
# vscode / eclipse files
*.classpath
*.project
*.settings
bin/
.vscode/
*.code-workspace

View File

@@ -5,13 +5,13 @@ android:
components:
# The BuildTools version used by NewPipe
- tools
- build-tools-28.0.3
- build-tools-29.0.3
# The SDK version used to compile NewPipe
- android-28
- android-29
before_install:
- yes | sdkmanager "platforms;android-28"
- yes | sdkmanager "platforms;android-29"
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
licenses:

View File

@@ -5,16 +5,16 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'checkstyle'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion 29
buildToolsVersion '29.0.3'
defaultConfig {
applicationId "org.schabi.newpipe"
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 28
versionCode 930
versionName "0.19.3"
targetSdkVersion 29
versionCode 951
versionName "0.19.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@@ -66,6 +66,7 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
encoding 'utf-8'
}
// Required and used only by groupie
@@ -79,22 +80,27 @@ android {
}
ext {
androidxLibVersion = '1.0.0'
exoPlayerLibVersion = '2.10.8'
roomDbLibVersion = '2.1.0'
leakCanaryLibVersion = '1.5.4' //1.6.1
okHttpLibVersion = '3.12.6'
icepickLibVersion = '3.2.0'
stethoLibVersion = '1.5.0'
markwonVersion = '4.2.1'
checkstyleVersion = '8.31'
icepickVersion = '3.2.0'
checkstyleVersion = '8.32'
stethoVersion = '1.5.1'
leakCanaryVersion = '2.2'
exoPlayerVersion = '2.11.6'
androidxLifecycleVersion = '2.2.0'
androidxRoomVersion = '2.2.5'
groupieVersion = '2.8.0'
markwonVersion = '4.3.1'
}
configurations {
checkstyle
ktlint
}
checkstyle {
configFile rootProject.file('checkstyle.xml')
ignoreFailures false
showViolations true
toolVersion = "${checkstyleVersion}"
toolVersion = checkstyleVersion
}
task runCheckstyle(type: Checkstyle) {
@@ -105,8 +111,7 @@ task runCheckstyle(type: Checkstyle) {
exclude '**/BuildConfig.java'
exclude 'main/java/us/shandian/giga/**'
// empty classpath
classpath = files()
classpath = configurations.checkstyle
showViolations true
@@ -116,88 +121,92 @@ task runCheckstyle(type: Checkstyle) {
}
}
tasks.withType(Checkstyle).each {
checkstyleTask -> checkstyleTask.doLast {
reports.all { report ->
def outputFile = report.destination
if (outputFile.exists() && outputFile.text.contains("severity=\"error\"")) {
throw new GradleException("There were checkstyle errors! For more info check $outputFile")
}
}
}
task runKtlint(type: JavaExec) {
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "src/**/*.kt"
}
task formatKtlint(type: JavaExec) {
main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint
args "-F", "src/**/*.kt"
}
afterEvaluate {
preDebugBuild.dependsOn runCheckstyle
preDebugBuild.dependsOn runCheckstyle, runKtlint
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
implementation "frankiesardo:icepick:${icepickVersion}"
kapt "frankiesardo:icepick-processor:${icepickVersion}"
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
ktlint "com.pinterest:ktlint:0.35.0"
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
debugImplementation "androidx.multidex:multidex:2.0.1"
testImplementation 'junit:junit:4.13'
testImplementation 'org.mockito:mockito-core:3.3.3'
androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", {
exclude module: 'support-annotations'
})
}
implementation 'com.github.TeamNewPipe:NewPipeExtractor:fc3a69ed54b393e3e4e3a78ae6e89edc1d47c45a'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.23.0'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:a70cb0283ffc3bba2709815673a5a7940aab0a3a'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation "androidx.legacy:legacy-support-v4:${androidxLibVersion}"
implementation "com.google.android.material:material:${androidxLibVersion}"
implementation "androidx.recyclerview:recyclerview:${androidxLibVersion}"
implementation "androidx.legacy:legacy-preference-v14:${androidxLibVersion}"
implementation "androidx.cardview:cardview:${androidxLibVersion}"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1"
implementation 'com.xwray:groupie:2.7.0'
implementation 'com.xwray:groupie-kotlin-android-extensions:2.7.0'
implementation "com.squareup.okhttp3:okhttp:3.12.11"
implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
// Originally in NewPipeExtractor
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'org.jsoup:jsoup:1.9.2'
implementation "com.google.android.material:material:1.1.0"
implementation 'ch.acra:acra:4.9.2' //4.11
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.nononsenseapps:filepicker:4.2.1'
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerLibVersion}"
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
debugImplementation 'androidx.multidex:multidex:2.0.1'
implementation "com.xwray:groupie:${groupieVersion}"
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
implementation 'org.ocpsoft.prettytime:prettytime:4.0.3.Final'
implementation "androidx.room:room-runtime:${roomDbLibVersion}"
implementation "androidx.room:room-rxjava2:${roomDbLibVersion}"
kapt "androidx.room:room-compiler:${roomDbLibVersion}"
implementation "frankiesardo:icepick:${icepickLibVersion}"
kapt "frankiesardo:icepick-processor:${icepickLibVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}"
implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"
implementation "de.hdodenhof:circleimageview:3.1.0"
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
implementation "io.noties.markwon:core:${markwonVersion}"
implementation "io.noties.markwon:linkify:${markwonVersion}"
implementation "com.nononsenseapps:filepicker:4.2.1"
implementation "ch.acra:acra-core:5.5.0"
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
implementation "org.ocpsoft.prettytime:prettytime:4.0.5.Final"
}
static String getGitWorkingBranch() {

View File

@@ -116,4 +116,4 @@ class AppDatabaseTest {
testHelper.closeWhenFinished(database)
return database
}
}
}

View File

@@ -1,107 +0,0 @@
package org.schabi.newpipe;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.multidex.MultiDex;
import com.facebook.stetho.Stetho;
import com.facebook.stetho.okhttp3.StethoInterceptor;
import com.squareup.leakcanary.AndroidHeapDumper;
import com.squareup.leakcanary.DefaultLeakDirectoryProvider;
import com.squareup.leakcanary.HeapDumper;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.LeakDirectoryProvider;
import com.squareup.leakcanary.RefWatcher;
import org.schabi.newpipe.extractor.downloader.Downloader;
import java.io.File;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
public class DebugApp extends App {
private static final String TAG = DebugApp.class.toString();
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
@Override
public void onCreate() {
super.onCreate();
initStetho();
}
@Override
protected Downloader getDownloader() {
DownloaderImpl downloader = DownloaderImpl.init(new OkHttpClient.Builder()
.addNetworkInterceptor(new StethoInterceptor()));
setCookiesToDownloader(downloader);
return downloader;
}
private void initStetho() {
// Create an InitializerBuilder
Stetho.InitializerBuilder initializerBuilder =
Stetho.newInitializerBuilder(this);
// Enable Chrome DevTools
initializerBuilder.enableWebKitInspector(
Stetho.defaultInspectorModulesProvider(this)
);
// Enable command line interface
initializerBuilder.enableDumpapp(
Stetho.defaultDumperPluginsProvider(getApplicationContext())
);
// Use the InitializerBuilder to generate an Initializer
Stetho.Initializer initializer = initializerBuilder.build();
// Initialize Stetho with the Initializer
Stetho.initialize(initializer);
}
@Override
protected boolean isDisposedRxExceptionsReported() {
return PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false);
}
@Override
protected RefWatcher installLeakCanary() {
return LeakCanary.refWatcher(this)
.heapDumper(new ToggleableHeapDumper(this))
// give each object 10 seconds to be gc'ed, before leak canary gets nosy on it
.watchDelay(10, TimeUnit.SECONDS)
.buildAndInstall();
}
public static class ToggleableHeapDumper implements HeapDumper {
private final HeapDumper dumper;
private final SharedPreferences preferences;
private final String dumpingAllowanceKey;
ToggleableHeapDumper(@NonNull final Context context) {
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
this.dumper = new AndroidHeapDumper(context, leakDirectoryProvider);
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
this.dumpingAllowanceKey = context.getString(R.string.allow_heap_dumping_key);
}
private boolean isDumpingAllowed() {
return preferences.getBoolean(dumpingAllowanceKey, false);
}
@Override
public File dumpHeap() {
return isDumpingAllowed() ? dumper.dumpHeap() : HeapDumper.RETRY_LATER;
}
}
}

View File

@@ -0,0 +1,59 @@
package org.schabi.newpipe
import android.content.Context
import androidx.multidex.MultiDex
import androidx.preference.PreferenceManager
import com.facebook.stetho.Stetho
import com.facebook.stetho.okhttp3.StethoInterceptor
import leakcanary.AppWatcher
import leakcanary.LeakCanary
import okhttp3.OkHttpClient
import org.schabi.newpipe.extractor.downloader.Downloader
class DebugApp : App() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
initStetho()
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
LeakCanary.config = LeakCanary.config.copy(dumpHeap = PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(getString(
R.string.allow_heap_dumping_key), false))
}
override fun getDownloader(): Downloader {
val downloader = DownloaderImpl.init(OkHttpClient.Builder()
.addNetworkInterceptor(StethoInterceptor()))
setCookiesToDownloader(downloader)
return downloader
}
private fun initStetho() {
// Create an InitializerBuilder
val initializerBuilder = Stetho.newInitializerBuilder(this)
// Enable Chrome DevTools
initializerBuilder.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
// Enable command line interface
initializerBuilder.enableDumpapp(
Stetho.defaultDumperPluginsProvider(applicationContext))
// Use the InitializerBuilder to generate an Initializer
val initializer = initializerBuilder.build()
// Initialize Stetho with the Initializer
Stetho.initialize(initializer)
}
override fun isDisposedRxExceptionsReported(): Boolean {
return PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
}
}

View File

@@ -1,25 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.schabi.newpipe">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<application
android:banner="@mipmap/newpipe_tv_banner"
android:name=".App"
android:allowBackup="true"
android:banner="@mipmap/newpipe_tv_banner"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:theme="@style/OpeningTheme"
tools:ignore="AllowBackup">
<activity
@@ -27,13 +29,14 @@
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
@@ -49,36 +52,36 @@
<activity
android:name=".player.BackgroundPlayerActivity"
android:launchMode="singleTask"
android:label="@string/title_activity_background_player"/>
android:label="@string/title_activity_background_player"
android:launchMode="singleTask" />
<activity
android:name=".player.PopupVideoPlayerActivity"
android:launchMode="singleTask"
android:label="@string/title_activity_popup_player"/>
android:label="@string/title_activity_popup_player"
android:launchMode="singleTask" />
<service
android:name=".player.PopupVideoPlayer"
android:exported="false"/>
android:exported="false" />
<activity
android:name=".player.MainVideoPlayer"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/VideoPlayerTheme"/>
android:theme="@style/VideoPlayerTheme" />
<activity
android:name=".settings.SettingsActivity"
android:label="@string/settings"/>
android:label="@string/settings" />
<activity
android:name=".about.AboutActivity"
android:label="@string/title_activity_about"/>
android:label="@string/title_activity_about" />
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
<service android:name=".local.feed.service.FeedLoadService"/>
<service android:name=".local.subscription.services.SubscriptionsImportService" />
<service android:name=".local.subscription.services.SubscriptionsExportService" />
<service android:name=".local.feed.service.FeedLoadService" />
<activity
android:name=".PanicResponderActivity"
@@ -86,25 +89,25 @@
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ExitActivity"
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay"/>
<activity android:name=".report.ErrorActivity"/>
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".report.ErrorActivity" />
<!-- giga get related -->
<activity
android:name=".download.DownloadActivity"
android:label="@string/app_name"
android:launchMode="singleTask"/>
android:launchMode="singleTask" />
<service android:name="us.shandian.giga.service.DownloadManagerService"/>
<service android:name="us.shandian.giga.service.DownloadManagerService" />
<activity
android:name=".util.FilePickerActivityHelper"
@@ -118,7 +121,7 @@
<activity
android:name=".ReCaptchaActivity"
android:label="@string/recaptcha"/>
android:label="@string/recaptcha" />
<provider
android:name="androidx.core.content.FileProvider"
@@ -127,7 +130,7 @@
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/nnf_provider_paths"/>
android:resource="@xml/nnf_provider_paths" />
</provider>
<activity
@@ -139,165 +142,197 @@
<!-- Youtube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtube.com"/>
<data android:host="m.youtube.com"/>
<data android:host="www.youtube.com"/>
<data android:host="music.youtube.com"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtube.com" />
<data android:host="m.youtube.com" />
<data android:host="www.youtube.com" />
<data android:host="music.youtube.com" />
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<data android:pathPrefix="/attribution_link"/>
<data android:pathPrefix="/v/" />
<data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" />
<data android:pathPrefix="/attribution_link" />
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
<data android:pathPrefix="/c/"/>
<data android:pathPrefix="/channel/" />
<data android:pathPrefix="/user/" />
<data android:pathPrefix="/c/" />
<!-- playlist prefix -->
<data android:pathPrefix="/playlist"/>
<data android:pathPrefix="/playlist" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtu.be"/>
<data android:pathPrefix="/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtu.be" />
<data android:pathPrefix="/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="www.youtube-nocookie.com"/>
<data android:pathPrefix="/embed/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="www.youtube-nocookie.com" />
<data android:pathPrefix="/embed/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
<data android:scheme="vnd.youtube" />
<data android:scheme="vnd.youtube.launch" />
</intent-filter>
<!-- Hooktube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="hooktube.com"/>
<data android:host="*.hooktube.com"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="hooktube.com" />
<data android:host="*.hooktube.com" />
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<data android:pathPrefix="/v/" />
<data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" />
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
<data android:pathPrefix="/channel/" />
<data android:pathPrefix="/user/" />
</intent-filter>
<!-- Invidious filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="invidio.us"/>
<data android:host="dev.invidio.us"/>
<data android:host="www.invidio.us"/>
<data android:host="invidious.snopyta.org"/>
<data android:host="de.invidious.snopyta.org"/>
<data android:host="fi.invidious.snopyta.org"/>
<data android:host="vid.wxzm.sx"/>
<data android:host="invidious.kabi.tk"/>
<data android:host="invidiou.sh"/>
<data android:host="www.invidiou.sh"/>
<data android:host="no.invidiou.sh"/>
<data android:host="invidious.enkirton.net"/>
<data android:host="tube.poal.co"/>
<data android:host="invidious.13ad.de"/>
<data android:host="yt.elukerio.org"/>
<data android:pathPrefix="/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="invidio.us" />
<data android:host="dev.invidio.us" />
<data android:host="www.invidio.us" />
<data android:host="invidious.snopyta.org" />
<data android:host="fi.invidious.snopyta.org" />
<data android:host="yewtu.be" />
<data android:host="invidious.ggc-project.de" />
<data android:host="yt.maisputain.ovh" />
<data android:host="invidious.13ad.de" />
<data android:host="invidious.toot.koeln" />
<data android:host="invidious.fdn.fr" />
<data android:host="watch.nettohikari.com" />
<data android:host="invidious.snwmds.net" />
<data android:host="invidious.snwmds.org" />
<data android:host="invidious.snwmds.com" />
<data android:host="invidious.sunsetravens.com" />
<data android:host="invidious.gachirangers.com" />
<data android:pathPrefix="/" />
</intent-filter>
<!-- Soundcloud filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="soundcloud.com"/>
<data android:host="m.soundcloud.com"/>
<data android:host="www.soundcloud.com"/>
<data android:pathPrefix="/"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="soundcloud.com" />
<data android:host="m.soundcloud.com" />
<data android:host="www.soundcloud.com" />
<data android:pathPrefix="/" />
</intent-filter>
<!-- Share filter -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<!-- MediaCCC filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="media.ccc.de"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="media.ccc.de" />
<!-- video prefix -->
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/v/" />
<!-- channel prefix-->
<data android:pathPrefix="/c/"/>
<data android:pathPrefix="/b/"/>
<data android:pathPrefix="/c/" />
<data android:pathPrefix="/b/" />
</intent-filter>
<!-- PeerTube filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="framatube.org" />
<data android:host="media.assassinate-you.net" />
<data android:host="peertube.co.uk" />
<data android:host="peertube.cpy.re" />
<data android:host="peertube.mastodon.host" />
<data android:host="peertube.fr" />
<data android:host="peertube.live" />
<data android:host="peertube.video" />
<data android:host="tube.privacytools.io" />
<data android:host="video.ploud.fr" />
<data android:host="video.lqdn.fr" />
<data android:host="skeptikon.fr" />
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
<data android:pathPrefix="/accounts/" />
<data android:pathPrefix="/video-channels/" />
</intent-filter>
</activity>
<service
android:name=".RouterActivity$FetcherService"
android:exported="false"/>
android:exported="false" />
</application>
</manifest>

View File

@@ -9,19 +9,16 @@ import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import org.acra.ACRA;
import org.acra.config.ACRAConfiguration;
import org.acra.config.ACRAConfigurationException;
import org.acra.config.ConfigurationBuilder;
import org.acra.config.CoreConfiguration;
import org.acra.config.CoreConfigurationBuilder;
import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
@@ -72,13 +69,6 @@ public class App extends Application {
private static final Class<? extends ReportSenderFactory>[]
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
private static App app;
private RefWatcher refWatcher;
@Nullable
public static RefWatcher getRefWatcher(final Context context) {
final App application = (App) context.getApplicationContext();
return application.refWatcher;
}
public static App getApp() {
return app;
@@ -95,13 +85,6 @@ public class App extends Application {
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
refWatcher = installLeakCanary();
app = this;
// Initialize settings first because others inits can use its values
@@ -136,7 +119,8 @@ public class App extends Application {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
getApplicationContext());
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
downloader.setCookies(prefs.getString(key, ""));
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, ""));
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
}
private void configureRxJavaErrorHandler() {
@@ -218,7 +202,7 @@ public class App extends Application {
private void initACRA() {
try {
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
.setBuildConfigClass(BuildConfig.class)
.build();
@@ -279,10 +263,6 @@ public class App extends Application {
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
}
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
protected boolean isDisposedRxExceptionsReported() {
return false;
}

View File

@@ -11,10 +11,10 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.squareup.leakcanary.RefWatcher;
import icepick.Icepick;
import icepick.State;
import leakcanary.AppWatcher;
public abstract class BaseFragment extends Fragment {
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
@@ -78,16 +78,14 @@ public abstract class BaseFragment extends Fragment {
Icepick.saveInstanceState(this, outState);
}
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { }
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
}
@Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = App.getRefWatcher(getActivity());
if (refWatcher != null) {
refWatcher.watch(this);
}
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
}
@Override
@@ -100,9 +98,11 @@ public abstract class BaseFragment extends Fragment {
// Init
//////////////////////////////////////////////////////////////////////////*/
protected void initViews(final View rootView, final Bundle savedInstanceState) { }
protected void initViews(final View rootView, final Bundle savedInstanceState) {
}
protected void initListeners() { }
protected void initListeners() {
}
/*//////////////////////////////////////////////////////////////////////////
// Utils

View File

@@ -1,7 +1,8 @@
package org.schabi.newpipe;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -10,6 +11,8 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request;
import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.util.CookieUtils;
import org.schabi.newpipe.util.InfoCache;
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
import java.io.IOException;
@@ -20,6 +23,7 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -40,9 +44,13 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
public final class DownloaderImpl extends Downloader {
public static final String USER_AGENT
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY
= "youtube_restricted_mode_key";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
public static final String YOUTUBE_DOMAIN = "youtube.com";
private static DownloaderImpl instance;
private String mCookies;
private Map<String, String> mCookies;
private OkHttpClient client;
private DownloaderImpl(final OkHttpClient.Builder builder) {
@@ -54,6 +62,7 @@ public final class DownloaderImpl extends Downloader {
// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
// 16 * 1024 * 1024))
.build();
this.mCookies = new HashMap<>();
}
/**
@@ -121,12 +130,50 @@ public final class DownloaderImpl extends Downloader {
}
}
public String getCookies() {
return mCookies;
public String getCookies(final String url) {
List<String> resultCookies = new ArrayList<>();
if (url.contains(YOUTUBE_DOMAIN)) {
String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
if (youtubeCookie != null) {
resultCookies.add(youtubeCookie);
}
}
// Recaptcha cookie is always added TODO: not sure if this is necessary
String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
if (recaptchaCookie != null) {
resultCookies.add(recaptchaCookie);
}
return CookieUtils.concatCookies(resultCookies);
}
public void setCookies(final String cookies) {
mCookies = cookies;
public String getCookie(final String key) {
return mCookies.get(key);
}
public void setCookie(final String key, final String cookie) {
mCookies.put(key, cookie);
}
public void removeCookie(final String key) {
mCookies.remove(key);
}
public void updateYoutubeRestrictedModeCookies(final Context context) {
String restrictedModeEnabledKey =
context.getString(R.string.youtube_restricted_mode_enabled);
boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(restrictedModeEnabledKey, false);
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
}
public void updateYoutubeRestrictedModeCookies(final boolean youtubeRestrictedModeEnabled) {
if (youtubeRestrictedModeEnabled) {
setCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY,
YOUTUBE_RESTRICTED_MODE_COOKIE);
} else {
removeCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
}
InfoCache.getInstance().clearCache();
}
/**
@@ -152,8 +199,9 @@ public final class DownloaderImpl extends Downloader {
.method("GET", null).url(siteUrl)
.addHeader("User-Agent", USER_AGENT);
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
String cookies = getCookies(siteUrl);
if (!cookies.isEmpty()) {
requestBuilder.addHeader("Cookie", cookies);
}
final okhttp3.Request request = requestBuilder.build();
@@ -192,8 +240,9 @@ public final class DownloaderImpl extends Downloader {
.method(httpMethod, requestBody).url(url)
.addHeader("User-Agent", USER_AGENT);
if (!TextUtils.isEmpty(mCookies)) {
requestBuilder.addHeader("Cookie", mCookies);
String cookies = getCookies(url);
if (!cookies.isEmpty()) {
requestBuilder.addHeader("Cookie", cookies);
}
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {

View File

@@ -34,8 +34,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
@@ -127,12 +125,6 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = getWindow();
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
if (getSupportFragmentManager() != null
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
@@ -165,7 +157,7 @@ public class MainActivity extends AppCompatActivity {
drawerItems.getMenu()
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this));
.setIcon(KioskTranslator.getKioskIcon(ks, this));
kioskId++;
}
@@ -175,24 +167,24 @@ public class MainActivity extends AppCompatActivity {
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
//Settings and About
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
R.string.drawer_close);
@@ -420,7 +412,7 @@ public class MainActivity extends AppCompatActivity {
drawerItems.getMenu()
.add(R.id.menu_tabs_group, kioskId, ORDER,
KioskTranslator.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcons(ks, this));
.setIcon(KioskTranslator.getKioskIcon(ks, this));
kioskId++;
}
@@ -429,24 +421,24 @@ public class MainActivity extends AppCompatActivity {
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
drawerItems.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
//Settings and About
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
drawerItems.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
}
@Override

View File

@@ -51,6 +51,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
public static final String TAG = ReCaptchaActivity.class.toString();
public static final String YT_URL = "https://www.youtube.com";
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
private WebView webView;
private String foundCookies = "";
@@ -168,7 +169,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
prefs.edit().putString(key, foundCookies).apply();
// give cookies to Downloader class
DownloaderImpl.getInstance().setCookies(foundCookies);
DownloaderImpl.getInstance().setCookie(RECAPTCHA_COOKIES_KEY, foundCookies);
setResult(RESULT_OK);
}

View File

@@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.NotificationCompat;
import androidx.fragment.app.FragmentManager;
@@ -43,15 +44,15 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
import org.schabi.newpipe.util.urlfinder.UrlFinder;
import org.schabi.newpipe.views.FocusOverlayView;
import java.io.Serializable;
import java.util.ArrayList;
@@ -313,7 +314,9 @@ public class RouterActivity extends AppCompatActivity {
final RadioButton radioButton
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
radioButton.setText(item.description);
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
radioButton.setCompoundDrawablesWithIntrinsicBounds(
AppCompatResources.getDrawable(getApplicationContext(), item.icon),
null, null, null);
radioButton.setChecked(false);
radioButton.setId(id++);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
@@ -366,26 +369,26 @@ public class RouterActivity extends AppCompatActivity {
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
getString(R.string.show_info),
resolveResourceIdFromAttr(context, R.attr.info)));
resolveResourceIdFromAttr(context, R.attr.ic_info_outline)));
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
getString(R.string.video_player),
resolveResourceIdFromAttr(context, R.attr.play)));
resolveResourceIdFromAttr(context, R.attr.ic_play_arrow)));
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
getString(R.string.popup_player),
resolveResourceIdFromAttr(context, R.attr.popup)));
resolveResourceIdFromAttr(context, R.attr.ic_popup)));
}
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
getString(R.string.background_player),
resolveResourceIdFromAttr(context, R.attr.audio)));
resolveResourceIdFromAttr(context, R.attr.ic_headset)));
}
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
getString(R.string.download),
resolveResourceIdFromAttr(context, R.attr.download)));
resolveResourceIdFromAttr(context, R.attr.ic_file_download)));
return returnList;
}

View File

@@ -1,8 +1,6 @@
package org.schabi.newpipe.about;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -26,6 +24,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static org.schabi.newpipe.util.ShareUtils.openUrlInBrowser;
public class AboutActivity extends AppCompatActivity {
/**
@@ -143,28 +142,23 @@ public class AboutActivity extends AppCompatActivity {
View githubLink = rootView.findViewById(R.id.github_link);
githubLink.setOnClickListener(nv ->
openWebsite(context.getString(R.string.github_url), context));
openUrlInBrowser(context, context.getString(R.string.github_url)));
View donationLink = rootView.findViewById(R.id.donation_link);
donationLink.setOnClickListener(v ->
openWebsite(context.getString(R.string.donation_url), context));
openUrlInBrowser(context, context.getString(R.string.donation_url)));
View websiteLink = rootView.findViewById(R.id.website_link);
websiteLink.setOnClickListener(nv ->
openWebsite(context.getString(R.string.website_url), context));
openUrlInBrowser(context, context.getString(R.string.website_url)));
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
privacyPolicyLink.setOnClickListener(v ->
openWebsite(context.getString(R.string.privacy_policy_url), context));
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
return rootView;
}
private void openWebsite(final String url, final Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
}
}
/**

View File

@@ -2,8 +2,6 @@ package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
@@ -17,9 +15,9 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ShareUtils;
import java.util.Arrays;
import java.util.Comparator;
/**
* Fragment containing the software licenses.
@@ -27,7 +25,7 @@ import java.util.Comparator;
public class LicenseFragment extends Fragment {
private static final String ARG_COMPONENTS = "components";
private SoftwareComponent[] softwareComponents;
private SoftwareComponent mComponentForContextMenu;
private SoftwareComponent componentForContextMenu;
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
if (softwareComponents == null) {
@@ -46,7 +44,7 @@ public class LicenseFragment extends Fragment {
* @param context the context to use
* @param license the license to show
*/
public static void showLicense(final Context context, final License license) {
private static void showLicense(final Context context, final License license) {
new LicenseFragmentHelper((Activity) context).execute(license);
}
@@ -57,45 +55,34 @@ public class LicenseFragment extends Fragment {
.getParcelableArray(ARG_COMPONENTS);
// Sort components by name
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
@Override
public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
return o1.getName().compareTo(o2.getName());
}
});
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
}
@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
final View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
View licenseLink = rootView.findViewById(R.id.app_read_license);
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
final View licenseLink = rootView.findViewById(R.id.app_read_license);
licenseLink.setOnClickListener(v ->
showLicense(getActivity(), StandardLicenses.GPL3));
for (final SoftwareComponent component : softwareComponents) {
View componentView = inflater
final View componentView = inflater
.inflate(R.layout.item_software_component, container, false);
TextView softwareName = componentView.findViewById(R.id.name);
TextView copyright = componentView.findViewById(R.id.copyright);
final TextView softwareName = componentView.findViewById(R.id.name);
final TextView copyright = componentView.findViewById(R.id.copyright);
softwareName.setText(component.getName());
copyright.setText(getContext().getString(R.string.copyright,
copyright.setText(getString(R.string.copyright,
component.getYears(),
component.getCopyrightOwner(),
component.getLicense().getAbbreviation()));
componentView.setTag(component);
componentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
Context context = v.getContext();
if (context != null) {
showLicense(context, component.getLicense());
}
}
});
componentView.setOnClickListener(v ->
showLicense(getActivity(), component.getLicense()));
softwareComponentsView.addView(componentView);
registerForContextMenu(componentView);
}
@@ -105,40 +92,28 @@ public class LicenseFragment extends Fragment {
@Override
public void onCreateContextMenu(final ContextMenu menu, final View v,
final ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflater = getActivity().getMenuInflater();
SoftwareComponent component = (SoftwareComponent) v.getTag();
final MenuInflater inflater = getActivity().getMenuInflater();
final SoftwareComponent component = (SoftwareComponent) v.getTag();
menu.setHeaderTitle(component.getName());
inflater.inflate(R.menu.software_component, menu);
super.onCreateContextMenu(menu, v, menuInfo);
mComponentForContextMenu = (SoftwareComponent) v.getTag();
componentForContextMenu = (SoftwareComponent) v.getTag();
}
@Override
public boolean onContextItemSelected(final MenuItem item) {
// item.getMenuInfo() is null so we use the tag of the view
final SoftwareComponent component = mComponentForContextMenu;
final SoftwareComponent component = componentForContextMenu;
if (component == null) {
return false;
}
switch (item.getItemId()) {
case R.id.action_website:
openWebsite(component.getLink());
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
return true;
case R.id.action_show_license:
showLicense(getContext(), component.getLicense());
showLicense(getActivity(), component.getLicense());
}
return false;
}
private void openWebsite(final String componentLink) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
startActivity(browserIntent);
}
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
@Override
public void onClick(final View v) {
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
}
}
}

View File

@@ -3,8 +3,10 @@ package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Base64;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
@@ -12,6 +14,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
@@ -26,28 +29,18 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
weakReference = new WeakReference<>(activity);
}
private static String getFinishString(final Activity activity) {
return activity.getApplicationContext().getResources().getString(R.string.finish);
}
/**
* @param context the context to use
* @param license the license
* @return String which contains a HTML formatted license page
* styled according to the context's theme
*/
public static String getFormattedLicense(final Context context, final License license) {
if (context == null) {
throw new NullPointerException("context is null");
}
if (license == null) {
throw new NullPointerException("license is null");
}
StringBuilder licenseContent = new StringBuilder();
String webViewData;
private static String getFormattedLicense(@NonNull final Context context,
@NonNull final License license) {
final StringBuilder licenseContent = new StringBuilder();
final String webViewData;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(
final BufferedReader in = new BufferedReader(new InputStreamReader(
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
String str;
while ((str = in.readLine()) != null) {
@@ -56,13 +49,11 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
in.close();
// split the HTML file and insert the stylesheet into the HEAD of the file
String[] insert = licenseContent.toString().split("</head>");
webViewData = insert[0] + "<style type=\"text/css\">"
+ getLicenseStylesheet(context) + "</style></head>"
+ insert[1];
} catch (Exception e) {
throw new NullPointerException("could not get license file:"
+ getLicenseStylesheet(context));
webViewData = licenseContent.toString().replace("</head>",
"<style>" + getLicenseStylesheet(context) + "</style></head>");
} catch (IOException e) {
throw new IllegalArgumentException(
"Could not get license file: " + license.getFilename(), e);
}
return webViewData;
}
@@ -71,21 +62,19 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
* @param context
* @return String which is a CSS stylesheet according to the context's theme
*/
public static String getLicenseStylesheet(final Context context) {
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
return "body{padding:12px 15px;margin:0;background:#"
+ getHexRGBColor(context, isLightTheme
private static String getLicenseStylesheet(final Context context) {
final boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
return "body{padding:12px 15px;margin:0;"
+ "background:#" + getHexRGBColor(context, isLightTheme
? R.color.light_license_background_color
: R.color.dark_license_background_color)
+ ";color:#"
+ getHexRGBColor(context, isLightTheme
: R.color.dark_license_background_color) + ";"
+ "color:#" + getHexRGBColor(context, isLightTheme
? R.color.light_license_text_color
: R.color.dark_license_text_color) + ";}"
+ "a[href]{color:#"
+ getHexRGBColor(context, isLightTheme
: R.color.dark_license_text_color) + "}"
+ "a[href]{color:#" + getHexRGBColor(context, isLightTheme
? R.color.light_youtube_primary_color
: R.color.dark_youtube_primary_color) + ";}"
+ "pre{white-space: pre-wrap;}";
: R.color.dark_youtube_primary_color) + "}"
+ "pre{white-space:pre-wrap}";
}
/**
@@ -95,13 +84,13 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
public static String getHexRGBColor(final Context context, final int color) {
private static String getHexRGBColor(final Context context, final int color) {
return context.getResources().getString(color).substring(3);
}
@Nullable
private Activity getActivity() {
Activity activity = weakReference.get();
final Activity activity = weakReference.get();
if (activity != null && activity.isFinishing()) {
return null;
@@ -118,22 +107,22 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
@Override
protected void onPostExecute(final Integer result) {
Activity activity = getActivity();
final Activity activity = getActivity();
if (activity == null) {
return;
}
String webViewData = getFormattedLicense(activity, license);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
final String webViewData = Base64.encodeToString(getFormattedLicense(activity, license)
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
final WebView webView = new WebView(activity);
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
final AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setTitle(license.getName());
WebView wv = new WebView(activity);
wv.loadData(webViewData, "text/html; charset=UTF-8", null);
alert.setView(wv);
assureCorrectAppLanguage(activity.getApplicationContext());
alert.setNegativeButton(getFinishString(activity), (dialog, which) -> dialog.dismiss());
alert.setView(webView);
assureCorrectAppLanguage(activity);
alert.setNegativeButton(activity.getString(R.string.finish),
(dialog, which) -> dialog.dismiss());
alert.show();
}
}

View File

@@ -1,12 +1,17 @@
package org.schabi.newpipe.database.feed.dao
import androidx.room.*
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
import java.util.Date
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.util.*
@Dao
abstract class FeedDAO {

View File

@@ -1,6 +1,11 @@
package org.schabi.newpipe.database.feed.dao
import androidx.room.*
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
import io.reactivex.Maybe
import org.schabi.newpipe.database.feed.model.FeedGroupEntity

View File

@@ -27,11 +27,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
]
)
data class FeedEntity(
@ColumnInfo(name = STREAM_ID)
var streamId: Long,
@ColumnInfo(name = STREAM_ID)
var streamId: Long,
@ColumnInfo(name = SUBSCRIPTION_ID)
var subscriptionId: Long
@ColumnInfo(name = SUBSCRIPTION_ID)
var subscriptionId: Long
) {
companion object {
@@ -40,4 +40,4 @@ data class FeedEntity(
const val STREAM_ID = "stream_id"
const val SUBSCRIPTION_ID = "subscription_id"
}
}
}

View File

@@ -13,18 +13,18 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon
indices = [Index(SORT_ORDER)]
)
data class FeedGroupEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = ID)
val uid: Long,
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = ID)
val uid: Long,
@ColumnInfo(name = NAME)
var name: String,
@ColumnInfo(name = NAME)
var name: String,
@ColumnInfo(name = ICON)
var icon: FeedGroupIcon,
@ColumnInfo(name = ICON)
var icon: FeedGroupIcon,
@ColumnInfo(name = SORT_ORDER)
var sortOrder: Long = -1
@ColumnInfo(name = SORT_ORDER)
var sortOrder: Long = -1
) {
companion object {
const val FEED_GROUP_TABLE = "feed_group"
@@ -36,4 +36,4 @@ data class FeedGroupEntity(
const val GROUP_ALL_ID = -1L
}
}
}

View File

@@ -29,11 +29,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
]
)
data class FeedGroupSubscriptionEntity(
@ColumnInfo(name = GROUP_ID)
var feedGroupId: Long,
@ColumnInfo(name = GROUP_ID)
var feedGroupId: Long,
@ColumnInfo(name = SUBSCRIPTION_ID)
var subscriptionId: Long
@ColumnInfo(name = SUBSCRIPTION_ID)
var subscriptionId: Long
) {
companion object {
@@ -42,4 +42,4 @@ data class FeedGroupSubscriptionEntity(
const val GROUP_ID = "group_id"
const val SUBSCRIPTION_ID = "subscription_id"
}
}
}

View File

@@ -4,10 +4,10 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.util.Date
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.util.*
@Entity(
tableName = FEED_LAST_UPDATED_TABLE,
@@ -20,12 +20,12 @@ import java.util.*
]
)
data class FeedLastUpdatedEntity(
@PrimaryKey
@ColumnInfo(name = SUBSCRIPTION_ID)
var subscriptionId: Long,
@PrimaryKey
@ColumnInfo(name = SUBSCRIPTION_ID)
var subscriptionId: Long,
@ColumnInfo(name = LAST_UPDATED)
var lastUpdated: Date? = null
@ColumnInfo(name = LAST_UPDATED)
var lastUpdated: Date? = null
) {
companion object {
@@ -34,4 +34,4 @@ data class FeedLastUpdatedEntity(
const val SUBSCRIPTION_ID = "subscription_id"
const val LAST_UPDATED = "last_updated"
}
}
}

View File

@@ -2,21 +2,21 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import java.util.Date
import org.schabi.newpipe.database.stream.model.StreamEntity
import java.util.*
data class StreamHistoryEntry(
@Embedded
val streamEntity: StreamEntity,
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
val accessDate: Date,
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
val accessDate: Date,
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
val repeatCount: Long
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
val repeatCount: Long
) {
fun toStreamHistoryEntity(): StreamHistoryEntity {

View File

@@ -1,7 +1,33 @@
package org.schabi.newpipe.database.playlist;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public interface PlaylistLocalItem extends LocalItem {
String getOrderingName();
static List<PlaylistLocalItem> merge(
final List<PlaylistMetadataEntry> localPlaylists,
final List<PlaylistRemoteEntity> remotePlaylists) {
final List<PlaylistLocalItem> items = new ArrayList<>(
localPlaylists.size() + remotePlaylists.size());
items.addAll(localPlaylists);
items.addAll(remotePlaylists);
Collections.sort(items, (left, right) -> {
final String on1 = left.getOrderingName();
final String on2 = right.getOrderingName();
if (on1 == null) {
return on2 == null ? 0 : 1;
} else {
return on2 == null ? -1 : on1.compareToIgnoreCase(on2);
}
});
return items;
}
}

View File

@@ -8,14 +8,14 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
class PlaylistStreamEntry(
@Embedded
val streamEntity: StreamEntity,
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
val joinIndex: Int
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
val joinIndex: Int
) : LocalItem {
@Throws(IllegalArgumentException::class)

View File

@@ -2,24 +2,24 @@ package org.schabi.newpipe.database.stream
import androidx.room.ColumnInfo
import androidx.room.Embedded
import java.util.Date
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import java.util.*
class StreamStatisticsEntry(
@Embedded
val streamEntity: StreamEntity,
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = STREAM_LATEST_DATE)
val latestAccessDate: Date,
@ColumnInfo(name = STREAM_LATEST_DATE)
val latestAccessDate: Date,
@ColumnInfo(name = STREAM_WATCH_COUNT)
val watchCount: Long
@ColumnInfo(name = STREAM_WATCH_COUNT)
val watchCount: Long
) : LocalItem {
fun toStreamInfoItem(): StreamInfoItem {

View File

@@ -1,15 +1,19 @@
package org.schabi.newpipe.database.stream.dao
import androidx.room.*
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
import java.util.Date
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import java.util.*
import kotlin.collections.ArrayList
@Dao
abstract class StreamDAO : BasicDAO<StreamEntity> {
@@ -94,7 +98,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
if (existentMinimalStream.duration > 0 && newerStream.duration < 0) {
newerStream.duration = existentMinimalStream.duration
}
}
}
@@ -116,21 +119,22 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
* Minimal entry class used when comparing/updating an existent stream.
*/
internal data class StreamCompareFeed(
@ColumnInfo(name = STREAM_ID)
var uid: Long = 0,
@ColumnInfo(name = STREAM_ID)
var uid: Long = 0,
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
var streamType: StreamType,
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
var streamType: StreamType,
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
var textualUploadDate: String? = null,
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
var textualUploadDate: String? = null,
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
var uploadDate: Date? = null,
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
var uploadDate: Date? = null,
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
var isUploadDateApproximation: Boolean? = null,
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
var isUploadDateApproximation: Boolean? = null,
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
var duration: Long)
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
var duration: Long
)
}

View File

@@ -1,6 +1,13 @@
package org.schabi.newpipe.database.stream.model
import androidx.room.*
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import java.io.Serializable
import java.util.Calendar
import java.util.Date
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
@@ -9,8 +16,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem
import java.io.Serializable
import java.util.*
@Entity(tableName = STREAM_TABLE,
indices = [
@@ -18,42 +23,42 @@ import java.util.*
]
)
data class StreamEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = STREAM_ID)
var uid: Long = 0,
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = STREAM_ID)
var uid: Long = 0,
@ColumnInfo(name = STREAM_SERVICE_ID)
var serviceId: Int,
@ColumnInfo(name = STREAM_SERVICE_ID)
var serviceId: Int,
@ColumnInfo(name = STREAM_URL)
var url: String,
@ColumnInfo(name = STREAM_URL)
var url: String,
@ColumnInfo(name = STREAM_TITLE)
var title: String,
@ColumnInfo(name = STREAM_TITLE)
var title: String,
@ColumnInfo(name = STREAM_TYPE)
var streamType: StreamType,
@ColumnInfo(name = STREAM_TYPE)
var streamType: StreamType,
@ColumnInfo(name = STREAM_DURATION)
var duration: Long,
@ColumnInfo(name = STREAM_DURATION)
var duration: Long,
@ColumnInfo(name = STREAM_UPLOADER)
var uploader: String,
@ColumnInfo(name = STREAM_UPLOADER)
var uploader: String,
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
var thumbnailUrl: String? = null,
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
var thumbnailUrl: String? = null,
@ColumnInfo(name = STREAM_VIEWS)
var viewCount: Long? = null,
@ColumnInfo(name = STREAM_VIEWS)
var viewCount: Long? = null,
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
var textualUploadDate: String? = null,
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
var textualUploadDate: String? = null,
@ColumnInfo(name = STREAM_UPLOAD_DATE)
var uploadDate: Date? = null,
@ColumnInfo(name = STREAM_UPLOAD_DATE)
var uploadDate: Date? = null,
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
var isUploadDateApproximation: Boolean? = null
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
var isUploadDateApproximation: Boolean? = null
) : Serializable {
@Ignore

View File

@@ -1,6 +1,10 @@
package org.schabi.newpipe.database.subscription
import androidx.room.*
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
import io.reactivex.Maybe
import org.schabi.newpipe.database.BasicDAO
@@ -16,6 +20,45 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
@Query("""
SELECT * FROM subscriptions
WHERE name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
""")
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
@Query("""
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
ON s.uid = fgs.subscription_id
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
ORDER BY name COLLATE NOCASE ASC
""")
abstract fun getSubscriptionsOnlyUngrouped(
currentGroupId: Long
): Flowable<List<SubscriptionEntity>>
@Query("""
SELECT * FROM subscriptions s
LEFT JOIN feed_group_subscription_join fgs
ON s.uid = fgs.subscription_id
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
AND s.name LIKE '%' || :filter || '%'
ORDER BY name COLLATE NOCASE ASC
""")
abstract fun getSubscriptionsOnlyUngroupedFiltered(
currentGroupId: Long,
filter: String
): Flowable<List<SubscriptionEntity>>
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
@@ -48,7 +91,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
entity.uid = uidFromInsert
} else {
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
entity.uid = subscriptionIdFromDb
update(entity)

View File

@@ -130,4 +130,55 @@ public class SubscriptionEntity {
item.setDescription(getDescription());
return item;
}
// TODO: Remove these generated methods by migrating this class to a data class from Kotlin.
@Override
@SuppressWarnings("EqualsReplaceableByObjectsCall")
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final SubscriptionEntity that = (SubscriptionEntity) o;
if (uid != that.uid) {
return false;
}
if (serviceId != that.serviceId) {
return false;
}
if (!url.equals(that.url)) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) {
return false;
}
if (subscriberCount != null
? !subscriberCount.equals(that.subscriberCount)
: that.subscriberCount != null) {
return false;
}
return description != null
? description.equals(that.description)
: that.description == null;
}
@Override
public int hashCode() {
int result = (int) (uid ^ (uid >>> 32));
result = 31 * result + serviceId;
result = 31 * result + url.hashCode();
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
result = 31 * result + (subscriberCount != null ? subscriberCount.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
return result;
}
}

View File

@@ -396,13 +396,11 @@ public class DownloadDialog extends DialogFragment
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
}
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
toolbar.setTitle(R.string.download_dialog_title);
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp
: R.drawable.ic_arrow_back_white_24dp);
toolbar.setNavigationIcon(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_arrow_back));
toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss());
toolbar.setNavigationContentDescription(R.string.cancel);
okButton = toolbar.findViewById(R.id.okay);

View File

@@ -1,7 +1,9 @@
package org.schabi.newpipe.fragments;
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -30,6 +32,7 @@ import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.ScrollableTabLayout;
import java.util.ArrayList;
@@ -45,6 +48,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
private boolean hasTabsChanged = false;
private boolean previousYoutubeRestrictedModeEnabled;
private String youtubeRestrictedModeEnabledKey;
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@@ -53,7 +59,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
tabsManager = TabsManager.getManager(activity);
tabsManager.setSavedTabsListener(() -> {
if (DEBUG) {
@@ -66,6 +71,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
hasTabsChanged = true;
}
});
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
previousYoutubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(getContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
}
@Override
@@ -82,6 +92,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
tabLayout = rootView.findViewById(R.id.main_tab_layout);
viewPager = rootView.findViewById(R.id.pager);
tabLayout.setTabIconTint(ColorStateList.valueOf(
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
tabLayout.setupWithViewPager(viewPager);
tabLayout.addOnTabSelectedListener(this);
@@ -92,7 +104,13 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
public void onResume() {
super.onResume();
if (hasTabsChanged) {
boolean youtubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(getContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
setupTabs();
} else if (hasTabsChanged) {
setupTabs();
}
}

View File

@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -35,6 +36,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
@@ -85,6 +87,7 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.AnimatedProgressBar;
import org.schabi.newpipe.views.LargeTextMovementMethod;
@@ -175,6 +178,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
private View uploaderRootLayout;
private TextView uploaderTextView;
private ImageView uploaderThumb;
private TextView subChannelTextView;
private ImageView subChannelThumb;
private TextView thumbsUpTextView;
private ImageView thumbsUpImageView;
@@ -419,18 +424,17 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
}
break;
case R.id.detail_uploader_root_layout:
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
Log.w(TAG, "Can't open channel because we got no channel URL");
} else {
try {
NavigationHelper.openChannelFragment(
getFragmentManager(),
currentInfo.getServiceId(),
currentInfo.getUploaderUrl(),
currentInfo.getUploaderName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
if (!TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
}
if (DEBUG) {
Log.i(TAG, "Can't open sub-channel because we got no channel URL");
}
} else {
openChannel(currentInfo.getSubChannelUrl(),
currentInfo.getSubChannelName());
}
break;
case R.id.detail_thumbnail_root_layout:
@@ -447,6 +451,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
}
}
private void openChannel(final String subChannelUrl, final String subChannelName) {
try {
NavigationHelper.openChannelFragment(
getFragmentManager(),
currentInfo.getServiceId(),
subChannelUrl,
subChannelName);
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
}
@Override
public boolean onLongClick(final View v) {
if (isLoading.get() || currentInfo == null) {
@@ -463,6 +479,17 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
case R.id.detail_controls_download:
NavigationHelper.openDownloads(getActivity());
break;
case R.id.detail_uploader_root_layout:
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
Log.w(TAG,
"Can't open parent channel because we got no parent channel URL");
} else {
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
}
break;
case R.id.detail_title_root_layout:
ShareUtils.copyToClipboard(getContext(), videoTitleTextView.getText().toString());
break;
}
return true;
@@ -473,13 +500,15 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
videoTitleTextView.setMaxLines(1);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoDescriptionView.setFocusable(false);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
} else {
videoTitleTextView.setMaxLines(10);
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
videoDescriptionView.setFocusable(true);
videoDescriptionView.setMovementMethod(new LargeTextMovementMethod());
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
videoTitleToggleArrow.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less));
}
}
@@ -525,6 +554,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
subChannelTextView = rootView.findViewById(R.id.detail_sub_channel_text_view);
subChannelThumb = rootView.findViewById(R.id.detail_sub_channel_thumbnail_view);
appBarLayout = rootView.findViewById(R.id.appbarlayout);
viewPager = rootView.findViewById(R.id.viewpager);
@@ -554,8 +585,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
protected void initListeners() {
super.initListeners();
videoTitleRoot.setOnClickListener(this);
videoTitleRoot.setOnLongClickListener(this);
uploaderRootLayout.setOnClickListener(this);
uploaderRootLayout.setOnLongClickListener(this);
videoTitleRoot.setOnClickListener(this);
thumbnailBackgroundButton.setOnClickListener(this);
detailControlsBackground.setOnClickListener(this);
detailControlsPopup.setOnClickListener(this);
@@ -603,6 +636,11 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
}
if (!TextUtils.isEmpty(info.getSubChannelAvatarUrl())) {
IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
}
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
@@ -632,9 +670,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
}
private void updateMenuItemVisibility() {
// show kodi if set in settings
// show kodi button if it supports the current service and it is enabled in settings
menu.findItem(R.id.action_play_with_kodi).setVisible(
PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(
KoreUtil.isServiceSupportedByKore(serviceId)
&& PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(
activity.getString(R.string.show_play_with_kodi_key), false));
}
@@ -665,8 +704,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
return true;
case R.id.action_play_with_kodi:
try {
NavigationHelper.playWithKore(activity, Uri.parse(
url.replace("https", "http")));
NavigationHelper.playWithKore(activity, Uri.parse(currentInfo.getUrl()));
} catch (Exception e) {
if (DEBUG) {
Log.i(TAG, "Failed to start kore", e);
@@ -964,7 +1002,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
@NonNull final StreamInfo info,
@NonNull final Stream selectedStream) {
NavigationHelper.playOnExternalPlayer(context, currentInfo.getName(),
currentInfo.getUploaderName(), selectedStream);
currentInfo.getSubChannelName(), selectedStream);
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
disposables.add(recordManager.onViewed(info).onErrorComplete()
@@ -1043,7 +1081,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
return;
}
thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource));
thumbnailImageView.setImageDrawable(
AppCompatResources.getDrawable(requireContext(), imageResource));
animateView(thumbnailImageView, false, 0, 0,
() -> animateView(thumbnailImageView, true, 500));
}
@@ -1084,7 +1123,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
animateView(videoTitleTextView, true, 0);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setVisibility(View.GONE);
videoTitleRoot.setClickable(false);
@@ -1097,9 +1135,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
}
IMAGE_LOADER.cancelDisplayTask(thumbnailImageView);
IMAGE_LOADER.cancelDisplayTask(uploaderThumb);
IMAGE_LOADER.cancelDisplayTask(subChannelThumb);
thumbnailImageView.setImageBitmap(null);
uploaderThumb.setImageBitmap(null);
subChannelThumb.setImageBitmap(null);
}
@Override
@@ -1127,14 +1165,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
animateView(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(name);
if (!TextUtils.isEmpty(info.getUploaderName())) {
uploaderTextView.setText(info.getUploaderName());
uploaderTextView.setVisibility(View.VISIBLE);
uploaderTextView.setSelected(true);
if (!TextUtils.isEmpty(info.getSubChannelName())) {
displayBothUploaderAndSubChannel(info);
} else if (!TextUtils.isEmpty(info.getUploaderName())) {
displayUploaderAsSubChannel(info);
} else {
uploaderTextView.setVisibility(View.GONE);
uploaderThumb.setVisibility(View.GONE);
}
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy);
subChannelThumb.setImageDrawable(buddyDrawable);
uploaderThumb.setImageDrawable(buddyDrawable);
if (info.getViewCount() >= 0) {
if (info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
@@ -1196,8 +1238,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
videoDescriptionView.setVisibility(View.GONE);
videoTitleRoot.setClickable(true);
videoTitleToggleArrow.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
videoTitleToggleArrow.setVisibility(View.VISIBLE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoDescriptionRootLayout.setVisibility(View.GONE);
if (info.getUploadDate() != null) {
@@ -1243,7 +1286,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
detailControlsPopup.setVisibility(View.GONE);
spinnerToolbar.setVisibility(View.GONE);
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_shadow);
break;
}
@@ -1265,6 +1308,31 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
tabLayout.setVisibility(View.GONE);
}
private void displayUploaderAsSubChannel(final StreamInfo info) {
subChannelTextView.setText(info.getUploaderName());
subChannelTextView.setVisibility(View.VISIBLE);
subChannelTextView.setSelected(true);
uploaderTextView.setVisibility(View.GONE);
}
private void displayBothUploaderAndSubChannel(final StreamInfo info) {
subChannelTextView.setText(info.getSubChannelName());
subChannelTextView.setVisibility(View.VISIBLE);
subChannelTextView.setSelected(true);
subChannelThumb.setVisibility(View.VISIBLE);
if (!TextUtils.isEmpty(info.getUploaderName())) {
uploaderTextView.setText(
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
uploaderTextView.setVisibility(View.VISIBLE);
uploaderTextView.setSelected(true);
} else {
uploaderTextView.setVisibility(View.GONE);
}
}
public void openDownloadDialog() {
try {
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);

View File

@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.views.NewPipeRecyclerView;
@@ -30,7 +31,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
protected String url;
protected I currentInfo;
protected String currentNextPageUrl;
protected Page currentNextPage;
protected Disposable currentWorker;
@Override
@@ -78,7 +79,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentInfo);
objectsToSave.add(currentNextPageUrl);
objectsToSave.add(currentNextPage);
}
@Override
@@ -86,7 +87,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentInfo = (I) savedObjects.poll();
currentNextPageUrl = (String) savedObjects.poll();
currentNextPage = (Page) savedObjects.poll();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -130,7 +131,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
.subscribe((@NonNull I result) -> {
isLoading.set(false);
currentInfo = result;
currentNextPageUrl = result.getNextPageUrl();
currentNextPage = result.getNextPage();
handleResult(result);
}, (@NonNull Throwable throwable) -> onError(throwable));
}
@@ -182,7 +183,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
currentNextPageUrl = result.getNextPageUrl();
currentNextPage = result.getNextPage();
infoListAdapter.addInfoItemList(result.getItems());
showListFooter(hasMoreItems());
@@ -190,7 +191,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
@Override
protected boolean hasMoreItems() {
return !TextUtils.isEmpty(currentNextPageUrl);
return Page.isValid(currentNextPage);
}
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -21,6 +21,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.jakewharton.rxbinding2.view.RxView;
@@ -38,6 +39,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -45,6 +47,7 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.Iterator;
@@ -65,7 +68,8 @@ import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
implements View.OnClickListener {
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor;
@@ -79,6 +83,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private ImageView headerChannelBanner;
private ImageView headerAvatarView;
private TextView headerTitleView;
private ImageView headerSubChannelAvatarView;
private TextView headerSubChannelTitleView;
private TextView headerSubscribersTextView;
private Button headerSubscribeButton;
private View playlistCtrl;
@@ -156,7 +162,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
headerSubChannelAvatarView =
headerRootLayout.findViewById(R.id.sub_channel_avatar_view);
headerSubChannelTitleView =
headerRootLayout.findViewById(R.id.sub_channel_title_view);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
@@ -165,6 +174,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return headerRootLayout;
}
@Override
protected void initListeners() {
super.initListeners();
headerSubChannelTitleView.setOnClickListener(this);
headerSubChannelAvatarView.setOnClickListener(this);
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@@ -358,8 +375,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
int backgroundDuration = isButtonVisible ? 300 : 0;
int textDuration = isButtonVisible ? 200 : 0;
int subscribeBackground = ContextCompat
.getColor(activity, R.color.subscribe_background_color);
int subscribeBackground = ThemeHelper
.resolveColorFromAttr(activity, R.attr.colorPrimary);
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
int subscribedBackground = ContextCompat
.getColor(activity, R.color.subscribed_background_color);
@@ -386,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
}
@Override
@@ -394,6 +411,34 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
}
/*//////////////////////////////////////////////////////////////////////////
// OnClick
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onClick(final View v) {
if (isLoading.get() || currentInfo == null) {
return;
}
switch (v.getId()) {
case R.id.sub_channel_avatar_view:
case R.id.sub_channel_title_view:
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
try {
NavigationHelper.openChannelFragment(getFragmentManager(),
currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
}
} else if (DEBUG) {
Log.i(TAG, "Can't open parent channel because we got no channel URL");
}
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@@ -404,6 +449,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
IMAGE_LOADER.cancelDisplayTask(headerSubChannelAvatarView);
animateView(headerSubscribeButton, false, 100);
}
@@ -416,6 +462,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), headerSubChannelAvatarView,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
headerSubscribersTextView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) {
@@ -425,6 +473,17 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
}
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
headerSubChannelTitleView.setText(String.format(
getString(R.string.channel_created_by),
currentInfo.getParentChannelName())
);
headerSubChannelTitleView.setVisibility(View.VISIBLE);
headerSubChannelAvatarView.setVisibility(View.VISIBLE);
} else {
headerSubChannelTitleView.setVisibility(View.GONE);
}
if (menuRssButton != null) {
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
}
@@ -496,7 +555,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
}
}
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
currentInfo.getNextPageUrl(), streamItems, index);
currentInfo.getNextPage(), streamItems, index);
}
@Override

View File

@@ -71,7 +71,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPage);
}
@Override

View File

@@ -44,7 +44,7 @@ public class DefaultKioskFragment extends KioskFragment {
name = kioskTranslatedName;
currentInfo = null;
currentNextPageUrl = null;
currentNextPage = null;
} catch (ExtractionException e) {
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
"Loading default kiosk from selected service", 0);

View File

@@ -150,7 +150,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
@Override
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPage);
}
/*//////////////////////////////////////////////////////////////////////////

View File

@@ -229,7 +229,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
@Override
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl);
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPage);
}
@Override
@@ -349,7 +349,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
return new PlaylistPlayQueue(
currentInfo.getServiceId(),
currentInfo.getUrl(),
currentInfo.getNextPageUrl(),
currentInfo.getNextPage(),
infoItems,
index
);

View File

@@ -7,6 +7,7 @@ import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
@@ -37,16 +38,17 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
@@ -71,6 +73,7 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import static android.text.Html.escapeHtml;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
@@ -118,13 +121,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State
String lastSearchedString;
@State
String searchSuggestion;
@State
boolean isCorrectedSearch;
@State
boolean wasSearchFocused = false;
private Map<Integer, String> menuItemToFilterName;
private StreamingService service;
private String currentPageUrl;
private String nextPageUrl;
private Page nextPage;
private String contentCountry;
private boolean isSuggestionsEnabled = true;
@@ -143,6 +151,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
private EditText searchEditText;
private View searchClear;
private TextView correctSuggestion;
private View suggestionsPanel;
private RecyclerView suggestionsRecyclerView;
@@ -257,6 +267,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
}
handleSearchSuggestion();
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
initSuggestionObserver();
}
@@ -345,6 +357,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -354,15 +368,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(currentPageUrl);
objectsToSave.add(nextPageUrl);
objectsToSave.add(nextPage);
}
@Override
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
currentPageUrl = (String) savedObjects.poll();
nextPageUrl = (String) savedObjects.poll();
nextPage = (Page) savedObjects.poll();
}
@Override
@@ -497,6 +509,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return;
}
correctSuggestion.setVisibility(View.GONE);
searchEditText.setText("");
suggestionListAdapter.setItems(new ArrayList<>());
showKeyboardSearch();
@@ -554,11 +568,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) { }
final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) { }
final int before, final int count) {
}
@Override
public void afterTextChanged(final Editable s) {
@@ -688,10 +704,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return false;
}
public void giveSearchEditTextFocus() {
showKeyboardSearch();
}
private void initSuggestionObserver() {
if (DEBUG) {
Log.d(TAG, "initSuggestionObserver() called");
@@ -845,7 +857,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
protected void loadMoreItems() {
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
if (!Page.isValid(nextPage)) {
return;
}
isLoading.set(true);
@@ -858,7 +870,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchString,
asList(contentFilter),
sortFilter,
nextPageUrl)
nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
@@ -961,9 +973,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
NewPipe.getNameOfService(serviceId), searchString, 0);
}
searchSuggestion = result.getSearchSuggestion();
isCorrectedSearch = result.isCorrectedSearch();
handleSearchSuggestion();
lastSearchedString = searchString;
nextPageUrl = result.getNextPageUrl();
currentPageUrl = result.getUrl();
nextPage = result.getNextPage();
if (infoListAdapter.getItemsList().size() == 0) {
if (!result.getRelatedItems().isEmpty()) {
@@ -978,17 +994,49 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
super.handleResult(result);
}
private void handleSearchSuggestion() {
if (TextUtils.isEmpty(searchSuggestion)) {
correctSuggestion.setVisibility(View.GONE);
} else {
final String helperText = getString(isCorrectedSearch
? R.string.search_showing_result_for
: R.string.did_you_mean);
final String highlightedSearchSuggestion =
"<b><i>" + escapeHtml(searchSuggestion) + "</i></b>";
correctSuggestion.setText(
Html.fromHtml(String.format(helperText, highlightedSearchSuggestion)));
correctSuggestion.setOnClickListener(v -> {
correctSuggestion.setVisibility(View.GONE);
search(searchSuggestion, contentFilter, sortFilter);
searchEditText.setText(searchSuggestion);
});
correctSuggestion.setOnLongClickListener(v -> {
searchEditText.setText(searchSuggestion);
searchEditText.setSelection(searchSuggestion.length());
showKeyboardSearch();
return true;
});
correctSuggestion.setVisibility(View.VISIBLE);
}
}
@Override
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
showListFooter(false);
currentPageUrl = result.getNextPageUrl();
infoListAdapter.addInfoItemList(result.getItems());
nextPageUrl = result.getNextPageUrl();
nextPage = result.getNextPage();
if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
NewPipe.getNameOfService(serviceId),
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
+ "pageIds: " + nextPage.getIds() + ", "
+ "pageCookies: " + nextPage.getCookies(), 0);
}
super.handleNextItems(result);
}
@@ -1020,6 +1068,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
if (position == RecyclerView.NO_POSITION) {
return 0;
}
final SuggestionItem item = suggestionListAdapter.getItem(position);
return item.fromHistory ? makeMovementFlags(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;

View File

@@ -117,8 +117,8 @@ public class SuggestionListAdapter
queryView = rootView.findViewById(R.id.suggestion_search);
insertView = rootView.findViewById(R.id.suggestion_insert);
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history);
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_history);
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_search);
}
private static int resolveResourceIdFromAttr(final Context context,

View File

@@ -48,6 +48,6 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemTitleView.setText(item.getAuthorName());
itemTitleView.setText(item.getUploaderName());
}
}

View File

@@ -1,18 +1,14 @@
package org.schabi.newpipe.info_list.holder;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import org.jsoup.helper.StringUtil;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
@@ -24,6 +20,7 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -89,7 +86,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemBuilder.getImageLoader()
.displayImage(item.getAuthorThumbnail(),
.displayImage(item.getUploaderAvatarUrl(),
itemThumbnailView,
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
@@ -114,10 +111,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
itemLikesCountView.setText("-");
}
if (item.getPublishedTime() != null) {
itemPublishedTime.setText(Localization.relativeTime(item.getPublishedTime().date()));
if (item.getUploadDate() != null) {
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate().date()));
} else {
itemPublishedTime.setText(item.getTextualPublishedTime());
itemPublishedTime.setText(item.getTextualUploadDate());
}
itemView.setOnClickListener(view -> {
@@ -129,21 +126,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
itemView.setOnLongClickListener(view -> {
if (!AndroidTvUtils.isTv(itemBuilder.getContext())) {
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText));
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT)
.show();
} else {
if (AndroidTvUtils.isTv(itemBuilder.getContext())) {
openCommentAuthor(item);
} else {
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
}
return true;
});
}
private void openCommentAuthor(final CommentsInfoItem item) {
if (StringUtil.isBlank(item.getAuthorEndpoint())) {
if (TextUtils.isEmpty(item.getUploaderUrl())) {
return;
}
try {
@@ -151,8 +144,8 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
NavigationHelper.openChannelFragment(
activity.getSupportFragmentManager(),
item.getServiceId(),
item.getAuthorEndpoint(),
item.getAuthorName());
item.getUploaderUrl(),
item.getUploaderName());
} catch (Exception e) {
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
}

View File

@@ -30,8 +30,6 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import icepick.State;
@@ -54,31 +52,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
// Fragment LifeCycle - Creation
///////////////////////////////////////////////////////////////////////////
private static List<PlaylistLocalItem> merge(
final List<PlaylistMetadataEntry> localPlaylists,
final List<PlaylistRemoteEntity> remotePlaylists) {
List<PlaylistLocalItem> items = new ArrayList<>(
localPlaylists.size() + remotePlaylists.size());
items.addAll(localPlaylists);
items.addAll(remotePlaylists);
Collections.sort(items, (left, right) -> {
String on1 = left.getOrderingName();
String on2 = right.getOrderingName();
if (on1 == null && on2 == null) {
return 0;
} else if (on1 != null && on2 == null) {
return -1;
} else if (on1 == null && on2 != null) {
return 1;
} else {
return on1.compareToIgnoreCase(on2);
}
});
return items;
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -164,7 +137,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
super.startLoading(forceLoad);
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistsSubscriber());

View File

@@ -7,6 +7,8 @@ import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.util.Calendar
import java.util.Date
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedEntity
@@ -16,8 +18,6 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import java.util.*
import kotlin.collections.ArrayList
class FeedDatabaseManager(context: Context) {
private val database = NewPipeDatabase.getInstance(context)
@@ -70,8 +70,11 @@ class FeedDatabaseManager(context: Context) {
fun markAsOutdated(subscriptionId: Long) = feedTable
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
fun upsertAll(subscriptionId: Long, items: List<StreamInfoItem>,
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time) {
fun upsertAll(
subscriptionId: Long,
items: List<StreamInfoItem>,
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time
) {
val itemsToInsert = ArrayList<StreamInfoItem>()
loop@ for (streamItem in items) {
val uploadDate = streamItem.uploadDate
@@ -107,9 +110,9 @@ class FeedDatabaseManager(context: Context) {
if (DEBUG) Log.d(this::class.java.simpleName, "clear() → streamTable.deleteOrphans() → $deletedOrphans")
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Feed Groups
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
return feedGroupTable.getSubscriptionIdsFor(groupId)
@@ -161,6 +164,5 @@ class FeedDatabaseManager(context: Context) {
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
else -> feedTable.oldestSubscriptionUpdate(groupId)
}
}
}

View File

@@ -22,14 +22,28 @@ package org.schabi.newpipe.local.feed
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.preference.PreferenceManager
import icepick.State
import kotlinx.android.synthetic.main.error_retry.*
import kotlinx.android.synthetic.main.fragment_feed.*
import java.util.Calendar
import kotlinx.android.synthetic.main.error_retry.error_button_retry
import kotlinx.android.synthetic.main.error_retry.error_message_view
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
import kotlinx.android.synthetic.main.fragment_feed.error_panel
import kotlinx.android.synthetic.main.fragment_feed.items_list
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_bar
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_text
import kotlinx.android.synthetic.main.fragment_feed.refresh_root_view
import kotlinx.android.synthetic.main.fragment_feed.refresh_subtitle_text
import kotlinx.android.synthetic.main.fragment_feed.refresh_text
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.fragments.list.BaseListFragment
@@ -37,7 +51,6 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.report.UserAction
import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.Localization
import java.util.*
class FeedFragment : BaseListFragment<FeedState, Unit>() {
private lateinit var viewModel: FeedViewModel
@@ -98,9 +111,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
}
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Menu
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
@@ -150,9 +163,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
activity?.supportActionBar?.subtitle = null
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Handling
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
override fun showLoading() {
animateView(refresh_root_view, false, 0)
@@ -259,7 +272,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
}
}
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
hideLoading()
errorState.error?.let {
@@ -283,9 +295,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
refresh_text?.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Load Service Handling
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
override fun doInitialLoadLogic() {}
override fun reloadContent() = triggerUpdate()

View File

@@ -1,24 +1,24 @@
package org.schabi.newpipe.local.feed
import androidx.annotation.StringRes
import java.util.Calendar
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import java.util.*
sealed class FeedState {
data class ProgressState(
val currentProgress: Int = -1,
val maxProgress: Int = -1,
@StringRes val progressMessage: Int = 0
val currentProgress: Int = -1,
val maxProgress: Int = -1,
@StringRes val progressMessage: Int = 0
) : FeedState()
data class LoadedState(
val items: List<StreamInfoItem>,
val oldestUpdate: Calendar? = null,
val notLoadedCount: Long,
val itemsErrors: List<Throwable> = emptyList()
val items: List<StreamInfoItem>,
val oldestUpdate: Calendar? = null,
val notLoadedCount: Long,
val itemsErrors: List<Throwable> = emptyList()
) : FeedState()
data class ErrorState(
val error: Throwable? = null
val error: Throwable? = null
) : FeedState()
}
}

View File

@@ -9,13 +9,17 @@ import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Function4
import io.reactivex.schedulers.Schedulers
import java.util.Calendar
import java.util.Date
import java.util.concurrent.TimeUnit
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.service.FeedEventManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
import java.util.*
import java.util.concurrent.TimeUnit
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
@@ -68,4 +72,4 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
}
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamInfoItem>, val t3: Long, val t4: Date?)
}
}

View File

@@ -3,8 +3,8 @@ package org.schabi.newpipe.local.feed.service
import androidx.annotation.StringRes
import io.reactivex.Flowable
import io.reactivex.processors.BehaviorProcessor
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import java.util.concurrent.atomic.AtomicBoolean
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
object FeedEventManager {
private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create()
@@ -34,5 +34,4 @@ object FeedEventManager {
data class SuccessResultEvent(val itemsErrors: List<Throwable> = emptyList()) : Event()
data class ErrorResultEvent(val error: Throwable) : Event()
}
}

View File

@@ -40,6 +40,11 @@ import io.reactivex.functions.Consumer
import io.reactivex.functions.Function
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import java.io.IOException
import java.util.Calendar
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import org.schabi.newpipe.MainActivity.DEBUG
@@ -49,17 +54,14 @@ import org.schabi.newpipe.extractor.ListInfo
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.ExceptionUtils
import org.schabi.newpipe.util.ExtractorHelper
import java.io.IOException
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.ArrayList
class FeedLoadService : Service() {
companion object {
@@ -94,9 +96,9 @@ class FeedLoadService : Service() {
private var disposables = CompositeDisposable()
private var notificationUpdater = PublishProcessor.create<String>()
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
override fun onCreate() {
super.onCreate()
@@ -151,9 +153,9 @@ class FeedLoadService : Service() {
return null
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Loading & Handling
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
private class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) {
companion object {
@@ -312,7 +314,6 @@ class FeedLoadService : Service() {
feedResultsHolder.addErrors(RequestException.wrapList(subscriptionId, info))
feedDatabaseManager.markAsOutdated(subscriptionId)
}
} else if (notification.isOnError) {
val error = notification.error!!
feedResultsHolder.addError(error)
@@ -325,7 +326,6 @@ class FeedLoadService : Service() {
}
}
private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
get() = Consumer {
if (it.isOnError) {
@@ -354,9 +354,9 @@ class FeedLoadService : Service() {
broadcastProgress()
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Notification
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
private lateinit var notificationManager: NotificationManagerCompat
private lateinit var notificationBuilder: NotificationCompat.Builder
@@ -412,9 +412,9 @@ class FeedLoadService : Service() {
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Notification Actions
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
private lateinit var broadcastReceiver: BroadcastReceiver
private val cancelSignal = AtomicBoolean()
@@ -430,18 +430,18 @@ class FeedLoadService : Service() {
registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL))
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Error handling
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
private fun handleError(error: Throwable) {
postEvent(ErrorResultEvent(error))
stopService()
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Results Holder
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
class ResultsHolder {
/**
@@ -463,4 +463,4 @@ class FeedLoadService : Service() {
itemsErrors = itemsErrorsHolder.toList()
}
}
}
}

View File

@@ -362,14 +362,14 @@ public class StatisticsPlaylistFragment
if (sortMode == StatisticSortMode.LAST_PLAYED) {
sortMode = StatisticSortMode.MOST_PLAYED;
setTitle(getString(R.string.title_most_played));
sortButtonIcon
.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
sortButtonIcon.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_history));
sortButtonText.setText(R.string.title_last_played);
} else {
sortMode = StatisticSortMode.LAST_PLAYED;
setTitle(getString(R.string.title_last_played));
sortButtonIcon
.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
sortButtonIcon.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_filter_list));
sortButtonText.setText(R.string.title_most_played);
}
startLoading(true);

View File

@@ -7,23 +7,23 @@ import org.schabi.newpipe.R
import org.schabi.newpipe.util.ThemeHelper
enum class FeedGroupIcon(
/**
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
*/
val id: Int,
/**
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
*/
val id: Int,
/**
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
*/
@AttrRes val drawableResourceAttr: Int
/**
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
*/
@AttrRes val drawableResourceAttr: Int
) {
ALL(0, R.attr.ic_asterisk),
MUSIC(1, R.attr.ic_music_note),
EDUCATION(2, R.attr.ic_school),
FITNESS(3, R.attr.ic_fitness),
FITNESS(3, R.attr.ic_fitness_center),
SPACE(4, R.attr.ic_telescope),
COMPUTER(5, R.attr.ic_computer),
GAMING(6, R.attr.ic_videogame),
GAMING(6, R.attr.ic_videogame_asset),
SPORTS(7, R.attr.ic_sports),
NEWS(8, R.attr.ic_megaphone),
FAVORITES(9, R.attr.ic_heart),
@@ -32,32 +32,32 @@ enum class FeedGroupIcon(
TREND(12, R.attr.ic_trending_up),
MOVIE(13, R.attr.ic_movie),
BACKUP(14, R.attr.ic_backup),
ART(15, R.attr.palette),
ART(15, R.attr.ic_palette),
PERSON(16, R.attr.ic_person),
PEOPLE(17, R.attr.ic_people),
MONEY(18, R.attr.ic_money),
KIDS(19, R.attr.ic_kids),
KIDS(19, R.attr.ic_child_care),
FOOD(20, R.attr.ic_fastfood),
SMILE(21, R.attr.ic_smile),
EXPLORE(22, R.attr.ic_explore),
RESTAURANT(23, R.attr.ic_restaurant),
MIC(24, R.attr.ic_mic),
HEADSET(25, R.attr.audio),
HEADSET(25, R.attr.ic_headset),
RADIO(26, R.attr.ic_radio),
SHOPPING_CART(27, R.attr.ic_shopping_cart),
WATCH_LATER(28, R.attr.ic_watch_later),
WORK(29, R.attr.ic_work),
HOT(30, R.attr.ic_hot),
HOT(30, R.attr.ic_kiosk_hot),
CHANNEL(31, R.attr.ic_channel),
BOOKMARK(32, R.attr.ic_bookmark),
PETS(33, R.attr.ic_pets),
WORLD(34, R.attr.ic_world),
STAR(35, R.attr.ic_stars),
SUN(36, R.attr.ic_sunny),
RSS(37, R.attr.rss);
RSS(37, R.attr.ic_rss);
@DrawableRes
fun getDrawableRes(context: Context): Int {
return ThemeHelper.resolveResourceIdFromAttr(context, drawableResourceAttr)
}
}
}

View File

@@ -2,13 +2,21 @@ package org.schabi.newpipe.local.subscription
import android.app.Activity
import android.app.AlertDialog
import android.content.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
import android.preference.PreferenceManager
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProviders
import androidx.localbroadcastmanager.content.LocalBroadcastManager
@@ -21,8 +29,15 @@ import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.State
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.dialog_title.view.*
import kotlinx.android.synthetic.main.fragment_subscription.*
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.floor
import kotlin.math.max
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
import kotlinx.android.synthetic.main.fragment_subscription.items_list
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
@@ -30,21 +45,29 @@ import org.schabi.newpipe.fragments.BaseStateFragment
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
import org.schabi.newpipe.local.subscription.item.*
import org.schabi.newpipe.local.subscription.item.ChannelItem
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
import org.schabi.newpipe.local.subscription.item.FeedImportExportItem
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.*
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
import org.schabi.newpipe.report.UserAction
import org.schabi.newpipe.util.*
import org.schabi.newpipe.util.AnimationUtils.animateView
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.floor
import kotlin.math.max
import org.schabi.newpipe.util.FilePickerActivityHelper
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private lateinit var viewModel: SubscriptionViewModel
@@ -74,9 +97,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
setHasOptionsMenu(true)
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -120,9 +143,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
disposables.dispose()
}
//////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
@@ -150,7 +173,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
importExportItem.isExpanded = false
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
}
}
}
@@ -198,9 +220,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}
}
//////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Fragment Views
//////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
private fun setupInitialLayout() {
Section().apply {
@@ -243,7 +265,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
{ onExportSelected() },
importExportItemExpandedState ?: false)
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
}
override fun initViews(rootView: View, savedInstanceState: Bundle?) {
@@ -366,9 +387,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Contract
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
override fun showLoading() {
super.showLoading()
@@ -380,9 +401,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
animateView(items_list, true, 200)
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Fragment Error Handling
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
override fun onError(exception: Throwable): Boolean {
if (super.onError(exception)) return true
@@ -391,9 +412,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
return true
}
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// Grid Mode
///////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////
// TODO: Move these out of this class, as it can be reused
@@ -405,8 +426,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
getString(R.string.list_view_mode_auto_key) -> {
val configuration = resources.configuration
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
}
getString(R.string.list_view_mode_grid_key) -> true
else -> false

View File

@@ -2,9 +2,11 @@ package org.schabi.newpipe.local.subscription
import android.content.Context
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionDAO
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.extractor.ListInfo
@@ -21,9 +23,28 @@ class SubscriptionManager(context: Context) {
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
fun subscriptions() = subscriptionTable.all
fun getSubscriptions(
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
filterQuery: String = "",
showOnlyUngrouped: Boolean = false
): Flowable<List<SubscriptionEntity>> {
return when {
filterQuery.isNotEmpty() -> {
return if (showOnlyUngrouped) {
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
currentGroupId, filterQuery)
} else {
subscriptionTable.getSubscriptionsFiltered(filterQuery)
}
}
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
else -> subscriptionTable.all
}
}
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
val listEntities = subscriptionTable.upsertAll(
infoList.map { SubscriptionEntity.from(it) })
infoList.map { SubscriptionEntity.from(it) })
database.runInTransaction {
infoList.forEachIndexed { index, info ->
@@ -35,13 +56,13 @@ class SubscriptionManager(context: Context) {
}
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
.flatMapCompletable {
Completable.fromRunnable {
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
subscriptionTable.update(it)
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
}
.flatMapCompletable {
Completable.fromRunnable {
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
subscriptionTable.update(it)
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
}
}
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
@@ -57,8 +78,8 @@ class SubscriptionManager(context: Context) {
fun deleteSubscription(serviceId: Int, url: String): Completable {
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {

View File

@@ -6,11 +6,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.xwray.groupie.Group
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.item.ChannelItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
import java.util.concurrent.TimeUnit
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)

View File

@@ -32,4 +32,4 @@ class FeedGroupCarouselDecoration(context: Context) : RecyclerView.ItemDecoratio
outRect.right = marginStartEnd
}
}
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
@@ -13,30 +14,36 @@ import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.OnItemClickListener
import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
import java.io.Serializable
import kotlin.collections.contains
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
import kotlinx.android.synthetic.main.toolbar_search_layout.*
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.fragments.BackPressable
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.*
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.InitialScreen
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.SubscriptionsPickerScreen
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.ProcessingEvent
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.SuccessEvent
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
import org.schabi.newpipe.local.subscription.item.PickerIconItem
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
import org.schabi.newpipe.util.AndroidTvUtils
import org.schabi.newpipe.util.ThemeHelper
import java.io.Serializable
class FeedGroupDialog : DialogFragment() {
class FeedGroupDialog : DialogFragment(), BackPressable {
private lateinit var viewModel: FeedGroupDialogViewModel
private var groupId: Long = NO_GROUP_SELECTED
private var groupIcon: FeedGroupIcon? = null
@@ -49,22 +56,20 @@ class FeedGroupDialog : DialogFragment() {
object DeleteScreen : ScreenState()
}
@State
@JvmField
var selectedIcon: FeedGroupIcon? = null
@State
@JvmField
var selectedSubscriptions: HashSet<Long> = HashSet()
@State
@JvmField
var currentScreen: ScreenState = InitialScreen
@State @JvmField var selectedIcon: FeedGroupIcon? = null
@State @JvmField var selectedSubscriptions: HashSet<Long> = HashSet()
@State @JvmField var wasSubscriptionSelectionChanged: Boolean = false
@State @JvmField var currentScreen: ScreenState = InitialScreen
@State
@JvmField
var subscriptionsListState: Parcelable? = null
@State
@JvmField
var iconsListState: Parcelable? = null
@State @JvmField var subscriptionsListState: Parcelable? = null
@State @JvmField var iconsListState: Parcelable? = null
@State @JvmField var wasSearchSubscriptionsVisible = false
@State @JvmField var subscriptionsCurrentSearchQuery = ""
@State @JvmField var subscriptionsShowOnlyUngrouped = false
private val subscriptionMainSection = Section()
private val subscriptionEmptyFooter = Section()
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -74,22 +79,30 @@ class FeedGroupDialog : DialogFragment() {
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_feed_group_create, container)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return object : Dialog(requireActivity(), theme) {
override fun onBackPressed() {
if (currentScreen !is InitialScreen) {
showScreen(InitialScreen)
} else {
if (!this@FeedGroupDialog.onBackPressed()) {
super.onBackPressed()
}
}
}
}
override fun onPause() {
super.onPause()
wasSearchSubscriptionsVisible = isSearchVisible()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
@@ -102,11 +115,15 @@ class FeedGroupDialog : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this, FeedGroupDialogViewModel.Factory(requireContext(), groupId))
.get(FeedGroupDialogViewModel::class.java)
viewModel = ViewModelProvider(this,
FeedGroupDialogViewModel.Factory(requireContext(),
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
).get(FeedGroupDialogViewModel::class.java)
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer { setupSubscriptionPicker(it.first, it.second) })
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
setupSubscriptionPicker(it.first, it.second)
})
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
when (it) {
ProcessingEvent -> disableInput()
@@ -114,15 +131,54 @@ class FeedGroupDialog : DialogFragment() {
}
})
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
add(subscriptionMainSection)
add(subscriptionEmptyFooter)
spanCount = 4
}
subscriptions_selector_list.apply {
// Disable animations, too distracting.
itemAnimator = null
adapter = subscriptionGroupAdapter
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
RecyclerView.VERTICAL, false).apply {
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
}
}
setupIconPicker()
setupListeners()
showScreen(currentScreen)
if (currentScreen == SubscriptionsPickerScreen && wasSearchSubscriptionsVisible) {
showSearch()
} else if (currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED) {
showKeyboard()
}
}
///////////////////////////////////////////////////////////////////////////
override fun onDestroyView() {
super.onDestroyView()
subscriptions_selector_list?.adapter = null
icon_selector?.adapter = null
}
/*///////////////////////////////////////////////////////////////////////////
// Setup
///////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////// */
override fun onBackPressed(): Boolean {
if (currentScreen is SubscriptionsPickerScreen && isSearchVisible()) {
hideSearch()
return true
} else if (currentScreen !is InitialScreen) {
showScreen(InitialScreen)
return true
}
return false
}
private fun setupListeners() {
delete_button.setOnClickListener { showScreen(DeleteScreen) }
@@ -146,13 +202,64 @@ class FeedGroupDialog : DialogFragment() {
}
})
confirm_button.setOnClickListener {
when (currentScreen) {
InitialScreen -> handlePositiveButtonInitialScreen()
DeleteScreen -> viewModel.deleteGroup()
else -> showScreen(InitialScreen)
confirm_button.setOnClickListener { handlePositiveButton() }
select_channel_button.setOnClickListener {
subscriptions_selector_list.scrollToPosition(0)
showScreen(SubscriptionsPickerScreen)
}
val headerMenu = subscriptions_header_toolbar.menu
requireActivity().menuInflater.inflate(R.menu.menu_feed_group_dialog, headerMenu)
headerMenu.findItem(R.id.action_search).setOnMenuItemClickListener {
showSearch()
true
}
headerMenu.findItem(R.id.feed_group_toggle_show_only_ungrouped_subscriptions).apply {
isChecked = subscriptionsShowOnlyUngrouped
setOnMenuItemClickListener {
subscriptionsShowOnlyUngrouped = !subscriptionsShowOnlyUngrouped
it.isChecked = subscriptionsShowOnlyUngrouped
viewModel.toggleShowOnlyUngrouped(subscriptionsShowOnlyUngrouped)
true
}
}
toolbar_search_clear.setOnClickListener {
if (TextUtils.isEmpty(toolbar_search_edit_text.text)) {
hideSearch()
return@setOnClickListener
}
resetSearch()
showKeyboardSearch()
}
toolbar_search_edit_text.setOnClickListener {
if (AndroidTvUtils.isTv(context)) {
showKeyboardSearch()
}
}
toolbar_search_edit_text.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
override fun afterTextChanged(s: Editable) = Unit
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
val newQuery: String = toolbar_search_edit_text.text.toString()
subscriptionsCurrentSearchQuery = newQuery
viewModel.filterSubscriptionsBy(newQuery)
}
})
subscriptionGroupAdapter?.setOnItemClickListener(subscriptionPickerItemListener)
}
private fun handlePositiveButton() = when {
currentScreen is InitialScreen -> handlePositiveButtonInitialScreen()
currentScreen is DeleteScreen -> viewModel.deleteGroup()
currentScreen is SubscriptionsPickerScreen && isSearchVisible() -> hideSearch()
else -> showScreen(InitialScreen)
}
private fun handlePositiveButtonInitialScreen() {
@@ -185,80 +292,73 @@ class FeedGroupDialog : DialogFragment() {
groupIcon = feedGroupEntity?.icon
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
icon_preview.setImageResource((if (selectedIcon == null) icon else selectedIcon!!).getDrawableRes(requireContext()))
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
icon_preview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
if (group_name_input.text.isNullOrBlank()) {
group_name_input.setText(name)
}
}
private fun setupSubscriptionPicker(subscriptions: List<SubscriptionEntity>, selectedSubscriptions: Set<Long>) {
this.selectedSubscriptions.addAll(selectedSubscriptions)
val useGridLayout = subscriptions.isNotEmpty()
private val subscriptionPickerItemListener = OnItemClickListener { item, view ->
if (item is PickerSubscriptionItem) {
val subscriptionId = item.subscriptionEntity.uid
wasSubscriptionSelectionChanged = true
val groupAdapter = GroupAdapter<GroupieViewHolder>()
groupAdapter.spanCount = if (useGridLayout) 4 else 1
val subscriptionsCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
selected_subscription_count_view.text = selectedCountText
subscriptions_selector_header_info.text = selectedCountText
Section().apply {
addAll(subscriptions.map {
val isSelected = this@FeedGroupDialog.selectedSubscriptions.contains(it.uid)
PickerSubscriptionItem(it, isSelected)
})
setPlaceholder(EmptyPlaceholderItem())
groupAdapter.add(this)
}
subscriptions_selector_list.apply {
layoutManager = if (useGridLayout) {
GridLayoutManager(requireContext(), groupAdapter.spanCount, RecyclerView.VERTICAL, false)
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
this.selectedSubscriptions.remove(subscriptionId)
false
} else {
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
this.selectedSubscriptions.add(subscriptionId)
true
}
adapter = groupAdapter
item.updateSelected(view, isSelected)
updateSubscriptionSelectedCount()
}
}
if (subscriptionsListState != null) {
layoutManager?.onRestoreInstanceState(subscriptionsListState)
subscriptionsListState = null
}
private fun setupSubscriptionPicker(
subscriptions: List<PickerSubscriptionItem>,
selectedSubscriptions: Set<Long>
) {
if (!wasSubscriptionSelectionChanged) {
this.selectedSubscriptions.addAll(selectedSubscriptions)
}
groupAdapter.setOnItemClickListener { item, _ ->
when (item) {
is PickerSubscriptionItem -> {
val subscriptionId = item.subscriptionEntity.uid
updateSubscriptionSelectedCount()
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
this.selectedSubscriptions.remove(subscriptionId)
false
} else {
this.selectedSubscriptions.add(subscriptionId)
true
}
item.isSelected = isSelected
item.notifyChanged(PickerSubscriptionItem.UPDATE_SELECTED)
val subscriptionsCount = this.selectedSubscriptions.size
val updateSelectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
selected_subscription_count_view.text = updateSelectedCountText
subscriptions_selector_header_info.text = updateSelectedCountText
}
}
if (subscriptions.isEmpty()) {
subscriptionEmptyFooter.clear()
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
} else {
subscriptionEmptyFooter.clear()
}
select_channel_button.setOnClickListener {
subscriptions.forEach {
it.isSelected = this@FeedGroupDialog.selectedSubscriptions
.contains(it.subscriptionEntity.uid)
}
subscriptionMainSection.update(subscriptions, false)
if (subscriptionsListState != null) {
subscriptions_selector_list.layoutManager?.onRestoreInstanceState(subscriptionsListState)
subscriptionsListState = null
} else {
subscriptions_selector_list.scrollToPosition(0)
showScreen(SubscriptionsPickerScreen)
}
}
private fun updateSubscriptionSelectedCount() {
val selectedCount = this.selectedSubscriptions.size
val selectedCountText = resources.getQuantityString(
R.plurals.feed_group_dialog_selection_count,
selectedCount, selectedCount)
selected_subscription_count_view.text = selectedCountText
subscriptions_header_info.text = selectedCountText
}
private fun setupIconPicker() {
val groupAdapter = GroupAdapter<GroupieViewHolder>()
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
@@ -294,9 +394,9 @@ class FeedGroupDialog : DialogFragment() {
}
}
///////////////////////////////////////////////////////////////////////////
/*///////////////////////////////////////////////////////////////////////////
// Screen Selector
///////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////// */
private fun showScreen(screen: ScreenState) {
currentScreen = screen
@@ -320,7 +420,8 @@ class FeedGroupDialog : DialogFragment() {
else -> View.VISIBLE
}
if (currentScreen != InitialScreen) hideKeyboard()
hideKeyboard()
hideSearch()
}
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
@@ -330,13 +431,58 @@ class FeedGroupDialog : DialogFragment() {
}
}
///////////////////////////////////////////////////////////////////////////
/*///////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////// */
private fun isSearchVisible() = subscriptions_header_search_container?.visibility == View.VISIBLE
private fun resetSearch() {
toolbar_search_edit_text.setText("")
subscriptionsCurrentSearchQuery = ""
viewModel.clearSubscriptionsFilter()
}
private fun hideSearch() {
resetSearch()
subscriptions_header_search_container.visibility = View.GONE
subscriptions_header_info_container.visibility = View.VISIBLE
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = true
hideKeyboardSearch()
}
private fun showSearch() {
subscriptions_header_search_container.visibility = View.VISIBLE
subscriptions_header_info_container.visibility = View.GONE
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = false
showKeyboardSearch()
}
private val inputMethodManager by lazy {
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
}
private fun showKeyboardSearch() {
if (toolbar_search_edit_text.requestFocus()) {
inputMethodManager.showSoftInput(toolbar_search_edit_text, InputMethodManager.SHOW_IMPLICIT)
}
}
private fun hideKeyboardSearch() {
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN)
toolbar_search_edit_text.clearFocus()
}
private fun showKeyboard() {
if (group_name_input.requestFocus()) {
inputMethodManager.showSoftInput(group_name_input, InputMethodManager.SHOW_IMPLICIT)
}
}
private fun hideKeyboard() {
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN)
group_name_input.clearFocus()
}
@@ -363,4 +509,4 @@ class FeedGroupDialog : DialogFragment() {
return dialog
}
}
}
}

View File

@@ -9,43 +9,56 @@ import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.disposables.Disposable
import io.reactivex.functions.BiFunction
import io.reactivex.processors.BehaviorProcessor
import io.reactivex.schedulers.Schedulers
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
}
}
class FeedGroupDialogViewModel(
applicationContext: Context,
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
initialQuery: String = "",
initialShowOnlyUngrouped: Boolean = false
) : ViewModel() {
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
private var subscriptionManager = SubscriptionManager(applicationContext)
private var filterSubscriptions = BehaviorProcessor.create<String>()
private var toggleShowOnlyUngrouped = BehaviorProcessor.create<Boolean>()
private var subscriptionsFlowable = Flowable
.combineLatest(
filterSubscriptions.startWith(initialQuery),
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
)
.distinctUntilChanged()
.switchMap { filter ->
subscriptionManager.getSubscriptions(groupId, filter.query, filter.showOnlyUngrouped)
}.map { list -> list.map { PickerSubscriptionItem(it) } }
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
val subscriptionsLiveData: LiveData<Pair<List<SubscriptionEntity>, Set<Long>>> = mutableSubscriptionsLiveData
val subscriptionsLiveData: LiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>> = mutableSubscriptionsLiveData
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
private var actionProcessingDisposable: Disposable? = null
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
.subscribeOn(Schedulers.io())
.subscribe(mutableGroupLiveData::postValue)
.subscribeOn(Schedulers.io())
.subscribe(mutableGroupLiveData::postValue)
private var subscriptionsDisposable = Flowable
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
.subscribeOn(Schedulers.io())
.subscribe(mutableSubscriptionsLiveData::postValue)
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
.subscribeOn(Schedulers.io())
.subscribe(mutableSubscriptionsLiveData::postValue)
override fun onCleared() {
super.onCleared()
@@ -56,14 +69,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
.flatMapCompletable {
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
})
.flatMapCompletable {
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
})
}
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
}
fun deleteGroup() {
@@ -75,13 +88,40 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
actionProcessingDisposable = completable
.subscribeOn(Schedulers.io())
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
.subscribeOn(Schedulers.io())
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
}
}
fun filterSubscriptionsBy(query: String) {
filterSubscriptions.onNext(query)
}
fun clearSubscriptionsFilter() {
filterSubscriptions.onNext("")
}
fun toggleShowOnlyUngrouped(showOnlyUngrouped: Boolean) {
toggleShowOnlyUngrouped.onNext(showOnlyUngrouped)
}
sealed class DialogEvent {
object ProcessingEvent : DialogEvent()
object SuccessEvent : DialogEvent()
}
}
data class Filter(val query: String, val showOnlyUngrouped: Boolean)
class Factory(
private val context: Context,
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
private val initialQuery: String = "",
private val initialShowOnlyUngrouped: Boolean = false
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FeedGroupDialogViewModel(context.applicationContext,
groupId, initialQuery, initialShowOnlyUngrouped) as T
}
}
}

View File

@@ -16,15 +16,15 @@ import com.xwray.groupie.TouchCallback
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import icepick.Icepick
import icepick.State
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.*
import java.util.Collections
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.ProcessingEvent
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
import org.schabi.newpipe.util.ThemeHelper
import java.util.*
import kotlin.collections.ArrayList
class FeedGroupReorderDialog : DialogFragment() {
private lateinit var viewModel: FeedGroupReorderDialogViewModel
@@ -93,8 +93,11 @@ class FeedGroupReorderDialog : DialogFragment() {
private fun getItemTouchCallback(): SimpleCallback {
return object : TouchCallback() {
override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean {
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val sourceIndex = source.adapterPosition
val targetIndex = target.adapterPosition
@@ -109,4 +112,4 @@ class FeedGroupReorderDialog : DialogFragment() {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {}
}
}
}
}

View File

@@ -49,4 +49,4 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
object ProcessingEvent : DialogEvent()
object SuccessEvent : DialogEvent()
}
}
}

View File

@@ -4,19 +4,21 @@ import android.content.Context
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.list_channel_item.*
import kotlinx.android.synthetic.main.list_channel_item.itemAdditionalDetails
import kotlinx.android.synthetic.main.list_channel_item.itemChannelDescriptionView
import kotlinx.android.synthetic.main.list_channel_item.itemThumbnailView
import kotlinx.android.synthetic.main.list_channel_item.itemTitleView
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.util.ImageDisplayConstants
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.OnClickGesture
class ChannelItem(
private val infoItem: ChannelInfoItem,
private val subscriptionId: Long = -1L,
var itemVersion: ItemVersion = ItemVersion.NORMAL,
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
private val infoItem: ChannelInfoItem,
private val subscriptionId: Long = -1L,
var itemVersion: ItemVersion = ItemVersion.NORMAL,
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
) : Item() {
override fun getId(): Long = if (subscriptionId == -1L) super.getId() else subscriptionId
@@ -62,4 +64,4 @@ class ChannelItem(
override fun getSpanSize(spanCount: Int, position: Int): Int {
return if (itemVersion == ItemVersion.GRID) 1 else spanCount
}
}
}

View File

@@ -7,4 +7,5 @@ import org.schabi.newpipe.R
class EmptyPlaceholderItem : Item() {
override fun getLayout(): Int = R.layout.list_empty_view
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
}
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
}

View File

@@ -7,4 +7,4 @@ import org.schabi.newpipe.R
class FeedGroupAddItem : Item() {
override fun getLayout(): Int = R.layout.feed_group_add_new_item
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
}
}

View File

@@ -2,15 +2,16 @@ package org.schabi.newpipe.local.subscription.item
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.feed_group_card_item.*
import kotlinx.android.synthetic.main.feed_group_card_item.icon
import kotlinx.android.synthetic.main.feed_group_card_item.title
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.local.subscription.FeedGroupIcon
data class FeedGroupCardItem(
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
val name: String,
val icon: FeedGroupIcon
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
val name: String,
val icon: FeedGroupIcon
) : Item() {
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)
@@ -27,4 +28,4 @@ data class FeedGroupCardItem(
viewHolder.title.text = name
viewHolder.icon.setImageResource(icon.getDrawableRes(viewHolder.containerView.context))
}
}
}

View File

@@ -8,7 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.feed_item_carousel.*
import kotlinx.android.synthetic.main.feed_item_carousel.recycler_view
import org.schabi.newpipe.R
import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration
@@ -54,4 +54,4 @@ class FeedGroupCarouselItem(context: Context, private val carouselAdapter: Group
listState = linearLayoutManager?.onSaveInstanceState()
}
}
}

View File

@@ -6,19 +6,21 @@ import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.UP
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.feed_group_reorder_item.*
import kotlinx.android.synthetic.main.feed_group_reorder_item.group_icon
import kotlinx.android.synthetic.main.feed_group_reorder_item.group_name
import kotlinx.android.synthetic.main.feed_group_reorder_item.handle
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.local.subscription.FeedGroupIcon
data class FeedGroupReorderItem(
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
val name: String,
val icon: FeedGroupIcon,
val dragCallback: ItemTouchHelper
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
val name: String,
val icon: FeedGroupIcon,
val dragCallback: ItemTouchHelper
) : Item() {
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper)
: this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) :
this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
override fun getId(): Long {
return when (groupId) {
@@ -45,4 +47,4 @@ data class FeedGroupReorderItem(
override fun getDragDirs(): Int {
return UP or DOWN
}
}
}

View File

@@ -9,7 +9,11 @@ import android.widget.TextView
import androidx.annotation.DrawableRes
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.feed_import_export_group.*
import kotlinx.android.synthetic.main.feed_import_export_group.export_to_options
import kotlinx.android.synthetic.main.feed_import_export_group.import_export
import kotlinx.android.synthetic.main.feed_import_export_group.import_export_expand_icon
import kotlinx.android.synthetic.main.feed_import_export_group.import_export_options
import kotlinx.android.synthetic.main.feed_import_export_group.import_from_options
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.exceptions.ExtractionException
@@ -19,10 +23,10 @@ import org.schabi.newpipe.util.ThemeHelper
import org.schabi.newpipe.views.CollapsibleView
class FeedImportExportItem(
val onImportPreviousSelected: () -> Unit,
val onImportFromServiceSelected: (Int) -> Unit,
val onExportSelected: () -> Unit,
var isExpanded: Boolean = false
val onImportPreviousSelected: () -> Unit,
val onImportFromServiceSelected: (Int) -> Unit,
val onExportSelected: () -> Unit,
var isExpanded: Boolean = false
) : Item() {
companion object {
const val REFRESH_EXPANDED_STATUS = 123
@@ -104,7 +108,6 @@ class FeedImportExportItem(
} catch (e: ExtractionException) {
throw RuntimeException("Services array contains an entry that it's not a valid service name ($serviceName)", e)
}
}
}
@@ -113,4 +116,4 @@ class FeedImportExportItem(
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder)
previousBackupItem.setOnClickListener { onExportSelected() }
}
}
}

View File

@@ -3,7 +3,7 @@ package org.schabi.newpipe.local.subscription.item
import android.view.View.OnClickListener
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.header_item.*
import kotlinx.android.synthetic.main.header_item.header_title
import org.schabi.newpipe.R
class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? = null) : Item() {
@@ -16,4 +16,4 @@ class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? =
val listener: OnClickListener? = if (onClickListener != null) OnClickListener { onClickListener.invoke() } else null
viewHolder.root.setOnClickListener(listener)
}
}
}

View File

@@ -1,18 +1,21 @@
package org.schabi.newpipe.local.subscription.item
import android.view.View.*
import android.view.View.GONE
import android.view.View.OnClickListener
import android.view.View.VISIBLE
import androidx.annotation.DrawableRes
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.header_with_menu_item.*
import kotlinx.android.synthetic.main.header_with_menu_item.header_menu_item
import kotlinx.android.synthetic.main.header_with_menu_item.header_title
import org.schabi.newpipe.R
class HeaderWithMenuItem(
val title: String,
@DrawableRes val itemIcon: Int = 0,
var showMenuItem: Boolean = true,
private val onClickListener: (() -> Unit)? = null,
private val menuItemOnClickListener: (() -> Unit)? = null
val title: String,
@DrawableRes val itemIcon: Int = 0,
var showMenuItem: Boolean = true,
private val onClickListener: (() -> Unit)? = null,
private val menuItemOnClickListener: (() -> Unit)? = null
) : Item() {
companion object {
const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1
@@ -46,4 +49,4 @@ class HeaderWithMenuItem(
private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) {
viewHolder.header_menu_item.visibility = if (showMenuItem) VISIBLE else GONE
}
}
}

View File

@@ -4,7 +4,7 @@ import android.content.Context
import androidx.annotation.DrawableRes
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.picker_icon_item.*
import kotlinx.android.synthetic.main.picker_icon_item.icon_view
import org.schabi.newpipe.R
import org.schabi.newpipe.local.subscription.FeedGroupIcon
@@ -17,4 +17,4 @@ class PickerIconItem(context: Context, val icon: FeedGroupIcon) : Item() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.icon_view.setImageResource(iconRes)
}
}
}

View File

@@ -1,37 +1,28 @@
package org.schabi.newpipe.local.subscription.item
import android.view.View
import com.nostra13.universalimageloader.core.DisplayImageOptions
import com.nostra13.universalimageloader.core.ImageLoader
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import kotlinx.android.synthetic.main.picker_subscription_item.*
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
import org.schabi.newpipe.R
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.util.AnimationUtils
import org.schabi.newpipe.util.AnimationUtils.animateView
import org.schabi.newpipe.util.ImageDisplayConstants
data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, var isSelected: Boolean = false) : Item() {
companion object {
const val UPDATE_SELECTED = 123
val IMAGE_LOADING_OPTIONS: DisplayImageOptions = ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
}
data class PickerSubscriptionItem(
val subscriptionEntity: SubscriptionEntity,
var isSelected: Boolean = false
) : Item() {
override fun getId(): Long = subscriptionEntity.uid
override fun getLayout(): Int = R.layout.picker_subscription_item
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(UPDATE_SELECTED)) {
animateView(viewHolder.selected_highlight, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
return
}
super.bind(viewHolder, position, payloads)
}
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl, viewHolder.thumbnail_view, IMAGE_LOADING_OPTIONS)
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
viewHolder.title_view.text = subscriptionEntity.name
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
@@ -45,7 +36,9 @@ data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, va
viewHolder.selected_highlight.alpha = 1F
}
override fun getId(): Long {
return subscriptionEntity.uid
fun updateSelected(containerView: View, isSelected: Boolean) {
this.isSelected = isSelected
animateView(containerView.selected_highlight,
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
}
}
}

View File

@@ -49,7 +49,6 @@ import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
@@ -91,7 +90,6 @@ public final class BackgroundPlayer extends Service {
/*//////////////////////////////////////////////////////////////////////////
// Service-Activity Binder
//////////////////////////////////////////////////////////////////////////*/
private LockManager lockManager;
private SharedPreferences sharedPreferences;
/*//////////////////////////////////////////////////////////////////////////
@@ -116,7 +114,6 @@ public final class BackgroundPlayer extends Service {
Log.d(TAG, "onCreate() called");
}
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
lockManager = new LockManager(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this);
@@ -166,9 +163,6 @@ public final class BackgroundPlayer extends Service {
Log.d(TAG, "onClose() called");
}
if (lockManager != null) {
lockManager.releaseWifiAndCpu();
}
if (basePlayerImpl != null) {
basePlayerImpl.savePlaybackState();
basePlayerImpl.stopActivityBinding();
@@ -179,7 +173,6 @@ public final class BackgroundPlayer extends Service {
}
mBinder = null;
basePlayerImpl = null;
lockManager = null;
stopForeground(true);
stopSelf();
@@ -208,9 +201,10 @@ public final class BackgroundPlayer extends Service {
}
private NotificationCompat.Builder createNotification() {
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_background_notification);
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
R.layout.player_notification_expanded);
R.layout.player_background_notification_expanded);
setupNotification(notRemoteView);
setupNotification(bigNotRemoteView);
@@ -662,8 +656,7 @@ public final class BackgroundPlayer extends Service {
super.onPlaying();
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.ic_pause_white);
lockManager.acquireWifiAndCpu();
updateNotification(R.drawable.exo_controls_pause);
}
@Override
@@ -671,8 +664,7 @@ public final class BackgroundPlayer extends Service {
super.onPaused();
resetNotification();
updateNotificationThumbnail();
updateNotification(R.drawable.ic_play_arrow_white);
lockManager.releaseWifiAndCpu();
updateNotification(R.drawable.exo_controls_play);
}
@Override
@@ -686,8 +678,7 @@ public final class BackgroundPlayer extends Service {
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
}
updateNotificationThumbnail();
updateNotification(R.drawable.ic_replay_white);
lockManager.releaseWifiAndCpu();
updateNotification(R.drawable.ic_replay_white_24dp);
}
}
}

View File

@@ -35,9 +35,9 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
@@ -64,7 +64,6 @@ import org.schabi.newpipe.player.helper.LoadController;
import org.schabi.newpipe.player.helper.MediaSessionManager;
import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
import org.schabi.newpipe.player.playback.BasePlayerMediaSession;
import org.schabi.newpipe.player.playback.CustomTrackSelector;
import org.schabi.newpipe.player.playback.MediaSourceManager;
@@ -77,7 +76,6 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.SerializedCache;
import java.io.IOException;
import java.net.UnknownHostException;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
@@ -217,7 +215,7 @@ public abstract class BasePlayer implements
final TrackSelection.Factory trackSelectionFactory = PlayerHelper
.getQualitySelector(context);
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
this.trackSelector = new CustomTrackSelector(context, trackSelectionFactory);
this.loadControl = new LoadController();
this.renderFactory = new DefaultRenderersFactory(context);
@@ -232,14 +230,18 @@ public abstract class BasePlayer implements
public void initPlayer(final boolean playOnReady) {
if (DEBUG) {
Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]");
}
simpleExoPlayer = ExoPlayerFactory
.newSimpleInstance(context, renderFactory, trackSelector, loadControl);
simpleExoPlayer = new SimpleExoPlayer.Builder(context, renderFactory)
.setTrackSelector(trackSelector)
.setLoadControl(loadControl)
.build();
simpleExoPlayer.addListener(this);
simpleExoPlayer.setPlayWhenReady(playOnReady);
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK);
simpleExoPlayer.setHandleAudioBecomingNoisy(true);
audioReactor = new AudioReactor(context, simpleExoPlayer);
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
@@ -329,13 +331,12 @@ public abstract class BasePlayer implements
final SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
final float speed = preferences
.getFloat(context.getString(R.string.playback_speed_key), getPlaybackSpeed());
final float pitch = preferences.getFloat(context.getString(R.string.playback_pitch_key),
getPlaybackPitch());
final boolean skipSilence = preferences
.getBoolean(context.getString(R.string.playback_skip_silence_key),
getPlaybackSkipSilence());
final float speed = preferences.getFloat(
context.getString(R.string.playback_speed_key), getPlaybackSpeed());
final float pitch = preferences.getFloat(
context.getString(R.string.playback_pitch_key), getPlaybackPitch());
final boolean skipSilence = preferences.getBoolean(
context.getString(R.string.playback_skip_silence_key), getPlaybackSkipSilence());
return new PlaybackParameters(speed, pitch, skipSilence);
}
@@ -666,11 +667,9 @@ public abstract class BasePlayer implements
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onTimelineChanged(final Timeline timeline, final Object manifest,
@Player.TimelineChangeReason final int reason) {
public void onTimelineChanged(final Timeline timeline, final int reason) {
if (DEBUG) {
Log.d(TAG, "ExoPlayer - onTimelineChanged() called with "
+ (manifest == null ? "no manifest" : "available manifest") + ", "
+ "timeline size = [" + timeline.getWindowCount() + "], "
+ "reason = [" + reason + "]");
}
@@ -833,16 +832,8 @@ public abstract class BasePlayer implements
final Throwable cause = error.getCause();
if (error instanceof BehindLiveWindowException) {
reload();
} else if (cause instanceof UnknownHostException) {
playQueue.error(/*isNetworkProblem=*/true);
} else if (isCurrentWindowValid()) {
playQueue.error(/*isTransitioningToBadStream=*/true);
} else if (cause instanceof FailedMediaSource.MediaSourceResolutionException) {
playQueue.error(/*recoverableWithNoAvailableStream=*/false);
} else if (cause instanceof FailedMediaSource.StreamInfoLoadException) {
playQueue.error(/*recoverableIfLoadFailsWhenNetworkIsFine=*/false);
} else {
playQueue.error(/*noIdeaWhatHappenedAndLetUserChooseWhatToDo=*/true);
playQueue.error();
}
}
@@ -1129,6 +1120,7 @@ public abstract class BasePlayer implements
Log.d(TAG, "onFastRewind() called");
}
seekBy(-getSeekDuration());
triggerProgressUpdate();
}
public void onFastForward() {
@@ -1136,6 +1128,7 @@ public abstract class BasePlayer implements
Log.d(TAG, "onFastForward() called");
}
seekBy(getSeekDuration());
triggerProgressUpdate();
}
private int getSeekDuration() {
@@ -1477,10 +1470,21 @@ public abstract class BasePlayer implements
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
}
/**
* Sets the playback parameters of the player, and also saves them to shared preferences.
* Speed and pitch are rounded up to 2 decimal places before being used or saved.
* @param speed the playback speed, will be rounded to up to 2 decimal places
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
* @param skipSilence skip silence during playback
*/
public void setPlaybackParameters(final float speed, final float pitch,
final boolean skipSilence) {
savePlaybackParametersToPreferences(speed, pitch, skipSilence);
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
final float roundedPitch = Math.round(pitch * 100.0f) / 100.0f;
savePlaybackParametersToPreferences(roundedSpeed, roundedPitch, skipSilence);
simpleExoPlayer.setPlaybackParameters(
new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
}
private void savePlaybackParametersToPreferences(final float speed, final float pitch,

View File

@@ -80,8 +80,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.AndroidTvUtils;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
@@ -491,7 +491,7 @@ public final class MainVideoPlayer extends AppCompatActivity
protected void setMuteButton(final ImageButton muteButton, final boolean isMuted) {
muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(), isMuted
? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp));
? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
}
@@ -593,9 +593,6 @@ public final class MainVideoPlayer extends AppCompatActivity
titleTextView.setSelected(true);
channelTextView.setSelected(true);
boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context)
.getBoolean(this.context.getString(R.string.show_play_with_kodi_key), false);
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
getRootView().setKeepScreenOn(true);
}
@@ -712,6 +709,13 @@ public final class MainVideoPlayer extends AppCompatActivity
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
super.onMetadataChanged(tag);
// show kodi button if it supports the current service and it is enabled in settings
final boolean showKodiButton =
KoreUtil.isServiceSupportedByKore(tag.getMetadata().getServiceId())
&& PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
titleTextView.setText(tag.getMetadata().getName());
channelTextView.setText(tag.getMetadata().getUploaderName());
}
@@ -725,13 +729,12 @@ public final class MainVideoPlayer extends AppCompatActivity
public void onKodiShare() {
onPause();
try {
NavigationHelper.playWithKore(this.context,
Uri.parse(playerImpl.getVideoUrl().replace("https", "http")));
NavigationHelper.playWithKore(context, Uri.parse(playerImpl.getVideoUrl()));
} catch (Exception e) {
if (DEBUG) {
Log.i(TAG, "Failed to start kore", e);
}
KoreUtil.showInstallKoreDialog(this.context);
KoreUtil.showInstallKoreDialog(context);
}
}
@@ -986,7 +989,7 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
public void onBlocked() {
super.onBlocked();
playPauseButton.setImageResource(R.drawable.ic_pause_white);
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
animatePlayButtons(false, 100);
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
getRootView().setKeepScreenOn(true);
@@ -1002,7 +1005,7 @@ public final class MainVideoPlayer extends AppCompatActivity
public void onPlaying() {
super.onPlaying();
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
animatePlayButtons(true, 200);
playPauseButton.requestFocus();
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
@@ -1015,7 +1018,7 @@ public final class MainVideoPlayer extends AppCompatActivity
public void onPaused() {
super.onPaused();
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
animatePlayButtons(true, 200);
playPauseButton.requestFocus();
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
@@ -1036,7 +1039,7 @@ public final class MainVideoPlayer extends AppCompatActivity
@Override
public void onCompleted() {
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
playPauseButton.setImageResource(R.drawable.ic_replay_white);
playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
animateView(closeButton, true, DEFAULT_CONTROLS_DURATION);
});
@@ -1322,6 +1325,13 @@ public final class MainVideoPlayer extends AppCompatActivity
return false;
}
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight();
final boolean isTouchingNavigationBar = initialEvent.getY()
> playerImpl.getRootView().getHeight() - getNavigationBarHeight();
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false;
}
// if (DEBUG) {
// Log.d(TAG, "MainVideoPlayer.onScroll = " +
// "e1.getRaw = [" + initialEvent.getRawX() + ", "
@@ -1358,12 +1368,12 @@ public final class MainVideoPlayer extends AppCompatActivity
}
final int resId = currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_72dp
? R.drawable.ic_volume_off_white_24dp
: currentProgressPercent < 0.25
? R.drawable.ic_volume_mute_white_72dp
? R.drawable.ic_volume_mute_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_volume_down_white_72dp
: R.drawable.ic_volume_up_white_72dp;
? R.drawable.ic_volume_down_white_24dp
: R.drawable.ic_volume_up_white_24dp;
playerImpl.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(getApplicationContext(), resId)
@@ -1390,10 +1400,10 @@ public final class MainVideoPlayer extends AppCompatActivity
}
final int resId = currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_72dp
? R.drawable.ic_brightness_low_white_24dp
: currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_72dp
: R.drawable.ic_brightness_high_white_72dp;
? R.drawable.ic_brightness_medium_white_24dp
: R.drawable.ic_brightness_high_white_24dp;
playerImpl.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(getApplicationContext(), resId)
@@ -1410,6 +1420,22 @@ public final class MainVideoPlayer extends AppCompatActivity
return true;
}
private int getNavigationBarHeight() {
int resId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resId > 0) {
return getResources().getDimensionPixelSize(resId);
}
return 0;
}
private int getStatusBarHeight() {
int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
return getResources().getDimensionPixelSize(resId);
}
return 0;
}
private void onScrollEnd() {
if (DEBUG) {
Log.d(TAG, "onScrollEnd() called");

View File

@@ -68,7 +68,6 @@ import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.LockManager;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
@@ -132,7 +131,6 @@ public final class PopupVideoPlayer extends Service {
private RemoteViews notRemoteView;
private VideoPlayerImpl playerImpl;
private LockManager lockManager;
private boolean isPopupClosing = false;
/*//////////////////////////////////////////////////////////////////////////
@@ -152,7 +150,6 @@ public final class PopupVideoPlayer extends Service {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
lockManager = new LockManager(this);
playerImpl = new VideoPlayerImpl(this);
ThemeHelper.setTheme(this);
@@ -169,9 +166,6 @@ public final class PopupVideoPlayer extends Service {
initPopup();
initPopupCloseOverlay();
}
if (!playerImpl.isPlaying()) {
playerImpl.getPlayer().setPlayWhenReady(true);
}
playerImpl.handleIntent(intent);
@@ -378,9 +372,6 @@ public final class PopupVideoPlayer extends Service {
}
mBinder = null;
if (lockManager != null) {
lockManager.releaseWifiAndCpu();
}
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
}
@@ -898,7 +889,7 @@ public final class PopupVideoPlayer extends Service {
public void onBlocked() {
super.onBlocked();
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
updateNotification(R.drawable.exo_controls_play);
}
@Override
@@ -908,20 +899,19 @@ public final class PopupVideoPlayer extends Service {
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
resetNotification();
updateNotification(R.drawable.ic_pause_white);
updateNotification(R.drawable.exo_controls_pause);
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_pause);
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
startForeground(NOTIFICATION_ID, notBuilder.build());
lockManager.acquireWifiAndCpu();
}
@Override
public void onBuffering() {
super.onBuffering();
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
updateNotification(R.drawable.exo_controls_play);
}
@Override
@@ -931,10 +921,8 @@ public final class PopupVideoPlayer extends Service {
updateWindowFlags(IDLE_WINDOW_FLAGS);
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white);
lockManager.releaseWifiAndCpu();
updateNotification(R.drawable.exo_controls_play);
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
stopForeground(false);
}
@@ -943,9 +931,9 @@ public final class PopupVideoPlayer extends Service {
public void onPausedSeek() {
super.onPausedSeek();
resetNotification();
updateNotification(R.drawable.ic_play_arrow_white);
updateNotification(R.drawable.exo_controls_play);
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
}
@Override
@@ -955,10 +943,8 @@ public final class PopupVideoPlayer extends Service {
updateWindowFlags(IDLE_WINDOW_FLAGS);
resetNotification();
updateNotification(R.drawable.ic_replay_white);
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white);
lockManager.releaseWifiAndCpu();
updateNotification(R.drawable.ic_replay_white_24dp);
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white_24dp);
stopForeground(false);
}

View File

@@ -45,7 +45,6 @@ import org.schabi.newpipe.util.ThemeHelper;
import java.util.Collections;
import java.util.List;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatPitch;
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@@ -84,14 +83,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private ImageButton repeatButton;
private ImageButton backwardButton;
private ImageButton fastRewindButton;
private ImageButton playPauseButton;
private ImageButton fastForwardButton;
private ImageButton forwardButton;
private ImageButton shuffleButton;
private ProgressBar progressBar;
private TextView playbackSpeedButton;
private TextView playbackPitchButton;
private Menu menu;
////////////////////////////////////////////////////////////////////////////
@@ -166,6 +164,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
case R.id.action_append_playlist:
appendAllToPlaylist();
return true;
case R.id.action_playback_speed:
openPlaybackParameterDialog();
return true;
case R.id.action_mute:
player.onMuteUnmuteButtonClicked();
return true;
@@ -310,20 +311,20 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void buildControls() {
repeatButton = rootView.findViewById(R.id.control_repeat);
backwardButton = rootView.findViewById(R.id.control_backward);
fastRewindButton = rootView.findViewById(R.id.control_fast_rewind);
playPauseButton = rootView.findViewById(R.id.control_play_pause);
fastForwardButton = rootView.findViewById(R.id.control_fast_forward);
forwardButton = rootView.findViewById(R.id.control_forward);
shuffleButton = rootView.findViewById(R.id.control_shuffle);
playbackSpeedButton = rootView.findViewById(R.id.control_playback_speed);
playbackPitchButton = rootView.findViewById(R.id.control_playback_pitch);
progressBar = rootView.findViewById(R.id.control_progress_bar);
repeatButton.setOnClickListener(this);
backwardButton.setOnClickListener(this);
fastRewindButton.setOnClickListener(this);
playPauseButton.setOnClickListener(this);
fastForwardButton.setOnClickListener(this);
forwardButton.setOnClickListener(this);
shuffleButton.setOnClickListener(this);
playbackSpeedButton.setOnClickListener(this);
playbackPitchButton.setOnClickListener(this);
}
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
@@ -473,16 +474,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
player.onRepeatClicked();
} else if (view.getId() == backwardButton.getId()) {
player.onPlayPrevious();
} else if (view.getId() == fastRewindButton.getId()) {
player.onFastRewind();
} else if (view.getId() == playPauseButton.getId()) {
player.onPlayPause();
} else if (view.getId() == fastForwardButton.getId()) {
player.onFastForward();
} else if (view.getId() == forwardButton.getId()) {
player.onPlayNext();
} else if (view.getId() == shuffleButton.getId()) {
player.onShuffleClicked();
} else if (view.getId() == playbackSpeedButton.getId()) {
openPlaybackParameterDialog();
} else if (view.getId() == playbackPitchButton.getId()) {
openPlaybackParameterDialog();
} else if (view.getId() == metadata.getId()) {
scrollToSelected();
} else if (view.getId() == progressLiveSync.getId()) {
@@ -643,13 +644,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void onStateChanged(final int state) {
switch (state) {
case BasePlayer.STATE_PAUSED:
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
break;
case BasePlayer.STATE_PLAYING:
playPauseButton.setImageResource(R.drawable.ic_pause_white);
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
break;
case BasePlayer.STATE_COMPLETED:
playPauseButton.setImageResource(R.drawable.ic_replay_white);
playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
break;
default:
break;
@@ -690,8 +691,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
if (parameters != null) {
playbackSpeedButton.setText(formatSpeed(parameters.speed));
playbackPitchButton.setText(formatPitch(parameters.pitch));
if (menu != null && player != null) {
final MenuItem item = menu.findItem(R.id.action_playback_speed);
item.setTitle(formatSpeed(parameters.speed));
}
}
}
@@ -717,9 +720,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
// using rootView.getContext() because getApplicationContext() didn't work
item.setIcon(player.isMuted()
? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
R.attr.volume_off)
R.attr.ic_volume_off)
: ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
R.attr.volume_on));
R.attr.ic_volume_up));
}
}
}

View File

@@ -46,7 +46,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.appcompat.content.res.AppCompatResources;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.PlaybackParameters;
@@ -682,13 +682,13 @@ public abstract class VideoPlayer extends BasePlayer
@Override
public void onFastRewind() {
super.onFastRewind();
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
showAndAnimateControl(R.drawable.ic_fast_rewind_white_24dp, true);
}
@Override
public void onFastForward() {
super.onFastForward();
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
showAndAnimateControl(R.drawable.ic_fast_forward_white_24dp, true);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -956,7 +956,7 @@ public abstract class VideoPlayer extends BasePlayer
controlAnimationView.setVisibility(View.VISIBLE);
controlAnimationView.setImageDrawable(ContextCompat.getDrawable(context, drawableId));
controlAnimationView.setImageDrawable(AppCompatResources.getDrawable(context, drawableId));
controlViewAnimator.start();
}

View File

@@ -36,9 +36,9 @@ public class LoadedMediaSource implements ManagedMediaSource {
}
@Override
public void prepareSource(final SourceInfoRefreshListener listener,
public void prepareSource(final MediaSourceCaller mediaSourceCaller,
@Nullable final TransferListener mediaTransferListener) {
source.prepareSource(listener, mediaTransferListener);
source.prepareSource(mediaSourceCaller, mediaTransferListener);
}
@Override
@@ -46,6 +46,11 @@ public class LoadedMediaSource implements ManagedMediaSource {
source.maybeThrowSourceInfoRefreshError();
}
@Override
public void enable(final MediaSourceCaller caller) {
source.enable(caller);
}
@Override
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
final long startPositionUs) {
@@ -58,8 +63,13 @@ public class LoadedMediaSource implements ManagedMediaSource {
}
@Override
public void releaseSource(final SourceInfoRefreshListener listener) {
source.releaseSource(listener);
public void disable(final MediaSourceCaller caller) {
source.disable(caller);
}
@Override
public void releaseSource(final MediaSourceCaller mediaSourceCaller) {
source.releaseSource(mediaSourceCaller);
}
@Override

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.player.mediasource;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource;
@@ -27,4 +28,10 @@ public interface ManagedMediaSource extends MediaSource {
* @return whether this source is for the specified stream
*/
boolean isStreamEqual(@NonNull PlayQueueItem stream);
@Nullable
@Override
default Object getTag() {
return this;
}
}

View File

@@ -35,7 +35,7 @@ public class ManagedMediaSourcePlaylist {
@Nullable
public ManagedMediaSource get(final int index) {
return (index < 0 || index >= size())
? null : (ManagedMediaSource) internalSource.getMediaSource(index);
? null : (ManagedMediaSource) internalSource.getMediaSource(index).getTag();
}
@NonNull

View File

@@ -1,5 +1,6 @@
package org.schabi.newpipe.player.playback;
import android.content.Context;
import android.text.TextUtils;
import android.util.Pair;
@@ -8,6 +9,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities.Capabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
@@ -25,8 +27,9 @@ import com.google.android.exoplayer2.util.Assertions;
public class CustomTrackSelector extends DefaultTrackSelector {
private String preferredTextLanguage;
public CustomTrackSelector(final TrackSelection.Factory adaptiveTrackSelectionFactory) {
super(adaptiveTrackSelectionFactory);
public CustomTrackSelector(final Context context,
final TrackSelection.Factory adaptiveTrackSelectionFactory) {
super(context, adaptiveTrackSelectionFactory);
}
private static boolean formatHasLanguage(final Format format, final String language) {
@@ -48,27 +51,31 @@ public class CustomTrackSelector extends DefaultTrackSelector {
@Override
@Nullable
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
final TrackGroupArray groups, final int[][] formatSupport, final Parameters params,
final TrackGroupArray groups,
@NonNull final int[][] formatSupport,
@NonNull final Parameters params,
@Nullable final String selectedAudioLanguage) {
TrackGroup selectedGroup = null;
int selectedTrackIndex = C.INDEX_UNSET;
int newPipeTrackScore = 0;
TextTrackScore selectedTrackScore = null;
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
TrackGroup trackGroup = groups.get(groupIndex);
int[] trackFormatSupport = formatSupport[groupIndex];
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
Format format = trackGroup.getFormat(trackIndex);
TextTrackScore trackScore = new TextTrackScore(format, params,
trackFormatSupport[trackIndex], selectedAudioLanguage);
if (formatHasLanguage(format, preferredTextLanguage)) {
selectedGroup = trackGroup;
selectedTrackIndex = trackIndex;
selectedTrackScore = trackScore;
// found user selected match (perfect!)
break;
break; // found user selected match (perfect!)
} else if (trackScore.isWithinConstraints && (selectedTrackScore == null
|| trackScore.compareTo(selectedTrackScore) > 0)) {
selectedGroup = trackGroup;

View File

@@ -5,6 +5,7 @@ import android.util.Log;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import java.util.ArrayList;
@@ -21,7 +22,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
final int serviceId;
final String baseUrl;
String nextUrl;
Page nextPage;
private transient Disposable fetchReactor;
@@ -29,16 +30,16 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0);
}
AbstractInfoPlayQueue(final int serviceId, final String url, final String nextPageUrl,
AbstractInfoPlayQueue(final int serviceId, final String url, final Page nextPage,
final List<StreamInfoItem> streams, final int index) {
super(index, extractListItems(streams));
this.baseUrl = url;
this.nextUrl = nextPageUrl;
this.nextPage = nextPage;
this.serviceId = serviceId;
this.isInitial = streams.isEmpty();
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
this.isComplete = !isInitial && !Page.isValid(nextPage);
}
protected abstract String getTag();
@@ -66,7 +67,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
if (!result.hasNextPage()) {
isComplete = true;
}
nextUrl = result.getNextPageUrl();
nextPage = result.getNextPage();
append(extractListItems(result.getRelatedItems()));
@@ -100,7 +101,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
if (!result.hasNextPage()) {
isComplete = true;
}
nextUrl = result.getNextPageUrl();
nextPage = result.getNextPage();
append(extractListItems(result.getItems()));

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.player.playqueue;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
@@ -17,15 +18,15 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
}
public ChannelPlayQueue(final ChannelInfo info) {
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems(), 0);
}
public ChannelPlayQueue(final int serviceId,
final String url,
final String nextPageUrl,
final Page nextPage,
final List<StreamInfoItem> streams,
final int index) {
super(serviceId, url, nextPageUrl, streams, index);
super(serviceId, url, nextPage, streams, index);
}
@Override
@@ -41,7 +42,7 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
} else {
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getNextPageObserver());

View File

@@ -305,25 +305,16 @@ public abstract class PlayQueue implements Serializable {
}
/**
* Report an exception for the item at the current index in order and the course of action:
* if the error can be skipped or the current item should be removed.
* Report an exception for the item at the current index in order and skip to the next one
* <p>
* This is done as a separate event as the underlying manager may have
* different implementation regarding exceptions.
* </p>
*
* @param skippable whether the error could be skipped
*/
public synchronized void error(final boolean skippable) {
final int index = getIndex();
if (skippable) {
queueIndex.incrementAndGet();
} else {
removeInternal(index);
}
broadcast(new ErrorEvent(index, getIndex(), skippable));
public synchronized void error() {
final int oldIndex = getIndex();
queueIndex.incrementAndGet();
broadcast(new ErrorEvent(oldIndex, getIndex()));
}
private synchronized void removeInternal(final int removeIndex) {

View File

@@ -115,9 +115,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
break;
case ERROR:
final ErrorEvent errorEvent = (ErrorEvent) message;
if (!errorEvent.isSkippable()) {
notifyItemRemoved(errorEvent.getErrorIndex());
}
notifyItemChanged(errorEvent.getErrorIndex());
notifyItemChanged(errorEvent.getQueueIndex());
break;

View File

@@ -1,5 +1,6 @@
package org.schabi.newpipe.player.playqueue;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
@@ -16,15 +17,15 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo,
}
public PlaylistPlayQueue(final PlaylistInfo info) {
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems(), 0);
}
public PlaylistPlayQueue(final int serviceId,
final String url,
final String nextPageUrl,
final Page nextPage,
final List<StreamInfoItem> streams,
final int index) {
super(serviceId, url, nextPageUrl, streams, index);
super(serviceId, url, nextPage, streams, index);
}
@Override
@@ -40,7 +41,7 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo,
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
} else {
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getNextPageObserver());

View File

@@ -3,12 +3,10 @@ package org.schabi.newpipe.player.playqueue.events;
public class ErrorEvent implements PlayQueueEvent {
private final int errorIndex;
private final int queueIndex;
private final boolean skippable;
public ErrorEvent(final int errorIndex, final int queueIndex, final boolean skippable) {
public ErrorEvent(final int errorIndex, final int queueIndex) {
this.errorIndex = errorIndex;
this.queueIndex = queueIndex;
this.skippable = skippable;
}
@Override
@@ -23,8 +21,4 @@ public class ErrorEvent implements PlayQueueEvent {
public int getQueueIndex() {
return queueIndex;
}
public boolean isSkippable() {
return skippable;
}
}

View File

@@ -4,7 +4,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import org.acra.collector.CrashReportData;
import org.acra.data.CrashReportData;
import org.acra.sender.ReportSender;
import org.schabi.newpipe.R;

View File

@@ -4,7 +4,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import org.acra.config.ACRAConfiguration;
import org.acra.config.CoreConfiguration;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderFactory;
@@ -31,7 +31,7 @@ import org.acra.sender.ReportSenderFactory;
public class AcraReportSenderFactory implements ReportSenderFactory {
@NonNull
public ReportSender create(@NonNull final Context context,
@NonNull final ACRAConfiguration config) {
@NonNull final CoreConfiguration config) {
return new AcraReportSender();
}
}

View File

@@ -11,7 +11,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -20,6 +19,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -32,11 +32,13 @@ import com.google.android.material.snackbar.Snackbar;
import com.grack.nanojson.JsonWriter;
import org.acra.ReportField;
import org.acra.collector.CrashReportData;
import org.acra.data.CrashReportData;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.PrintWriter;
@@ -45,7 +47,6 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Vector;
@@ -81,6 +82,10 @@ public class ErrorActivity extends AppCompatActivity {
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
public static final String ERROR_EMAIL_SUBJECT
= "Exception in NewPipe " + BuildConfig.VERSION_NAME;
public static final String ERROR_GITHUB_ISSUE_URL
= "https://github.com/TeamNewPipe/NewPipe/issues";
private String[] errorList;
private ErrorInfo errorInfo;
private Class returnActivity;
@@ -149,14 +154,7 @@ public class ErrorActivity extends AppCompatActivity {
public static void reportError(final Context context, final CrashReportData report,
final ErrorInfo errorInfo) {
// get key first (don't ask about this solution)
ReportField key = null;
for (ReportField k : report.keySet()) {
if (k.toString().equals("STACK_TRACE")) {
key = k;
}
}
String[] el = new String[]{report.get(key).toString()};
String[] el = new String[]{report.getString(ReportField.STACK_TRACE)};
Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra(ERROR_INFO, errorInfo);
@@ -200,7 +198,10 @@ public class ErrorActivity extends AppCompatActivity {
actionBar.setDisplayShowTitleEnabled(true);
}
Button reportButton = findViewById(R.id.errorReportButton);
final Button reportEmailButton = findViewById(R.id.errorReportEmailButton);
final Button copyButton = findViewById(R.id.errorReportCopyButton);
final Button reportGithubButton = findViewById(R.id.errorReportGitHubButton);
userCommentBox = findViewById(R.id.errorCommentBox);
TextView errorView = findViewById(R.id.errorView);
TextView infoView = findViewById(R.id.errorInfosView);
@@ -212,37 +213,23 @@ public class ErrorActivity extends AppCompatActivity {
errorList = intent.getStringArrayExtra(ERROR_LIST);
// important add guru meditation
addGuruMeditaion();
addGuruMeditation();
currentTimeStamp = getCurrentTimeStamp();
reportButton.setOnClickListener((View v) -> {
Context context = this;
new AlertDialog.Builder(context)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.privacy_policy_title)
.setMessage(R.string.start_accept_privacy_policy)
.setCancelable(false)
.setNeutralButton(R.string.read_privacy_policy, (dialog, which) -> {
Intent webIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(context.getString(R.string.privacy_policy_url))
);
context.startActivity(webIntent);
})
.setPositiveButton(R.string.accept, (dialog, which) -> {
Intent i = new Intent(Intent.ACTION_SENDTO);
i.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS))
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
.putExtra(Intent.EXTRA_TEXT, buildJson());
startActivity(Intent.createChooser(i, "Send Email"));
})
.setNegativeButton(R.string.decline, (dialog, which) -> {
// do nothing
})
.show();
reportEmailButton.setOnClickListener((View v) -> {
openPrivacyPolicyDialog(this, "EMAIL");
});
copyButton.setOnClickListener((View v) -> {
ShareUtils.copyToClipboard(this, buildMarkdown());
Toast.makeText(this, R.string.msg_copied, Toast.LENGTH_SHORT).show();
});
reportGithubButton.setOnClickListener((View v) -> {
openPrivacyPolicyDialog(this, "GITHUB");
});
// normal bugreport
buildInfo(errorInfo);
if (errorInfo.message != 0) {
@@ -254,7 +241,7 @@ public class ErrorActivity extends AppCompatActivity {
errorView.setText(formErrorText(errorList));
//print stack trace once again for debugging:
// print stack trace once again for debugging:
for (String e : errorList) {
Log.e(TAG, e);
}
@@ -285,6 +272,37 @@ public class ErrorActivity extends AppCompatActivity {
return false;
}
private void openPrivacyPolicyDialog(final Context context, final String action) {
new AlertDialog.Builder(context)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.privacy_policy_title)
.setMessage(R.string.start_accept_privacy_policy)
.setCancelable(false)
.setNeutralButton(R.string.read_privacy_policy, (dialog, which) -> {
ShareUtils.openUrlInBrowser(context,
context.getString(R.string.privacy_policy_url));
})
.setPositiveButton(R.string.accept, (dialog, which) -> {
if (action.equals("EMAIL")) { // send on email
final Intent i = new Intent(Intent.ACTION_SENDTO)
.setData(Uri.parse("mailto:")) // only email apps should handle this
.putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS})
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
.putExtra(Intent.EXTRA_TEXT, buildJson());
if (i.resolveActivity(getPackageManager()) != null) {
startActivity(i);
}
} else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub
ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL);
}
})
.setNegativeButton(R.string.decline, (dialog, which) -> {
// do nothing
})
.show();
}
private String formErrorText(final String[] el) {
StringBuilder text = new StringBuilder();
if (el != null) {
@@ -335,7 +353,9 @@ public class ErrorActivity extends AppCompatActivity {
text += getUserActionString(info.userAction) + "\n"
+ info.request + "\n"
+ getContentLangString() + "\n"
+ getContentLanguageString() + "\n"
+ getContentCountryString() + "\n"
+ getAppLanguage() + "\n"
+ info.serviceName + "\n"
+ currentTimeStamp + "\n"
+ getPackageName() + "\n"
@@ -351,7 +371,9 @@ public class ErrorActivity extends AppCompatActivity {
.object()
.value("user_action", getUserActionString(errorInfo.userAction))
.value("request", errorInfo.request)
.value("content_language", getContentLangString())
.value("content_language", getContentLanguageString())
.value("content_country", getContentCountryString())
.value("app_language", getAppLanguage())
.value("service", errorInfo.serviceName)
.value("package", getPackageName())
.value("version", BuildConfig.VERSION_NAME)
@@ -369,6 +391,63 @@ public class ErrorActivity extends AppCompatActivity {
return "";
}
private String buildMarkdown() {
try {
final StringBuilder htmlErrorReport = new StringBuilder();
final String userComment = userCommentBox.getText().toString();
if (!userComment.isEmpty()) {
htmlErrorReport.append(userComment).append("\n");
}
// basic error info
htmlErrorReport
.append("## Exception")
.append("\n* __User Action:__ ")
.append(getUserActionString(errorInfo.userAction))
.append("\n* __Request:__ ").append(errorInfo.request)
.append("\n* __Content Country:__ ").append(getContentCountryString())
.append("\n* __Content Language:__ ").append(getContentLanguageString())
.append("\n* __App Language:__ ").append(getAppLanguage())
.append("\n* __Service:__ ").append(errorInfo.serviceName)
.append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME)
.append("\n* __OS:__ ").append(getOsString()).append("\n");
// Collapse all logs to a single paragraph when there are more than one
// to keep the GitHub issue clean.
if (errorList.length > 1) {
htmlErrorReport
.append("<details><summary><b>Exceptions (")
.append(errorList.length)
.append(")</b></summary><p>\n");
}
// add the logs
for (int i = 0; i < errorList.length; i++) {
htmlErrorReport.append("<details><summary><b>Crash log ");
if (errorList.length > 1) {
htmlErrorReport.append(i + 1);
}
htmlErrorReport.append("</b>")
.append("</summary><p>\n")
.append("\n```\n").append(errorList[i]).append("\n```\n")
.append("</details>\n");
}
// make sure to close everything
if (errorList.length > 1) {
htmlErrorReport.append("</p></details>\n");
}
htmlErrorReport.append("<hr>\n");
return htmlErrorReport.toString();
} catch (Throwable e) {
Log.e(TAG, "Error while erroring: Could not build markdown");
e.printStackTrace();
return "";
}
}
private String getUserActionString(final UserAction userAction) {
if (userAction == null) {
return "Your description is in another castle.";
@@ -377,24 +456,27 @@ public class ErrorActivity extends AppCompatActivity {
}
}
private String getContentLangString() {
String contentLanguage = PreferenceManager.getDefaultSharedPreferences(this)
.getString(this.getString(R.string.content_country_key), "none");
if (contentLanguage.equals(getString(R.string.default_localization_key))) {
contentLanguage = Locale.getDefault().toString();
}
return contentLanguage;
private String getContentCountryString() {
return Localization.getPreferredContentCountry(this).getCountryCode();
}
private String getContentLanguageString() {
return Localization.getPreferredLocalization(this).getLocalizationCode();
}
private String getAppLanguage() {
return Localization.getAppLocale(getApplicationContext()).toString();
}
private String getOsString() {
String osBase = Build.VERSION.SDK_INT >= 23 ? Build.VERSION.BASE_OS : "Android";
final String osBase = Build.VERSION.SDK_INT >= 23 ? Build.VERSION.BASE_OS : "Android";
return System.getProperty("os.name")
+ " " + (osBase.isEmpty() ? "Android" : osBase)
+ " " + Build.VERSION.RELEASE
+ " - " + Build.VERSION.SDK_INT;
}
private void addGuruMeditaion() {
private void addGuruMeditation() {
//just an easter egg
TextView sorryView = findViewById(R.id.errorSorryView);
String text = sorryView.getText().toString();

View File

@@ -2,6 +2,7 @@ package org.schabi.newpipe.settings;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -17,6 +18,7 @@ import androidx.preference.Preference;
import com.nononsenseapps.filepicker.Utils;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
@@ -56,6 +58,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private File newpipeSettings;
private String thumbnailLoadToggleKey;
private String youtubeRestrictedModeEnabledKey;
private Localization initialSelectedLocalization;
private ContentCountry initialSelectedContentCountry;
@@ -65,6 +68,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
initialSelectedLocalization = org.schabi.newpipe.util.Localization
.getPreferredLocalization(requireContext());
@@ -86,6 +90,15 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
Toast.LENGTH_SHORT).show();
}
if (preference.getKey().equals(youtubeRestrictedModeEnabledKey)) {
Context context = getContext();
if (context != null) {
DownloaderImpl.getInstance().updateYoutubeRestrictedModeCookies(context);
} else {
Log.w(TAG, "onPreferenceTreeClick: null context");
}
}
return super.onPreferenceTreeClick(preference);
}

View File

@@ -10,6 +10,7 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -22,6 +23,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.List;
import java.util.Vector;
@@ -62,7 +64,7 @@ public class SelectChannelFragment extends DialogFragment {
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnSelectedLisener onSelectedLisener = null;
private OnSelectedListener onSelectedListener = null;
private OnCancelListener onCancelListener = null;
private ProgressBar progressBar;
@@ -71,8 +73,8 @@ public class SelectChannelFragment extends DialogFragment {
private List<SubscriptionEntity> subscriptions = new Vector<>();
public void setOnSelectedLisener(final OnSelectedLisener listener) {
onSelectedLisener = listener;
public void setOnSelectedListener(final OnSelectedListener listener) {
onSelectedListener = listener;
}
public void setOnCancelListener(final OnCancelListener listener) {
@@ -83,6 +85,12 @@ public class SelectChannelFragment extends DialogFragment {
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()));
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
@@ -121,9 +129,9 @@ public class SelectChannelFragment extends DialogFragment {
}
private void clickedItem(final int position) {
if (onSelectedLisener != null) {
if (onSelectedListener != null) {
SubscriptionEntity entry = subscriptions.get(position);
onSelectedLisener
onSelectedListener
.onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName());
}
dismiss();
@@ -178,7 +186,7 @@ public class SelectChannelFragment extends DialogFragment {
// Interfaces
//////////////////////////////////////////////////////////////////////////*/
public interface OnSelectedLisener {
public interface OnSelectedListener {
void onChannelSelected(int serviceId, String url, String name);
}

View File

@@ -9,7 +9,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -21,6 +22,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.List;
import java.util.Vector;
@@ -50,17 +52,27 @@ public class SelectKioskFragment extends DialogFragment {
private RecyclerView recyclerView = null;
private SelectKioskAdapter selectKioskAdapter = null;
private OnSelectedLisener onSelectedLisener = null;
private OnSelectedListener onSelectedListener = null;
private OnCancelListener onCancelListener = null;
public void setOnSelectedLisener(final OnSelectedLisener listener) {
onSelectedLisener = listener;
public void setOnSelectedListener(final OnSelectedListener listener) {
onSelectedListener = listener;
}
public void setOnCancelListener(final OnCancelListener listener) {
onCancelListener = listener;
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()));
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
@@ -90,8 +102,8 @@ public class SelectKioskFragment extends DialogFragment {
}
private void clickedItem(final SelectKioskAdapter.Entry entry) {
if (onSelectedLisener != null) {
onSelectedLisener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
if (onSelectedListener != null) {
onSelectedListener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
}
dismiss();
}
@@ -110,7 +122,7 @@ public class SelectKioskFragment extends DialogFragment {
// Interfaces
//////////////////////////////////////////////////////////////////////////*/
public interface OnSelectedLisener {
public interface OnSelectedListener {
void onKioskSelected(int serviceId, String kioskId, String kioskName);
}
@@ -148,13 +160,8 @@ public class SelectKioskFragment extends DialogFragment {
final Entry entry = kioskList.get(position);
holder.titleView.setText(entry.kioskName);
holder.thumbnailView
.setImageDrawable(ContextCompat.getDrawable(getContext(), entry.icon));
holder.view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
clickedItem(entry);
}
});
.setImageDrawable(AppCompatResources.getDrawable(requireContext(), entry.icon));
holder.view.setOnClickListener(view -> clickedItem(entry));
}
class Entry {

View File

@@ -0,0 +1,225 @@
package org.schabi.newpipe.settings;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.AppDatabase;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import java.util.List;
import java.util.Vector;
import io.reactivex.Flowable;
import io.reactivex.disposables.Disposable;
public class SelectPlaylistFragment extends DialogFragment {
/**
* This contains the base display options for images.
*/
private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS
= new DisplayImageOptions.Builder().cacheInMemory(true).build();
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnSelectedListener onSelectedListener = null;
private OnCancelListener onCancelListener = null;
private ProgressBar progressBar;
private TextView emptyView;
private RecyclerView recyclerView;
private Disposable playlistsSubscriber;
private List<PlaylistLocalItem> playlists = new Vector<>();
public void setOnSelectedListener(final OnSelectedListener listener) {
onSelectedListener = listener;
}
public void setOnCancelListener(final OnCancelListener listener) {
onCancelListener = listener;
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's Lifecycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final View v =
inflater.inflate(R.layout.select_playlist_fragment, container, false);
recyclerView = v.findViewById(R.id.items_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter();
recyclerView.setAdapter(playlistAdapter);
progressBar = v.findViewById(R.id.progressBar);
emptyView = v.findViewById(R.id.empty_state_view);
progressBar.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.GONE);
final AppDatabase database = NewPipeDatabase.getInstance(requireContext());
final LocalPlaylistManager localPlaylistManager = new LocalPlaylistManager(database);
final RemotePlaylistManager remotePlaylistManager = new RemotePlaylistManager(database);
playlistsSubscriber = Flowable.combineLatest(localPlaylistManager.getPlaylists(),
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
.subscribe(this::displayPlaylists, this::onError);
return v;
}
@Override
public void onDestroy() {
super.onDestroy();
if (playlistsSubscriber != null) {
playlistsSubscriber.dispose();
playlistsSubscriber = null;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Handle actions
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCancel(final DialogInterface dialogInterface) {
super.onCancel(dialogInterface);
if (onCancelListener != null) {
onCancelListener.onCancel();
}
}
private void clickedItem(final int position) {
if (onSelectedListener != null) {
final LocalItem selectedItem = playlists.get(position);
if (selectedItem instanceof PlaylistMetadataEntry) {
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
onSelectedListener
.onLocalPlaylistSelected(entry.uid, entry.name);
} else if (selectedItem instanceof PlaylistRemoteEntity) {
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
onSelectedListener.onRemotePlaylistSelected(
entry.getServiceId(), entry.getUrl(), entry.getName());
}
}
dismiss();
}
/*//////////////////////////////////////////////////////////////////////////
// Item handling
//////////////////////////////////////////////////////////////////////////*/
private void displayPlaylists(final List<PlaylistLocalItem> newPlaylists) {
this.playlists = newPlaylists;
progressBar.setVisibility(View.GONE);
if (newPlaylists.isEmpty()) {
emptyView.setVisibility(View.VISIBLE);
return;
}
recyclerView.setVisibility(View.VISIBLE);
}
/*//////////////////////////////////////////////////////////////////////////
// Error
//////////////////////////////////////////////////////////////////////////*/
protected void onError(final Throwable e) {
final Activity activity = getActivity();
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
}
/*//////////////////////////////////////////////////////////////////////////
// Interfaces
//////////////////////////////////////////////////////////////////////////*/
public interface OnSelectedListener {
void onLocalPlaylistSelected(long id, String name);
void onRemotePlaylistSelected(int serviceId, String url, String name);
}
public interface OnCancelListener {
void onCancel();
}
private class SelectPlaylistAdapter
extends RecyclerView.Adapter<SelectPlaylistAdapter.SelectPlaylistItemHolder> {
@Override
public SelectPlaylistItemHolder onCreateViewHolder(final ViewGroup parent,
final int viewType) {
final View item = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_playlist_mini_item, parent, false);
return new SelectPlaylistItemHolder(item);
}
@Override
public void onBindViewHolder(final SelectPlaylistItemHolder holder, final int position) {
final PlaylistLocalItem selectedItem = playlists.get(position);
if (selectedItem instanceof PlaylistMetadataEntry) {
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
holder.titleView.setText(entry.name);
holder.view.setOnClickListener(view -> clickedItem(position));
imageLoader.displayImage(entry.thumbnailUrl, holder.thumbnailView,
DISPLAY_IMAGE_OPTIONS);
} else if (selectedItem instanceof PlaylistRemoteEntity) {
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
holder.titleView.setText(entry.getName());
holder.view.setOnClickListener(view -> clickedItem(position));
imageLoader.displayImage(entry.getThumbnailUrl(), holder.thumbnailView,
DISPLAY_IMAGE_OPTIONS);
}
}
@Override
public int getItemCount() {
return playlists.size();
}
public class SelectPlaylistItemHolder extends RecyclerView.ViewHolder {
public final View view;
final ImageView thumbnailView;
final TextView titleView;
SelectPlaylistItemHolder(final View v) {
super(v);
this.view = v;
thumbnailView = v.findViewById(R.id.itemThumbnailView);
titleView = v.findViewById(R.id.itemTitleView);
}
}
}
}

View File

@@ -43,4 +43,4 @@ class DurationListPreference : ListPreference {
entries = newEntryTitles
}
}
}

View File

@@ -60,7 +60,7 @@ public final class AddTabDialog {
private DialogListAdapter(final Context context, final ChooseTabListItem[] items) {
this.inflater = LayoutInflater.from(context);
this.items = items;
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot);
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot);
}
@Override

View File

@@ -34,6 +34,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SelectChannelFragment;
import org.schabi.newpipe.settings.SelectKioskFragment;
import org.schabi.newpipe.settings.SelectPlaylistFragment;
import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem;
import org.schabi.newpipe.util.ThemeHelper;
@@ -48,7 +49,7 @@ public class ChooseTabsFragment extends Fragment {
private TabsManager tabsManager;
private List<Tab> tabList = new ArrayList<>();
private final List<Tab> tabList = new ArrayList<>();
private ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter;
/*//////////////////////////////////////////////////////////////////////////
@@ -78,10 +79,10 @@ public class ChooseTabsFragment extends Fragment {
initButton(rootView);
RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs);
final RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs);
listSelectedTabs.setLayoutManager(new LinearLayoutManager(requireContext()));
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
itemTouchHelper.attachToRecyclerView(listSelectedTabs);
selectedTabsAdapter = new SelectedTabsAdapter(requireContext(), itemTouchHelper);
@@ -138,7 +139,7 @@ public class ChooseTabsFragment extends Fragment {
private void updateTitle() {
if (getActivity() instanceof AppCompatActivity) {
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.main_page_content);
}
@@ -201,16 +202,33 @@ public class ChooseTabsFragment extends Fragment {
switch (type) {
case KIOSK:
SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
selectKioskFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) ->
selectKioskFragment.setOnSelectedListener((serviceId, kioskId, kioskName) ->
addTab(new Tab.KioskTab(serviceId, kioskId)));
selectKioskFragment.show(requireFragmentManager(), "select_kiosk");
return;
case CHANNEL:
SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
selectChannelFragment.setOnSelectedLisener((serviceId, url, name) ->
selectChannelFragment.setOnSelectedListener((serviceId, url, name) ->
addTab(new Tab.ChannelTab(serviceId, url, name)));
selectChannelFragment.show(requireFragmentManager(), "select_channel");
return;
case PLAYLIST:
SelectPlaylistFragment selectPlaylistFragment = new SelectPlaylistFragment();
selectPlaylistFragment.setOnSelectedListener(
new SelectPlaylistFragment.OnSelectedListener() {
@Override
public void onLocalPlaylistSelected(final long id, final String name) {
addTab(new Tab.PlaylistTab(id, name));
}
@Override
public void onRemotePlaylistSelected(
final int serviceId, final String url, final String name) {
addTab(new Tab.PlaylistTab(serviceId, url, name));
}
});
selectPlaylistFragment.show(requireFragmentManager(), "select_playlist");
return;
default:
addTab(type.getTab());
break;
@@ -233,7 +251,7 @@ public class ChooseTabsFragment extends Fragment {
case KIOSK:
returnList.add(new ChooseTabListItem(tab.getTabId(),
getString(R.string.kiosk_page_summary),
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot)));
break;
case CHANNEL:
returnList.add(new ChooseTabListItem(tab.getTabId(),
@@ -244,9 +262,15 @@ public class ChooseTabsFragment extends Fragment {
if (!tabList.contains(tab)) {
returnList.add(new ChooseTabListItem(tab.getTabId(),
getString(R.string.default_kiosk_page_summary),
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
ThemeHelper.resolveResourceIdFromAttr(context,
R.attr.ic_kiosk_hot)));
}
break;
case PLAYLIST:
returnList.add(new ChooseTabListItem(tab.getTabId(),
getString(R.string.playlist_page_summary),
tab.getTabIconRes(context)));
break;
default:
if (!tabList.contains(tab)) {
returnList.add(new ChooseTabListItem(context, tab));
@@ -336,7 +360,7 @@ public class ChooseTabsFragment extends Fragment {
@Override
public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(
@NonNull final ViewGroup parent, final int viewType) {
View view = inflater.inflate(R.layout.list_choose_tabs, parent, false);
final View view = inflater.inflate(R.layout.list_choose_tabs, parent, false);
return new ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder(view);
}
@@ -392,6 +416,13 @@ public class ChooseTabsFragment extends Fragment {
tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab)
.getChannelServiceId()) + "/" + tab.getTabName(requireContext());
break;
case PLAYLIST:
final int serviceId = ((Tab.PlaylistTab) tab).getPlaylistServiceId();
final String serviceName = serviceId == -1
? getString(R.string.local)
: NewPipe.getNameOfService(serviceId);
tabName = serviceName + "/" + tab.getTabName(requireContext());
break;
default:
tabName = tab.getTabName(requireContext());
break;

View File

@@ -11,6 +11,7 @@ import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonSink;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem.LocalItemType;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -18,9 +19,11 @@ import org.schabi.newpipe.fragments.BlankFragment;
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
import org.schabi.newpipe.fragments.list.kiosk.DefaultKioskFragment;
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
import org.schabi.newpipe.local.feed.FeedFragment;
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
@@ -33,7 +36,8 @@ import java.util.Objects;
public abstract class Tab {
private static final String JSON_TAB_ID_KEY = "tab_id";
Tab() { }
Tab() {
}
Tab(@NonNull final JsonObject jsonObject) {
readDataFromJson(jsonObject);
@@ -83,6 +87,8 @@ public abstract class Tab {
return new KioskTab(jsonObject);
case CHANNEL:
return new ChannelTab(jsonObject);
case PLAYLIST:
return new PlaylistTab(jsonObject);
}
}
@@ -147,7 +153,8 @@ public abstract class Tab {
BOOKMARKS(new BookmarksTab()),
HISTORY(new HistoryTab()),
KIOSK(new KioskTab()),
CHANNEL(new ChannelTab());
CHANNEL(new ChannelTab()),
PLAYLIST(new PlaylistTab());
private Tab tab;
@@ -231,7 +238,7 @@ public abstract class Tab {
@DrawableRes
@Override
public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss);
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_rss);
}
@Override
@@ -281,7 +288,7 @@ public abstract class Tab {
@DrawableRes
@Override
public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.history);
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_history);
}
@Override
@@ -323,7 +330,7 @@ public abstract class Tab {
@DrawableRes
@Override
public int getTabIconRes(final Context context) {
final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context);
final int kioskIcon = KioskTranslator.getKioskIcon(kioskId, context);
if (kioskIcon <= 0) {
throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\"");
@@ -459,7 +466,7 @@ public abstract class Tab {
@DrawableRes
@Override
public int getTabIconRes(final Context context) {
return KioskTranslator.getKioskIcons(getDefaultKioskId(context), context);
return KioskTranslator.getKioskIcon(getDefaultKioskId(context), context);
}
@Override
@@ -482,4 +489,123 @@ public abstract class Tab {
return kioskId;
}
}
public static class PlaylistTab extends Tab {
public static final int ID = 8;
private static final String JSON_PLAYLIST_SERVICE_ID_KEY = "playlist_service_id";
private static final String JSON_PLAYLIST_URL_KEY = "playlist_url";
private static final String JSON_PLAYLIST_NAME_KEY = "playlist_name";
private static final String JSON_PLAYLIST_ID_KEY = "playlist_id";
private static final String JSON_PLAYLIST_TYPE_KEY = "playlist_type";
private int playlistServiceId;
private String playlistUrl;
private String playlistName;
private long playlistId;
private LocalItemType playlistType;
private PlaylistTab() {
this(-1, "<no-name>");
}
public PlaylistTab(final long playlistId, final String playlistName) {
this.playlistName = playlistName;
this.playlistId = playlistId;
this.playlistType = LocalItemType.PLAYLIST_LOCAL_ITEM;
this.playlistServiceId = -1;
this.playlistUrl = "<no-url>";
}
public PlaylistTab(final int playlistServiceId, final String playlistUrl,
final String playlistName) {
this.playlistServiceId = playlistServiceId;
this.playlistUrl = playlistUrl;
this.playlistName = playlistName;
this.playlistType = LocalItemType.PLAYLIST_REMOTE_ITEM;
this.playlistId = -1;
}
public PlaylistTab(final JsonObject jsonObject) {
super(jsonObject);
}
@Override
public int getTabId() {
return ID;
}
@Override
public String getTabName(final Context context) {
return playlistName;
}
@DrawableRes
@Override
public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark);
}
@Override
public Fragment getFragment(final Context context) {
if (playlistType == LocalItemType.PLAYLIST_LOCAL_ITEM) {
return LocalPlaylistFragment.getInstance(playlistId, playlistName);
} else { // playlistType == LocalItemType.PLAYLIST_REMOTE_ITEM
return PlaylistFragment.getInstance(playlistServiceId, playlistUrl, playlistName);
}
}
@Override
protected void writeDataToJson(final JsonSink writerSink) {
writerSink.value(JSON_PLAYLIST_SERVICE_ID_KEY, playlistServiceId)
.value(JSON_PLAYLIST_URL_KEY, playlistUrl)
.value(JSON_PLAYLIST_NAME_KEY, playlistName)
.value(JSON_PLAYLIST_ID_KEY, playlistId)
.value(JSON_PLAYLIST_TYPE_KEY, playlistType.toString());
}
@Override
protected void readDataFromJson(final JsonObject jsonObject) {
playlistServiceId = jsonObject.getInt(JSON_PLAYLIST_SERVICE_ID_KEY, -1);
playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, "<no-url>");
playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, "<no-name>");
playlistId = jsonObject.getInt(JSON_PLAYLIST_ID_KEY, -1);
playlistType = LocalItemType.valueOf(
jsonObject.getString(JSON_PLAYLIST_TYPE_KEY,
LocalItemType.PLAYLIST_LOCAL_ITEM.toString())
);
}
@Override
public boolean equals(final Object obj) {
if (!(super.equals(obj)
&& Objects.equals(playlistType, ((PlaylistTab) obj).playlistType)
&& Objects.equals(playlistName, ((PlaylistTab) obj).playlistName))) {
return false; // base objects are different
}
return (playlistId == ((PlaylistTab) obj).playlistId) // local
|| (playlistServiceId == ((PlaylistTab) obj).playlistServiceId // remote
&& Objects.equals(playlistUrl, ((PlaylistTab) obj).playlistUrl));
}
public int getPlaylistServiceId() {
return playlistServiceId;
}
public String getPlaylistUrl() {
return playlistUrl;
}
public String getPlaylistName() {
return playlistName;
}
public long getPlaylistId() {
return playlistId;
}
public LocalItemType getPlaylistType() {
return playlistType;
}
}
}

View File

@@ -43,7 +43,8 @@ public class DataReader {
return readBuffer[readOffset++] & 0xFF;
}
public long skipBytes(long amount) throws IOException {
public long skipBytes(final long byteAmount) throws IOException {
long amount = byteAmount;
if (readCount < 0) {
return 0;
} else if (readCount == 0) {
@@ -90,7 +91,10 @@ public class DataReader {
return read(buffer, 0, buffer.length);
}
public int read(final byte[] buffer, int offset, int count) throws IOException {
public int read(final byte[] buffer, final int off, final int c) throws IOException {
int offset = off;
int count = c;
if (readCount < 0) {
return -1;
}

View File

@@ -5,8 +5,8 @@ import org.schabi.newpipe.streams.Mp4DashReader.Mdia;
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashChunk;
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashSample;
import org.schabi.newpipe.streams.Mp4DashReader.Mp4Track;
import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry;
import org.schabi.newpipe.streams.Mp4DashReader.TrackKind;
import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry;
import org.schabi.newpipe.streams.io.SharpStream;
import java.io.IOException;
@@ -464,16 +464,16 @@ public class Mp4FromDashWriter {
}
private void initChunkTables(final TablesInfo tables, final int firstCount,
final int succesiveCount) {
final int successiveCount) {
// tables.stsz holds amount of samples of the track (total)
int totalSamples = (tables.stsz - firstCount);
float chunkAmount = totalSamples / (float) succesiveCount;
float chunkAmount = totalSamples / (float) successiveCount;
int remainChunkOffset = (int) Math.ceil(chunkAmount);
boolean remain = remainChunkOffset != (int) chunkAmount;
int index = 0;
tables.stsc = 1;
if (firstCount != succesiveCount) {
if (firstCount != successiveCount) {
tables.stsc++;
}
if (remain) {
@@ -488,15 +488,15 @@ public class Mp4FromDashWriter {
tables.stscBEntries[index++] = firstCount;
tables.stscBEntries[index++] = 1;
if (firstCount != succesiveCount) {
if (firstCount != successiveCount) {
tables.stscBEntries[index++] = 2;
tables.stscBEntries[index++] = succesiveCount;
tables.stscBEntries[index++] = successiveCount;
tables.stscBEntries[index++] = 1;
}
if (remain) {
tables.stscBEntries[index++] = remainChunkOffset + 1;
tables.stscBEntries[index++] = totalSamples % succesiveCount;
tables.stscBEntries[index++] = totalSamples % successiveCount;
tables.stscBEntries[index] = 1;
}
}
@@ -640,19 +640,20 @@ public class Mp4FromDashWriter {
return size;
}
private byte[] makeMdat(long refSize, final boolean is64) {
private byte[] makeMdat(final long refSize, final boolean is64) {
long size = refSize;
if (is64) {
refSize += 16;
size += 16;
} else {
refSize += 8;
size += 8;
}
ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
.putInt(is64 ? 0x01 : (int) refSize)
.putInt(is64 ? 0x01 : (int) size)
.putInt(0x6D646174); // mdat
if (is64) {
buffer.putLong(refSize);
buffer.putLong(size);
}
return buffer.array();
@@ -711,23 +712,12 @@ public class Mp4FromDashWriter {
for (int i = 0; i < tracks.length; i++) {
if (tracks[i].trak.tkhd.matrix.length != 36) {
throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
throw
new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
}
makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
}
// udta/meta/ilst/©too
auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x5C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x54, 0x6D, 0x65,
0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70,
0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00,
0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65 // "NewPipe" binary string
});
return lengthFor(start);
}
@@ -849,20 +839,10 @@ public class Mp4FromDashWriter {
private byte[] makeHdlr(final Hdlr hdlr) {
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72, // hdlr
0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, // hdlr
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// binary string
// "ISO Media file created in NewPipe (
// A libre lightweight streaming frontend for Android)."
0x49, 0x53, 0x4F, 0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x66, 0x69, 0x6C, 0x65,
0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x4E, 0x65,
0x77, 0x50, 0x69, 0x70, 0x65, 0x20, 0x28, 0x41, 0x20, 0x6C, 0x69, 0x62, 0x72, 0x65,
0x20, 0x6C, 0x69, 0x67, 0x68, 0x74, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x73,
0x74, 0x72, 0x65, 0x61, 0x6D, 0x69, 0x6E, 0x67,
0x20, 0x66, 0x72, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20,
0x41, 0x6E,
0x64, 0x72, 0x6F, 0x69, 0x64, 0x29, 0x2E
0x00// null string character
});
buffer.position(12);
@@ -898,7 +878,7 @@ public class Mp4FromDashWriter {
* characteristics of sample groups. The descriptive information is any other
* information needed to define or characterize the sample group.
*
* ¿is replicabled this box?
* ¿is replicable this box?
* NO due lacks of documentation about this box but...
* most of m4a encoders and ffmpeg uses this box with dummy values (same values)
*/

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