1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-01-14 02:32:40 +00:00

Compare commits

..

139 Commits

Author SHA1 Message Date
Christian Schabesberger
140cfaec90 moved on to version 0.9.7 2017-05-20 17:45:50 +02:00
Дима Гайнуллин
a0f20aac23 Translated using Weblate (Russian)
Currently translated at 86.9% (153 of 176 strings)
2017-05-20 15:40:42 +02:00
ktln
93fafb362f Translated using Weblate (Russian)
Currently translated at 86.9% (153 of 176 strings)
2017-05-20 15:39:44 +02:00
9152df5512 Translated using Weblate (Russian)
Currently translated at 84.0% (148 of 176 strings)
2017-05-20 15:37:48 +02:00
Anton Shestakov
415bcd9f7e Translated using Weblate (Russian)
Currently translated at 83.5% (147 of 176 strings)
2017-05-20 15:37:13 +02:00
Дима Гайнуллин
2224033a85 Translated using Weblate (Russian)
Currently translated at 82.9% (146 of 176 strings)
2017-05-20 15:36:57 +02:00
Anton Shestakov
ade0498684 Translated using Weblate (Russian)
Currently translated at 82.9% (146 of 176 strings)
2017-05-20 15:34:06 +02:00
Дима Гайнуллин
211b00fff4 Translated using Weblate (Russian)
Currently translated at 82.9% (146 of 176 strings)
2017-05-20 15:33:52 +02:00
ktln
77c72d03a8 Translated using Weblate (Russian)
Currently translated at 82.9% (146 of 176 strings)
2017-05-20 15:32:32 +02:00
Anton Shestakov
f429d93351 Translated using Weblate (Russian)
Currently translated at 82.3% (145 of 176 strings)
2017-05-20 15:25:48 +02:00
Дима Гайнуллин
e428f8b116 Translated using Weblate (Russian)
Currently translated at 82.3% (145 of 176 strings)
2017-05-20 15:20:18 +02:00
Anton Shestakov
9482b9d638 Translated using Weblate (Russian)
Currently translated at 82.3% (145 of 176 strings)
2017-05-20 15:19:15 +02:00
Дима Гайнуллин
e019c9f720 Translated using Weblate (Russian)
Currently translated at 82.3% (145 of 176 strings)
2017-05-20 15:12:17 +02:00
Anton Shestakov
e2778366e9 Translated using Weblate (Russian)
Currently translated at 82.3% (145 of 176 strings)
2017-05-20 15:11:49 +02:00
ktln
36d2f8339c Translated using Weblate (Russian)
Currently translated at 82.3% (145 of 176 strings)
2017-05-20 15:11:30 +02:00
a4a0c3b9fd Translated using Weblate (Russian)
Currently translated at 81.2% (143 of 176 strings)
2017-05-20 15:10:44 +02:00
ktln
1b1b6c8af8 Translated using Weblate (Russian)
Currently translated at 80.6% (142 of 176 strings)
2017-05-20 15:10:20 +02:00
Anton Shestakov
064fd2bc68 Translated using Weblate (Russian)
Currently translated at 80.1% (141 of 176 strings)
2017-05-20 15:10:11 +02:00
ktln
13152ab6ea Translated using Weblate (Russian)
Currently translated at 79.5% (140 of 176 strings)
2017-05-20 15:07:58 +02:00
Anton Shestakov
1743d821eb Translated using Weblate (Russian)
Currently translated at 78.9% (139 of 176 strings)
2017-05-20 15:04:08 +02:00
ktln
ff16e577ef Translated using Weblate (Russian)
Currently translated at 78.9% (139 of 176 strings)
2017-05-20 15:02:58 +02:00
Anton Shestakov
86062c6e94 Translated using Weblate (Russian)
Currently translated at 78.4% (138 of 176 strings)
2017-05-20 15:02:43 +02:00
ktln
8ce2350563 Translated using Weblate (Russian)
Currently translated at 78.4% (138 of 176 strings)
2017-05-20 15:00:51 +02:00
Anton Shestakov
cb8989af7f Translated using Weblate (Russian)
Currently translated at 77.8% (137 of 176 strings)
2017-05-20 15:00:25 +02:00
ktln
877fa45eb4 Translated using Weblate (Russian)
Currently translated at 77.2% (136 of 176 strings)
2017-05-20 14:59:24 +02:00
Anton Shestakov
e33942486d Translated using Weblate (Russian)
Currently translated at 76.7% (135 of 176 strings)
2017-05-20 14:58:50 +02:00
Дима Гайнуллин
9606e080ef Translated using Weblate (Russian)
Currently translated at 76.7% (135 of 176 strings)
2017-05-20 14:57:45 +02:00
Anton Shestakov
d5cd9c55be Translated using Weblate (Russian)
Currently translated at 76.7% (135 of 176 strings)
2017-05-20 14:56:39 +02:00
Дима Гайнуллин
9c197ced80 Translated using Weblate (Russian)
Currently translated at 75.0% (132 of 176 strings)
2017-05-20 14:40:15 +02:00
Anton Shestakov
f8424599e1 Translated using Weblate (Russian)
Currently translated at 75.0% (132 of 176 strings)
2017-05-20 14:38:59 +02:00
naofum
ccee18057a Translated using Weblate (Japanese)
Currently translated at 100.0% (176 of 176 strings)
2017-05-20 07:11:53 +02:00
Freddy Morán Jr
1ad0f342ad Translated using Weblate (Spanish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-19 15:39:44 +02:00
Eduardo Caron
7a16ac574b Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (176 of 176 strings)
2017-05-19 15:16:03 +02:00
Matej U
f46738f750 Translated using Weblate (Slovenian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-19 13:49:47 +02:00
monolifed
fb8ff3fece Translated using Weblate (Turkish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-19 12:20:03 +02:00
Weblate
7498dd3800 Merge remote-tracking branch 'origin/master' 2017-05-19 03:46:42 +02:00
Freddy Morán Jr
957c31b9c5 Translated using Weblate (Spanish)
Currently translated at 100.0% (172 of 172 strings)
2017-05-19 03:46:40 +02:00
Mauricio Colli
b260570e8c Merge pull request #568 from mauriciocolli/option-search-suggestions
Add option to disable the search suggestions
2017-05-16 22:17:28 -03:00
Mauricio Colli
46542747b7 Merge pull request #567 from mauriciocolli/option-gestures-player
Add option to disable the gesture controls of the player
2017-05-16 22:14:31 -03:00
Mauricio Colli
d64480fc9b Add option to disable the search suggestions 2017-05-16 22:14:07 -03:00
Mauricio Colli
c00e694d40 Add option to disable the gesture controls of the player 2017-05-16 21:45:38 -03:00
Bruno Guerreiro
f0761cc95e Translated using Weblate (Portuguese)
Currently translated at 100.0% (172 of 172 strings)
2017-05-16 21:32:24 +02:00
Eduardo Caron
068554955c Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (172 of 172 strings)
2017-05-16 21:25:48 +02:00
Freddy Morán Jr
a2f915f556 Translated using Weblate (Spanish)
Currently translated at 100.0% (172 of 172 strings)
2017-05-16 14:38:29 +02:00
Mohamad Hasan Al Banna
cffd35c974 Translated using Weblate (Indonesian)
Currently translated at 100.0% (172 of 172 strings)
2017-05-15 17:53:50 +02:00
naofum
2ef2aa98a4 Translated using Weblate (Japanese)
Currently translated at 100.0% (172 of 172 strings)
2017-05-15 17:23:57 +02:00
Théophane
ba98b30aa4 Translated using Weblate (French)
Currently translated at 100.0% (172 of 172 strings)
2017-05-15 16:23:59 +02:00
E T
87c0f9c803 Translated using Weblate (Turkish)
Currently translated at 100.0% (172 of 172 strings)
2017-05-15 16:08:54 +02:00
Matej U
ce620b9e28 Translated using Weblate (Slovenian)
Currently translated at 100.0% (172 of 172 strings)
2017-05-15 14:44:49 +02:00
Weblate
fcbb5536eb Merge remote-tracking branch 'origin/master' 2017-05-15 14:42:35 +02:00
Matej U
72df6dbd80 Translated using Weblate (Slovenian)
Currently translated at 100.0% (171 of 171 strings)
2017-05-15 14:42:32 +02:00
Christian Schabesberger
3a6b023dbf update gradle to version 2.3.2 2017-05-15 12:27:46 +02:00
Mauricio Colli
8b67f1358d Implement no-audio icon 2017-05-15 00:57:57 -03:00
Mauricio Colli
2aebb3b8db Add and update resources 2017-05-15 00:57:04 -03:00
Weblate
f2b38be2b0 Merge remote-tracking branch 'origin/master' 2017-05-14 21:46:25 +02:00
E T
a43001f30d Translated using Weblate (Turkish)
Currently translated at 100.0% (171 of 171 strings)
2017-05-14 21:46:25 +02:00
cwgt
17abe1ea5c Translated using Weblate (Bengali (Bangladesh))
Currently translated at 100.0% (167 of 167 strings)
2017-05-14 21:46:24 +02:00
Bruno Guerreiro
6aaefab618 Translated using Weblate (Portuguese)
Currently translated at 98.2% (168 of 171 strings)
2017-05-14 21:46:22 +02:00
Mauricio Colli
cd867fb4d1 Merge pull request #559 from mauriciocolli/default-popup-resolution
Change popup default resolution
2017-05-14 14:01:12 -03:00
Mauricio Colli
25988f61a6 Change popup default resolution 2017-05-14 13:57:08 -03:00
Mauricio Colli
be421e580d Merge pull request #557 from mauriciocolli/fix-settings-rotation
Fix settings on screen rotation
2017-05-14 11:20:29 -03:00
Mauricio Colli
641ab25470 Fix Settings on screen rotation 2017-05-14 11:10:00 -03:00
Mauricio Colli
a508539c2e Merge pull request #556 from mauriciocolli/fix-audio-focus
Improve behavior on audio focus gain
2017-05-14 10:54:13 -03:00
Mauricio Colli
cac79d9a0d Improve behavior on audio focus gain 2017-05-14 10:46:28 -03:00
Christian Schabesberger
9ede8118da Merge pull request #553 from mauriciocolli/fix-screen-on
Fix keep screen on
2017-05-14 10:48:05 +02:00
Mauricio Colli
e47761750a Fix 'keep screen on' 2017-05-14 00:32:28 -03:00
Christian Schabesberger
2f181ce7c9 move on to version 0.9.6 2017-05-13 22:13:28 +02:00
Christian Schabesberger
3a13d4a1de Merge pull request #546 from mueller-ma/kodi
Change order of buttons in the title bar
2017-05-13 20:16:24 +02:00
Christian Schabesberger
719de00e0f Merge branch 'fix-menu-update' of https://github.com/mauriciocolli/NewPipe 2017-05-13 20:08:18 +02:00
cwgt
ddffe99f53 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 100.0% (167 of 167 strings)
2017-05-13 11:36:43 +02:00
Eduardo Caron
9ade596f06 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (171 of 171 strings)
2017-05-11 20:33:38 +02:00
nailyk
46f413b7f2 Translated using Weblate (French)
Currently translated at 100.0% (171 of 171 strings)
2017-05-11 19:41:37 +02:00
nailyk
5fe2e7b8ad Translated using Weblate (French)
Currently translated at 92.3% (158 of 171 strings)

 popup_mode_share_menu_title a été traduit "Module popup de Newpipe", restons cohérents.
2017-05-11 19:33:46 +02:00
nailyk
3008cbb5f4 Translated using Weblate (French)
Currently translated at 91.8% (157 of 171 strings)
2017-05-11 19:20:57 +02:00
Weblate
f7983960e5 Merge remote-tracking branch 'origin/master' 2017-05-10 21:07:55 +02:00
monolifed
bb8007bb7c Translated using Weblate (Turkish)
Currently translated at 100.0% (171 of 171 strings)
2017-05-10 21:07:55 +02:00
Freddy Morán Jr
01751ba97a Translated using Weblate (Spanish)
Currently translated at 100.0% (171 of 171 strings)
2017-05-10 21:07:55 +02:00
Tobias Groza
f41475d11c Translated using Weblate (German)
Currently translated at 96.4% (165 of 171 strings)
2017-05-10 21:07:54 +02:00
Nathan Follens
d8914d9b6d Translated using Weblate (Dutch)
Currently translated at 100.0% (171 of 171 strings)
2017-05-10 21:07:52 +02:00
mueller-ma
94cc2ad365 change order of buttons, see https://github.com/TeamNewPipe/NewPipe/issues/471#issuecomment-300267118 2017-05-09 20:57:42 +02:00
Christian Schabesberger
e07464b81c Merge pull request #545 from mauriciocolli/fix-image-loader
Fix image loading bug
2017-05-09 20:19:43 +02:00
Jose Maeso
9d231b55b5 Translated using Weblate (Spanish)
Currently translated at 100.0% (171 of 171 strings)
2017-05-09 18:09:23 +02:00
Freddy Morán Jr
77d8dac3c1 Translated using Weblate (Spanish)
Currently translated at 100.0% (171 of 171 strings)
2017-05-09 18:07:54 +02:00
Freddy Morán Jr
e160015283 Translated using Weblate (Spanish)
Currently translated at 100.0% (171 of 171 strings)
2017-05-09 18:05:47 +02:00
mueller-ma
aeb0cac3ee change order of buttons. closes #471 2017-05-09 17:11:19 +02:00
naofum
a539f94837 Translated using Weblate (Japanese)
Currently translated at 100.0% (171 of 171 strings)
2017-05-09 16:19:42 +02:00
monolifed
df70751071 Translated using Weblate (Turkish)
Currently translated at 100.0% (171 of 171 strings)
2017-05-09 14:34:49 +02:00
E T
e55f1dff78 Translated using Weblate (Turkish)
Currently translated at 97.6% (167 of 171 strings)
2017-05-09 14:12:43 +02:00
Weblate
47646e1c62 Merge remote-tracking branch 'origin/master' 2017-05-09 14:11:45 +02:00
E T
80e673f20c Translated using Weblate (Turkish)
Currently translated at 100.0% (167 of 167 strings)
2017-05-09 14:11:41 +02:00
Mauricio Colli
9ca8c5480c Fix image loader bug 2017-05-09 00:12:06 -03:00
Mauricio Colli
4d0d3c7ead Update menu items after change in settings 2017-05-08 22:23:27 -03:00
Christian Schabesberger
58137aadc9 Merge pull request #543 from mueller-ma/patch-1
Remove untranslatable string
2017-05-08 22:45:21 +02:00
mueller-ma
82a59ae479 Remove untranslatable string 2017-05-08 20:51:13 +02:00
Mauricio Colli
affd23b14e Fix animations 2017-05-08 10:33:26 -03:00
Mauricio Colli
9c7f249756 Clean and move some classes 2017-05-08 10:28:33 -03:00
Christian Schabesberger
e2a0502171 Merge pull request #538 from cwgt/bn-bd-translation
Add Bangla translation (for Bangladesh)
2017-05-08 11:44:27 +02:00
cwgt
3832a4b355 Add Bangla translation (for Bangladesh) 2017-05-08 15:27:55 +06:00
cwgt
84f059415c Create strings.xml 2017-05-08 13:32:33 +06:00
Christian Schabesberger
bb292e3199 add some more screenshots 2017-05-07 20:41:44 +02:00
Christian Schabesberger
70541bf561 moved on to version 0.9.5 2017-05-07 20:05:00 +02:00
Christian Schabesberger
48dd8e88e3 Merge branch 'master' of github.com:theScrabi/NewPipe 2017-05-07 19:48:21 +02:00
Eduardo Caron
52c2e0899d Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.4% (166 of 167 strings)
2017-05-07 18:46:09 +02:00
Christian Schabesberger
9955d5b62f Merge branch 'resize' 2017-05-04 21:31:38 +02:00
Marian Hanzel
8e26247fa1 Translated using Weblate (Slovak)
Currently translated at 100.0% (167 of 167 strings)
2017-05-04 21:02:23 +02:00
Weblate
72924e2692 Merge remote-tracking branch 'origin/master' 2017-05-04 18:46:39 +02:00
Eduardo Caron
8c2c8d630f Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.8% (165 of 167 strings)
2017-05-04 18:46:39 +02:00
Freddy Morán Jr
5ccf0baa6b Translated using Weblate (Spanish)
Currently translated at 100.0% (167 of 167 strings)
2017-05-04 18:46:36 +02:00
Christian Schabesberger
8fff0ccdf2 Merge pull request #532 from mauriciocolli/improve-readme
Update Readme
2017-05-04 00:10:44 +02:00
Mauricio Colli
35264bfb97 Update Readme 2017-05-03 16:13:31 -03:00
Matej U
4c9b0dc8e9 Translated using Weblate (Slovenian)
Currently translated at 100.0% (167 of 167 strings)
2017-05-02 21:02:58 +02:00
Freddy Morán Jr
4deb7caa83 Translated using Weblate (Spanish)
Currently translated at 100.0% (167 of 167 strings)
2017-05-02 17:22:14 +02:00
Enol P
45f1a512b4 Translated using Weblate (Asturian)
Currently translated at 98.2% (164 of 167 strings)
2017-05-01 18:44:12 +02:00
Mladen Pejaković
ab95fdc087 Translated using Weblate (Serbian)
Currently translated at 99.4% (166 of 167 strings)
2017-05-01 15:46:36 +02:00
Christian Schabesberger
df608b9ded Merge branch 'feature-resize' of https://github.com/mauriciocolli/NewPipe into resize 2017-05-01 15:15:30 +02:00
Tobias Groza
6a4c81d160 Translated using Weblate (German)
Currently translated at 98.2% (164 of 167 strings)
2017-05-01 12:45:14 +02:00
Mauricio Colli
040d658540 Implement resizable popup 2017-05-01 02:23:50 -03:00
naofum
cbc9913e9c Translated using Weblate (Japanese)
Currently translated at 100.0% (167 of 167 strings)
2017-04-30 05:47:23 +02:00
Wout Bertrums
f007cbd5ee Translated using Weblate (Dutch)
Currently translated at 100.0% (167 of 167 strings)
2017-04-29 18:46:39 +02:00
zmni
7351591df7 Translated using Weblate (Indonesian)
Currently translated at 100.0% (167 of 167 strings)
2017-04-29 16:52:22 +02:00
monolifed
5222d4cbba Translated using Weblate (Turkish)
Currently translated at 100.0% (167 of 167 strings)
2017-04-29 09:48:33 +02:00
Weblate
ef9966815e Merge remote-tracking branch 'origin/master' 2017-04-29 08:27:55 +02:00
ad73440ce9 Translated using Weblate (Czech)
Currently translated at 93.9% (154 of 164 strings)
2017-04-29 08:27:51 +02:00
Christian Schabesberger
c81cfdc455 moved on to version 0.9.4 2017-04-28 21:26:11 +02:00
Christian Schabesberger
134c3804db Merge branch 'fix-search' of https://github.com/mauriciocolli/NewPipe into fix-search 2017-04-28 20:42:37 +02:00
Christian Schabesberger
e355df5eda update NewPipeExtractor 2017-04-28 20:19:23 +02:00
Mauricio Colli
a68e0a95f4 Fix NullPointerException 2017-04-28 01:07:54 -03:00
Mauricio Colli
6fd6facf72 Improve animations of VideoDetailFragment 2017-04-28 00:58:50 -03:00
Mauricio Colli
00102d4048 Try to fix travis 2017-04-26 17:07:24 -03:00
Mauricio Colli
adbeff11d4 Minor improvements 2017-04-26 16:32:20 -03:00
Mauricio Colli
a8fe329678 Improve fragments
- They save the state now, that means, no more reloading after rotating the screen or switching between apps
2017-04-26 16:32:04 -03:00
Mauricio Colli
a4b61bf730 Add new workers 2017-04-26 16:25:09 -03:00
Mauricio Colli
ee592def0c Add drawables and improve layouts 2017-04-26 16:24:33 -03:00
Allan Nordhøy
c14e117239 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (164 of 164 strings)
2017-04-25 03:46:10 +02:00
rapi3
b6e94dde0c Translated using Weblate (Romanian)
Currently translated at 100.0% (164 of 164 strings)
2017-04-23 15:46:17 +02:00
monolifed
63a4a44d7e Translated using Weblate (Turkish)
Currently translated at 100.0% (164 of 164 strings)
2017-04-23 12:46:56 +02:00
Allan Nordhøy
eb8ab5d527 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (164 of 164 strings)
2017-04-23 00:57:22 +02:00
riotism
b778d40d65 Translated using Weblate (Chinese (Hong Kong))
Currently translated at 97.5% (160 of 164 strings)
2017-04-22 18:44:35 +02:00
rapi3
2f570672dc Translated using Weblate (Romanian)
Currently translated at 100.0% (164 of 164 strings)
2017-04-21 14:11:07 +02:00
188 changed files with 4786 additions and 3463 deletions

View File

@@ -5,7 +5,7 @@ android:
components:
# The BuildTools version used by NewPipe
- tools
- build-tools-25.0.0
- build-tools-25.0.2
# The SDK version used to compile NewPipe
- android-25

View File

@@ -19,11 +19,16 @@ Project status:
## Screenshots
[<img src="screenshots/screenshot_1.png" width=150>](screenshots/screenshot_1.png)
[<img src="screenshots/screenshot_2.png" width=150>](screenshots/screenshot_2.png)
[<img src="screenshots/screenshot_3.png" width=150>](screenshots/screenshot_3.png)
[<img src="screenshots/screenshot_4.png" width=150>](screenshots/screenshot_4.png)
[<img src="screenshots/screenshot_5.png" width=150>](screenshots/screenshot_5.png)
[<img src="screenshots/screenshot_1.png" width=160>](screenshots/screenshot_1.png)
[<img src="screenshots/screenshot_2.png" width=160>](screenshots/screenshot_2.png)
[<img src="screenshots/screenshot_3.png" width=160>](screenshots/screenshot_3.png)
[<img src="screenshots/screenshot_4.png" width=160>](screenshots/screenshot_4.png)
[<img src="screenshots/screenshot_5.png" width=160>](screenshots/screenshot_5.png)
[<img src="screenshots/screenshot_6.png" width=160>](screenshots/screenshot_6.png)
[<img src="screenshots/screenshot_7.png" width=160>](screenshots/screenshot_7.png)
[<img src="screenshots/screenshot_8.png" width=160>](screenshots/screenshot_8.png)
[<img src="screenshots/screenshot_9.png" width=160>](screenshots/screenshot_9.png)
## Description
@@ -35,6 +40,7 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only
* Display general information about a video
* Watch YouTube videos
* Listen to YouTube videos (experimental)
* Popup mode (floating player)
* Select the streaming player to watch the video with
* Download videos
* Download audio only

View File

@@ -8,8 +8,8 @@ android {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 25
versionCode 30
versionName "0.9.3"
versionCode 34
versionName "0.9.7"
}
buildTypes {
release {
@@ -39,14 +39,16 @@ dependencies {
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.google.code.gson:gson:2.7'
compile 'org.jsoup:jsoup:1.8.3'
compile 'org.mozilla:rhino:1.7.7'
compile 'info.guardianproject.netcipher:netcipher:1.2'
compile 'de.hdodenhof:circleimageview:2.0.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.github.nirhart:parallaxscroll:1.0'
compile 'com.google.code.gson:gson:2.7'
compile 'com.nononsenseapps:filepicker:3.0.0'
compile 'ch.acra:acra:4.9.0'
compile 'info.guardianproject.netcipher:netcipher:1.2'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'de.hdodenhof:circleimageview:2.0.0'
compile 'com.github.nirhart:parallaxscroll:1.0'
compile 'com.nononsenseapps:filepicker:3.0.0'
compile 'com.google.android.exoplayer:exoplayer:r2.3.1'
}

View File

@@ -15,6 +15,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ThemeHelper;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
@@ -82,6 +83,8 @@ public class App extends Application {
// DO NOT REMOVE THIS FUNCTION!!!
// Otherwise downloadPathPreference has invalid value.
SettingsActivity.initSettings(this);
ThemeHelper.setTheme(getApplicationContext());
}
/**

View File

@@ -21,19 +21,23 @@
package org.schabi.newpipe;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.fragments.channel.ChannelFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
@@ -45,7 +49,8 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
//private static final String TAG = "MainActivity";
private static final String TAG = "MainActivity";
public static final boolean DEBUG = false;
/*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle
@@ -53,18 +58,22 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
@Override
protected void onCreate(Bundle savedInstanceState) {
ThemeHelper.setTheme(this, true);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
@Override
protected void onNewIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
if (intent != null) {
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
// to not destroy the already created backstack
@@ -79,22 +88,11 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
@Override
public void onBackPressed() {
if (DEBUG) Log.d(TAG, "onBackPressed() called");
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) if (((VideoDetailFragment) fragment).onActivityBackPressed()) return;
if (getSupportFragmentManager().getBackStackEntryCount() >= 2) {
getSupportFragmentManager().popBackStackImmediate();
} else {
if (fragment instanceof SearchFragment) {
SearchFragment searchFragment = (SearchFragment) fragment;
if (!searchFragment.isMainBgVisible()) {
getSupportFragmentManager().beginTransaction().remove(fragment).commitNow();
NavigationHelper.openMainActivity(this);
return;
}
}
finish();
}
super.onBackPressed();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -103,14 +101,32 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (!(fragment instanceof VideoDetailFragment)) {
findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
}
if (!(fragment instanceof SearchFragment)) {
findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container).setVisibility(View.GONE);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayHomeAsUpEnabled(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
int id = item.getItemId();
switch (id) {
@@ -144,9 +160,10 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
//////////////////////////////////////////////////////////////////////////*/
private void initFragments() {
openMainFragment();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_URL)) {
handleIntent(getIntent());
} else openSearchFragment();
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -170,6 +187,13 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
//////////////////////////////////////////////////////////////////////////*/
private void handleIntent(Intent intent) {
if (intent.hasExtra(Constants.KEY_THEME_CHANGE) && intent.getBooleanExtra(Constants.KEY_THEME_CHANGE, false)) {
this.recreate();
Intent setI = new Intent(this, SettingsActivity.class);
startActivity(setI);
return;
}
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
@@ -187,24 +211,34 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
} catch (Exception e) {
e.printStackTrace();
}
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
String searchQuery = intent.getStringExtra(Constants.KEY_QUERY);
if (searchQuery == null) searchQuery = "";
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
openSearchFragment(serviceId, searchQuery);
} else {
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
openSearchFragment();
openMainFragment();//openSearchFragment();
}
}
private void openSearchFragment() {
private void openMainFragment() {
ImageLoader.getInstance().clearMemoryCache();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
.replace(R.id.fragment_holder, new SearchFragment())
.replace(R.id.fragment_holder, new MainFragment())
.commit();
}
private void openSearchFragment(int serviceId, String query) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
.addToBackStack(null)
.commit();
}
private void openVideoDetailFragment(int serviceId, String url, String title, boolean autoPlay) {
ImageLoader.getInstance().clearMemoryCache();
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (title == null) title = "";
@@ -226,11 +260,10 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
}
private void openChannelFragment(int serviceId, String url, String name) {
ImageLoader.getInstance().clearMemoryCache();
if (name == null) name = "";
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
.replace(R.id.fragment_holder, ChannelFragment.newInstance(serviceId, url, name))
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
.addToBackStack(null)
.commit();
}

View File

@@ -8,6 +8,7 @@ import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.webkit.CookieManager;
import android.webkit.ValueCallback;
@@ -48,10 +49,15 @@ public class ReCaptchaActivity extends AppCompatActivity {
// Set return to Cancel by default
setResult(RESULT_CANCELED);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.reCaptcha_title);
actionBar.setDisplayShowTitleEnabled(true);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.reCaptcha_title);
actionBar.setDisplayShowTitleEnabled(true);
}
WebView myWebView = (WebView) findViewById(R.id.reCaptchaWebView);

View File

@@ -1,28 +0,0 @@
package org.schabi.newpipe;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
public class ThemableActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (PreferenceManager.getDefaultSharedPreferences(this)
.getString("theme", getResources().getString(R.string.light_theme_title)).
equals(getResources().getString(R.string.dark_theme_title))) {
setTheme(R.style.DarkTheme);
}
}
@Override
protected void onResume() {
super.onResume();
if (PreferenceManager.getDefaultSharedPreferences(this)
.getString("theme", getResources().getString(R.string.light_theme_title)).
equals(getResources().getString(R.string.dark_theme_title))) {
setTheme(R.style.DarkTheme);
}
}
}

View File

@@ -8,7 +8,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
@@ -26,13 +25,11 @@ import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.File;
import java.util.Vector;
import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.fragment.AllMissionsFragment;
@@ -64,17 +61,19 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
i.setClass(this, DownloadManagerService.class);
startService(i);
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this, true);
setContentView(R.layout.activity_downloader);
//noinspection ConstantConditions
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// its ok if this fails, we will catch that error later, and send it as report
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.downloads_title);
actionBar.setDisplayShowTitleEnabled(true);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.downloads_title);
actionBar.setDisplayShowTitleEnabled(true);
}
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -159,7 +158,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
name.setText(getIntent().getStringExtra("fileName"));
toolbar.setTitle(R.string.add);
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(this) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url);
// Show the dialog
@@ -183,7 +182,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
if (item.getItemId() == R.id.okay) {
String location;
if(audioButton.isChecked()) {
if (audioButton.isChecked()) {
location = NewPipeSettings.getAudioDownloadPath(DownloadActivity.this);
} else {
location = NewPipeSettings.getVideoDownloadPath(DownloadActivity.this);
@@ -201,7 +200,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
audioButton.isChecked(), threads.getProgress() + 1);
mFragment.notifyChange();
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).commit();
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).apply();
mPendingUrl = null;
dialog.dismiss();
}

View File

@@ -1,119 +1,135 @@
package org.schabi.newpipe.download;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.IdRes;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.DialogFragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.fragments.detail.SpinnerToolbarAdapter;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.Utils;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.service.DownloadManagerService;
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG;
/**
* Created by Christian Schabesberger on 21.09.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* DownloadDialog.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
private static final String INFO_KEY = "info_key";
private static final String SORTED_VIDEOS_LIST_KEY = "sorted_videos_list_key";
private static final String SELECTED_VIDEO_KEY = "selected_video_key";
private static final String SELECTED_AUDIO_KEY = "selected_audio_key";
public class DownloadDialog extends DialogFragment {
private static final String TAG = DialogFragment.class.getName();
private StreamInfo currentInfo;
private ArrayList<VideoStream> sortedStreamVideosList;
private int selectedVideoIndex;
private int selectedAudioIndex;
public static final String TITLE = "name";
public static final String FILE_SUFFIX_AUDIO = "file_suffix_audio";
public static final String FILE_SUFFIX_VIDEO = "file_suffix_video";
public static final String AUDIO_URL = "audio_url";
public static final String VIDEO_URL = "video_url";
private EditText nameEditText;
private Spinner streamsSpinner;
private RadioGroup radioVideoAudioGroup;
private TextView threadsCountTextView;
private SeekBar threadsSeekBar;
public DownloadDialog() {
}
public static DownloadDialog newInstance(Bundle args)
{
public static DownloadDialog newInstance(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) {
DownloadDialog dialog = new DownloadDialog();
dialog.setArguments(args);
dialog.setInfo(info, sortedStreamVideosList, selectedVideoIndex);
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
return dialog;
}
private void setInfo(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) {
this.currentInfo = info;
this.selectedVideoIndex = selectedVideoIndex;
this.sortedStreamVideosList = sortedStreamVideosList;
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (!PermissionHelper.checkStoragePermissions(getActivity())) {
getDialog().dismiss();
return;
}
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
if (savedInstanceState != null) {
Serializable serial = savedInstanceState.getSerializable(INFO_KEY);
if (serial instanceof StreamInfo) currentInfo = (StreamInfo) serial;
serial = savedInstanceState.getSerializable(SORTED_VIDEOS_LIST_KEY);
if (serial instanceof ArrayList) { //noinspection unchecked
sortedStreamVideosList = (ArrayList<VideoStream>) serial;
}
selectedVideoIndex = savedInstanceState.getInt(SELECTED_VIDEO_KEY, 0);
selectedAudioIndex = savedInstanceState.getInt(SELECTED_AUDIO_KEY, 0);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.dialog_url, container);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
nameEditText = ((EditText) view.findViewById(R.id.file_name));
nameEditText.setText(createFileName(currentInfo.title));
selectedAudioIndex = Utils.getPreferredAudioFormat(getContext(), currentInfo.audio_streams);
Bundle arguments = getArguments();
final Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
final EditText name = (EditText) view.findViewById(R.id.file_name);
final TextView tCount = (TextView) view.findViewById(R.id.threads_count);
final SeekBar threads = (SeekBar) view.findViewById(R.id.threads);
streamsSpinner = (Spinner) view.findViewById(R.id.quality_spinner);
streamsSpinner.setOnItemSelectedListener(this);
toolbar.setTitle(R.string.download_dialog_title);
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getDialog().dismiss();
}
});
threadsCountTextView = (TextView) view.findViewById(R.id.threads_count);
threadsSeekBar = (SeekBar) view.findViewById(R.id.threads);
radioVideoAudioGroup = (RadioGroup) view.findViewById(R.id.video_audio_group);
radioVideoAudioGroup.setOnCheckedChangeListener(this);
threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
initToolbar((Toolbar) view.findViewById(R.id.toolbar));
checkDownloadOptions(view);
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
int def = 3;
threadsCountTextView.setText(String.valueOf(def));
threadsSeekBar.setProgress(def - 1);
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
tCount.setText(String.valueOf(progress + 1));
threadsCountTextView.setText(String.valueOf(progress + 1));
}
@Override
@@ -126,41 +142,111 @@ public class DownloadDialog extends DialogFragment {
}
});
}
checkDownloadOptions();
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, currentInfo);
outState.putSerializable(SORTED_VIDEOS_LIST_KEY, sortedStreamVideosList);
outState.putInt(SELECTED_VIDEO_KEY, selectedVideoIndex);
outState.putInt(SELECTED_AUDIO_KEY, selectedAudioIndex);
}
//int def = mPrefs.getInt("threads", 4);
int def = 3;
threads.setProgress(def - 1);
tCount.setText(String.valueOf(def));
name.setText(createFileName(arguments.getString(TITLE)));
/*//////////////////////////////////////////////////////////////////////////
// Inits
//////////////////////////////////////////////////////////////////////////*/
private void initToolbar(Toolbar toolbar) {
if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
toolbar.setTitle(R.string.download_dialog_title);
toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getDialog().dismiss();
}
});
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.okay) {
download();
downloadSelected();
return true;
} else {
return false;
}
} else return false;
}
});
}
protected void checkDownloadOptions(){
View view = getView();
Bundle arguments = getArguments();
public void setupAudioSpinner(final List<AudioStream> audioStreams, Spinner spinner) {
String[] items = new String[audioStreams.size()];
for (int i = 0; i < audioStreams.size(); i++) {
AudioStream audioStream = audioStreams.get(i);
items[i] = MediaFormat.getNameById(audioStream.format) + " " + audioStream.avgBitrate + "kbps";
}
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, items);
spinner.setAdapter(itemAdapter);
spinner.setSelection(selectedAudioIndex);
}
public void setupVideoSpinner(final List<VideoStream> videoStreams, Spinner spinner) {
spinner.setAdapter(new SpinnerToolbarAdapter(getContext(), videoStreams, true));
spinner.setSelection(selectedVideoIndex);
}
/*//////////////////////////////////////////////////////////////////////////
// Radio group Video&Audio options - Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
switch (checkedId) {
case R.id.audio_button:
setupAudioSpinner(currentInfo.audio_streams, streamsSpinner);
break;
case R.id.video_button:
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Streams Spinner Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (DEBUG) Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedAudioIndex = position;
break;
case R.id.video_button:
selectedVideoIndex = position;
break;
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void checkDownloadOptions(View view) {
RadioButton audioButton = (RadioButton) view.findViewById(R.id.audio_button);
RadioButton videoButton = (RadioButton) view.findViewById(R.id.video_button);
if(arguments.getString(AUDIO_URL) == null) {
if (currentInfo.audio_streams == null || currentInfo.audio_streams.size() == 0) {
audioButton.setVisibility(View.GONE);
videoButton.setChecked(true);
} else if(arguments.getString(VIDEO_URL) == null) {
} else if (sortedStreamVideosList == null || sortedStreamVideosList.size() == 0) {
videoButton.setVisibility(View.GONE);
audioButton.setChecked(true);
}
@@ -169,15 +255,15 @@ public class DownloadDialog extends DialogFragment {
/**
* #143 #44 #42 #22: make shure that the filename does not contain illegal chars.
* This should fix some of the "cannot download" problems.
* */
private String createFileName(String fName) {
*/
private String createFileName(String fileName) {
// from http://eng-przemelek.blogspot.de/2009/07/how-to-create-valid-file-name.html
List<String> forbiddenCharsPatterns = new ArrayList<> ();
List<String> forbiddenCharsPatterns = new ArrayList<>();
forbiddenCharsPatterns.add("[:]+"); // Mac OS, but it looks that also Windows XP
forbiddenCharsPatterns.add("[\\*\"/\\\\\\[\\]\\:\\;\\|\\=\\,]+"); // Windows
forbiddenCharsPatterns.add("[^\\w\\d\\.]+"); // last chance... only latin letters and digits
String nameToTest = fName;
String nameToTest = fileName;
for (String pattern : forbiddenCharsPatterns) {
nameToTest = nameToTest.replaceAll(pattern, "_");
}
@@ -185,56 +271,20 @@ public class DownloadDialog extends DialogFragment {
}
//download audio, video or both?
private void download()
{
View view = getView();
Bundle arguments = getArguments();
final EditText name = (EditText) view.findViewById(R.id.file_name);
final SeekBar threads = (SeekBar) view.findViewById(R.id.threads);
RadioButton audioButton = (RadioButton) view.findViewById(R.id.audio_button);
RadioButton videoButton = (RadioButton) view.findViewById(R.id.video_button);
private void downloadSelected() {
String url, location;
String fName = name.getText().toString().trim();
String fileName = nameEditText.getText().toString().trim();
if (fileName.isEmpty()) fileName = createFileName(currentInfo.title);
boolean isAudio = audioButton.isChecked();
String url, location, filename;
if(isAudio) {
url = arguments.getString(AUDIO_URL);
location = NewPipeSettings.getAudioDownloadPath(getContext());
filename = fName + arguments.getString(FILE_SUFFIX_AUDIO);
} else {
url = arguments.getString(VIDEO_URL);
location = NewPipeSettings.getVideoDownloadPath(getContext());
filename = fName + arguments.getString(FILE_SUFFIX_VIDEO);
}
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
url = isAudio ? currentInfo.audio_streams.get(selectedAudioIndex).url : sortedStreamVideosList.get(selectedVideoIndex).url;
location = isAudio ? NewPipeSettings.getAudioDownloadPath(getContext()) : NewPipeSettings.getVideoDownloadPath(getContext());
DownloadManagerService.startMission(getContext(), url, location, filename, isAudio,
threads.getProgress() + 1);
if (isAudio) fileName += "." + MediaFormat.getSuffixById(currentInfo.audio_streams.get(selectedAudioIndex).format);
else fileName += "." + MediaFormat.getSuffixById(sortedStreamVideosList.get(selectedVideoIndex).format);
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
getDialog().dismiss();
}
private void download(String url, String title,
String fileSuffix, File downloadDir, Context context) {
File saveFilePath = new File(downloadDir,createFileName(title) + fileSuffix);
long id = 0;
Log.i(TAG,"Started downloading '" + url +
"' => '" + saveFilePath + "' #" + id);
if (App.isUsingTor()) {
//if using Tor, do not use DownloadManager because the proxy cannot be set
//we'll see later
FileDownloader.downloadFile(getContext(), url, saveFilePath, title);
} else {
Intent intent = new Intent(getContext(), DownloadActivity.class);
intent.setAction(DownloadActivity.INTENT_DOWNLOAD);
intent.setData(Uri.parse(url));
intent.putExtra("fileName", createFileName(title) + fileSuffix);
startActivity(intent);
}
}
}

View File

@@ -1,165 +0,0 @@
package org.schabi.newpipe.download;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import org.schabi.newpipe.R;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import javax.net.ssl.HttpsURLConnection;
import info.guardianproject.netcipher.NetCipher;
/**
* Created by Christian Schabesberger on 14.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* FileDownloader.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
// TODO: FOR HEAVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
public class FileDownloader extends AsyncTask<Void, Integer, Void> {
public static final String TAG = "FileDownloader";
private NotificationManager nm;
private NotificationCompat.Builder builder;
private int notifyId = 0x1234;
private int fileSize = 0xffffffff;
private final Context context;
private final String fileURL;
private final File saveFilePath;
private final String title;
private final String debugContext;
public FileDownloader(Context context, String fileURL, File saveFilePath, String title) {
this.context = context;
this.fileURL = fileURL;
this.saveFilePath = saveFilePath;
this.title = title;
this.debugContext = "'" + fileURL +
"' => '" + saveFilePath + "'";
}
/**
* Downloads a file from a URL in the background using an {@link AsyncTask}.
*
* @param fileURL HTTP URL of the file to be downloaded
* @param saveFilePath path of the directory to save the file
* @param title
* @throws IOException
*/
public static void downloadFile(final Context context, final String fileURL, final File saveFilePath, String title) {
new FileDownloader(context, fileURL, saveFilePath, title).execute();
}
/** AsyncTask impl: executed in gui thread */
@Override
protected void onPreExecute() {
super.onPreExecute();
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Drawable icon = context.getResources().getDrawable(R.mipmap.ic_launcher);
builder = new NotificationCompat.Builder(context)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
.setContentTitle(saveFilePath.getName())
.setContentText(saveFilePath.getAbsolutePath())
.setProgress(fileSize, 0, false);
nm.notify(notifyId, builder.build());
}
/** AsyncTask impl: executed in background thread does the download */
@Override
protected Void doInBackground(Void... voids) {
HttpsURLConnection con = null;
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
con = NetCipher.getHttpsURLConnection(fileURL);
int responseCode = con.getResponseCode();
// always check HTTP response code first
if (responseCode == HttpURLConnection.HTTP_OK) {
fileSize = con.getContentLength();
inputStream = new BufferedInputStream(con.getInputStream());
outputStream = new FileOutputStream(saveFilePath);
int bufferSize = 8192;
int downloaded = 0;
int bytesRead = -1;
byte[] buffer = new byte[bufferSize];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
downloaded += bytesRead;
if (downloaded % 50000 < bufferSize) {
publishProgress(downloaded);
}
}
publishProgress(bufferSize);
} else {
Log.i(TAG, "No file to download. Server replied HTTP code: " + responseCode);
}
} catch (IOException e) {
Log.e(TAG, "No file to download. Server replied HTTP code: ", e);
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (con != null) {
con.disconnect();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... progress) {
builder.setProgress(fileSize, progress[0], false);
nm.notify(notifyId, builder.build());
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
nm.cancel(notifyId);
}
}

View File

@@ -0,0 +1,179 @@
package org.schabi.newpipe.fragments;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.AttrRes;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public abstract class BaseFragment extends Fragment {
protected final String TAG = "BaseFragment@" + Integer.toHexString(hashCode());
protected static final boolean DEBUG = MainActivity.DEBUG;
protected AppCompatActivity activity;
protected OnItemSelectedListener onItemSelectedListener;
protected AtomicBoolean isLoading = new AtomicBoolean(false);
protected AtomicBoolean wasLoading = new AtomicBoolean(false);
protected static final ImageLoader imageLoader = ImageLoader.getInstance();
protected static final DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
protected Toolbar toolbar;
protected View errorPanel;
protected Button errorButtonRetry;
protected TextView errorTextView;
protected ProgressBar loadingProgressBar;
//protected SwipeRefreshLayout swipeRefreshLayout;
/*//////////////////////////////////////////////////////////////////////////
// Fragment's Lifecycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (DEBUG) Log.d(TAG, "onAttach() called with: context = [" + context + "]");
activity = (AppCompatActivity) context;
onItemSelectedListener = (OnItemSelectedListener) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
isLoading.set(false);
setHasOptionsMenu(true);
}
@Override
public void onViewCreated(View rootView, Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]");
initViews(rootView, savedInstanceState);
initListeners();
wasLoading.set(false);
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (DEBUG) Log.d(TAG, "onDestroyView() called");
toolbar = null;
errorPanel = null;
errorButtonRetry = null;
errorTextView = null;
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
protected void initViews(View rootView, Bundle savedInstanceState) {
toolbar = (Toolbar) activity.findViewById(R.id.toolbar);
loadingProgressBar = (ProgressBar) rootView.findViewById(R.id.loading_progress_bar);
//swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh);
errorPanel = rootView.findViewById(R.id.error_panel);
errorButtonRetry = (Button) rootView.findViewById(R.id.error_button_retry);
errorTextView = (TextView) rootView.findViewById(R.id.error_message_view);
}
protected void initListeners() {
errorButtonRetry.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onRetryButtonClicked();
}
});
}
protected abstract void reloadContent();
protected void onRetryButtonClicked() {
if (DEBUG) Log.d(TAG, "onRetryButtonClicked() called");
reloadContent();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void setErrorMessage(String message, boolean showRetryButton) {
if (errorTextView == null || activity == null) return;
errorTextView.setText(message);
if (showRetryButton) animateView(errorButtonRetry, true, 300);
else animateView(errorButtonRetry, false, 0);
animateView(errorPanel, true, 300);
isLoading.set(false);
animateView(loadingProgressBar, false, 200);
}
protected int getResourceIdFromAttr(@AttrRes int attr) {
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr});
int attributeResourceId = a.getResourceId(0, 0);
a.recycle();
return attributeResourceId;
}
public static void showMenuTooltip(View v, String message) {
final int[] screenPos = new int[2];
final Rect displayFrame = new Rect();
v.getLocationOnScreen(screenPos);
v.getWindowVisibleDisplayFrame(displayFrame);
final Context context = v.getContext();
final int width = v.getWidth();
final int height = v.getHeight();
final int midy = screenPos[1] + height / 2;
int referenceX = screenPos[0] + width / 2;
if (ViewCompat.getLayoutDirection(v) == View.LAYOUT_DIRECTION_LTR) {
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
referenceX = screenWidth - referenceX; // mirror
}
Toast cheatSheet = Toast.makeText(context, message, Toast.LENGTH_SHORT);
if (midy < displayFrame.height()) {
// Show along the top; follow action buttons
cheatSheet.setGravity(Gravity.TOP | Gravity.END, referenceX,
screenPos[1] + height - displayFrame.top);
} else {
// Show along the bottom center
cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
}
cheatSheet.show();
}
}

View File

@@ -0,0 +1,76 @@
package org.schabi.newpipe.fragments;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
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 org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.NavigationHelper;
public class MainFragment extends Fragment {
private final String TAG = "MainFragment@" + Integer.toHexString(hashCode());
private static final boolean DEBUG = MainActivity.DEBUG;
private AppCompatActivity activity;
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (DEBUG) Log.d(TAG, "onAttach() called with: context = [" + context + "]");
activity = ((AppCompatActivity) context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.fragment_main, container, false);
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
inflater.inflate(R.menu.main_fragment_menu, menu);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(false);
supportActionBar.setDisplayHomeAsUpEnabled(false);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
NavigationHelper.openSearch(activity, 0, "");
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -1,6 +1,5 @@
package org.schabi.newpipe.fragments.channel;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -8,9 +7,9 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -20,222 +19,146 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.fragments.BaseFragment;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.workers.ChannelExtractorWorker;
import java.io.Serializable;
import java.text.NumberFormat;
import java.util.ArrayList;
import static android.os.Build.VERSION.SDK_INT;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class ChannelFragment extends BaseFragment implements ChannelExtractorWorker.OnChannelInfoReceive {
private final String TAG = "ChannelFragment@" + Integer.toHexString(hashCode());
/**
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ChannelFragment.java is part of NewPipe.
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
private static final String INFO_LIST_KEY = "info_list_key";
private static final String CHANNEL_INFO_KEY = "channel_info_key";
private static final String PAGE_NUMBER_KEY = "page_number_key";
public class ChannelFragment extends Fragment implements ChannelExtractorWorker.OnChannelInfoReceive {
private static final String TAG = "ChannelFragment";
private AppCompatActivity activity;
private OnItemSelectedListener onItemSelectedListener;
private InfoListAdapter infoListAdapter;
private ChannelExtractorWorker currentExtractorWorker;
private ChannelExtractorWorker currentChannelWorker;
private ChannelInfo currentChannelInfo;
private int serviceId = -1;
private String channelName = "";
private String channelUrl = "";
private boolean isLoading = false;
private int pageNumber = 0;
private boolean hasNextPage = true;
private ImageLoader imageLoader = ImageLoader.getInstance();
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private View rootView = null;
private RecyclerView channelVideosList;
private LinearLayoutManager layoutManager;
private ProgressBar loadingProgressBar;
private View headerRootLayout;
private ImageView headerChannelBanner;
private ImageView headerAvatarView;
private TextView headerTitleView;
private TextView headerSubscriberView;
private Button headerSubscriberButton;
private View headerSubscriberLayout;
private TextView headerSubscribersTextView;
private Button headerRssButton;
/*////////////////////////////////////////////////////////////////////////*/
public ChannelFragment() {
}
public static ChannelFragment newInstance(int serviceId, String url, String name) {
ChannelFragment instance = newInstance();
Bundle bundle = new Bundle();
bundle.putString(Constants.KEY_URL, url);
bundle.putString(Constants.KEY_TITLE, name);
bundle.putInt(Constants.KEY_SERVICE_ID, serviceId);
instance.setArguments(bundle);
public static Fragment getInstance(int serviceId, String channelUrl, String name) {
ChannelFragment instance = new ChannelFragment();
instance.setChannel(serviceId, channelUrl, name);
return instance;
}
public static ChannelFragment newInstance() {
return new ChannelFragment();
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
activity = ((AppCompatActivity) context);
onItemSelectedListener = ((OnItemSelectedListener) context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
isLoading = false;
if (savedInstanceState != null) {
channelUrl = savedInstanceState.getString(Constants.KEY_URL);
channelName = savedInstanceState.getString(Constants.KEY_TITLE);
serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, -1);
} else {
try {
Bundle args = getArguments();
if (args != null) {
channelUrl = args.getString(Constants.KEY_URL);
channelName = args.getString(Constants.KEY_TITLE);
serviceId = args.getInt(Constants.KEY_SERVICE_ID, 0);
}
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(getActivity(), e, null,
getActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(serviceId),
"", R.string.general_error));
}
pageNumber = savedInstanceState.getInt(PAGE_NUMBER_KEY, 0);
Serializable serializable = savedInstanceState.getSerializable(CHANNEL_INFO_KEY);
if (serializable instanceof ChannelInfo) currentChannelInfo = (ChannelInfo) serializable;
}
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_channel, container, false);
return rootView;
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.fragment_channel, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
loadingProgressBar = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
channelVideosList = (RecyclerView) view.findViewById(R.id.channel_streams_view);
super.onViewCreated(view, savedInstanceState);
infoListAdapter = new InfoListAdapter(activity, rootView);
layoutManager = new LinearLayoutManager(activity);
channelVideosList.setLayoutManager(layoutManager);
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, channelVideosList, false);
infoListAdapter.setHeader(headerRootLayout);
infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, channelVideosList, false));
channelVideosList.setAdapter(infoListAdapter);
headerChannelBanner = (ImageView) headerRootLayout.findViewById(R.id.channel_banner_image);
headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.channel_avatar_view);
headerTitleView = (TextView) headerRootLayout.findViewById(R.id.channel_title_view);
headerSubscriberView = (TextView) headerRootLayout.findViewById(R.id.channel_subscriber_view);
headerSubscriberButton = (Button) headerRootLayout.findViewById(R.id.channel_subscribe_button);
headerSubscriberLayout = headerRootLayout.findViewById(R.id.channel_subscriber_layout);
initListeners();
isLoading = true;
if (currentChannelInfo == null) loadPage(0);
else handleChannelInfo(currentChannelInfo, false, false);
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (DEBUG) Log.d(TAG, "onDestroyView() called");
headerAvatarView.setImageBitmap(null);
headerChannelBanner.setImageBitmap(null);
channelVideosList.removeAllViews();
rootView = null;
channelVideosList = null;
layoutManager = null;
loadingProgressBar = null;
headerRootLayout = null;
headerChannelBanner = null;
headerAvatarView = null;
headerTitleView = null;
headerSubscriberView = null;
headerSubscriberButton = null;
headerSubscriberLayout = null;
headerSubscribersTextView = null;
headerRssButton = null;
super.onDestroyView();
}
@Override
public void onResume() {
if (DEBUG) Log.d(TAG, "onResume() called");
super.onResume();
if (isLoading) {
requestData(false);
if (wasLoading.getAndSet(false) && (currentChannelWorker == null || !currentChannelWorker.isRunning())) {
loadPage(pageNumber);
}
}
@Override
public void onStop() {
if (DEBUG) Log.d(TAG, "onStop() called");
super.onStop();
if (currentExtractorWorker != null) currentExtractorWorker.cancel();
}
@Override
public void onDestroy() {
super.onDestroy();
imageLoader.clearMemoryCache();
wasLoading.set(currentChannelWorker != null && currentChannelWorker.isRunning());
if (currentChannelWorker != null && currentChannelWorker.isRunning()) currentChannelWorker.cancel();
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (DEBUG) Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]");
super.onSaveInstanceState(outState);
outState.putString(Constants.KEY_URL, channelUrl);
outState.putString(Constants.KEY_TITLE, channelName);
outState.putInt(Constants.KEY_SERVICE_ID, serviceId);
outState.putSerializable(INFO_LIST_KEY, ((ArrayList<InfoItem>) infoListAdapter.getItemsList()));
outState.putSerializable(CHANNEL_INFO_KEY, currentChannelInfo);
outState.putInt(PAGE_NUMBER_KEY, pageNumber);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -243,6 +166,7 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker.
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_channel, menu);
@@ -250,20 +174,18 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker.
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(true);
supportActionBar.setDisplayHomeAsUpEnabled(true);
//noinspection deprecation
supportActionBar.setNavigationMode(0);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_item_openInBrowser: {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(channelUrl));
startActivity(Intent.createChooser(intent, getString(R.string.choose_browser)));
return true;
}
@@ -283,79 +205,185 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker.
// Init's
//////////////////////////////////////////////////////////////////////////*/
private void initListeners() {
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
channelVideosList = (RecyclerView) rootView.findViewById(R.id.channel_streams_view);
channelVideosList.setLayoutManager(new LinearLayoutManager(activity));
if (infoListAdapter == null) {
infoListAdapter = new InfoListAdapter(activity, rootView);
if (savedInstanceState != null) {
//noinspection unchecked
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
infoListAdapter.addInfoItemList(serializable);
}
}
channelVideosList.setAdapter(infoListAdapter);
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, channelVideosList, false);
infoListAdapter.setHeader(headerRootLayout);
infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, channelVideosList, false));
headerChannelBanner = (ImageView) headerRootLayout.findViewById(R.id.channel_banner_image);
headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.channel_avatar_view);
headerTitleView = (TextView) headerRootLayout.findViewById(R.id.channel_title_view);
headerSubscribersTextView = (TextView) headerRootLayout.findViewById(R.id.channel_subscriber_view);
headerRssButton = (Button) headerRootLayout.findViewById(R.id.channel_rss_button);
}
protected void initListeners() {
super.initListeners();
infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
if (DEBUG) Log.d(TAG, "selected() called with: serviceId = [" + serviceId + "], url = [" + url + "], title = [" + title + "]");
NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
}
});
// detect if list has ben scrolled to the bottom
channelVideosList.setOnScrollListener(new RecyclerView.OnScrollListener() {
channelVideosList.clearOnScrollListeners();
channelVideosList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
//check for scroll down
if (dy > 0) {
LinearLayoutManager layoutManager = (LinearLayoutManager) channelVideosList.getLayoutManager();
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !currentExtractorWorker.isRunning() && hasNextPage) {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && (currentChannelWorker == null || !currentChannelWorker.isRunning()) && hasNextPage && !isLoading.get()) {
pageNumber++;
requestData(true);
loadMoreVideos();
}
}
}
});
headerSubscriberButton.setOnClickListener(new View.OnClickListener() {
headerRssButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, currentChannelInfo.feed_url);
if (DEBUG) Log.d(TAG, "onClick() called with: view = [" + view + "] feed url > " + currentChannelInfo.feed_url);
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(currentChannelInfo.feed_url));
startActivity(i);
}
});
}
@Override
protected void reloadContent() {
if (DEBUG) Log.d(TAG, "reloadContent() called");
currentChannelInfo = null;
infoListAdapter.clearStreamItemList();
loadPage(0);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private String buildSubscriberString(long count) {
String out = NumberFormat.getNumberInstance().format(count);
out += " " + getString(count > 1 ? R.string.subscriber_plural : R.string.subscriber);
return out;
}
private void requestData(boolean onlyVideos) {
if (currentExtractorWorker != null && currentExtractorWorker.isRunning()) currentExtractorWorker.cancel();
private void loadPage(int page) {
if (DEBUG) Log.d(TAG, "loadPage() called with: page = [" + page + "]");
if (currentChannelWorker != null && currentChannelWorker.isRunning()) currentChannelWorker.cancel();
isLoading.set(true);
pageNumber = page;
infoListAdapter.showFooter(false);
isLoading = true;
if (!onlyVideos) {
//delete already displayed content
loadingProgressBar.setVisibility(View.VISIBLE);
infoListAdapter.clearSteamItemList();
pageNumber = 0;
headerSubscriberLayout.setVisibility(View.GONE);
headerTitleView.setText(channelName != null ? channelName : "");
if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(channelName != null ? channelName : "");
if (SDK_INT >= 21) {
headerChannelBanner.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.channel_banner));
headerAvatarView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
}
infoListAdapter.showFooter(false);
}
animateView(loadingProgressBar, true, 200);
animateView(errorPanel, false, 200);
currentExtractorWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, pageNumber, this);
currentExtractorWorker.setOnlyVideos(onlyVideos);
currentExtractorWorker.start();
imageLoader.cancelDisplayTask(headerChannelBanner);
imageLoader.cancelDisplayTask(headerAvatarView);
headerRssButton.setVisibility(View.GONE);
headerSubscribersTextView.setVisibility(View.GONE);
headerTitleView.setText(channelName != null ? channelName : "");
headerChannelBanner.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.channel_banner));
headerAvatarView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(channelName != null ? channelName : "");
currentChannelWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, page, false, this);
currentChannelWorker.start();
}
private void addVideos(ChannelInfo info) {
infoListAdapter.addInfoItemList(info.related_streams);
private void loadMoreVideos() {
if (DEBUG) Log.d(TAG, "loadMoreVideos() called");
if (currentChannelWorker != null && currentChannelWorker.isRunning()) currentChannelWorker.cancel();
isLoading.set(true);
currentChannelWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, pageNumber, true, this);
currentChannelWorker.start();
}
private void setChannel(int serviceId, String channelUrl, String name) {
this.serviceId = serviceId;
this.channelUrl = channelUrl;
this.channelName = name;
}
private void handleChannelInfo(ChannelInfo info, boolean onlyVideos, boolean addVideos) {
currentChannelInfo = info;
animateView(errorPanel, false, 300);
animateView(channelVideosList, true, 200);
animateView(loadingProgressBar, false, 200);
if (!onlyVideos) {
headerRootLayout.setVisibility(View.VISIBLE);
//animateView(loadingProgressBar, false, 200, null);
if (!TextUtils.isEmpty(info.channel_name)) {
if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(info.channel_name);
headerTitleView.setText(info.channel_name);
channelName = info.channel_name;
} else channelName = "";
if (!TextUtils.isEmpty(info.banner_url)) {
imageLoader.displayImage(info.banner_url, headerChannelBanner, displayImageOptions, new ImageErrorLoadingListener(activity, getView(), info.service_id));
}
if (!TextUtils.isEmpty(info.avatar_url)) {
headerAvatarView.setVisibility(View.VISIBLE);
imageLoader.displayImage(info.avatar_url, headerAvatarView, displayImageOptions, new ImageErrorLoadingListener(activity, getView(), info.service_id));
}
if (info.subscriberCount != -1) {
headerSubscribersTextView.setText(buildSubscriberString(info.subscriberCount));
headerSubscribersTextView.setVisibility(View.VISIBLE);
} else headerSubscribersTextView.setVisibility(View.GONE);
if (!TextUtils.isEmpty(info.feed_url)) headerRssButton.setVisibility(View.VISIBLE);
else headerRssButton.setVisibility(View.INVISIBLE);
infoListAdapter.showFooter(true);
}
hasNextPage = info.hasNextPage;
if (!hasNextPage) infoListAdapter.showFooter(false);
//if (!listRestored) {
if (addVideos) infoListAdapter.addInfoItemList(info.related_streams);
//}
}
@Override
protected void setErrorMessage(String message, boolean showRetryButton) {
super.setErrorMessage(message, showRetryButton);
animateView(channelVideosList, false, 200);
currentChannelInfo = null;
}
/*//////////////////////////////////////////////////////////////////////////
@@ -363,59 +391,23 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker.
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onReceive(ChannelInfo info) {
public void onReceive(ChannelInfo info, boolean onlyVideos) {
if (DEBUG) Log.d(TAG, "onReceive() called with: info = [" + info + "]");
if (info == null || isRemoving() || !isVisible()) return;
currentChannelInfo = info;
if (!currentExtractorWorker.isOnlyVideos()) {
headerRootLayout.setVisibility(View.VISIBLE);
loadingProgressBar.setVisibility(View.GONE);
if (info.channel_name != null && !info.channel_name.isEmpty()) {
if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(info.channel_name);
headerTitleView.setText(info.channel_name);
channelName = info.channel_name;
} else channelName = "";
if (info.banner_url != null && !info.banner_url.isEmpty()) {
imageLoader.displayImage(info.banner_url, headerChannelBanner,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
if (info.avatar_url != null && !info.avatar_url.isEmpty()) {
headerAvatarView.setVisibility(View.VISIBLE);
imageLoader.displayImage(info.avatar_url, headerAvatarView,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
if (info.subscriberCount != -1) {
headerSubscriberView.setText(buildSubscriberString(info.subscriberCount));
}
if ((info.feed_url != null && !info.feed_url.isEmpty()) || (info.subscriberCount != -1)) {
headerSubscriberLayout.setVisibility(View.VISIBLE);
}
if (info.feed_url == null || info.feed_url.isEmpty()) {
headerSubscriberButton.setVisibility(View.INVISIBLE);
}
infoListAdapter.showFooter(true);
}
hasNextPage = info.hasNextPage;
if (!hasNextPage) infoListAdapter.showFooter(false);
addVideos(info);
isLoading = false;
handleChannelInfo(info, onlyVideos, true);
isLoading.set(false);
}
@Override
public void onError(int messageId) {
Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
if (DEBUG) Log.d(TAG, "onError() called with: messageId = [" + messageId + "]");
setErrorMessage(getString(messageId), true);
}
@Override
public void onUnrecoverableError(Exception exception) {
if (DEBUG) Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");
activity.finish();
}
}

View File

@@ -2,16 +2,16 @@ package org.schabi.newpipe.fragments.detail;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.util.Utils;
@@ -19,27 +19,28 @@ import java.util.List;
/**
* Created by Christian Schabesberger on 18.08.15.
*
* <p>
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* DetailsMenuHandler.java is part of NewPipe.
*
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
@SuppressWarnings("WeakerAccess")
class ActionBarHandler {
private static final String TAG = ActionBarHandler.class.toString();
private static final String TAG = "ActionBarHandler";
private AppCompatActivity activity;
private int selectedVideoStream = -1;
@@ -52,11 +53,8 @@ class ActionBarHandler {
// those are edited directly. Typically VideoDetailFragment will implement those callbacks.
private OnActionListener onShareListener;
private OnActionListener onOpenInBrowserListener;
private OnActionListener onOpenInPopupListener;
private OnActionListener onDownloadListener;
private OnActionListener onPlayWithKodiListener;
private OnActionListener onPlayAudioListener;
// Triggered when a stream related action is triggered.
public interface OnActionListener {
@@ -67,46 +65,25 @@ class ActionBarHandler {
this.activity = activity;
}
@SuppressWarnings({"deprecation", "ConstantConditions"})
public void setupNavMenu(AppCompatActivity activity) {
this.activity = activity;
try {
activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
public void setupStreamList(final List<VideoStream> videoStreams, Spinner toolbarSpinner) {
if (activity == null) return;
selectedVideoStream = 0;
public void setupStreamList(final List<VideoStream> videoStreams) {
if (activity != null) {
selectedVideoStream = 0;
// this array will be shown in the dropdown menu for selecting the stream/resolution.
String[] itemArray = new String[videoStreams.size()];
for (int i = 0; i < videoStreams.size(); i++) {
VideoStream item = videoStreams.get(i);
itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
int defaultResolutionIndex = Utils.getDefaultResolution(activity, videoStreams);
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(activity.getString(R.string.use_external_video_player_key), false);
toolbarSpinner.setAdapter(new SpinnerToolbarAdapter(activity, videoStreams, isExternalPlayerEnabled));
toolbarSpinner.setSelection(defaultResolutionIndex);
toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedVideoStream = position;
}
int defaultResolution = Utils.getDefaultResolution(activity, videoStreams);
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(activity.getBaseContext(),
android.R.layout.simple_spinner_dropdown_item, itemArray);
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
ActionBar ab = activity.getSupportActionBar();
//todo: make this throwsable
assert ab != null : "Could not get actionbar";
ab.setListNavigationCallbacks(itemAdapter
, new ActionBar.OnNavigationListener() {
@Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
selectedVideoStream = (int) itemId;
return true;
}
});
ab.setSelectedNavigationItem(defaultResolution);
}
}
public void setupMenu(Menu menu, MenuInflater inflater) {
@@ -116,55 +93,40 @@ class ActionBarHandler {
// appcompat itemsinflater.inflate(R.menu.videoitem_detail, menu);
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
inflater.inflate(R.menu.videoitem_detail, menu);
inflater.inflate(R.menu.video_detail_menu, menu);
showPlayWithKodiAction(defaultPreferences
.getBoolean(activity.getString(R.string.show_play_with_kodi_key), false));
updateItemsVisibility();
}
public void updateItemsVisibility(){
showPlayWithKodiAction(defaultPreferences.getBoolean(activity.getString(R.string.show_play_with_kodi_key), false));
}
public boolean onItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.menu_item_share: {
/*
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, websiteUrl);
intent.setType("text/plain");
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
*/
if(onShareListener != null) {
if (onShareListener != null) {
onShareListener.onActionSelected(selectedVideoStream);
}
return true;
}
case R.id.menu_item_openInBrowser: {
if(onOpenInBrowserListener != null) {
if (onOpenInBrowserListener != null) {
onOpenInBrowserListener.onActionSelected(selectedVideoStream);
}
return true;
}
return true;
case R.id.menu_item_download:
if(onDownloadListener != null) {
if (onDownloadListener != null) {
onDownloadListener.onActionSelected(selectedVideoStream);
}
return true;
case R.id.action_play_with_kodi:
if(onPlayWithKodiListener != null) {
if (onPlayWithKodiListener != null) {
onPlayWithKodiListener.onActionSelected(selectedVideoStream);
}
return true;
case R.id.menu_item_play_audio:
if(onPlayAudioListener != null) {
onPlayAudioListener.onActionSelected(selectedVideoStream);
}
return true;
case R.id.menu_item_popup: {
if(onOpenInPopupListener != null) {
onOpenInPopupListener.onActionSelected(selectedVideoStream);
}
return true;
}
default:
Log.e(TAG, "Menu Item not known");
}
@@ -183,10 +145,6 @@ class ActionBarHandler {
onOpenInBrowserListener = listener;
}
public void setOnOpenInPopupListener(OnActionListener listener) {
onOpenInPopupListener = listener;
}
public void setOnDownloadListener(OnActionListener listener) {
onDownloadListener = listener;
}
@@ -195,14 +153,6 @@ class ActionBarHandler {
onPlayWithKodiListener = listener;
}
public void setOnPlayAudioListener(OnActionListener listener) {
onPlayAudioListener = listener;
}
public void showAudioAction(boolean visible) {
menu.findItem(R.id.menu_item_play_audio).setVisible(visible);
}
public void showDownloadAction(boolean visible) {
menu.findItem(R.id.menu_item_download).setVisible(visible);
}

View File

@@ -0,0 +1,74 @@
package org.schabi.newpipe.fragments.detail;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import java.util.List;
public class SpinnerToolbarAdapter extends BaseAdapter {
private final List<VideoStream> videoStreams;
private final boolean showIconNoAudio;
private final Context context;
public SpinnerToolbarAdapter(Context context, List<VideoStream> videoStreams, boolean showIconNoAudio) {
this.context = context;
this.videoStreams = videoStreams;
this.showIconNoAudio = showIconNoAudio;
}
@Override
public int getCount() {
return videoStreams.size();
}
@Override
public Object getItem(int position) {
return videoStreams.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getCustomView(position, convertView, parent, true);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getCustomView(((Spinner) parent).getSelectedItemPosition(), convertView, parent, false);
}
private View getCustomView(int position, View convertView, ViewGroup parent, boolean isDropdownItem) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.resolutions_spinner_item, parent, false);
}
ImageView woSoundIcon = (ImageView) convertView.findViewById(R.id.wo_sound_icon);
TextView text = (TextView) convertView.findViewById(android.R.id.text1);
VideoStream item = (VideoStream) getItem(position);
text.setText(MediaFormat.getNameById(item.format) + " " + item.resolution);
int visibility = !showIconNoAudio ? View.GONE
: item.isVideoOnly ? View.VISIBLE
: isDropdownItem ? View.INVISIBLE
: View.GONE;
woSoundIcon.setVisibility(visibility);
return convertView;
}
}

View File

@@ -1,11 +1,14 @@
package org.schabi.newpipe.fragments.detail;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import java.io.Serializable;
@SuppressWarnings("WeakerAccess")
public class StackItem implements Serializable {
private String title, url;
private StreamInfo info;
public StackItem(String url, String title) {
this.title = title;
@@ -24,6 +27,14 @@ public class StackItem implements Serializable {
return url;
}
public void setInfo(StreamInfo info) {
this.info = info;
}
public StreamInfo getInfo() {
return info;
}
@Override
public String toString() {
return getUrl() + " > " + getTitle();

View File

@@ -0,0 +1,85 @@
package org.schabi.newpipe.fragments.detail;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import java.util.Iterator;
import java.util.LinkedHashMap;
@SuppressWarnings("WeakerAccess")
public class StreamInfoCache {
private static String TAG = "StreamInfoCache@";
private static final boolean DEBUG = MainActivity.DEBUG;
private static final StreamInfoCache instance = new StreamInfoCache();
private static final int MAX_ITEMS_ON_CACHE = 20;
private final LinkedHashMap<String, StreamInfo> myCache = new LinkedHashMap<>();
private StreamInfoCache() {
TAG += "" + Integer.toHexString(hashCode());
}
public static StreamInfoCache getInstance() {
if (DEBUG) Log.d(TAG, "getInstance() called");
return instance;
}
public boolean hasKey(@NonNull String url) {
if (DEBUG) Log.d(TAG, "hasKey() called with: url = [" + url + "]");
return !TextUtils.isEmpty(url) && myCache.containsKey(url) && myCache.get(url) != null;
}
public StreamInfo getFromKey(@NonNull String url) {
if (DEBUG) Log.d(TAG, "getFromKey() called with: url = [" + url + "]");
return myCache.get(url);
}
public void putInfo(@NonNull StreamInfo info) {
if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]");
putInfo(info.webpage_url, info);
}
public void putInfo(@NonNull String url, @NonNull StreamInfo info) {
if (DEBUG) Log.d(TAG, "putInfo() called with: url = [" + url + "], info = [" + info + "]");
myCache.put(url, info);
}
public void removeInfo(@NonNull StreamInfo info) {
if (DEBUG) Log.d(TAG, "removeInfo() called with: info = [" + info + "]");
myCache.remove(info.webpage_url);
}
public void removeInfo(@NonNull String url) {
if (DEBUG) Log.d(TAG, "removeInfo() called with: url = [" + url + "]");
myCache.remove(url);
}
@SuppressWarnings("unused")
public void clearCache() {
if (DEBUG) Log.d(TAG, "clearCache() called");
myCache.clear();
}
public void removeOldEntries() {
if (DEBUG) Log.d(TAG, "removeOldEntries() called , size = " + getSize());
if (getSize() > MAX_ITEMS_ON_CACHE) {
Iterator<String> iterator = myCache.keySet().iterator();
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
if (DEBUG) Log.d(TAG, "getSize() = " + getSize());
if (getSize() <= MAX_ITEMS_ON_CACHE) break;
}
}
}
public int getSize() {
return myCache.size();
}
}

View File

@@ -3,245 +3,195 @@ package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.fragments.BaseFragment;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.workers.SearchWorker;
import org.schabi.newpipe.workers.SuggestionWorker;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import static android.app.Activity.RESULT_OK;
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
/**
* Created by Christian Schabesberger on 02.08.16.
* <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SearchFragment.java is part of NewPipe.
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener, SearchWorker.SearchWorkerResultListener {
private static final String TAG = SearchFragment.class.toString();
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class SearchFragment extends BaseFragment implements SuggestionWorker.OnSuggestionResult, SearchWorker.OnSearchResult {
private final String TAG = "SearchFragment@" + Integer.toHexString(hashCode());
// savedInstanceBundle arguments
private static final String QUERY = "query";
private static final String STREAMING_SERVICE = "streaming_service";
private static final String QUERY_KEY = "query_key";
private static final String PAGE_NUMBER_KEY = "page_number_key";
private static final String INFO_LIST_KEY = "info_list_key";
private static final String WAS_LOADING_KEY = "was_loading_key";
private static final String ERROR_KEY = "error_key";
private static final String FILTER_CHECKED_ID_KEY = "filter_checked_id_key";
private int streamingServiceId = -1;
/*//////////////////////////////////////////////////////////////////////////
// Search
//////////////////////////////////////////////////////////////////////////*/
private int filterItemCheckedId = -1;
private EnumSet<SearchEngine.Filter> filter = EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM);
private int serviceId = -1;
private String searchQuery = "";
private boolean isLoading = false;
@SuppressWarnings("FieldCanBeLocal")
private SearchView searchView;
private RecyclerView recyclerView;
private ProgressBar loadingIndicator;
private int pageNumber = 0;
private boolean showSuggestions = true;
private SearchWorker curSearchWorker;
private SuggestionWorker curSuggestionWorker;
private SuggestionListAdapter suggestionListAdapter;
private InfoListAdapter infoListAdapter;
private LinearLayoutManager streamInfoListLayoutManager;
private EnumSet<SearchEngine.Filter> filter = EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM);
private OnItemSelectedListener onItemSelectedListener;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public SearchFragment() {
}
private View searchToolbarContainer;
private AutoCompleteTextView searchEditText;
private View searchClear;
@SuppressWarnings("unused")
public static SearchFragment newInstance(int streamingServiceId, String searchQuery) {
Bundle args = new Bundle();
args.putInt(STREAMING_SERVICE, streamingServiceId);
args.putString(QUERY, searchQuery);
SearchFragment fragment = new SearchFragment();
fragment.setArguments(args);
return fragment;
private RecyclerView resultRecyclerView;
/*////////////////////////////////////////////////////////////////////////*/
public static SearchFragment getInstance(int serviceId, String query) {
SearchFragment searchFragment = new SearchFragment();
searchFragment.setQuery(serviceId, query);
return searchFragment;
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
onItemSelectedListener = ((OnItemSelectedListener) context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
searchQuery = "";
isLoading = false;
if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY);
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
} else {
try {
Bundle args = getArguments();
if (args != null) {
searchQuery = args.getString(QUERY);
streamingServiceId = args.getInt(STREAMING_SERVICE);
} else {
streamingServiceId = NewPipe.getIdOfService("Youtube");
}
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(getActivity(), e, null,
getActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(streamingServiceId),
"", R.string.general_error));
}
}
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
setHasOptionsMenu(true);
SearchWorker sw = SearchWorker.getInstance();
sw.setSearchWorkerResultListener(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_search, container, false);
Context context = view.getContext();
loadingIndicator = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
recyclerView = (RecyclerView) view.findViewById(R.id.list);
streamInfoListLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(streamInfoListLayoutManager);
infoListAdapter = new InfoListAdapter(getActivity(),
getActivity().findViewById(android.R.id.content));
infoListAdapter.setFooter(inflater.inflate(R.layout.pignate_footer, recyclerView, false));
infoListAdapter.showFooter(false);
infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
}
});
infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
}
});
recyclerView.setAdapter(infoListAdapter);
recyclerView.clearOnScrollListeners();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) //check for scroll down
{
visibleItemCount = streamInfoListLayoutManager.getChildCount();
totalItemCount = streamInfoListLayoutManager.getItemCount();
pastVisiblesItems = streamInfoListLayoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading) {
pageNumber++;
recyclerView.post(new Runnable() {
@Override
public void run() {
infoListAdapter.showFooter(true);
}
});
search(searchQuery, pageNumber);
}
}
}
});
return view;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!searchQuery.isEmpty()) {
search(searchQuery);
if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY_KEY);
serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, 0);
pageNumber = savedInstanceState.getInt(PAGE_NUMBER_KEY, 0);
wasLoading.set(savedInstanceState.getBoolean(WAS_LOADING_KEY, false));
filterItemCheckedId = savedInstanceState.getInt(FILTER_CHECKED_ID_KEY, 0);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
recyclerView.removeAllViews();
infoListAdapter.clearSteamItemList();
recyclerView = null;
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.fragment_search, container, false);
}
@Override
public void onViewCreated(View rootView, @Nullable Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
if (DEBUG) Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]");
if (savedInstanceState != null && savedInstanceState.getBoolean(ERROR_KEY, false)) {
search(searchQuery, 0, true);
}
}
@Override
public void onResume() {
super.onResume();
if (isLoading && !searchQuery.isEmpty()) {
search(searchQuery);
if (DEBUG) Log.d(TAG, "onResume() called");
if (wasLoading.getAndSet(false) && !TextUtils.isEmpty(searchQuery)) {
if (pageNumber > 0) search(searchQuery, pageNumber);
else search(searchQuery, 0, true);
}
showSuggestions = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.show_search_suggestions_key), true);
}
@Override
public void onStop() {
super.onStop();
SearchWorker.getInstance().terminate();
if (DEBUG) Log.d(TAG, "onStop() called");
hideSoftKeyboard(searchEditText);
wasLoading.set(curSearchWorker != null && curSearchWorker.isRunning());
if (curSearchWorker != null && curSearchWorker.isRunning()) curSearchWorker.cancel();
if (curSuggestionWorker != null && curSuggestionWorker.isRunning()) curSuggestionWorker.cancel();
}
@Override
public void onDestroyView() {
if (DEBUG) Log.d(TAG, "onDestroyView() called");
unsetSearchListeners();
resultRecyclerView.removeAllViews();
searchToolbarContainer = null;
searchEditText = null;
searchClear = null;
resultRecyclerView = null;
super.onDestroyView();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(QUERY, searchQuery);
outState.putInt(STREAMING_SERVICE, streamingServiceId);
if (DEBUG) Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]");
String query = searchEditText != null && !TextUtils.isEmpty(searchEditText.getText().toString())
? searchEditText.getText().toString() : searchQuery;
outState.putString(QUERY_KEY, query);
outState.putInt(Constants.KEY_SERVICE_ID, serviceId);
outState.putInt(PAGE_NUMBER_KEY, pageNumber);
outState.putSerializable(INFO_LIST_KEY, ((ArrayList<InfoItem>) infoListAdapter.getItemsList()));
outState.putBoolean(WAS_LOADING_KEY, curSearchWorker != null && curSearchWorker.isRunning());
if (errorPanel != null && errorPanel.getVisibility() == View.VISIBLE) outState.putBoolean(ERROR_KEY, true);
if (filterItemCheckedId != -1) outState.putInt(FILTER_CHECKED_ID_KEY, filterItemCheckedId);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RECAPTCHA_REQUEST:
if (resultCode == RESULT_OK && searchQuery.length() != 0) {
search(searchQuery);
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK && searchQuery.length() != 0) {
search(searchQuery, pageNumber, true);
} else Log.e(TAG, "ReCaptcha failed");
break;
@@ -251,6 +201,88 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
}
}
/*//////////////////////////////////////////////////////////////////////////
// Init's
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void initViews(View rootView, Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
resultRecyclerView = ((RecyclerView) rootView.findViewById(R.id.result_list_view));
resultRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
if (infoListAdapter == null) {
infoListAdapter = new InfoListAdapter(getActivity(), getActivity().findViewById(android.R.id.content));
if (savedInstanceState != null) {
//noinspection unchecked
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
infoListAdapter.addInfoItemList(serializable);
}
infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, resultRecyclerView, false));
infoListAdapter.showFooter(false);
infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
}
});
infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
}
});
}
resultRecyclerView.setAdapter(infoListAdapter);
}
@Override
protected void initListeners() {
super.initListeners();
resultRecyclerView.clearOnScrollListeners();
resultRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
//check for scroll down
if (dy > 0) {
LinearLayoutManager layoutManager = (LinearLayoutManager) resultRecyclerView.getLayoutManager();
visibleItemCount = resultRecyclerView.getLayoutManager().getChildCount();
totalItemCount = resultRecyclerView.getLayoutManager().getItemCount();
pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading.get()) {
pageNumber++;
recyclerView.post(new Runnable() {
@Override
public void run() {
infoListAdapter.showFooter(true);
}
});
search(searchQuery, pageNumber);
}
}
}
});
}
@Override
protected void reloadContent() {
if (DEBUG) Log.d(TAG, "reloadContent() called");
if (!TextUtils.isEmpty(searchQuery) || (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
search(!TextUtils.isEmpty(searchQuery) ? searchQuery : searchEditText.getText().toString(), 0, true);
} else {
if (searchEditText != null) {
searchEditText.setText("");
showSoftKeyboard(searchEditText);
}
animateView(errorPanel, false, 200);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@@ -258,22 +290,47 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(false);
supportActionBar.setDisplayShowTitleEnabled(false);
//noinspection deprecation
supportActionBar.setNavigationMode(0);
}
if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
inflater.inflate(R.menu.search_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
searchView = (SearchView) searchItem.getActionView();
setupSearchView(searchView);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(false);
supportActionBar.setDisplayHomeAsUpEnabled(true);
}
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
searchEditText = (AutoCompleteTextView) searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
setupSearchView();
restoreFilterChecked(menu, filterItemCheckedId);
}
private void restoreFilterChecked(Menu menu, int itemId) {
if (itemId != -1) {
MenuItem item = menu.findItem(itemId);
if (item == null) return;
item.setChecked(true);
switch (itemId) {
case R.id.menu_filter_all:
filter = EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL);
break;
case R.id.menu_filter_video:
filter = EnumSet.of(SearchEngine.Filter.STREAM);
break;
case R.id.menu_filter_channel:
filter = EnumSet.of(SearchEngine.Filter.CHANNEL);
break;
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
switch (item.getItemId()) {
case R.id.menu_filter_all:
changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL));
@@ -289,119 +346,243 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
}
}
/*//////////////////////////////////////////////////////////////////////////
// Search
//////////////////////////////////////////////////////////////////////////*/
private TextWatcher textWatcher;
private void setupSearchView() {
searchEditText.setText(searchQuery != null ? searchQuery : "");
searchEditText.setHint(getString(R.string.search) + "...");
////searchEditText.setCursorVisible(true);
suggestionListAdapter = new SuggestionListAdapter(activity);
searchEditText.setAdapter(suggestionListAdapter);
if (TextUtils.isEmpty(searchQuery) || TextUtils.isEmpty(searchEditText.getText())) {
searchToolbarContainer.setTranslationX(100);
searchToolbarContainer.setAlpha(0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
searchToolbarContainer.animate().translationX(0).alpha(1f).setDuration(400).setInterpolator(new DecelerateInterpolator()).start();
} else {
searchToolbarContainer.setTranslationX(0);
searchToolbarContainer.setAlpha(1f);
searchToolbarContainer.setVisibility(View.VISIBLE);
}
//
initSearchListeners();
if (TextUtils.isEmpty(searchQuery)) showSoftKeyboard(searchEditText);
else hideSoftKeyboard(searchEditText);
if (!TextUtils.isEmpty(searchQuery) && searchQuery.length() > 2 && suggestionListAdapter != null && suggestionListAdapter.isEmpty()) {
searchSuggestions(searchQuery);
}
}
private void initSearchListeners() {
searchClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (TextUtils.isEmpty(searchEditText.getText())) {
NavigationHelper.openMainActivity(activity);
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
searchEditText.setText("", false);
} else searchEditText.setText("");
suggestionListAdapter.updateAdapter(new ArrayList<String>());
showSoftKeyboard(searchEditText);
}
});
searchClear.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (DEBUG) Log.d(TAG, "onLongClick() called with: v = [" + v + "]");
showMenuTooltip(v, getString(R.string.clear));
return true;
}
});
searchEditText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchEditText.showDropDown();
}
});
searchEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) searchEditText.showDropDown();
}
});
searchEditText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (DEBUG) Log.d(TAG, "onItemClick() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
String s = suggestionListAdapter.getSuggestion(position);
if (DEBUG) Log.d(TAG, "onItemClick text = " + s);
submitQuery(s);
}
});
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String newText = searchEditText.getText().toString();
if (!TextUtils.isEmpty(newText) && newText.length() > 1) onQueryTextChange(newText);
}
};
searchEditText.addTextChangedListener(textWatcher);
searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (DEBUG) Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
submitQuery(searchEditText.getText().toString());
return true;
}
return false;
}
});
}
private void unsetSearchListeners() {
searchClear.setOnClickListener(null);
searchClear.setOnLongClickListener(null);
if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
searchEditText.setOnClickListener(null);
searchEditText.setOnItemClickListener(null);
searchEditText.setOnFocusChangeListener(null);
searchEditText.setOnEditorActionListener(null);
textWatcher = null;
}
public void showSoftKeyboard(View view) {
if (DEBUG) Log.d(TAG, "showSoftKeyboard() called with: view = [" + view + "]");
if (view == null) return;
if (view.requestFocus()) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
}
public void hideSoftKeyboard(View view) {
if (DEBUG) Log.d(TAG, "hideSoftKeyboard() called with: view = [" + view + "]");
if (view == null) return;
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
view.clearFocus();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void changeFilter(MenuItem item, EnumSet<SearchEngine.Filter> filter) {
this.filter = filter;
this.filterItemCheckedId = item.getItemId();
item.setChecked(true);
if (searchQuery != null && !searchQuery.isEmpty()) {
Log.e(TAG, "Fuck+ " + searchQuery);
search(searchQuery);
}
if (searchQuery != null && !searchQuery.isEmpty()) search(searchQuery, 0, true);
}
private void setupSearchView(SearchView searchView) {
suggestionListAdapter = new SuggestionListAdapter(getActivity());
searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
searchView.setOnQueryTextListener(this);
if (searchQuery != null && !searchQuery.isEmpty()) {
searchView.setQuery(searchQuery, false);
searchView.setIconifiedByDefault(false);
}
}
private void search(String query) {
infoListAdapter.clearSteamItemList();
infoListAdapter.showFooter(false);
pageNumber = 0;
public void submitQuery(String query) {
if (DEBUG) Log.d(TAG, "submitQuery() called with: query = [" + query + "]");
if (query.isEmpty()) return;
search(query, 0, true);
searchQuery = query;
search(query, pageNumber);
hideBackground();
loadingIndicator.setVisibility(View.VISIBLE);
}
private void search(String query, int page) {
isLoading = true;
SearchWorker sw = SearchWorker.getInstance();
sw.search(streamingServiceId,
query,
page,
getActivity(),
filter);
public void onQueryTextChange(String newText) {
if (DEBUG) Log.d(TAG, "onQueryTextChange() called with: newText = [" + newText + "]");
if (!newText.isEmpty()) searchSuggestions(newText);
}
private void setDoneLoading() {
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
infoListAdapter.showFooter(false);
}
/**
* Hides the "dummy" background when no results are shown
*/
private void hideBackground() {
View view = getView();
if (view == null) return;
view.findViewById(R.id.mainBG).setVisibility(View.GONE);
private void setQuery(int serviceId, String searchQuery) {
this.serviceId = serviceId;
this.searchQuery = searchQuery;
}
private void searchSuggestions(String query) {
SuggestionSearchRunnable suggestionSearchRunnable =
new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
Thread suggestionThread = new Thread(suggestionSearchRunnable);
suggestionThread.start();
if (!showSuggestions) {
if (DEBUG) Log.d(TAG, "searchSuggestions() showSuggestions is disabled");
return;
}
if (DEBUG) Log.d(TAG, "searchSuggestions() called with: query = [" + query + "]");
if (curSuggestionWorker != null && curSuggestionWorker.isRunning()) curSuggestionWorker.cancel();
curSuggestionWorker = SuggestionWorker.startForQuery(activity, serviceId, query, this);
}
public boolean isMainBgVisible() {
return getActivity().findViewById(R.id.mainBG).getVisibility() == View.VISIBLE;
private void search(String query, int pageNumber) {
if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "], pageNumber = [" + pageNumber + "]");
search(query, pageNumber, false);
}
private void search(String query, int pageNumber, boolean clearList) {
if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "], pageNumber = [" + pageNumber + "], clearList = [" + clearList + "]");
isLoading.set(true);
hideSoftKeyboard(searchEditText);
searchQuery = query;
this.pageNumber = pageNumber;
if (clearList) {
animateView(resultRecyclerView, false, 50);
infoListAdapter.clearStreamItemList();
infoListAdapter.showFooter(false);
animateView(loadingProgressBar, true, 200);
}
animateView(errorPanel, false, 200);
if (curSearchWorker != null && curSearchWorker.isRunning()) curSearchWorker.cancel();
curSearchWorker = SearchWorker.startForQuery(activity, serviceId, query, pageNumber, filter, this);
}
protected void setErrorMessage(String message, boolean showRetryButton) {
super.setErrorMessage(message, showRetryButton);
animateView(resultRecyclerView, false, 400);
hideSoftKeyboard(searchEditText);
}
/*//////////////////////////////////////////////////////////////////////////
// OnQueryTextListener
// OnSuggestionResult
//////////////////////////////////////////////////////////////////////////*/
@Override
public boolean onQueryTextSubmit(String query) {
Activity a = getActivity();
try {
search(query);
// hide virtual keyboard
InputMethodManager inputManager =
(InputMethodManager) a.getSystemService(Context.INPUT_METHOD_SERVICE);
try {
//noinspection ConstantConditions
inputManager.hideSoftInputFromWindow(
a.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
} catch (NullPointerException e) {
e.printStackTrace();
ErrorActivity.reportError(a, e, null,
a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(streamingServiceId),
"Could not get widget with focus", R.string.general_error));
}
// clear focus
// 1. to not open up the keyboard after switching back to this
// 2. It's a workaround to a seeming bug by the Android OS it self, causing
// onQueryTextSubmit to trigger twice when focus is not cleared.
// See: http://stackoverflow.com/questions/17874951/searchview-onquerytextsubmit-runs-twice-while-i-pressed-once
a.getCurrentFocus().clearFocus();
} catch (Exception e) {
e.printStackTrace();
}
return true;
public void onSuggestionResult(@NonNull List<String> suggestions) {
if (DEBUG) Log.d(TAG, "onSuggestionResult() called with: suggestions = [" + suggestions + "]");
suggestionListAdapter.updateAdapter(suggestions);
}
@Override
public boolean onQueryTextChange(String newText) {
if (!newText.isEmpty()) {
searchSuggestions(newText);
}
return true;
public void onSuggestionError(int messageId) {
if (DEBUG) Log.d(TAG, "onSuggestionError() called with: messageId = [" + messageId + "]");
setErrorMessage(getString(messageId), true);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -409,32 +590,35 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onResult(SearchResult result) {
public void onSearchResult(SearchResult result) {
if (DEBUG) Log.d(TAG, "onSearchResult() called with: result = [" + result + "]");
infoListAdapter.addInfoItemList(result.resultList);
setDoneLoading();
animateView(resultRecyclerView, true, 400);
animateView(loadingProgressBar, false, 200);
isLoading.set(false);
}
@Override
public void onNothingFound(int stringResource) {
//setListShown(true);
Toast.makeText(getActivity(), getString(stringResource), Toast.LENGTH_SHORT).show();
setDoneLoading();
public void onNothingFound(String message) {
if (DEBUG) Log.d(TAG, "onNothingFound() called with: messageId = [" + message + "]");
setErrorMessage(message, false);
}
@Override
public void onError(String message) {
//setListShown(true);
Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
setDoneLoading();
public void onSearchError(int messageId) {
if (DEBUG) Log.d(TAG, "onSearchError() called with: messageId = [" + messageId + "]");
//Toast.makeText(getActivity(), messageId, Toast.LENGTH_LONG).show();
setErrorMessage(getString(messageId), true);
}
@Override
public void onReCaptchaChallenge() {
Toast.makeText(getActivity(), "ReCaptcha Challenge requested",
Toast.LENGTH_LONG).show();
if (DEBUG) Log.d(TAG, "onReCaptchaChallenge() called");
Toast.makeText(getActivity(), R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
setErrorMessage(getString(R.string.recaptcha_request_toast), false);
// Starting ReCaptcha Challenge Activity
startActivityForResult(new Intent(getActivity(), ReCaptchaActivity.class), RECAPTCHA_REQUEST);
startActivityForResult(new Intent(getActivity(), ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST);
}
}

View File

@@ -1,49 +0,0 @@
package org.schabi.newpipe.fragments.search;
import android.support.v7.widget.SearchView;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SearchSuggestionListener.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SearchSuggestionListener implements SearchView.OnSuggestionListener{
private final SearchView searchView;
private final SuggestionListAdapter adapter;
public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) {
this.searchView = searchView;
this.adapter = adapter;
}
@Override
public boolean onSuggestionSelect(int position) {
String suggestion = adapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
@Override
public boolean onSuggestionClick(int position) {
String suggestion = adapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
}

View File

@@ -1,216 +0,0 @@
package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.EnumSet;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SearchWorker.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SearchWorker {
private static final String TAG = SearchWorker.class.toString();
public interface SearchWorkerResultListener {
void onResult(SearchResult result);
void onNothingFound(final int stringResource);
void onError(String message);
void onReCaptchaChallenge();
}
private class ResultRunnable implements Runnable {
private final SearchResult result;
private int requestId = 0;
public ResultRunnable(SearchResult result, int requestId) {
this.result = result;
this.requestId = requestId;
}
@Override
public void run() {
if(this.requestId == SearchWorker.this.requestId) {
searchWorkerResultListener.onResult(result);
}
}
}
private class SearchRunnable implements Runnable {
public static final String YOUTUBE = "Youtube";
private final String query;
private final int page;
private final EnumSet<SearchEngine.Filter> filter;
final Handler h = new Handler();
private volatile boolean runs = true;
private Activity a = null;
private int serviceId = -1;
public SearchRunnable(int serviceId,
String query,
int page,
EnumSet<SearchEngine.Filter> filter,
Activity activity,
int requestId) {
this.serviceId = serviceId;
this.query = query;
this.page = page;
this.filter = filter;
this.a = activity;
}
void terminate() {
runs = false;
}
@Override
public void run() {
final String serviceName = NewPipe.getNameOfService(serviceId);
SearchResult result = null;
SearchEngine engine = null;
try {
engine = NewPipe.getService(serviceId)
.getSearchEngineInstance();
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
Integer.toString(serviceId), query, R.string.general_error));
return;
}
try {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
String searchLanguageKey = a.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
result = SearchResult
.getSearchResult(engine, query, page, searchLanguage, filter);
if(runs) {
h.post(new ResultRunnable(result, requestId));
}
// look for errors during extraction
// soft errors:
View rootView = a.findViewById(android.R.id.content);
if(result != null &&
!result.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
for(Throwable e : result.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
if(result.resultList.isEmpty()&& !result.errors.isEmpty()) {
// if it compleatly failes dont show snackbar, instead show error directlry
ErrorActivity.reportError(h, a, result.errors, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
serviceName, query, R.string.parsing_error));
} else {
// if it partly show snackbar
ErrorActivity.reportError(h, a, result.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
serviceName, query, R.string.light_parsing_error));
}
}
// hard errors:
} catch (ReCaptchaException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListener.onReCaptchaChallenge();
}
});
} catch(IOException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListener.onNothingFound(R.string.network_error);
}
});
e.printStackTrace();
} catch(final SearchEngine.NothingFoundException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListener.onError(e.getMessage());
}
});
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
serviceName, query, R.string.parsing_error));
//postNewErrorToast(h, R.string.parsing_error);
e.printStackTrace();
} catch(Exception e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
e.printStackTrace();
}
}
}
private static SearchWorker searchWorker = null;
private SearchWorkerResultListener searchWorkerResultListener = null;
private SearchRunnable runnable = null;
private int requestId = 0; //prevents running requests that have already ben expired
public static SearchWorker getInstance() {
return searchWorker == null ? (searchWorker = new SearchWorker()) : searchWorker;
}
public void setSearchWorkerResultListener(SearchWorkerResultListener listener) {
searchWorkerResultListener = listener;
}
private SearchWorker() {
}
public void search(int serviceId,
String query,
int page,
Activity a,
EnumSet<SearchEngine.Filter> filter) {
if(runnable != null) {
terminate();
}
runnable = new SearchRunnable(serviceId, query, page, filter, a, requestId);
Thread thread = new Thread(runnable);
thread.start();
}
public void terminate() {
if (runnable == null) return;
requestId++;
runnable.terminate();
}
}

View File

@@ -75,6 +75,11 @@ public class SuggestionListAdapter extends ResourceCursorAdapter {
return ((Cursor) getItem(position)).getString(INDEX_TITLE);
}
@Override
public CharSequence convertToString(Cursor cursor) {
return cursor.getString(INDEX_TITLE);
}
private class ViewHolder {
private final TextView suggestionTitle;
private ViewHolder(View view) {

View File

@@ -1,105 +0,0 @@
package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.SuggestionExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.List;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SuggestionSearchRunnable.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SuggestionSearchRunnable implements Runnable{
/**
* Runnable to update a {@link SuggestionListAdapter}
*/
private class SuggestionResultRunnable implements Runnable{
private final List<String> suggestions;
private SuggestionResultRunnable(List<String> suggestions) {
this.suggestions = suggestions;
}
@Override
public void run() {
adapter.updateAdapter(suggestions);
}
}
private final int serviceId;
private final String query;
private final Handler h = new Handler();
private final Activity a;
private final SuggestionListAdapter adapter;
public SuggestionSearchRunnable(int serviceId, String query,
Activity activity, SuggestionListAdapter adapter) {
this.serviceId = serviceId;
this.query = query;
this.a = activity;
this.adapter = adapter;
}
@Override
public void run() {
try {
SuggestionExtractor se =
NewPipe.getService(serviceId).getSuggestionExtractorInstance();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
String searchLanguageKey = a.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
List<String> suggestions = se.suggestionList(query, searchLanguage);
h.post(new SuggestionResultRunnable(suggestions));
} catch (ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(serviceId), query, R.string.parsing_error));
e.printStackTrace();
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
e.printStackTrace();
} catch (Exception e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(serviceId), query, R.string.general_error));
}
}
private void postNewErrorToast(Handler h, final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
Toast.makeText(a, a.getString(stringResource),
Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@@ -1,7 +1,6 @@
package org.schabi.newpipe.info_list;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.schabi.newpipe.R;
@@ -35,16 +34,17 @@ public class ChannelInfoItemHolder extends InfoItemHolder {
public final TextView itemSubscriberCountView;
public final TextView itemVideoCountView;
public final TextView itemChannelDescriptionView;
public final Button itemButton;
public final View itemRoot;
ChannelInfoItemHolder(View v) {
super(v);
itemRoot = v.findViewById(R.id.itemRoot);
itemThumbnailView = (CircleImageView) v.findViewById(R.id.itemThumbnailView);
itemChannelTitleView = (TextView) v.findViewById(R.id.itemChannelTitleView);
itemSubscriberCountView = (TextView) v.findViewById(R.id.itemSubscriberCountView);
itemVideoCountView = (TextView) v.findViewById(R.id.itemVideoCountView);
itemChannelDescriptionView = (TextView) v.findViewById(R.id.itemChannelDescriptionView);
itemButton = (Button) v.findViewById(R.id.item_button);
}
@Override

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.info_list;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -18,20 +19,20 @@ import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
/**
* Created by Christian Schabesberger on 26.09.16.
*
* <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* InfoItemBuilder.java is part of NewPipe.
*
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -48,11 +49,13 @@ public class InfoItemBuilder {
private final String billion;
private static final String TAG = InfoItemBuilder.class.toString();
public interface OnInfoItemSelectedListener {
void selected(int serviceId, String url, String title);
}
private Context mContext = null;
private LayoutInflater inflater;
private View rootView = null;
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions =
@@ -70,6 +73,7 @@ public class InfoItemBuilder {
thousand = context.getString(R.string.short_thousand);
million = context.getString(R.string.short_million);
billion = context.getString(R.string.short_billion);
inflater = LayoutInflater.from(context);
}
public void setOnStreamInfoItemSelectedListener(
@@ -83,9 +87,9 @@ public class InfoItemBuilder {
}
public void buildByHolder(InfoItemHolder holder, final InfoItem i) {
if(i.infoType() != holder.infoType())
if (i.infoType() != holder.infoType())
return;
switch(i.infoType()) {
switch (i.infoType()) {
case STREAM:
buildStreamInfoItem((StreamInfoItemHolder) holder, (StreamInfoItem) i);
break;
@@ -103,15 +107,15 @@ public class InfoItemBuilder {
public View buildView(ViewGroup parent, final InfoItem info) {
View itemView = null;
InfoItemHolder holder = null;
switch(info.infoType()) {
switch (info.infoType()) {
case STREAM:
itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.stream_item, parent, false);
//long start = System.nanoTime();
itemView = inflater.inflate(R.layout.stream_item, parent, false);
//Log.d(TAG, "time to inflate: " + ((System.nanoTime() - start) / 1000000L) + "ms");
holder = new StreamInfoItemHolder(itemView);
break;
case CHANNEL:
itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.channel_item, parent, false);
itemView = inflater.inflate(R.layout.channel_item, parent, false);
holder = new ChannelInfoItemHolder(itemView);
break;
case PLAYLIST:
@@ -124,43 +128,39 @@ public class InfoItemBuilder {
}
private void buildStreamInfoItem(StreamInfoItemHolder holder, final StreamInfoItem info) {
if(info.infoType() != InfoItem.InfoType.STREAM) {
if (info.infoType() != InfoItem.InfoType.STREAM) {
Log.e("InfoItemBuilder", "Info type not yet supported");
}
// fill holder with information
holder.itemVideoTitleView.setText(info.title);
if(info.uploader != null && !info.uploader.isEmpty()) {
holder.itemUploaderView.setText(info.uploader);
} else {
holder.itemUploaderView.setVisibility(View.INVISIBLE);
}
if(info.duration > 0) {
if (!TextUtils.isEmpty(info.title)) holder.itemVideoTitleView.setText(info.title);
if (!TextUtils.isEmpty(info.uploader)) holder.itemUploaderView.setText(info.uploader);
else holder.itemUploaderView.setVisibility(View.INVISIBLE);
if (info.duration > 0) {
holder.itemDurationView.setText(getDurationString(info.duration));
} else {
if(info.stream_type == AbstractStreamInfo.StreamType.LIVE_STREAM) {
if (info.stream_type == AbstractStreamInfo.StreamType.LIVE_STREAM) {
holder.itemDurationView.setText(R.string.duration_live);
} else {
holder.itemDurationView.setVisibility(View.GONE);
}
}
if(info.view_count >= 0) {
if (info.view_count >= 0) {
holder.itemViewCountView.setText(shortViewCount(info.view_count));
} else {
holder.itemViewCountView.setVisibility(View.GONE);
}
if(info.upload_date != null && !info.upload_date.isEmpty()) {
holder.itemUploadDateView.setText(info.upload_date + "");
}
if (!TextUtils.isEmpty(info.upload_date)) holder.itemUploadDateView.setText(info.upload_date + "");
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
if (!TextUtils.isEmpty(info.thumbnail_url)) {
imageLoader.displayImage(info.thumbnail_url,
holder.itemThumbnailView,
displayImageOptions,
holder.itemThumbnailView, displayImageOptions,
new ImageErrorLoadingListener(mContext, rootView, info.service_id));
}
holder.itemButton.setOnClickListener(new View.OnClickListener() {
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onStreamInfoItemSelectedListener.selected(info.service_id, info.webpage_url, info.getTitle());
@@ -169,20 +169,20 @@ public class InfoItemBuilder {
}
private void buildChannelInfoItem(ChannelInfoItemHolder holder, final ChannelInfoItem info) {
holder.itemChannelTitleView.setText(info.getTitle());
if (!TextUtils.isEmpty(info.getTitle())) holder.itemChannelTitleView.setText(info.getTitle());
holder.itemSubscriberCountView.setText(shortSubscriber(info.subscriberCount) + "");
holder.itemVideoCountView.setText(info.videoAmount + " " + videosS);
holder.itemChannelDescriptionView.setText(info.description);
if (!TextUtils.isEmpty(info.description)) holder.itemChannelDescriptionView.setText(info.description);
holder.itemThumbnailView.setImageResource(R.drawable.buddy_channel_item);
if(info.thumbnailUrl != null && !info.thumbnailUrl.isEmpty()) {
if (!TextUtils.isEmpty(info.thumbnailUrl)) {
imageLoader.displayImage(info.thumbnailUrl,
holder.itemThumbnailView,
displayImageOptions,
new ImageErrorLoadingListener(mContext, rootView, info.serviceId));
}
holder.itemButton.setOnClickListener(new View.OnClickListener() {
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onChannelInfoItemSelectedListener.selected(info.serviceId, info.getLink(), info.channelName);
@@ -191,15 +191,15 @@ public class InfoItemBuilder {
}
public String shortViewCount(Long viewCount){
if(viewCount >= 1000000000){
return Long.toString(viewCount/1000000000)+ billion + " " + viewsS;
}else if(viewCount>=1000000){
return Long.toString(viewCount/1000000)+ million + " " + viewsS;
}else if(viewCount>=1000){
return Long.toString(viewCount/1000)+ thousand + " " + viewsS;
}else {
return Long.toString(viewCount)+ " " + viewsS;
public String shortViewCount(Long viewCount) {
if (viewCount >= 1000000000) {
return Long.toString(viewCount / 1000000000) + billion + " " + viewsS;
} else if (viewCount >= 1000000) {
return Long.toString(viewCount / 1000000) + million + " " + viewsS;
} else if (viewCount >= 1000) {
return Long.toString(viewCount / 1000) + thousand + " " + viewsS;
} else {
return Long.toString(viewCount) + " " + viewsS;
}
}
@@ -227,13 +227,13 @@ public class InfoItemBuilder {
int seconds = duration % 60;
//handle days
if(days > 0) {
if (days > 0) {
output = Integer.toString(days) + ":";
}
// handle hours
if(hours > 0 || !output.isEmpty()) {
if(hours > 0) {
if(hours >= 10 || output.isEmpty()) {
if (hours > 0 || !output.isEmpty()) {
if (hours > 0) {
if (hours >= 10 || output.isEmpty()) {
output += Integer.toString(hours);
} else {
output += "0" + Integer.toString(hours);
@@ -244,9 +244,9 @@ public class InfoItemBuilder {
output += ":";
}
//handle minutes
if(minutes > 0 || !output.isEmpty()) {
if(minutes > 0) {
if(minutes >= 10 || output.isEmpty()) {
if (minutes > 0 || !output.isEmpty()) {
if (minutes > 0) {
if (minutes >= 10 || output.isEmpty()) {
output += Integer.toString(minutes);
} else {
output += "0" + Integer.toString(minutes);
@@ -258,11 +258,11 @@ public class InfoItemBuilder {
}
//handle seconds
if(output.isEmpty()) {
if (output.isEmpty()) {
output += "0:";
}
if(seconds >= 10) {
if (seconds >= 10) {
output += Integer.toString(seconds);
} else {
output += "0" + Integer.toString(seconds);

View File

@@ -10,8 +10,8 @@ import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* Created by Christian Schabesberger on 01.08.16.
@@ -57,7 +57,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
public InfoListAdapter(Activity a, View rootView) {
infoItemBuilder = new InfoItemBuilder(a, rootView);
infoItemList = new Vector<>();
infoItemList = new ArrayList<>();
}
public void setOnStreamInfoItemSelectedListener
@@ -77,7 +77,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
}
public void clearSteamItemList() {
public void clearStreamItemList() {
infoItemList.clear();
notifyDataSetChanged();
}
@@ -92,12 +92,16 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
notifyDataSetChanged();
}
public List<InfoItem> getItemsList() {
return infoItemList;
}
@Override
public int getItemCount() {
int cound = infoItemList.size();
if(header != null) cound++;
if(footer != null && showFooter) cound++;
return cound;
int count = infoItemList.size();
if(header != null) count++;
if(footer != null && showFooter) count++;
return count;
}
// don't ask why we have to do that this way... it's android accept it -.-

View File

@@ -1,9 +1,6 @@
package org.schabi.newpipe.info_list;
import android.icu.text.IDNA;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -12,20 +9,20 @@ import org.schabi.newpipe.extractor.InfoItem;
/**
* Created by Christian Schabesberger on 01.08.16.
*
* <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamInfoItemHolder.java is part of NewPipe.
*
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -38,17 +35,17 @@ public class StreamInfoItemHolder extends InfoItemHolder {
itemDurationView,
itemUploadDateView,
itemViewCountView;
public final Button itemButton;
public final View itemRoot;
public StreamInfoItemHolder(View v) {
super(v);
itemRoot = v.findViewById(R.id.itemRoot);
itemThumbnailView = (ImageView) v.findViewById(R.id.itemThumbnailView);
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView);
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
itemUploadDateView = (TextView) v.findViewById(R.id.itemUploadDateView);
itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView);
itemButton = (Button) v.findViewById(R.id.item_button);
}
@Override

View File

@@ -78,7 +78,7 @@ public class BackgroundPlayer extends Service {
powerManager = ((PowerManager) getSystemService(POWER_SERVICE));
wifiManager = ((WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE));
ThemeHelper.setTheme(this, false);
ThemeHelper.setTheme(this);
basePlayerImpl = new BasePlayerImpl(this);
basePlayerImpl.setup();
}

View File

@@ -189,6 +189,7 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
ImageLoader.getInstance().loadImage(videoThumbnailUrl, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "onLoadingComplete() called with: imageUri = [" + imageUri + "], view = [" + view + "], loadedImage = [" + loadedImage + "]");
videoThumbnail = loadedImage;
onThumbnailReceived(loadedImage);
@@ -319,7 +320,6 @@ public abstract class BasePlayer implements ExoPlayer.EventListener, AudioManage
if (DEBUG) Log.d(TAG, "onAudioFocusGain() called");
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(DUCK_AUDIO_TO);
animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION);
simpleExoPlayer.setPlayWhenReady(true);
}
protected void onAudioFocusLoss() {

View File

@@ -21,10 +21,13 @@ import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**
* Activity Player implementing VideoPlayer
*
@@ -37,14 +40,7 @@ public class MainVideoPlayer extends Activity {
private AudioManager audioManager;
private GestureDetector gestureDetector;
private final Runnable hideUiRunnable = new Runnable() {
@Override
public void run() {
hideSystemUi();
}
};
private boolean activityPaused;
private VideoPlayerImpl playerImpl;
/*//////////////////////////////////////////////////////////////////////////
@@ -55,7 +51,7 @@ public class MainVideoPlayer extends Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
ThemeHelper.setTheme(this, false);
ThemeHelper.setTheme(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
@@ -67,7 +63,7 @@ public class MainVideoPlayer extends Activity {
}
showSystemUi();
setContentView(R.layout.activity_exo_player);
setContentView(R.layout.activity_main_player);
playerImpl = new VideoPlayerImpl();
playerImpl.setup(findViewById(android.R.id.content));
playerImpl.handleIntent(getIntent());
@@ -188,6 +184,7 @@ public class MainVideoPlayer extends Activity {
repeatButton.setAlpha(77);
}
getRootView().setKeepScreenOn(true);
}
@Override
@@ -265,14 +262,15 @@ public class MainVideoPlayer extends Activity {
else if (v.getId() == screenRotationButton.getId()) onScreenRotationClicked();
if (getCurrentState() != STATE_COMPLETED) {
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
animateView(playerImpl.getControlsRoot(), true, 300, 0, new Runnable() {
@Override
public void run() {
if (getCurrentState() == STATE_PLAYING && !playerImpl.isQualityMenuVisible()) {
animateView(playerImpl.getControlsRoot(), false, 300, DEFAULT_CONTROLS_HIDE_TIME, true);
hideControls(300, DEFAULT_CONTROLS_HIDE_TIME);
}
}
}, false);
});
}
}
@@ -285,14 +283,14 @@ public class MainVideoPlayer extends Activity {
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) {
animateView(playerImpl.getControlsRoot(), false, 100, 0);
hideControls(100, 0);
}
}
@Override
public void onDismiss(PopupMenu menu) {
super.onDismiss(menu);
if (isPlaying()) animateView(getControlsRoot(), false, 500, 0);
if (isPlaying()) hideControls(300, 0);
}
@Override
@@ -310,53 +308,51 @@ public class MainVideoPlayer extends Activity {
public void onLoading() {
super.onLoading();
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, false, 100, 0);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100);
getRootView().setKeepScreenOn(true);
}
@Override
public void onBuffering() {
super.onBuffering();
animateView(playPauseButton, false, 100, 0);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100);
getRootView().setKeepScreenOn(true);
}
@Override
public void onPlaying() {
super.onPlaying();
//playPauseButton.setImageResource(R.drawable.ic_pause_white);
//animateView(playPauseButton, true, 500, 0);
animateView(playPauseButton, false, 80, 0, new Runnable() {
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, true, 200, 0);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 200);
}
});
showSystemUi();
getRootView().setKeepScreenOn(true);
}
@Override
public void onPaused() {
super.onPaused();
//playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
//animateView(playPauseButton, true, 100, 0);
animateView(playPauseButton, false, 80, 0, new Runnable() {
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
animateView(playPauseButton, true, 200, 0);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 200);
}
});
showSystemUi();
getRootView().setKeepScreenOn(false);
}
@Override
public void onPausedSeek() {
super.onPausedSeek();
animateView(playPauseButton, false, 100, 0);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100);
getRootView().setKeepScreenOn(true);
}
@@ -366,14 +362,15 @@ public class MainVideoPlayer extends Activity {
playPauseButton.setImageResource(R.drawable.ic_pause_white);
} else {
showSystemUi();
animateView(playPauseButton, false, 0, 0, new Runnable() {
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, new Runnable() {
@Override
public void run() {
playPauseButton.setImageResource(R.drawable.ic_replay_white);
animateView(playPauseButton, true, 300, 0);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, true, 300);
}
});
}
getRootView().setKeepScreenOn(false);
super.onCompleted();
}
@@ -382,19 +379,20 @@ public class MainVideoPlayer extends Activity {
//////////////////////////////////////////////////////////////////////////*/
@Override
public void animateView(View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) {
//if (execOnEnd == null) playerImpl.setDefaultAnimationEnd(hideUiRunnable);
if (hideUi && execOnEnd != null) {
Runnable combinedRunnable = new Runnable() {
@Override
public void run() {
execOnEnd.run();
hideUiRunnable.run();
}
};
super.animateView(view, enterOrExit, duration, delay, combinedRunnable, true);
} else super.animateView(view, enterOrExit, duration, delay, hideUi ? hideUiRunnable : execOnEnd, hideUi);
public void hideControls(final long duration, long delay) {
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
getControlsVisibilityHandler().removeCallbacksAndMessages(null);
getControlsVisibilityHandler().postDelayed(new Runnable() {
@Override
public void run() {
animateView(getControlsRoot(), false, duration, 0, new Runnable() {
@Override
public void run() {
hideSystemUi();
}
});
}
}, delay);
}
///////////////////////////////////////////////////////////////////////////
@@ -443,19 +441,16 @@ public class MainVideoPlayer extends Activity {
if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
if (playerImpl.getCurrentState() != BasePlayer.STATE_PLAYING) return true;
if (playerImpl.isControlsVisible()) playerImpl.animateView(playerImpl.getControlsRoot(), false, 150, 0, true);
if (playerImpl.isControlsVisible()) playerImpl.hideControls(150, 0);
else {
playerImpl.animateView(playerImpl.getControlsRoot(), true, 500, 0, new Runnable() {
@Override
public void run() {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME, true);
}
});
playerImpl.showControlsThenHide();
showSystemUi();
}
return true;
}
private final boolean isGestureControlsEnabled = playerImpl.getSharedPreferences().getBoolean(getString(R.string.player_gesture_controls_key), true);
private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f;
private float currentBrightness = .5f;
@@ -473,8 +468,10 @@ public class MainVideoPlayer extends Activity {
// TODO: Improve video gesture controls
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isGestureControlsEnabled) return false;
//noinspection PointlessBooleanExpression
if (DEBUG && true) Log.d(TAG, "MainVideoPlayer.onScroll = " +
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");
@@ -500,7 +497,7 @@ public class MainVideoPlayer extends Activity {
if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume);
playerImpl.getVolumeTextView().setText(volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%");
if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), true, 200, 0);
if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getVolumeTextView(), true, 200);
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);
} else {
WindowManager.LayoutParams lp = getWindow().getAttributes();
@@ -515,7 +512,7 @@ public class MainVideoPlayer extends Activity {
playerImpl.getBrightnessTextView().setText(brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%");
if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), true, 200, 0);
if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), true, 200);
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
}
return true;
@@ -527,16 +524,18 @@ public class MainVideoPlayer extends Activity {
eventsNum = 0;
/* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE);
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getVolumeTextView(), false, 200, 200);
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) animateView(playerImpl.getVolumeTextView(), false, 200, 200);
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
playerImpl.hideControls(300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//noinspection PointlessBooleanExpression
if (DEBUG && false) Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
gestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isMoving) {
isMoving = false;

View File

@@ -7,13 +7,14 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -24,6 +25,8 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.PopupMenu;
import android.widget.RemoteViews;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
@@ -43,6 +46,8 @@ import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.Utils;
import org.schabi.newpipe.workers.StreamExtractorWorker;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**
* Service Popup Player implementing VideoPlayer
*
@@ -58,14 +63,19 @@ public class PopupVideoPlayer extends Service {
public static final String ACTION_OPEN_DETAIL = "org.schabi.newpipe.player.PopupVideoPlayer.OPEN_DETAIL";
public static final String ACTION_REPEAT = "org.schabi.newpipe.player.PopupVideoPlayer.REPEAT";
private static final String POPUP_SAVED_WIDTH = "popup_saved_width";
private static final String POPUP_SAVED_X = "popup_saved_x";
private static final String POPUP_SAVED_Y = "popup_saved_y";
private WindowManager windowManager;
private WindowManager.LayoutParams windowLayoutParams;
private GestureDetector gestureDetector;
private float screenWidth, screenHeight;
private float popupWidth, popupHeight;
private float currentPopupHeight = 110.0f * Resources.getSystem().getDisplayMetrics().density;
//private float minimumHeight = 100; // TODO: Use it when implementing the resize of the popup
private float minimumWidth, minimumHeight;
private float maximumWidth, maximumHeight;
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
private NotificationManager notificationManager;
@@ -89,7 +99,7 @@ public class PopupVideoPlayer extends Service {
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
playerImpl = new VideoPlayerImpl();
ThemeHelper.setTheme(this, false);
ThemeHelper.setTheme(this);
}
@Override
@@ -114,6 +124,8 @@ public class PopupVideoPlayer extends Service {
@Override
public void onConfigurationChanged(Configuration newConfig) {
updateScreenSize();
updatePopupSize(windowLayoutParams.width, -1);
checkPositionBounds();
}
@Override
@@ -124,12 +136,13 @@ public class PopupVideoPlayer extends Service {
playerImpl.destroy();
if (playerImpl.getRootView() != null) windowManager.removeView(playerImpl.getRootView());
}
if (imageLoader != null) imageLoader.clearMemoryCache();
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
if (currentExtractorWorker != null) {
currentExtractorWorker.cancel();
currentExtractorWorker = null;
}
savePositionAndSize();
}
@Override
@@ -145,21 +158,33 @@ public class PopupVideoPlayer extends Service {
private void initPopup() {
if (DEBUG) Log.d(TAG, "initPopup() called");
View rootView = View.inflate(this, R.layout.player_popup, null);
playerImpl.setup(rootView);
updateScreenSize();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean popupRememberSizeAndPos = sharedPreferences.getBoolean(getString(R.string.popup_remember_size_pos_key), true);
float defaultSize = getResources().getDimension(R.dimen.popup_default_width);
popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
windowLayoutParams = new WindowManager.LayoutParams(
(int) getMinimumVideoWidth(currentPopupHeight), (int) currentPopupHeight,
(int) popupWidth, (int) getMinimumVideoHeight(popupWidth),
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
windowLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
windowLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
checkPositionBounds();
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
gestureDetector = new GestureDetector(this, listener);
gestureDetector.setIsLongpressEnabled(false);
//gestureDetector.setIsLongpressEnabled(false);
rootView.setOnTouchListener(listener);
playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width);
playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height);
@@ -220,13 +245,13 @@ public class PopupVideoPlayer extends Service {
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
}
/*//////////////////////////////////////////////////////////////////////////
// Misc
//////////////////////////////////////////////////////////////////////////*/
public void onVideoClose() {
if (DEBUG) Log.d(TAG, "onVideoClose() called");
savePositionAndSize();
stopSelf();
}
@@ -246,10 +271,23 @@ public class PopupVideoPlayer extends Service {
// Utils
//////////////////////////////////////////////////////////////////////////*/
private float getMinimumVideoWidth(float height) {
float width = height * (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
if (DEBUG) Log.d(TAG, "getMinimumVideoWidth() called with: height = [" + height + "], returned: " + width);
return width;
private void checkPositionBounds() {
if (windowLayoutParams.x > screenWidth - windowLayoutParams.width) windowLayoutParams.x = (int) (screenWidth - windowLayoutParams.width);
if (windowLayoutParams.x < 0) windowLayoutParams.x = 0;
if (windowLayoutParams.y > screenHeight - windowLayoutParams.height) windowLayoutParams.y = (int) (screenHeight - windowLayoutParams.height);
if (windowLayoutParams.y < 0) windowLayoutParams.y = 0;
}
private void savePositionAndSize() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this);
sharedPreferences.edit().putInt(POPUP_SAVED_X, windowLayoutParams.x).apply();
sharedPreferences.edit().putInt(POPUP_SAVED_Y, windowLayoutParams.y).apply();
sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, windowLayoutParams.width).apply();
}
private float getMinimumVideoHeight(float width) {
//if (DEBUG) Log.d(TAG, "getMinimumVideoHeight() called with: width = [" + width + "], returned: " + height);
return width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have
}
private void updateScreenSize() {
@@ -259,11 +297,39 @@ public class PopupVideoPlayer extends Service {
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
if (DEBUG) Log.d(TAG, "updateScreenSize() called > screenWidth = " + screenWidth + ", screenHeight = " + screenHeight);
popupWidth = getResources().getDimension(R.dimen.popup_default_width);
popupHeight = getMinimumVideoHeight(popupWidth);
minimumWidth = getResources().getDimension(R.dimen.popup_minimum_width);
minimumHeight = getMinimumVideoHeight(minimumWidth);
maximumWidth = screenWidth;
maximumHeight = screenHeight;
}
private void updatePopupSize(int width, int height) {
//if (DEBUG) Log.d(TAG, "updatePopupSize() called with: width = [" + width + "], height = [" + height + "]");
width = (int) (width > maximumWidth ? maximumWidth : width < minimumWidth ? minimumWidth : width);
if (height == -1) height = (int) getMinimumVideoHeight(width);
else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height);
windowLayoutParams.width = width;
windowLayoutParams.height = height;
popupWidth = width;
popupHeight = height;
if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]");
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
}
///////////////////////////////////////////////////////////////////////////
private class VideoPlayerImpl extends VideoPlayer {
private TextView resizingIndicator;
VideoPlayerImpl() {
super("VideoPlayerImpl" + PopupVideoPlayer.TAG, PopupVideoPlayer.this);
}
@@ -272,13 +338,20 @@ public class PopupVideoPlayer extends Service {
public void playUrl(String url, String format, boolean autoPlay) {
super.playUrl(url, format, autoPlay);
windowLayoutParams.width = (int) getMinimumVideoWidth(currentPopupHeight);
windowLayoutParams.width = (int) popupWidth;
windowLayoutParams.height = (int) getMinimumVideoHeight(popupWidth);
windowManager.updateViewLayout(getRootView(), windowLayoutParams);
notBuilder = createNotification();
startForeground(NOTIFICATION_ID, notBuilder.build());
}
@Override
public void initViews(View rootView) {
super.initViews(rootView);
resizingIndicator = (TextView) rootView.findViewById(R.id.resizing_indicator);
}
@Override
public void destroy() {
super.destroy();
@@ -338,7 +411,7 @@ public class PopupVideoPlayer extends Service {
@Override
public void onDismiss(PopupMenu menu) {
super.onDismiss(menu);
if (isPlaying()) animateView(getControlsRoot(), false, 500, 0);
if (isPlaying()) hideControls(500, 0);
}
@Override
@@ -348,7 +421,14 @@ public class PopupVideoPlayer extends Service {
stopSelf();
}
/*//////////////////////////////////////////////////////////////////////////
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) {
hideControls(100, 0);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
@@ -423,12 +503,21 @@ public class PopupVideoPlayer extends Service {
showAndAnimateControl(R.drawable.ic_replay_white, false);
}
@SuppressWarnings("WeakerAccess")
public TextView getResizingIndicator() {
return resizingIndicator;
}
}
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
private int initialPopupX, initialPopupY;
private boolean isMoving;
private int onDownPopupWidth = 0;
private boolean isResizing;
private boolean isResizingRightSide;
@Override
public boolean onDoubleTap(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
@@ -451,15 +540,34 @@ public class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]");
initialPopupX = windowLayoutParams.x;
initialPopupY = windowLayoutParams.y;
popupWidth = playerImpl.getRootView().getWidth();
popupHeight = playerImpl.getRootView().getHeight();
popupWidth = windowLayoutParams.width;
popupHeight = windowLayoutParams.height;
onDownPopupWidth = windowLayoutParams.width;
return false;
}
@Override
public void onLongPress(MotionEvent e) {
if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.hideControls(0, 0);
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
animateView(playerImpl.getResizingIndicator(), true, 200, 0);
isResizing = true;
isResizingRightSide = e.getRawX() > windowLayoutParams.x + (windowLayoutParams.width / 2f);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f) playerImpl.animateView(playerImpl.getControlsRoot(), true, 30, 0);
if (isResizing) return false;
if (playerImpl.getCurrentState() != BasePlayer.STATE_BUFFERING
&& (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f)) playerImpl.showControls(0);
isMoving = true;
float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX);
float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY);
@@ -478,7 +586,7 @@ public class PopupVideoPlayer extends Service {
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]" +
", posXy = [" + posX + ", " + posY + "]" +
", popupWh rootView.get wh = [" + popupWidth + " x " + popupHeight + "]");
", popupWh = [" + popupWidth + " x " + popupHeight + "]");
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
return true;
}
@@ -486,16 +594,38 @@ public class PopupVideoPlayer extends Service {
private void onScrollEnd() {
if (DEBUG) Log.d(TAG, "onScrollEnd() called");
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
playerImpl.hideControls(300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isMoving) {
isMoving = false;
onScrollEnd();
if (event.getAction() == MotionEvent.ACTION_MOVE && isResizing && !isMoving) {
//if (DEBUG) Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
int width;
if (isResizingRightSide) width = (int) event.getRawX() - windowLayoutParams.x;
else {
width = (int) (windowLayoutParams.width + (windowLayoutParams.x - event.getRawX()));
if (width > minimumWidth) windowLayoutParams.x = initialPopupX - (width - onDownPopupWidth);
}
if (width <= maximumWidth && width >= minimumWidth) updatePopupSize(width, -1);
return true;
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (DEBUG) Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
if (isMoving) {
isMoving = false;
onScrollEnd();
}
if (isResizing) {
isResizing = false;
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
savePositionAndSize();
}
return true;
}
@@ -547,7 +677,9 @@ public class PopupVideoPlayer extends Service {
imageLoader.resume();
imageLoader.loadImage(info.thumbnail_url, displayImageOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, final Bitmap loadedImage) {
public void onLoadingComplete(final String imageUri, View view, final Bitmap loadedImage) {
if (playerImpl == null || playerImpl.getPlayer() == null) return;
if (DEBUG) Log.d(TAG, "FetcherRunnable.imageLoader.onLoadingComplete() called with: imageUri = [" + imageUri + "]");
mainHandler.post(new Runnable() {
@Override
public void run() {

View File

@@ -12,6 +12,7 @@ import android.graphics.Color;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.Menu;
@@ -36,12 +37,15 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.util.AnimationUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**
* Base for <b>video</b> players
*
@@ -101,6 +105,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
private ImageButton fullScreenButton;
private ValueAnimator controlViewAnimator;
private Handler controlsVisibilityHandler = new Handler();
private boolean isQualityPopupMenuVisible = false;
private boolean qualityChanged = false;
@@ -125,7 +130,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
this.aspectRatioFrameLayout = (AspectRatioFrameLayout) rootView.findViewById(R.id.aspectRatioLayout);
this.surfaceView = (SurfaceView) rootView.findViewById(R.id.surfaceView);
this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground);
this.loadingPanel = rootView.findViewById(R.id.loadingPanel);
this.loadingPanel = rootView.findViewById(R.id.loading_panel);
this.endScreen = (ImageView) rootView.findViewById(R.id.endScreen);
this.controlAnimationView = (ImageView) rootView.findViewById(R.id.controlAnimationView);
this.controlsRoot = rootView.findViewById(R.id.playbackControlRoot);
@@ -235,6 +240,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (!isProgressLoopRunning.get()) startProgressLoop();
controlsVisibilityHandler.removeCallbacksAndMessages(null);
animateView(controlsRoot, false, 300);
showAndAnimateControl(-1, true);
playbackSeekBar.setEnabled(true);
playbackSeekBar.setProgress(0);
@@ -242,10 +250,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
animateView(endScreen, false, 0, 0);
animateView(endScreen, false, 0);
loadingPanel.setBackgroundColor(Color.BLACK);
animateView(loadingPanel, true, 0, 0);
animateView(surfaceForeground, true, 100, 0);
animateView(loadingPanel, true, 0);
animateView(surfaceForeground, true, 100);
}
@Override
@@ -254,26 +262,21 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (!isProgressLoopRunning.get()) startProgressLoop();
showAndAnimateControl(-1, true);
loadingPanel.setVisibility(View.GONE);
animateView(controlsRoot, true, 500, 0, new Runnable() {
@Override
public void run() {
animateView(controlsRoot, false, 500, DEFAULT_CONTROLS_HIDE_TIME, true);
}
});
animateView(currentDisplaySeek, false, 200, 0);
showControlsThenHide();
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
}
@Override
public void onBuffering() {
if (DEBUG) Log.d(TAG, "onBuffering() called");
loadingPanel.setBackgroundColor(Color.TRANSPARENT);
animateView(loadingPanel, true, 500, 0);
animateView(loadingPanel, true, 500);
}
@Override
public void onPaused() {
if (DEBUG) Log.d(TAG, "onPaused() called");
animateView(controlsRoot, true, 500, 100);
showControls(400);
loadingPanel.setVisibility(View.GONE);
}
@@ -289,9 +292,9 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (isProgressLoopRunning.get()) stopProgressLoop();
animateView(controlsRoot, true, 500, 0);
animateView(endScreen, true, 800, 0);
animateView(currentDisplaySeek, false, 200, 0);
showControls(500);
animateView(endScreen, true, 800);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
loadingPanel.setVisibility(View.GONE);
playbackSeekBar.setMax((int) simpleExoPlayer.getDuration());
@@ -302,7 +305,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
// Bug on lower api, disabling and enabling the seekBar resets the thumb color -.-, so sets the color again
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
animateView(surfaceForeground, true, 100, 0);
animateView(surfaceForeground, true, 100);
if (currentRepeatMode == RepeatMode.REPEAT_ONE) {
changeState(STATE_LOADING);
@@ -324,7 +327,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
@Override
public void onRenderedFirstFrame() {
animateView(surfaceForeground, false, 100, 0);
animateView(surfaceForeground, false, 100);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -443,7 +446,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (DEBUG) Log.d(TAG, "onQualitySelectorClicked() called");
qualityPopupMenu.show();
isQualityPopupMenuVisible = true;
animateView(getControlsRoot(), true, 300, 0);
showControls(300);
VideoStream videoStream = getSelectedVideoStream();
qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
@@ -469,8 +472,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
wasPlaying = isPlaying();
if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false);
animateView(controlsRoot, true, 0, 0);
animateView(currentDisplaySeek, true, 300, 0);
showControls(0);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, true, 300);
}
@Override
@@ -481,7 +484,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) simpleExoPlayer.setPlayWhenReady(true);
playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
animateView(currentDisplaySeek, false, 200, 0);
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
if (getCurrentState() == STATE_PAUSED_SEEK) changeState(STATE_BUFFERING);
if (!isProgressLoopRunning.get()) startProgressLoop();
@@ -550,107 +553,37 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
controlViewAnimator.start();
}
public void animateView(View view, boolean enterOrExit, long duration, long delay) {
animateView(view, enterOrExit, duration, delay, null, false);
}
public void animateView(View view, boolean enterOrExit, long duration, long delay, boolean hideUi) {
animateView(view, enterOrExit, duration, delay, null, hideUi);
}
public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
animateView(view, enterOrExit, duration, delay, execOnEnd, false);
}
/**
* Animate the view
*
* @param view view that will be animated
* @param enterOrExit true to enter, false to exit
* @param duration how long the animation will take, in milliseconds
* @param delay how long the animation will wait to start, in milliseconds
* @param execOnEnd runnable that will be executed when the animation ends
* @param hideUi need to hide ui when animation ends,
* just a helper for classes extending this
*/
public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd, boolean hideUi) {
if (DEBUG) {
Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "], execOnEnd = [" + execOnEnd + "]");
}
if (view.getVisibility() == View.VISIBLE && enterOrExit) {
if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]");
view.animate().setListener(null).cancel();
view.setVisibility(View.VISIBLE);
view.setAlpha(1f);
if (execOnEnd != null) execOnEnd.run();
return;
} else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) {
if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]");
view.animate().setListener(null).cancel();
view.setVisibility(View.GONE);
view.setAlpha(0f);
if (execOnEnd != null) execOnEnd.run();
return;
}
view.animate().setListener(null).cancel();
view.setVisibility(View.VISIBLE);
if (view == controlsRoot) {
if (enterOrExit) {
view.animate().alpha(1f).setDuration(duration).setStartDelay(delay)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (execOnEnd != null) execOnEnd.run();
}
}).start();
} else {
view.animate().alpha(0f)
.setDuration(duration).setStartDelay(delay)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
if (execOnEnd != null) execOnEnd.run();
}
})
.start();
}
return;
}
if (enterOrExit) {
view.setAlpha(0f);
view.setScaleX(.8f);
view.setScaleY(.8f);
view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (execOnEnd != null) execOnEnd.run();
}
}).start();
} else {
view.setAlpha(1f);
view.setScaleX(1f);
view.setScaleY(1f);
view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
if (execOnEnd != null) execOnEnd.run();
}
})
.start();
}
}
public boolean isQualityMenuVisible() {
return isQualityPopupMenuVisible;
}
public void showControlsThenHide() {
if (DEBUG) Log.d(TAG, "showControlsThenHide() called");
animateView(controlsRoot, true, 300, 0, new Runnable() {
@Override
public void run() {
hideControls(300, DEFAULT_CONTROLS_HIDE_TIME);
}
});
}
public void showControls(long duration) {
if (DEBUG) Log.d(TAG, "showControls() called");
controlsVisibilityHandler.removeCallbacksAndMessages(null);
animateView(controlsRoot, true, duration);
}
public void hideControls(final long duration, long delay) {
if (DEBUG) Log.d(TAG, "hideControls() called with: delay = [" + delay + "]");
controlsVisibilityHandler.removeCallbacksAndMessages(null);
controlsVisibilityHandler.postDelayed(new Runnable() {
@Override
public void run() {
animateView(controlsRoot, false, duration);
}
}, delay);
}
/*//////////////////////////////////////////////////////////////////////////
// Getters and Setters
//////////////////////////////////////////////////////////////////////////*/
@@ -711,6 +644,10 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
this.startedFromNewPipe = startedFromNewPipe;
}
public Handler getControlsVisibilityHandler() {
return controlsVisibilityHandler;
}
public View getRootView() {
return rootView;
}

View File

@@ -15,6 +15,7 @@ import android.support.design.widget.Snackbar;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -205,19 +206,19 @@ public class ErrorActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this, true);
ThemeHelper.setTheme(this);
setContentView(R.layout.activity_error);
Intent intent = getIntent();
try {
ActionBar actionBar = getSupportActionBar();
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.error_report_title);
actionBar.setDisplayShowTitleEnabled(true);
} catch (Throwable e) {
Log.e(TAG, "Error turing exception handling");
e.printStackTrace();
}
reportButton = (Button) findViewById(R.id.errorReportButton);

View File

@@ -1,18 +1,11 @@
package org.schabi.newpipe.settings;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.view.MenuInflater;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
@@ -38,9 +31,7 @@ import org.schabi.newpipe.util.ThemeHelper;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SettingsActivity extends PreferenceActivity {
SettingsFragment f = new SettingsFragment();
private AppCompatDelegate mDelegate = null;
public class SettingsActivity extends AppCompatActivity {
public static void initSettings(Context context) {
NewPipeSettings.initSettings(context);
@@ -48,108 +39,31 @@ public class SettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceBundle) {
ThemeHelper.setTheme(this, true);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceBundle);
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceBundle);
setContentView(R.layout.settings_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.settings_title);
actionBar.setDisplayShowTitleEnabled(true);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, f)
.commit();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
f.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
private ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
@NonNull
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.settings_title);
actionBar.setDisplayShowTitleEnabled(true);
}
if (savedInstanceBundle == null) {
getFragmentManager().beginTransaction()
.replace(R.id.fragment_holder, new SettingsFragment())
.commit();
}
return mDelegate;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == android.R.id.home) {
if (id == android.R.id.home) {
finish();
}
return true;

View File

@@ -1,205 +1,84 @@
package org.schabi.newpipe.settings;
import android.app.Activity;
import android.content.ClipData;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.util.Log;
import com.nononsenseapps.filepicker.FilePickerActivity;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import java.util.ArrayList;
import org.schabi.newpipe.util.Constants;
import info.guardianproject.netcipher.proxy.OrbotHelper;
/**
* Created by david on 15/06/16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SettingsFragment.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final int REQUEST_INSTALL_ORBOT = 0x1234;
private static final int REQUEST_DOWNLOAD_PATH = 0x1235;
private static final int REQUEST_DOWNLOAD_AUDIO_PATH = 0x1236;
public class SettingsFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener
{
public static final int REQUEST_INSTALL_ORBOT = 0x1234;
SharedPreferences.OnSharedPreferenceChangeListener prefListener;
// get keys
String DEFAULT_RESOLUTION_PREFERENCE;
String DEFAULT_POPUP_RESOLUTION_PREFERENCE;
String PREFERRED_VIDEO_FORMAT_PREFERENCE;
String DEFAULT_AUDIO_FORMAT_PREFERENCE;
String SEARCH_LANGUAGE_PREFERENCE;
String DOWNLOAD_PATH_PREFERENCE;
String DOWNLOAD_PATH_AUDIO_PREFERENCE;
String USE_TOR_KEY;
String THEME;
private ListPreference defaultResolutionPreference;
private ListPreference defaultPopupResolutionPreference;
private ListPreference preferredVideoFormatPreference;
private ListPreference defaultAudioFormatPreference;
private ListPreference searchLanguagePreference;
private Preference downloadPathPreference;
private Preference downloadPathAudioPreference;
private Preference themePreference;
private String DOWNLOAD_PATH_PREFERENCE;
private String DOWNLOAD_PATH_AUDIO_PREFERENCE;
private String USE_TOR_KEY;
private String THEME;
private String currentTheme;
private SharedPreferences defaultPreferences;
private Activity activity;
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = getActivity();
addPreferencesFromResource(R.xml.settings);
final Activity activity = getActivity();
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
initKeys();
updatePreferencesSummary();
// get keys
DEFAULT_RESOLUTION_PREFERENCE = getString(R.string.default_resolution_key);
DEFAULT_POPUP_RESOLUTION_PREFERENCE = getString(R.string.default_popup_resolution_key);
PREFERRED_VIDEO_FORMAT_PREFERENCE = getString(R.string.preferred_video_format_key);
DEFAULT_AUDIO_FORMAT_PREFERENCE = getString(R.string.default_audio_format_key);
SEARCH_LANGUAGE_PREFERENCE = getString(R.string.search_language_key);
currentTheme = defaultPreferences.getString(THEME, getString(R.string.default_theme_value));
}
@Override
public void onResume() {
super.onResume();
defaultPreferences.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onStop() {
super.onStop();
defaultPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
private void initKeys() {
DOWNLOAD_PATH_PREFERENCE = getString(R.string.download_path_key);
DOWNLOAD_PATH_AUDIO_PREFERENCE = getString(R.string.download_path_audio_key);
THEME = getString(R.string.theme_key);
USE_TOR_KEY = getString(R.string.use_tor_key);
// get pref objects
defaultResolutionPreference =
(ListPreference) findPreference(DEFAULT_RESOLUTION_PREFERENCE);
defaultPopupResolutionPreference =
(ListPreference) findPreference(DEFAULT_POPUP_RESOLUTION_PREFERENCE);
preferredVideoFormatPreference =
(ListPreference) findPreference(PREFERRED_VIDEO_FORMAT_PREFERENCE);
defaultAudioFormatPreference =
(ListPreference) findPreference(DEFAULT_AUDIO_FORMAT_PREFERENCE);
searchLanguagePreference =
(ListPreference) findPreference(SEARCH_LANGUAGE_PREFERENCE);
downloadPathPreference = findPreference(DOWNLOAD_PATH_PREFERENCE);
downloadPathAudioPreference = findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE);
themePreference = findPreference(THEME);
final String currentTheme = defaultPreferences.getString(THEME, "Light");
// TODO: Clean this, as the class is already implementing the class
// and those double equals...
prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
Activity a = getActivity();
if(a == null)
{
return;
}
if (key == USE_TOR_KEY)
{
if (defaultPreferences.getBoolean(USE_TOR_KEY, false)) {
if (OrbotHelper.isOrbotInstalled(a)) {
App.configureTor(true);
OrbotHelper.requestStartTor(a);
} else {
Intent intent = OrbotHelper.getOrbotInstallIntent(a);
a.startActivityForResult(intent, REQUEST_INSTALL_ORBOT);
}
} else {
App.configureTor(false);
}
}
else if (key == DOWNLOAD_PATH_PREFERENCE)
{
String downloadPath = sharedPreferences
.getString(DOWNLOAD_PATH_PREFERENCE,
getString(R.string.download_path_summary));
downloadPathPreference
.setSummary(downloadPath);
}
else if (key == DOWNLOAD_PATH_AUDIO_PREFERENCE)
{
String downloadPath = sharedPreferences
.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE,
getString(R.string.download_path_audio_summary));
downloadPathAudioPreference
.setSummary(downloadPath);
}
else if (key == THEME)
{
String selectedTheme = sharedPreferences.getString(THEME, "Light");
themePreference.setSummary(selectedTheme);
if(!selectedTheme.equals(currentTheme)) { // If it's not the current theme
new AlertDialog.Builder(activity)
.setTitle(R.string.restart_title)
.setMessage(R.string.msg_restart)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intentToMain = new Intent(activity, MainActivity.class);
intentToMain.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
activity.startActivity(intentToMain);
activity.finish();
Runtime.getRuntime().exit(0);
}
})
.setNegativeButton(R.string.later, null)
.create().show();
}
}
updateSummary();
}
};
defaultPreferences.registerOnSharedPreferenceChangeListener(prefListener);
updateSummary();
}
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if(preference.getKey().equals(downloadPathPreference.getKey()) ||
preference.getKey().equals(downloadPathAudioPreference.getKey()))
{
Activity activity = getActivity();
Intent i = new Intent(activity, FilePickerActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
if(preference.getKey().equals(downloadPathPreference.getKey()))
{
activity.startActivityForResult(i, R.string.download_path_key);
}
else if (preference.getKey().equals(downloadPathAudioPreference.getKey()))
{
activity.startActivityForResult(i, R.string.download_path_audio_key);
Log.d("TAG", "onPreferenceTreeClick() called with: preferenceScreen = [" + preferenceScreen + "], preference = [" + preference + "]");
if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE) || preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
Intent i = new Intent(activity, FilePickerActivity.class)
.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE)) {
startActivityForResult(i, REQUEST_DOWNLOAD_PATH);
} else if (preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
startActivityForResult(i, REQUEST_DOWNLOAD_AUDIO_PATH);
}
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
@@ -208,90 +87,56 @@ public class SettingsFragment extends PreferenceFragment
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Activity a = getActivity();
Log.d("TAG", "onActivityResult() called with: requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]");
if ((requestCode == R.string.download_path_audio_key
|| requestCode == R.string.download_path_key)
&& resultCode == Activity.RESULT_OK) {
Uri uri = null;
if (data.getBooleanExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)) {
// For JellyBean and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ClipData clip = data.getClipData();
if (clip != null) {
for (int i = 0; i < clip.getItemCount(); i++) {
uri = clip.getItemAt(i).getUri();
}
}
// For Ice Cream Sandwich
} else {
ArrayList<String> paths = data.getStringArrayListExtra
(FilePickerActivity.EXTRA_PATHS);
if (paths != null) {
for (String path: paths) {
uri = Uri.parse(path);
}
}
}
} else {
uri = data.getData();
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(a);
//requestCode is equal to R.string.download_path_key or
//R.string.download_path_audio_key
String key = getString(requestCode);
if ((requestCode == REQUEST_DOWNLOAD_PATH || requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) && resultCode == Activity.RESULT_OK) {
String key = getString(requestCode == REQUEST_DOWNLOAD_PATH ? R.string.download_path_key : R.string.download_path_audio_key);
String path = data.getData().toString().substring(7);
prefs.edit()
.putString(key, path)
.apply();
}
else if(requestCode == REQUEST_INSTALL_ORBOT)
{
defaultPreferences.edit().putString(key, path).apply();
updatePreferencesSummary();
} else if (requestCode == REQUEST_INSTALL_ORBOT) {
// try to start tor regardless of resultCode since clicking back after
// installing the app does not necessarily return RESULT_OK
App.configureTor(requestCode == REQUEST_INSTALL_ORBOT
&& OrbotHelper.requestStartTor(a));
App.configureTor(OrbotHelper.requestStartTor(activity));
}
updateSummary();
super.onActivityResult(requestCode, resultCode, data);
}
// This is used to show the status of some preference in the description
private void updateSummary() {
defaultResolutionPreference.setSummary(
defaultPreferences.getString(DEFAULT_RESOLUTION_PREFERENCE,
getString(R.string.default_resolution_value)));
defaultPopupResolutionPreference.setSummary(
defaultPreferences.getString(DEFAULT_POPUP_RESOLUTION_PREFERENCE,
getString(R.string.default_popup_resolution_value)));
preferredVideoFormatPreference.setSummary(
defaultPreferences.getString(PREFERRED_VIDEO_FORMAT_PREFERENCE,
getString(R.string.preferred_video_format_default)));
defaultAudioFormatPreference.setSummary(
defaultPreferences.getString(DEFAULT_AUDIO_FORMAT_PREFERENCE,
getString(R.string.default_audio_format_value)));
searchLanguagePreference.setSummary(
defaultPreferences.getString(SEARCH_LANGUAGE_PREFERENCE,
getString(R.string.default_language_value)));
downloadPathPreference.setSummary(
defaultPreferences.getString(DOWNLOAD_PATH_PREFERENCE,
getString(R.string.download_path_summary)));
downloadPathAudioPreference.setSummary(
defaultPreferences.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE,
getString(R.string.download_path_audio_summary)));
themePreference.setSummary(
defaultPreferences.getString(THEME,
getString(R.string.light_theme_title)));
/**
* Update ONLY the summary of some preferences that don't fire in the onSharedPreferenceChanged or CAN'T be update via xml (%s)
*
* For example, the download_path use the startActivityForResult, firing the onStop of this fragment,
* unregistering the listener (unregisterOnSharedPreferenceChangeListener)
*/
private void updatePreferencesSummary() {
findPreference(DOWNLOAD_PATH_PREFERENCE).setSummary(defaultPreferences.getString(DOWNLOAD_PATH_PREFERENCE, getString(R.string.download_path_summary)));
findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE).setSummary(defaultPreferences.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE, getString(R.string.download_path_audio_summary)));
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d("TAG", "onSharedPreferenceChanged() called with: sharedPreferences = [" + sharedPreferences + "], key = [" + key + "]");
String summary = null;
if (key.equals(USE_TOR_KEY)) {
if (defaultPreferences.getBoolean(USE_TOR_KEY, false)) {
if (OrbotHelper.isOrbotInstalled(activity)) {
App.configureTor(true);
OrbotHelper.requestStartTor(activity);
} else {
Intent intent = OrbotHelper.getOrbotInstallIntent(activity);
startActivityForResult(intent, REQUEST_INSTALL_ORBOT);
}
} else App.configureTor(false);
return;
} else if (key.equals(THEME)) {
summary = sharedPreferences.getString(THEME, getString(R.string.default_theme_value));
if (!summary.equals(currentTheme)) { // If it's not the current theme
Intent intentToMain = new Intent(activity, MainActivity.class);
intentToMain.putExtra(Constants.KEY_THEME_CHANGE, true);
startActivity(intentToMain);
}
}
if (!TextUtils.isEmpty(summary)) findPreference(key).setSummary(summary);
}
}

View File

@@ -0,0 +1,126 @@
package org.schabi.newpipe.util;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.util.Log;
import android.view.View;
import org.schabi.newpipe.player.BasePlayer;
public class AnimationUtils {
private static final String TAG = "AnimationUtils";
private static final boolean DEBUG = BasePlayer.DEBUG;
public enum Type {
ALPHA, SCALE_AND_ALPHA
}
public static void animateView(View view, boolean enterOrExit, long duration) {
animateView(view, Type.ALPHA, enterOrExit, duration, 0, null);
}
public static void animateView(View view, boolean enterOrExit, long duration, long delay) {
animateView(view, Type.ALPHA, enterOrExit, duration, delay, null);
}
public static void animateView(View view, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) {
animateView(view, Type.ALPHA, enterOrExit, duration, delay, execOnEnd);
}
public static void animateView(View view, Type animationType, boolean enterOrExit, long duration) {
animateView(view, animationType, enterOrExit, duration, 0, null);
}
public static void animateView(View view, Type animationType, boolean enterOrExit, long duration, long delay) {
animateView(view, animationType, enterOrExit, duration, delay, null);
}
/**
* Animate the view
*
* @param view view that will be animated
* @param animationType {@link Type} of the animation
* @param enterOrExit true to enter, false to exit
* @param duration how long the animation will take, in milliseconds
* @param delay how long the animation will wait to start, in milliseconds
* @param execOnEnd runnable that will be executed when the animation ends
*/
public static void animateView(final View view, Type animationType, boolean enterOrExit, long duration, long delay, Runnable execOnEnd) {
if (DEBUG) {
Log.d(TAG, "animateView() called with: view = [" + view + "], animationType = [" + animationType + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], delay = [" + delay + "], execOnEnd = [" + execOnEnd + "]");
}
if (view.getVisibility() == View.VISIBLE && enterOrExit) {
if (DEBUG) Log.d(TAG, "animateView() view was already visible > view = [" + view + "]");
view.animate().setListener(null).cancel();
view.setVisibility(View.VISIBLE);
view.setAlpha(1f);
if (execOnEnd != null) execOnEnd.run();
return;
} else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) {
if (DEBUG) Log.d(TAG, "animateView() view was already gone > view = [" + view + "]");
view.animate().setListener(null).cancel();
view.setVisibility(View.GONE);
view.setAlpha(0f);
if (execOnEnd != null) execOnEnd.run();
return;
}
view.animate().setListener(null).cancel();
view.setVisibility(View.VISIBLE);
switch (animationType) {
case ALPHA:
animateAlpha(view, enterOrExit, duration, delay, execOnEnd);
break;
case SCALE_AND_ALPHA:
animateScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
break;
}
}
private static void animateScaleAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
if (enterOrExit) {
view.setAlpha(0f);
view.setScaleX(.8f);
view.setScaleY(.8f);
view.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (execOnEnd != null) execOnEnd.run();
}
}).start();
} else {
view.setAlpha(1f);
view.setScaleX(1f);
view.setScaleY(1f);
view.animate().alpha(0f).scaleX(.8f).scaleY(.8f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
if (execOnEnd != null) execOnEnd.run();
}
}).start();
}
}
private static void animateAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
if (enterOrExit) {
view.animate().alpha(1f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (execOnEnd != null) execOnEnd.run();
}
}).start();
} else {
view.animate().alpha(0f).setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
if (execOnEnd != null) execOnEnd.run();
}
}).start();
}
}
}

View File

@@ -5,4 +5,8 @@ public class Constants {
public static final String KEY_URL = "key_url";
public static final String KEY_TITLE = "key_title";
public static final String KEY_LINK_TYPE = "key_link_type";
public static final String KEY_OPEN_SEARCH = "key_open_search";
public static final String KEY_QUERY = "key_query";
public static final String KEY_THEME_CHANGE = "key_theme_change";
}

View File

@@ -1,10 +1,12 @@
package org.schabi.newpipe;
package org.schabi.newpipe.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.preference.PreferenceManager;
import org.schabi.newpipe.R;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;

View File

@@ -19,6 +19,10 @@ import org.schabi.newpipe.player.VideoPlayer;
@SuppressWarnings({"unused", "WeakerAccess"})
public class NavigationHelper {
/*//////////////////////////////////////////////////////////////////////////
// Players
//////////////////////////////////////////////////////////////////////////*/
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
Intent mIntent = new Intent(context, targetClazz)
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
@@ -61,7 +65,6 @@ public class NavigationHelper {
return mIntent;
}
/*//////////////////////////////////////////////////////////////////////////
// Through Interface (faster)
//////////////////////////////////////////////////////////////////////////*/
@@ -115,6 +118,14 @@ public class NavigationHelper {
context.startActivity(mIntent);
}
public static void openSearch(Context context, int serviceId, String query) {
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
mIntent.putExtra(Constants.KEY_QUERY, query);
mIntent.putExtra(Constants.KEY_OPEN_SEARCH, true);
context.startActivity(mIntent);
}
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);

View File

@@ -10,25 +10,33 @@ public class ThemeHelper {
/**
* Apply the selected theme (on NewPipe settings) in the context
*
* @param context context that the theme will be applied
* @param useActionbarTheme whether to use an action bar theme or not
* @param context context that the theme will be applied
*/
public static void setTheme(Context context, boolean useActionbarTheme) {
public static void setTheme(Context context) {
String themeKey = context.getString(R.string.theme_key);
String darkTheme = context.getResources().getString(R.string.dark_theme_title);
String blackTheme = context.getResources().getString(R.string.black_theme_title);
String sp = PreferenceManager.getDefaultSharedPreferences(context)
.getString(themeKey, context.getResources().getString(R.string.light_theme_title));
String sp = PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, context.getResources().getString(R.string.light_theme_title));
if (useActionbarTheme) {
if (sp.equals(darkTheme)) context.setTheme(R.style.DarkTheme);
else if (sp.equals(blackTheme)) context.setTheme(R.style.BlackTheme);
else context.setTheme(R.style.AppTheme);
} else {
if (sp.equals(darkTheme)) context.setTheme(R.style.DarkTheme_NoActionBar);
else if (sp.equals(blackTheme)) context.setTheme(R.style.BlackTheme_NoActionBar);
else context.setTheme(R.style.AppTheme_NoActionBar);
}
if (sp.equals(darkTheme)) context.setTheme(R.style.DarkTheme);
else if (sp.equals(blackTheme)) context.setTheme(R.style.BlackTheme);
else context.setTheme(R.style.AppTheme);
}
/**
* Return true if the selected theme (on NewPipe settings) is the Light theme
*
* @param context context to get the preference
*/
public static boolean isLightThemeSelected(Context context) {
String themeKey = context.getString(R.string.theme_key);
String darkTheme = context.getResources().getString(R.string.dark_theme_title);
String blackTheme = context.getResources().getString(R.string.black_theme_title);
String sp = PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, context.getResources().getString(R.string.light_theme_title));
return !(sp.equals(darkTheme) || sp.equals(blackTheme));
}
}

View File

@@ -0,0 +1,118 @@
package org.schabi.newpipe.workers;
import android.content.Context;
import android.os.Handler;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import java.io.InterruptedIOException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Common properties of Workers
*
* @author mauriciocolli
*/
@SuppressWarnings("WeakerAccess")
public abstract class AbstractWorker extends Thread {
private final AtomicBoolean isRunning = new AtomicBoolean(false);
private final int serviceId;
private Context context;
private Handler handler;
private StreamingService service;
public AbstractWorker(Context context, int serviceId) {
this.context = context;
this.serviceId = serviceId;
this.handler = new Handler(context.getMainLooper());
}
@Override
public void run() {
try {
isRunning.set(true);
service = NewPipe.getService(serviceId);
doWork(serviceId);
} catch (Exception e) {
// Handle the exception only if thread is not interrupted
e.printStackTrace();
if (!isInterrupted() && !(e instanceof InterruptedIOException) && !(e.getCause() instanceof InterruptedIOException)) {
handleException(e, serviceId);
}
} finally {
isRunning.set(false);
}
}
/**
* Here is the place that the heavy work is realized
*
* @param serviceId serviceId that was passed when created this object
*
* @throws Exception these exceptions are handled by the {@link #handleException(Exception, int)}
*/
protected abstract void doWork(int serviceId) throws Exception;
/**
* Method that handle the exception thrown by the {@link #doWork(int)}.
*
* @param exception {@link Exception} that was thrown by {@link #doWork(int)}
*/
protected abstract void handleException(Exception exception, int serviceId);
/**
* Return true if the extraction is not completed yet
*
* @return the value of the AtomicBoolean {@link #isRunning}
*/
public boolean isRunning() {
return isRunning.get();
}
/**
* Cancel this ExtractorWorker, calling {@link #onDestroy()} and interrupting this thread.
* <p>
* <b>Note:</b> Any I/O that is active in the moment that this method is called will be canceled and a Exception will be thrown, because of the {@link #interrupt()}.<br>
* This is useful when you don't want the resulting {@link StreamInfo} anymore, but don't want to waste bandwidth, otherwise it'd run till it receives the StreamInfo.
*/
public void cancel() {
onDestroy();
this.interrupt();
}
/**
* Method that discards everything that doesn't need anymore.<br>
* Subclasses can override this method to destroy their garbage.
*/
protected void onDestroy() {
this.isRunning.set(false);
this.context = null;
this.handler = null;
this.service = null;
}
public Handler getHandler() {
return handler;
}
public StreamingService getService() {
return service;
}
public int getServiceId() {
return serviceId;
}
public String getServiceName() {
return service == null ? "none" : service.getServiceInfo().name;
}
public Context getContext() {
return context;
}
}

View File

@@ -31,7 +31,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
* Interface which will be called for result and errors
*/
public interface OnChannelInfoReceive {
void onReceive(ChannelInfo info);
void onReceive(ChannelInfo info, boolean onlyVideos);
void onError(int messageId);
/**
* Called when an unrecoverable error has occurred.
@@ -44,12 +44,15 @@ public class ChannelExtractorWorker extends ExtractorWorker {
* @param context context for error reporting purposes
* @param serviceId id of the request service
* @param channelUrl channelUrl of the service (e.g. https://www.youtube.com/channel/UC_aEa8K-EOJ3D6gOs7HcyNg)
* @param pageNumber which page to extract
* @param onlyVideos flag that will be send by {@link OnChannelInfoReceive#onReceive(ChannelInfo, boolean)}
* @param callback listener that will be called-back when events occur (check {@link ChannelExtractorWorker.OnChannelInfoReceive})
*/
public ChannelExtractorWorker(Context context, int serviceId, String channelUrl, int pageNumber, OnChannelInfoReceive callback) {
public ChannelExtractorWorker(Context context, int serviceId, String channelUrl, int pageNumber, boolean onlyVideos, OnChannelInfoReceive callback) {
super(context, channelUrl, serviceId);
this.pageNumber = pageNumber;
this.callback = callback;
this.onlyVideos = onlyVideos;
}
@Override
@@ -71,7 +74,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
public void run() {
if (isInterrupted() || callback == null) return;
callback.onReceive(channelInfo);
callback.onReceive(channelInfo, onlyVideos);
onDestroy();
}
});
@@ -107,13 +110,5 @@ public class ChannelExtractorWorker extends ExtractorWorker {
});
}
}
public boolean isOnlyVideos() {
return onlyVideos;
}
public void setOnlyVideos(boolean onlyVideos) {
this.onlyVideos = onlyVideos;
}
}

View File

@@ -2,18 +2,12 @@ package org.schabi.newpipe.workers;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.InterruptedIOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Common properties of ExtractorWorkers
@@ -21,38 +15,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @author mauriciocolli
*/
@SuppressWarnings("WeakerAccess")
public abstract class ExtractorWorker extends Thread {
private final AtomicBoolean isRunning = new AtomicBoolean(false);
public abstract class ExtractorWorker extends AbstractWorker {
private final String url;
private final int serviceId;
private Context context;
private Handler handler;
private StreamingService service;
public ExtractorWorker(Context context, String url, int serviceId) {
this.context = context;
super(context, serviceId);
this.url = url;
this.serviceId = serviceId;
this.handler = new Handler(context.getMainLooper());
if (url.length() >= 40) setName("Thread-" + url.substring(url.length() - 11, url.length()));
}
@Override
public void run() {
try {
isRunning.set(true);
service = NewPipe.getService(serviceId);
doWork(serviceId, url);
} catch (Exception e) {
// Handle the exception only if thread is not interrupted
if (!isInterrupted() && !(e instanceof InterruptedIOException) && !(e.getCause() instanceof InterruptedIOException)) {
handleException(e, serviceId, url);
}
} finally {
isRunning.set(false);
}
protected void doWork(int serviceId) throws Exception {
doWork(serviceId, url);
}
/**
@@ -65,6 +39,10 @@ public abstract class ExtractorWorker extends Thread {
*/
protected abstract void doWork(int serviceId, String url) throws Exception;
@Override
protected void handleException(Exception exception, int serviceId) {
handleException(exception, serviceId, url);
}
/**
* Method that handle the exception thrown by the {@link #doWork(int, String)}.
@@ -99,63 +77,12 @@ public abstract class ExtractorWorker extends Thread {
}
if (getContext() instanceof Activity) {
View rootView = getContext() != null ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
ErrorActivity.reportError(getHandler(), getContext(), errorsList, null, rootView, ErrorActivity.ErrorInfo.make(errorUserAction, getServiceName(), url, 0 /* no message for the user */));
}
}
/**
* Return true if the extraction is not completed yet
*
* @return the value of the AtomicBoolean {@link #isRunning}
*/
public boolean isRunning() {
return isRunning.get();
}
/**
* Cancel this ExtractorWorker, calling {@link #onDestroy()} and interrupting this thread.
* <p>
* <b>Note:</b> Any I/O that is active in the moment that this method is called will be canceled and a Exception will be thrown, because of the {@link #interrupt()}.<br>
* This is useful when you don't want the resulting {@link StreamInfo} anymore, but don't want to waste bandwidth, otherwise it'd run till it receives the StreamInfo.
*/
public void cancel() {
onDestroy();
this.interrupt();
}
/**
* Method that discards everything that doesn't need anymore.<br>
* Subclasses can override this method to destroy their garbage.
*/
protected void onDestroy() {
this.isRunning.set(false);
this.context = null;
this.handler = null;
this.service = null;
}
public Handler getHandler() {
return handler;
}
public String getUrl() {
return url;
}
public StreamingService getService() {
return service;
}
public int getServiceId() {
return serviceId;
}
public String getServiceName() {
return service == null ? "none" : service.getServiceInfo().name;
}
public Context getContext() {
return context;
}
}

View File

@@ -0,0 +1,127 @@
package org.schabi.newpipe.workers;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.view.View;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.EnumSet;
/**
* Return list of results based on a query
*
* @author mauriciocolli
*/
public class SearchWorker extends AbstractWorker {
private EnumSet<SearchEngine.Filter> filter;
private String query;
private int page;
private OnSearchResult callback;
/**
* Interface which will be called for result and errors
*/
public interface OnSearchResult {
void onSearchResult(SearchResult result);
void onNothingFound(String message);
void onSearchError(int messageId);
void onReCaptchaChallenge();
}
public SearchWorker(Context context, int serviceId, String query, int page, EnumSet<SearchEngine.Filter> filter, OnSearchResult callback) {
super(context, serviceId);
this.callback = callback;
this.query = query;
this.page = page;
this.filter = filter;
}
public static SearchWorker startForQuery(Context context, int serviceId, @NonNull String query, int page, EnumSet<SearchEngine.Filter> filter, OnSearchResult callback) {
SearchWorker worker = new SearchWorker(context, serviceId, query, page, filter, callback);
worker.start();
return worker;
}
@Override
protected void onDestroy() {
super.onDestroy();
this.callback = null;
}
@Override
protected void doWork(int serviceId) throws Exception {
SearchEngine searchEngine = getService().getSearchEngineInstance();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
String searchLanguageKey = getContext().getString(R.string.search_language_key);
String searchLanguage = sharedPreferences.getString(searchLanguageKey, getContext().getString(R.string.default_language_value));
final SearchResult searchResult = SearchResult.getSearchResult(searchEngine, query, page, searchLanguage, filter);
if (callback != null && searchResult != null && !isInterrupted()) getHandler().post(new Runnable() {
@Override
public void run() {
if (isInterrupted() || callback == null) return;
callback.onSearchResult(searchResult);
onDestroy();
}
});
}
@Override
protected void handleException(final Exception exception, int serviceId) {
if (callback == null || getHandler() == null || isInterrupted()) return;
if (exception instanceof ReCaptchaException) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onReCaptchaChallenge();
}
});
} else if (exception instanceof IOException) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onSearchError(R.string.network_error);
}
});
} else if (exception instanceof SearchEngine.NothingFoundException) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onNothingFound(exception.getMessage());
}
});
} else if (exception instanceof ExtractionException) {
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, getServiceName(), query, R.string.parsing_error));
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onSearchError(R.string.parsing_error);
}
});
} else {
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, getServiceName(), query, R.string.general_error));
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onSearchError(R.string.general_error);
}
});
}
}
}

View File

@@ -0,0 +1,108 @@
package org.schabi.newpipe.workers;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.view.View;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.SuggestionExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.List;
/**
* Worker that get suggestions based on the query
*
* @author mauriciocolli
*/
public class SuggestionWorker extends AbstractWorker {
private String query;
private OnSuggestionResult callback;
/**
* Interface which will be called for result and errors
*/
public interface OnSuggestionResult {
void onSuggestionResult(@NonNull List<String> suggestions);
void onSuggestionError(int messageId);
}
public SuggestionWorker(Context context, int serviceId, String query, OnSuggestionResult callback) {
super(context, serviceId);
this.callback = callback;
this.query = query;
}
public static SuggestionWorker startForQuery(Context context, int serviceId, @NonNull String query, OnSuggestionResult callback) {
SuggestionWorker worker = new SuggestionWorker(context, serviceId, query, callback);
worker.start();
return worker;
}
@Override
protected void onDestroy() {
super.onDestroy();
this.callback = null;
this.query = null;
}
@Override
protected void doWork(int serviceId) throws Exception {
SuggestionExtractor suggestionExtractor = getService().getSuggestionExtractorInstance();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
String searchLanguageKey = getContext().getString(R.string.search_language_key);
String searchLanguage = sharedPreferences.getString(searchLanguageKey, getContext().getString(R.string.default_language_value));
final List<String> suggestions = suggestionExtractor.suggestionList(query, searchLanguage);
if (callback != null && suggestions != null && !isInterrupted()) getHandler().post(new Runnable() {
@Override
public void run() {
if (isInterrupted() || callback == null) return;
callback.onSuggestionResult(suggestions);
onDestroy();
}
});
}
@Override
protected void handleException(final Exception exception, int serviceId) {
if (callback == null || getHandler() == null || isInterrupted()) return;
if (exception instanceof ExtractionException) {
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.GET_SUGGESTIONS, getServiceName(), query, R.string.parsing_error));
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onSuggestionError(R.string.parsing_error);
}
});
} else if (exception instanceof IOException) {
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onSuggestionError(R.string.network_error);
}
});
} else {
View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.GET_SUGGESTIONS, getServiceName(), query, R.string.general_error));
getHandler().post(new Runnable() {
@Override
public void run() {
callback.onSuggestionError(R.string.general_error);
}
});
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

View File

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

View File

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

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