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

Compare commits

...

225 Commits

Author SHA1 Message Date
Christian Schabesberger
43a1c4ce11 move on to v0.9.9 2017-06-30 21:34:28 +02:00
Mauricio Colli
9ca048a881 Merge pull request #600 from coffeemakr/feature-code-improvements
Remove unused code
2017-06-29 17:51:33 -03:00
Coffeemakr
bab3dd417e Remove unused code
* Remove Giga crash handler
 * Some refactoring
 * Remove unused download dialog
 * Remove duplicated intent creation
2017-06-29 12:54:07 +02:00
Weblate
11541310d6 Merge remote-tracking branch 'origin/master' 2017-06-29 07:50:35 +02:00
Anton Shestakov
76740303c5 Translated using Weblate (Russian)
Currently translated at 100.0% (151 of 151 strings)
2017-06-29 07:50:32 +02:00
Mauricio Colli
0a7ecb89ce Adjust fragments' enter/exit animations
- Make a little bit faster
2017-06-28 16:38:13 -03:00
Mauricio Colli
c16a7d5da2 Merge pull request #599 from coffeemakr/feature-code-improvements
Code Improvements
2017-06-28 16:28:16 -03:00
Coffeemakr
b03723c3fb Code improvements
* Replace unchecked casts with checked casts
 * remove Utility.finViewById
 * Fix return activity checking
 * Create UserAction enum
 * Fix typos
 * Add instrumented test for error info
 * ErrorInfo make fields final
 * Log exception using logger
 * Add inherited annotations
 * Resolve deprecation warnings
 * Remove unused methods from utility
 * Reformat code
 * Remove unused methods from Utility and improve getFileExt
 * Create OnScrollBelowItemsListener
2017-06-28 18:56:05 +02:00
Mauricio Colli
40213b2d6a Fix autoplay
- Closes #595
2017-06-27 22:39:33 -03:00
Mauricio Colli
e8b71e867c Merge pull request #588 from coffeemakr/feature-speedup
Speed up detail page loading ⏱
2017-06-27 18:12:30 -03:00
Coffeemakr
8ab1b7fd8f Fix loading and retry positioning 2017-06-27 22:36:31 +02:00
Coffeemakr
8009aa975e Revert unused changes 2017-06-27 14:11:15 +02:00
Coffeemakr
cea706d14a Synchronize initRealtedVideoStreams* 2017-06-27 12:44:20 +02:00
Eduardo Caron
3cb4952281 Translated using Weblate (Portuguese)
Currently translated at 93.3% (141 of 151 strings)
2017-06-26 21:46:21 +02:00
Coffeemakr
ec1ae647b0 Revert toolbar_search_clear 2017-06-26 14:19:42 +02:00
Coffeemakr
a3be9f36b3 Fix landscape stream item height 2017-06-26 12:42:35 +02:00
Weblate
451910763d Merge remote-tracking branch 'origin/master' 2017-06-19 12:46:26 +02:00
Coffeemaker
884dc38701 Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)
2017-06-19 12:46:26 +02:00
Anton Shestakov
3cca7aead9 Translated using Weblate (Russian)
Currently translated at 96.0% (145 of 151 strings)
2017-06-19 12:46:24 +02:00
4c4852129e Layout fixes
* Add selectableItemBackground to the player button
* Make uploader margin to padding
* Convert layout gravity center_horizontal to width=match_parent
2017-06-18 17:25:13 +02:00
ae2b0cc76b Format code and remove unused methods 2017-06-18 15:43:11 +02:00
71e963c853 Correct icons and title alignment 2017-06-17 14:19:55 +02:00
89b680f6d9 Revert RecyclerView to LinearLayout 2017-06-17 13:43:09 +02:00
Christian Schabesberger
f3eacac4ce Merge pull request #586 from coffeemakr/fix-spaces-in-folder
Fix download path handling (#580)
2017-06-16 18:05:05 +02:00
Anton Shestakov
26ec32cbe1 Translated using Weblate (Russian)
Currently translated at 95.3% (144 of 151 strings)
2017-06-16 15:46:23 +02:00
Coffeemakr
6d74038866 Improve speed
* Replace relative layouts and use Recycler view
 * Handle HTML in background
2017-06-16 14:02:45 +02:00
Kristoffer Grundström
7e45e88914 Translated using Weblate (Swedish)
Currently translated at 66.8% (101 of 151 strings)
2017-06-15 03:46:30 +02:00
Coffeemakr
62a4869eb7 Fix download path handling (#580) 2017-06-13 10:47:40 +02:00
Weblate
4f8b51701b Merge remote-tracking branch 'origin/master' 2017-06-13 00:53:12 +02:00
Kristoffer Grundström
169bcc2550 Added translation using Weblate (Swedish) 2017-06-13 00:53:09 +02:00
Christian Schabesberger
88b29cbbf9 moved on to v0.8.9 2017-06-11 14:16:43 +02:00
Christian Schabesberger
51d4d0d3dc Merge branch 'improve-backstack' of https://github.com/mauriciocolli/NewPipe into back 2017-06-11 14:08:47 +02:00
Christian Schabesberger
a482aa1e21 Merge branch 'master' of https://github.com/SpajicM/NewPipe into count 2017-06-11 14:03:49 +02:00
Christian Schabesberger
040b38689d update extractor to v0.9.8 2017-06-11 14:02:57 +02:00
Christian Schabesberger
8e8e53c4d5 update gradle 2017-06-11 13:54:44 +02:00
Marian Hanzel
d717c6d2f6 Translated using Weblate (Slovak)
Currently translated at 100.0% (151 of 151 strings)
2017-06-11 11:27:55 +02:00
Gian Maria Viglianti
9587ce97a8 Translated using Weblate (Italian)
Currently translated at 100.0% (151 of 151 strings)
2017-06-10 00:45:35 +02:00
Janusz May
aaaf573475 Translated using Weblate (Polish)
Currently translated at 100.0% (151 of 151 strings)
2017-06-09 21:45:56 +02:00
zmni
6c6f322d90 Translated using Weblate (Indonesian)
Currently translated at 100.0% (151 of 151 strings)
2017-06-09 19:21:07 +02:00
Matej U
c03f0ed1fb Translated using Weblate (Slovenian)
Currently translated at 100.0% (151 of 151 strings)
2017-06-09 09:46:56 +02:00
Benedikt Freisen
55287393be Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)
2017-06-09 00:45:04 +02:00
Eduardo Caron
9575f92165 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (151 of 151 strings)
2017-06-09 00:24:10 +02:00
SpajicM
a5cbaad804 BackgroundPlayer: Add timestamp in expanded notification 2017-06-08 23:28:45 +02:00
monolifed
f492414b5e Translated using Weblate (Turkish)
Currently translated at 100.0% (151 of 151 strings)
2017-06-08 18:46:54 +02:00
monolifed
3e6ddf7176 Translated using Weblate (English)
Currently translated at 100.0% (151 of 151 strings)
2017-06-08 18:44:44 +02:00
Gian Maria Viglianti
29ffb05653 Translated using Weblate (Italian)
Currently translated at 100.0% (151 of 151 strings)
2017-06-08 00:17:12 +02:00
Freddy Morán Jr
50c3ee2e9c Translated using Weblate (Spanish)
Currently translated at 100.0% (151 of 151 strings)
2017-06-07 21:40:04 +02:00
Janusz May
6292470677 Translated using Weblate (Polish)
Currently translated at 100.0% (151 of 151 strings)
2017-06-07 20:05:38 +02:00
Nathan Follens
8d912f2673 Translated using Weblate (Dutch)
Currently translated at 100.0% (151 of 151 strings)
2017-06-07 16:53:40 +02:00
Coffeemaker
227d129c3a Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 23:03:03 +02:00
Benedikt Freisen
d9eeb6afa0 Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 23:00:51 +02:00
478aecb0f4 Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 23:00:14 +02:00
Benedikt Freisen
abb82154c9 Translated using Weblate (German)
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 22:53:06 +02:00
nailyk
03c8b6b5f5 Translated using Weblate (French)
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 20:13:20 +02:00
monolifed
2281300165 Translated using Weblate (Turkish)
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 17:45:46 +02:00
Mauricio Colli
84068d101b Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 17:19:26 +02:00
Mauricio Colli
62020fa85b Translated using Weblate (English)
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 17:09:05 +02:00
Mauricio Colli
3c8bf5ccc9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (151 of 151 strings)
2017-06-06 17:06:39 +02:00
Mauricio Colli
71d5762be8 Remove unnecessary check 2017-06-06 11:46:57 -03:00
monolifed
65d5358366 Translated using Weblate (Turkish)
Currently translated at 100.0% (151 of 151 strings)
2017-06-05 22:16:25 +02:00
Mauricio Colli
6ecdfaf19e Improve backstack and theme change 2017-06-05 16:33:01 -03:00
Nathan Follens
32bd6ae1ac Translated using Weblate (Dutch)
Currently translated at 100.0% (151 of 151 strings)
2017-06-05 18:48:51 +02:00
Mauricio Colli
92231a1e26 Translated using Weblate (French)
Currently translated at 98.6% (149 of 151 strings)
2017-06-05 16:13:31 +02:00
Koleon
38ad4dc440 Translated using Weblate (Czech)
Currently translated at 87.4% (132 of 151 strings)
2017-06-05 16:13:30 +02:00
Eduardo Caron
6645a47b0e Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (151 of 151 strings)
2017-06-05 16:05:12 +02:00
Freddy Morán Jr
75a2d20b0b Translated using Weblate (Spanish)
Currently translated at 100.0% (151 of 151 strings)
2017-06-05 16:00:44 +02:00
naofum
8c15d708e6 Translated using Weblate (Japanese)
Currently translated at 100.0% (151 of 151 strings)
2017-06-05 15:57:13 +02:00
Mauricio Colli
539b7ad87b Update some strings.xml files 2017-06-04 21:10:50 -03:00
Mauricio Colli
fc7c2c9f5a Merge remote-tracking branch 'weblate/master' into fix-strings
# Conflicts:
#	app/src/main/res/values-b+ast/strings.xml
#	app/src/main/res/values-fr/strings.xml
#	app/src/main/res/values-pl/strings.xml
#	app/src/main/res/values-pt-rBR/strings.xml
2017-06-04 20:57:00 -03:00
יובל הרמן
3cd760f654 Translated using Weblate (Hebrew)
Currently translated at 60.7% (107 of 176 strings)
2017-06-04 15:45:11 +02:00
Janusz May
2ec0a5d003 Translated using Weblate (Polish)
Currently translated at 100.0% (176 of 176 strings)
2017-06-02 12:52:45 +02:00
OrNicarZ
1a605e814b Translated using Weblate (French)
Currently translated at 99.4% (175 of 176 strings)
2017-06-02 12:52:44 +02:00
יובל הרמן
fe0053a15d Added translation using Weblate (Hebrew) 2017-06-02 12:52:43 +02:00
Eduardo Caron
014f3e5aff Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (176 of 176 strings)
2017-05-31 21:27:25 +02:00
Enol P
4a25e3b644 Translated using Weblate (Asturian)
Currently translated at 98.8% (174 of 176 strings)
2017-05-31 21:27:24 +02:00
e7f59bc436 Translated using Weblate (Polish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-31 21:27:22 +02:00
Christian Schabesberger
488d1ccd5a resolve another weblate crash 2017-05-30 22:08:08 +02:00
Janusz May
409b36c254 Translated using Weblate (Polish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-30 17:33:52 +02:00
Janusz May
b0e567dbfa Translated using Weblate (Polish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-30 17:29:14 +02:00
Freddy Morán Jr
c6086ba281 Translated using Weblate (Spanish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-30 16:33:32 +02:00
Marian Hanzel
aede925351 Translated using Weblate (Slovak)
Currently translated at 100.0% (176 of 176 strings)
2017-05-30 16:33:29 +02:00
Christian Schabesberger
9d38c66510 add .weblate to git ignore 2017-05-29 22:50:41 +02:00
Christian Schabesberger
0c516189c3 try to fix another weblate crash 2017-05-29 22:42:23 +02:00
Freddy Morán Jr
c2936ea289 Translated using Weblate (Spanish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-29 15:44:14 +02:00
Gian Maria Viglianti
dc33460a34 Translated using Weblate (Italian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-29 15:44:14 +02:00
ffacc93b55 Translated using Weblate (Armenian)
Currently translated at 18.7% (33 of 176 strings)
2017-05-29 15:44:12 +02:00
Christian Schabesberger
74f0ee2718 fix another weblate crash 2017-05-27 18:45:10 +02:00
ktln
7189791d9f Translated using Weblate (Armenian)
Currently translated at 18.1% (32 of 176 strings)
2017-05-27 13:24:00 +02:00
Eduardo Caron
dc18d53c8f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (176 of 176 strings)
2017-05-27 13:22:42 +02:00
Bruno Guerreiro
f71ef8e130 Translated using Weblate (Portuguese)
Currently translated at 100.0% (176 of 176 strings)
2017-05-27 13:22:41 +02:00
6eec9d8993 Translated using Weblate (Armenian)
Currently translated at 18.1% (32 of 176 strings)
2017-05-27 13:22:39 +02:00
Дима Гайнуллин
9560f98359 Translated using Weblate (Russian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-26 01:30:12 +02:00
Eduardo Caron
217433bf69 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (176 of 176 strings)
2017-05-26 01:30:11 +02:00
ktln
ff5db1b939 Translated using Weblate (Armenian)
Currently translated at 16.4% (29 of 176 strings)
2017-05-26 01:30:10 +02:00
Bruno Guerreiro
455a46d3ad Translated using Weblate (Portuguese)
Currently translated at 100.0% (176 of 176 strings)
2017-05-26 01:30:08 +02:00
Christian Schabesberger
039a879104 fix weblate crash 2017-05-25 10:43:17 +02:00
ktln
dfba9ea53b Translated using Weblate (Russian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-25 02:16:14 +02:00
177cce5e8f Translated using Weblate (Russian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-25 01:06:23 +02:00
ktln
309d36260e Translated using Weblate (Russian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-25 01:04:29 +02:00
ktln
bdc73eb755 Translated using Weblate (Russian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-25 00:57:39 +02:00
Anton Shestakov
0786750eb9 Translated using Weblate (Russian)
Currently translated at 96.5% (170 of 176 strings)
2017-05-25 00:54:12 +02:00
Слободан Симић(Slobodan Simić)
9f5d921275 Translated using Weblate (Serbian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-25 00:50:25 +02:00
ktln
8991b2d8e3 Translated using Weblate (Armenian)
Currently translated at 7.3% (13 of 176 strings)
2017-05-25 00:50:24 +02:00
ktln
79dffce59b Translated using Weblate (Russian)
Currently translated at 96.5% (170 of 176 strings)
2017-05-25 00:50:22 +02:00
Christian Schabesberger
e0301a621b Merge pull request #575 from mauriciocolli/clean-up-strings
Remove unused strings
2017-05-24 23:29:08 +02:00
Mauricio Colli
1e4361abdc Merge branch 'master' into clean-up-strings 2017-05-24 18:18:50 -03:00
Mauricio Colli
80c26fd278 Merge new translations 2017-05-24 18:11:34 -03:00
Weblate
3339531086 Merge remote-tracking branch 'origin/master' 2017-05-24 18:37:33 +02:00
monolifed
07ad9fbb11 Translated using Weblate (Turkish)
Currently translated at 100.0% (176 of 176 strings)
2017-05-24 18:37:33 +02:00
ktln
6ddc581d76 Translated using Weblate (Russian)
Currently translated at 96.5% (170 of 176 strings)
2017-05-24 18:37:33 +02:00
Слободан Симић(Slobodan Simić)
dfe94172aa Translated using Weblate (Serbian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-24 18:37:30 +02:00
Mauricio Colli
edb632f9c7 Remove unused strings 2017-05-23 11:53:50 -03:00
Christian Schabesberger
7f73612b9f add use english into contribution notes 2017-05-23 11:11:12 +02:00
Christian Schabesberger
7f130f18b6 Merge pull request #573 from sirekanyan/master
Initial string resources for Armenian language
2017-05-23 11:09:50 +02:00
Anton Shestakov
d05a4c53aa Translated using Weblate (Russian)
Currently translated at 93.7% (165 of 176 strings)
2017-05-22 23:38:03 +02:00
ktln
d99a9b90a0 Translated using Weblate (Russian)
Currently translated at 93.1% (164 of 176 strings)
2017-05-22 23:36:37 +02:00
sirekanyan
f311225312 Added initial string resources for Armenian language 2017-05-23 00:26:15 +03:00
Anton Shestakov
dee17fc313 Translated using Weblate (Russian)
Currently translated at 89.7% (158 of 176 strings)
2017-05-22 22:22:08 +02:00
Nathan Follens
0662538d60 Translated using Weblate (Dutch)
Currently translated at 100.0% (176 of 176 strings)
2017-05-21 21:11:27 +02:00
se7entime
5b52ad91ac Translated using Weblate (Indonesian)
Currently translated at 100.0% (176 of 176 strings)
2017-05-21 14:55:38 +02:00
ktln
8552c41bff Translated using Weblate (Russian)
Currently translated at 89.2% (157 of 176 strings)
2017-05-21 08:39:28 +02:00
Weblate
7ecd298285 Merge remote-tracking branch 'origin/master' 2017-05-21 08:35:39 +02:00
Mauricio Colli
2546f515d1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (176 of 176 strings)
2017-05-21 08:35:39 +02:00
naofum
8804924d27 Translated using Weblate (Japanese)
Currently translated at 100.0% (176 of 176 strings)
2017-05-21 08:35:38 +02:00
Anton Shestakov
e410cae141 Translated using Weblate (Russian)
Currently translated at 88.0% (155 of 176 strings)
2017-05-21 08:35:34 +02:00
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
141 changed files with 4646 additions and 5024 deletions

View File

@@ -5,13 +5,14 @@ READ THIS GUIDELINES CAREFULLY BEFORE CONTRIBUTING.
## Crash reporting
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report if a crash occures.
Do not report crashes in the GitHub issue tracker. NewPipe has an automated crash report system that will ask you to send a report if a crash occurs.
## Issue reporting/feature request
* Search the [existing issues](https://github.com/theScrabi/NewPipe/issues) first to make sure your issue/feature hasn't been reported/requested before
* Check if this issue/feature is already fixed/implemented in the repository
* If you are an android/java developer you are always welcome to fix/implement an issue/a feature yourself
* Use english
## Bugfixing
* If you want to help NewPipe getting bug free, you can send me a mail to tnp@newpipe.schabi.org to let me know that you intent to help, and than register at our [sentry](https://support.schabi.org) setup.

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@
/*.iml
gradle.properties
*~
.weblate

View File

@@ -24,6 +24,11 @@ Project status:
[<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

View File

@@ -8,8 +8,10 @@ android {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 25
versionCode 32
versionName "0.9.5"
versionCode 36
versionName "0.9.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
@@ -31,6 +33,11 @@ android {
}
dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
exclude module: 'support-annotations'
}
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile 'org.json:json:20160810'

View File

@@ -0,0 +1,37 @@
package org.schabi.newpipe.report;
import android.os.Parcel;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.schabi.newpipe.R;
import org.schabi.newpipe.report.ErrorActivity.ErrorInfo;
import static org.junit.Assert.assertEquals;
/**
* Instrumented tests for {@link ErrorInfo}
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ErrorInfoTest {
@Test
public void errorInfo_testParcelable() {
ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request", R.string.general_error);
// Obtain a Parcel object and write the parcelable object to it:
Parcel parcel = Parcel.obtain();
info.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction);
assertEquals("youtube", infoFromParcel.serviceName);
assertEquals("request", infoFromParcel.request);
assertEquals(R.string.general_error, infoFromParcel.message);
parcel.recycle();
}
}

View File

@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.schabi.newpipe">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
@@ -19,12 +20,10 @@
tools:ignore="AllowBackup">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask">
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
@@ -32,12 +31,15 @@
android:name=".player.PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/VideoPlayerTheme"
tools:ignore="UnusedAttribute" />
tools:ignore="UnusedAttribute"/>
<service
android:name=".player.BackgroundPlayer"
android:exported="false"
android:label="@string/background_player_name" />
android:exported="false"/>
<service
android:name=".player.PopupVideoPlayer"
android:exported="false"/>
<activity
android:name=".player.MainVideoPlayer"
@@ -48,32 +50,32 @@
<activity
android:name=".settings.SettingsActivity"
android:label="@string/settings_activity_title" />
android:label="@string/settings"/>
<activity
android:name=".PanicResponderActivity"
android:launchMode="singleInstance"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".ExitActivity"
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".report.ErrorActivity" />
android:theme="@android:style/Theme.NoDisplay"/>
<activity android:name=".report.ErrorActivity"/>
<!-- giga get related -->
<activity
android:name=".download.DownloadActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/AppTheme" />
android:theme="@style/AppTheme"/>
<service android:name="us.shandian.giga.service.DownloadManagerService" />
<service android:name="us.shandian.giga.service.DownloadManagerService"/>
<activity
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
@@ -82,7 +84,7 @@
android:theme="@style/FilePickerTheme"/>
<activity
android:name=".ReCaptchaActivity"
android:label="@string/reCaptchaActivity" />
android:label="@string/reCaptchaActivity"/>
<provider
android:name="android.support.v4.content.FileProvider"
@@ -91,7 +93,7 @@
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
android:resource="@xml/provider_paths"/>
</provider>
<activity
@@ -99,57 +101,57 @@
android:taskAffinity=""
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtube.com" />
<data android:host="m.youtube.com" />
<data android:host="www.youtube.com" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtube.com"/>
<data android:host="m.youtube.com"/>
<data android:host="www.youtube.com"/>
<!-- video prefix -->
<data android:pathPrefix="/v/" />
<data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" />
<data android:pathPrefix="/attribution_link" />
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<data android:pathPrefix="/attribution_link"/>
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtu.be" />
<data android:pathPrefix="/" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtu.be"/>
<data android:pathPrefix="/"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="vnd.youtube" />
<data android:scheme="vnd.youtube.launch" />
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
@@ -159,57 +161,55 @@
android:theme="@android:style/Theme.NoDisplay"
android:label="@string/popup_mode_share_menu_title">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtube.com" />
<data android:host="m.youtube.com" />
<data android:host="www.youtube.com" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtube.com"/>
<data android:host="m.youtube.com"/>
<data android:host="www.youtube.com"/>
<!-- video prefix -->
<data android:pathPrefix="/v/" />
<data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" />
<data android:pathPrefix="/attribution_link" />
<data android:pathPrefix="/v/"/>
<data android:pathPrefix="/embed/"/>
<data android:pathPrefix="/watch"/>
<data android:pathPrefix="/attribution_link"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtu.be" />
<data android:pathPrefix="/" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="youtu.be"/>
<data android:pathPrefix="/"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="vnd.youtube" />
<data android:scheme="vnd.youtube.launch" />
<data android:scheme="vnd.youtube"/>
<data android:scheme="vnd.youtube.launch"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
<service android:name=".player.PopupVideoPlayer"/>
</application>
</manifest>

View File

@@ -14,6 +14,7 @@ import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ThemeHelper;
@@ -59,7 +60,7 @@ public class App extends Application {
} catch(ACRAConfigurationException ace) {
ace.printStackTrace();
ErrorActivity.reportError(this, ace, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,"none",
ErrorActivity.ErrorInfo.make(UserAction.SEARCHED,"none",
"Could not initialize ACRA crash report", R.string.app_ui_crash));
}

View File

@@ -9,6 +9,7 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
/**
* Created by Christian Schabesberger on 01.08.16.
@@ -49,7 +50,7 @@ public class ImageErrorLoadingListener implements ImageLoadingListener {
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(context,
failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE,
NewPipe.getNameOfService(serviceId), imageUri,
R.string.could_not_load_image));
}

View File

@@ -21,7 +21,9 @@
package org.schabi.newpipe;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
@@ -33,13 +35,9 @@ 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;
import org.schabi.newpipe.fragments.search.SearchFragment;
import org.schabi.newpipe.settings.SettingsActivity;
@@ -48,7 +46,7 @@ import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static final boolean DEBUG = false;
@@ -71,6 +69,19 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
setSupportActionBar(toolbar);
}
@Override
protected void onResume() {
super.onResume();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
if (DEBUG) Log.d(TAG, "Theme has changed, recreating activity...");
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
this.recreate();
}
}
@Override
protected void onNewIntent(Intent intent) {
if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
@@ -89,10 +100,16 @@ 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;
super.onBackPressed();
fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (getSupportFragmentManager().getBackStackEntryCount() == 0 && !(fragment instanceof MainFragment)) {
super.onBackPressed();
}
}
/*//////////////////////////////////////////////////////////////////////////
@@ -134,7 +151,8 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) ((VideoDetailFragment) fragment).clearHistory();
NavigationHelper.openMainActivity(this);
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
NavigationHelper.openMainFragment(getSupportFragmentManager());
return true;
}
case R.id.action_settings: {
@@ -160,26 +178,9 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
//////////////////////////////////////////////////////////////////////////*/
private void initFragments() {
openMainFragment();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_URL)) {
handleIntent(getIntent());
}
}
/*//////////////////////////////////////////////////////////////////////////
// OnItemSelectedListener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onItemSelected(StreamingService.LinkType linkType, int serviceId, String url, String name) {
switch (linkType) {
case STREAM:
openVideoDetailFragment(serviceId, url, name, false);
break;
case CHANNEL:
openChannelFragment(serviceId, url, name);
break;
}
} else NavigationHelper.openMainFragment(getSupportFragmentManager());
}
/*//////////////////////////////////////////////////////////////////////////
@@ -187,96 +188,29 @@ 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 (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
try {
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
case STREAM:
handleVideoDetailIntent(serviceId, url, intent);
break;
case CHANNEL:
handleChannelIntent(serviceId, url, intent);
break;
case NONE:
throw new Exception("Url not known to service. service=" + Integer.toString(serviceId) + " url=" + url);
}
} catch (Exception e) {
e.printStackTrace();
String title = intent.getStringExtra(Constants.KEY_TITLE);
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
case STREAM:
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
break;
case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(), serviceId, url, title);
break;
}
} 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);
NavigationHelper.openSearchFragment(getSupportFragmentManager(), serviceId, searchQuery);
} else {
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
openMainFragment();//openSearchFragment();
NavigationHelper.openMainFragment(getSupportFragmentManager());
}
}
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 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) {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (title == null) title = "";
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
VideoDetailFragment detailFragment = (VideoDetailFragment) fragment;
detailFragment.setAutoplay(autoPlay);
detailFragment.selectAndLoadVideo(serviceId, url, title);
return;
}
VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url, title);
instance.setAutoplay(autoPlay);
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, instance)
.addToBackStack(null)
.commit();
}
private void openChannelFragment(int serviceId, String url, String name) {
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.getInstance(serviceId, url, name))
.addToBackStack(null)
.commit();
}
private void handleVideoDetailIntent(int serviceId, String url, Intent intent) {
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
String title = intent.getStringExtra(Constants.KEY_TITLE);
openVideoDetailFragment(serviceId, url, title, autoPlay);
}
private void handleChannelIntent(int serviceId, String url, Intent intent) {
String name = intent.getStringExtra(Constants.KEY_TITLE);
openChannelFragment(serviceId, url, name);
}
}

View File

@@ -33,26 +33,39 @@ import java.util.HashSet;
* to the part of the service which can handle the url.
*/
public class RouterActivity extends Activity {
//private static final String TAG = "RouterActivity"
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String videoUrl = getUrl(getIntent());
handleUrl(videoUrl);
finish();
}
protected void handleUrl(String url) {
try {
NavigationHelper.openByLink(this, url);
} catch (Exception e) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
/**
* Removes invisible separators (\p{Z}) and punctuation characters including
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
* more details.
*/
private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleIntent(getIntent());
finish();
}
private void handleIntent(Intent intent) {
String videoUrl = "";
protected final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
protected String getUrl(Intent intent) {
// first gather data and find service
String videoUrl = null;
if (intent.getData() != null) {
// this means the video was called though another app
videoUrl = intent.getData().toString();
@@ -62,14 +75,10 @@ public class RouterActivity extends Activity {
videoUrl = getUris(extraText)[0];
}
try {
NavigationHelper.openByLink(this, videoUrl);
} catch (Exception e) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
}
return videoUrl;
}
private static String removeHeadingGibberish(final String input) {
protected String removeHeadingGibberish(final String input) {
int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
if (!input.substring(i, i + 1).matches("\\p{L}")) {
@@ -80,7 +89,7 @@ public class RouterActivity extends Activity {
return input.substring(start, input.length());
}
private static String trim(final String input) {
protected String trim(final String input) {
if (input == null || input.length() < 1) {
return input;
} else {
@@ -103,7 +112,7 @@ public class RouterActivity extends Activity {
* @param sharedText text to scan for URLs.
* @return potential URLs
*/
private String[] getUris(final String sharedText) {
protected String[] getUris(final String sharedText) {
final Collection<String> result = new HashSet<>();
if (sharedText != null) {
final String[] array = sharedText.split("\\p{Space}");

View File

@@ -1,9 +1,7 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
import org.schabi.newpipe.extractor.NewPipe;
@@ -12,57 +10,26 @@ import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.PermissionHelper;
import java.util.Collection;
import java.util.HashSet;
/**
* This activity is thought to open video streams form an external app using the popup player.
* Get the url from the intent and open a popup player
*/
public class RouterPopupActivity extends Activity {
//private static final String TAG = "RouterPopupActivity";
/**
* Removes invisible separators (\p{Z}) and punctuation characters including
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
* more details.
*/
private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
public class RouterPopupActivity extends RouterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handleIntent(getIntent());
finish();
}
private void handleIntent(Intent intent) {
protected void handleUrl(String url) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !PermissionHelper.checkSystemAlertWindowPermission(this)) {
Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
return;
}
String videoUrl = "";
StreamingService service;
// first gather data and find service
if (intent.getData() != null) {
// this means the video was called though another app
videoUrl = intent.getData().toString();
} else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
//this means that vidoe was called through share menu
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
videoUrl = getUris(extraText)[0];
}
service = NewPipe.getServiceByUrl(videoUrl);
StreamingService service = NewPipe.getServiceByUrl(url);
if (service == null) {
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
return;
}
Intent callIntent = new Intent(this, PopupVideoPlayer.class);
switch (service.getLinkTypeByUrl(videoUrl)) {
switch (service.getLinkTypeByUrl(url)) {
case STREAM:
break;
default:
@@ -70,61 +37,8 @@ public class RouterPopupActivity extends Activity {
return;
}
callIntent.putExtra(Constants.KEY_URL, videoUrl);
callIntent.putExtra(Constants.KEY_URL, url);
callIntent.putExtra(Constants.KEY_SERVICE_ID, service.getServiceId());
startService(callIntent);
}
private static String removeHeadingGibberish(final String input) {
int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
if (!input.substring(i, i + 1).matches("\\p{L}")) {
start = i + 1;
break;
}
}
return input.substring(start, input.length());
}
private static String trim(final String input) {
if (input == null || input.length() < 1) {
return input;
} else {
String output = input;
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
output = output.substring(1);
}
while (output.length() > 0
&& output.substring(output.length() - 1, output.length()).matches(REGEX_REMOVE_FROM_URL)) {
output = output.substring(0, output.length() - 1);
}
return output;
}
}
/**
* Retrieves all Strings which look remotely like URLs from a text.
* Used if NewPipe was called through share menu.
*
* @param sharedText text to scan for URLs.
* @return potential URLs
*/
private String[] getUris(final String sharedText) {
final Collection<String> result = new HashSet<>();
if (sharedText != null) {
final String[] array = sharedText.split("\\p{Space}");
for (String s : array) {
s = trim(s);
if (s.length() != 0) {
if (s.matches(".+://.+")) {
result.add(removeHeadingGibberish(s));
} else if (s.matches(".+\\..+")) {
result.add("http://" + s);
}
}
}
}
return result.toArray(new String[result.size()]);
}
}

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

@@ -1,61 +1,28 @@
package org.schabi.newpipe.download;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.File;
import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.fragment.AllMissionsFragment;
import us.shandian.giga.ui.fragment.MissionsFragment;
import us.shandian.giga.util.CrashHandler;
import us.shandian.giga.util.Utility;
public class DownloadActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
public static final String INTENT_DOWNLOAD = "us.shandian.giga.intent.DOWNLOAD";
public static final String INTENT_LIST = "us.shandian.giga.intent.LIST";
public static final String THREADS = "threads";
private static final String TAG = DownloadActivity.class.toString();
private MissionsFragment mFragment;
private String mPendingUrl;
private SharedPreferences mPrefs;
public class DownloadActivity extends AppCompatActivity {
@Override
@TargetApi(21)
protected void onCreate(Bundle savedInstanceState) {
CrashHandler.init(this);
CrashHandler.register();
// Service
Intent i = new Intent();
i.setClass(this, DownloadManagerService.class);
@@ -75,8 +42,6 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
actionBar.setDisplayShowTitleEnabled(true);
}
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
// Fragment
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
@@ -85,140 +50,17 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
// Intent
if (getIntent().getAction() != null && getIntent().getAction().equals(INTENT_DOWNLOAD)) {
mPendingUrl = getIntent().getData().toString();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getAction().equals(INTENT_DOWNLOAD)) {
mPendingUrl = intent.getData().toString();
}
}
@Override
protected void onResume() {
super.onResume();
if (mPendingUrl != null) {
showUrlDialog();
mPendingUrl = null;
}
}
private void updateFragments() {
mFragment = new AllMissionsFragment();
MissionsFragment fragment = new AllMissionsFragment();
getFragmentManager().beginTransaction()
.replace(R.id.frame, mFragment)
.replace(R.id.frame, fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
private void showUrlDialog() {
// Create the view
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.dialog_url, null);
final EditText name = Utility.findViewById(v, R.id.file_name);
final TextView tCount = Utility.findViewById(v, R.id.threads_count);
final SeekBar threads = Utility.findViewById(v, R.id.threads);
final Toolbar toolbar = Utility.findViewById(v, R.id.toolbar);
final RadioButton audioButton = (RadioButton) Utility.findViewById(v, R.id.audio_button);
threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
tCount.setText(String.valueOf(progress + 1));
}
@Override
public void onStartTrackingTouch(SeekBar p1) {
}
@Override
public void onStopTrackingTouch(SeekBar p1) {
}
});
int def = mPrefs.getInt(THREADS, 4);
threads.setProgress(def - 1);
tCount.setText(String.valueOf(def));
name.setText(getIntent().getStringExtra("fileName"));
toolbar.setTitle(R.string.add);
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
final AlertDialog dialog = new AlertDialog.Builder(this)
.setCancelable(true)
.setView(v)
.create();
dialog.show();
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.okay) {
String location;
if (audioButton.isChecked()) {
location = NewPipeSettings.getAudioDownloadPath(DownloadActivity.this);
} else {
location = NewPipeSettings.getVideoDownloadPath(DownloadActivity.this);
}
String fName = name.getText().toString().trim();
File f = new File(location, fName);
if (f.exists()) {
Toast.makeText(DownloadActivity.this, R.string.msg_exists, Toast.LENGTH_SHORT).show();
} else {
DownloadManagerService.startMission(
DownloadActivity.this,
getIntent().getData().toString(), location, fName,
audioButton.isChecked(), threads.getProgress() + 1);
mFragment.notifyChange();
mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).apply();
mPendingUrl = null;
dialog.dismiss();
}
return true;
} else {
return false;
}
}
});
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
@@ -230,9 +72,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
switch (item.getItemId()) {
case android.R.id.home: {
onBackPressed();
return true;

View File

@@ -1,113 +1,135 @@
package org.schabi.newpipe.download;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
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.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.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.
* <p>
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* DownloadDialog.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_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(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();
}
});
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
@@ -120,58 +142,128 @@ 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);
}
}
/**
* #143 #44 #42 #22: make shure that the filename does not contain illegal chars.
* #143 #44 #42 #22: make sure 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<>();
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, "_");
}
@@ -179,55 +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

@@ -1,7 +1,5 @@
package org.schabi.newpipe.fragments;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -28,12 +26,13 @@ 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);
@@ -64,7 +63,6 @@ public abstract class BaseFragment extends Fragment {
if (DEBUG) Log.d(TAG, "onAttach() called with: context = [" + context + "]");
activity = (AppCompatActivity) context;
onItemSelectedListener = (OnItemSelectedListener) context;
}
@Override
@@ -130,70 +128,6 @@ public abstract class BaseFragment extends Fragment {
// Utils
//////////////////////////////////////////////////////////////////////////*/
public void animateView(final View view, final boolean enterOrExit, long duration) {
animateView(view, enterOrExit, duration, 0, null);
}
public void animateView(final View view, final boolean enterOrExit, long duration, final Runnable execOnEnd) {
animateView(view, enterOrExit, duration, 0, execOnEnd);
}
public void animateView(final View view, final boolean enterOrExit, long duration, long delay) {
animateView(view, enterOrExit, duration, delay, null);
}
/**
* 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 take to start, in milliseconds
* @param execOnEnd runnable that will be executed when the animation ends
*/
public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
if (DEBUG) Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], execOnEnd = [" + execOnEnd + "]");
if (view == null) return;
if (view.getVisibility() == View.VISIBLE && enterOrExit) {
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) {
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 (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();
}
}
protected void setErrorMessage(String message, boolean showRetryButton) {
if (errorTextView == null || activity == null) return;

View File

@@ -51,6 +51,7 @@ public class MainFragment extends Fragment {
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
@@ -68,7 +69,7 @@ public class MainFragment extends Fragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
NavigationHelper.openSearch(activity, 0, "");
NavigationHelper.openSearchFragment(getFragmentManager(), 0, "");
return true;
}
return super.onOptionsItemSelected(item);

View File

@@ -1,10 +0,0 @@
package org.schabi.newpipe.fragments;
import org.schabi.newpipe.extractor.StreamingService;
/**
* Interface for communication purposes between activity and fragment
*/
public interface OnItemSelectedListener {
void onItemSelected(StreamingService.LinkType linkType, int serviceId, String url, String name);
}

View File

@@ -26,6 +26,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.fragments.BaseFragment;
import org.schabi.newpipe.fragments.search.OnScrollBelowItemsListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.util.Constants;
@@ -36,6 +37,8 @@ import java.io.Serializable;
import java.text.NumberFormat;
import java.util.ArrayList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class ChannelFragment extends BaseFragment implements ChannelExtractorWorker.OnChannelInfoReceive {
private final String TAG = "ChannelFragment@" + Integer.toHexString(hashCode());
@@ -211,7 +214,7 @@ public class ChannelFragment extends BaseFragment implements ChannelExtractorWor
channelVideosList.setLayoutManager(new LinearLayoutManager(activity));
if (infoListAdapter == null) {
infoListAdapter = new InfoListAdapter(activity, rootView);
infoListAdapter = new InfoListAdapter(activity);
if (savedInstanceState != null) {
//noinspection unchecked
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
@@ -238,28 +241,17 @@ public class ChannelFragment extends BaseFragment implements ChannelExtractorWor
@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);
NavigationHelper.openVideoDetailFragment(getFragmentManager(), serviceId, url, title);
}
});
channelVideosList.clearOnScrollListeners();
channelVideosList.addOnScrollListener(new RecyclerView.OnScrollListener() {
channelVideosList.addOnScrollListener(new OnScrollBelowItemsListener() {
@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 && (currentChannelWorker == null || !currentChannelWorker.isRunning()) && hasNextPage && !isLoading.get()) {
pageNumber++;
loadMoreVideos();
}
public void onScrolledDown(RecyclerView recyclerView) {
if ((currentChannelWorker == null || !currentChannelWorker.isRunning()) && hasNextPage && !isLoading.get()) {
pageNumber++;
loadMoreVideos();
}
}
});

View File

@@ -9,11 +9,9 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
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;
@@ -40,6 +38,7 @@ import java.util.List;
*/
@SuppressWarnings("WeakerAccess")
class ActionBarHandler {
private static final String TAG = "ActionBarHandler";
@@ -68,19 +67,12 @@ class ActionBarHandler {
public void setupStreamList(final List<VideoStream> videoStreams, Spinner toolbarSpinner) {
if (activity == null) return;
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;
}
selectedVideoStream = Utils.getDefaultResolution(activity, videoStreams);
int defaultResolutionIndex = Utils.getDefaultResolution(activity, videoStreams);
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(activity.getBaseContext(), android.R.layout.simple_spinner_dropdown_item, itemArray);
toolbarSpinner.setAdapter(itemAdapter);
toolbarSpinner.setSelection(defaultResolutionIndex);
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(selectedVideoStream);
toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
@@ -103,6 +95,10 @@ class ActionBarHandler {
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
inflater.inflate(R.menu.video_detail_menu, menu);
updateItemsVisibility();
}
public void updateItemsVisibility(){
showPlayWithKodiAction(defaultPreferences.getBoolean(activity.getString(R.string.show_play_with_kodi_key), false));
}

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,12 +1,17 @@
package org.schabi.newpipe.fragments.detail;
import android.app.Activity;
import android.content.Context;
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.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
@@ -15,6 +20,7 @@ import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.Log;
@@ -26,7 +32,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -40,7 +46,6 @@ import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.Localization;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.download.DownloadDialog;
@@ -56,6 +61,9 @@ import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.Utils;
@@ -65,6 +73,8 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Stack;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class VideoDetailFragment extends BaseFragment implements StreamExtractorWorker.OnStreamInfoReceivedListener, SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener {
private final String TAG = "VideoDetailFragment@" + Integer.toHexString(hashCode());
@@ -72,9 +82,6 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
private static final int INITIAL_RELATED_VIDEOS = 8;
private static final String KORE_PACKET = "org.xbmc.kore";
private static final String SERVICE_ID_KEY = "service_id_key";
private static final String VIDEO_URL_KEY = "video_url_key";
private static final String VIDEO_TITLE_KEY = "video_title_key";
private static final String STACK_KEY = "stack_key";
private static final String INFO_KEY = "info_key";
private static final String WAS_RELATED_EXPANDED_KEY = "was_related_expanded_key";
@@ -98,12 +105,17 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2;
private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4;
private int updateFlags = 0;
private boolean autoPlayEnabled;
private boolean showRelatedStreams;
private boolean wasRelatedStreamsExpanded = false;
private Handler uiHandler;
private Handler backgroundHandler;
private HandlerThread backgroundHandlerThread;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
@@ -111,9 +123,9 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
private Spinner spinnerToolbar;
private ParallaxScrollView parallaxScrollRootView;
private RelativeLayout contentRootLayoutHiding;
private LinearLayout contentRootLayoutHiding;
private Button thumbnailBackgroundButton;
private View thumbnailBackgroundButton;
private ImageView thumbnailImageView;
private ImageView thumbnailPlayButton;
@@ -125,12 +137,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
private TextView detailControlsBackground;
private TextView detailControlsPopup;
private RelativeLayout videoDescriptionRootLayout;
private LinearLayout videoDescriptionRootLayout;
private TextView videoUploadDateView;
private TextView videoDescriptionView;
private View uploaderRootLayout;
private Button uploaderButton;
private TextView uploaderTextView;
private ImageView uploaderThumb;
@@ -141,7 +152,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
private TextView thumbsDisabledTextView;
private TextView nextStreamTitle;
private RelativeLayout relatedStreamRootLayout;
private LinearLayout relatedStreamRootLayout;
private LinearLayout relatedStreamsView;
private ImageButton relatedStreamExpandButton;
@@ -171,9 +182,9 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (savedInstanceState != null) {
videoTitle = savedInstanceState.getString(VIDEO_TITLE_KEY);
videoUrl = savedInstanceState.getString(VIDEO_URL_KEY);
serviceId = savedInstanceState.getInt(SERVICE_ID_KEY, 0);
videoTitle = savedInstanceState.getString(Constants.KEY_TITLE);
videoUrl = savedInstanceState.getString(Constants.KEY_URL);
serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, 0);
wasRelatedStreamsExpanded = savedInstanceState.getBoolean(WAS_RELATED_EXPANDED_KEY, false);
Serializable serializable = savedInstanceState.getSerializable(STACK_KEY);
if (serializable instanceof Stack) {
@@ -193,6 +204,16 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
thousand = getString(R.string.short_thousand);
million = getString(R.string.short_million);
billion = getString(R.string.short_billion);
if (uiHandler == null) {
uiHandler = new Handler(Looper.getMainLooper(), new UICallback());
}
if (backgroundHandler == null) {
HandlerThread handlerThread = new HandlerThread("VideoDetailFragment-BG");
handlerThread.start();
backgroundHandlerThread = handlerThread;
backgroundHandler = new Handler(handlerThread.getLooper(), new BackgroundCallback(uiHandler, getContext()));
}
}
@Override
@@ -219,6 +240,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) initRelatedVideos(currentStreamInfo);
if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBarHandler(currentStreamInfo);
}
if ((updateFlags & TOOLBAR_ITEMS_UPDATE_FLAG) != 0 && actionBarHandler != null) actionBarHandler.updateItemsVisibility();
updateFlags = 0;
}
@@ -238,6 +261,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
@Override
public void onDestroy() {
super.onDestroy();
if (backgroundHandlerThread != null) {
backgroundHandlerThread.quit();
}
backgroundHandlerThread = null;
backgroundHandler = null;
PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this);
}
@@ -269,7 +297,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
videoUploadDateView = null;
videoDescriptionView = null;
uploaderButton = null;
uploaderRootLayout = null;
uploaderTextView = null;
uploaderThumb = null;
@@ -290,9 +318,9 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
@Override
public void onSaveInstanceState(Bundle outState) {
if (DEBUG) Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]");
outState.putString(VIDEO_URL_KEY, videoUrl);
outState.putString(VIDEO_TITLE_KEY, videoTitle);
outState.putInt(SERVICE_ID_KEY, serviceId);
outState.putString(Constants.KEY_URL, videoUrl);
outState.putString(Constants.KEY_TITLE, videoTitle);
outState.putInt(Constants.KEY_SERVICE_ID, serviceId);
outState.putSerializable(STACK_KEY, stack);
int nextCount = currentStreamInfo != null && currentStreamInfo.next_video != null ? 2 : 0;
@@ -311,7 +339,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK) {
NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, videoUrl, videoTitle);
NavigationHelper.openVideoDetailFragment(getFragmentManager(), serviceId, videoUrl, videoTitle);
} else Log.e(TAG, "ReCaptcha failed");
break;
default:
@@ -327,8 +355,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
updateFlags |= RELATED_STREAMS_UPDATE_FLAG;
} else if (key.equals(getString(R.string.preferred_video_format_key))
|| key.equals(getString(R.string.default_resolution_key))
|| key.equals(getString(R.string.show_higher_resolutions_key))) {
|| key.equals(getString(R.string.show_higher_resolutions_key))
|| key.equals(getString(R.string.use_external_video_player_key))) {
updateFlags |= RESOLUTIONS_MENU_UPDATE_FLAG;
} else if (key.equals(getString(R.string.show_play_with_kodi_key))) {
updateFlags |= TOOLBAR_ITEMS_UPDATE_FLAG;
}
}
@@ -347,10 +378,14 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
case R.id.detail_controls_popup:
openInPopup();
break;
case R.id.detail_uploader_button:
NavigationHelper.openChannel(onItemSelectedListener, currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader);
case R.id.detail_uploader_root_layout:
if (currentStreamInfo.channel_url == null || currentStreamInfo.channel_url.isEmpty()) {
Log.w(TAG, "Can't open channel because we got no channel URL");
} else {
NavigationHelper.openChannelFragment(getFragmentManager(), currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader);
}
break;
case R.id.detail_thumbnail_background_button:
case R.id.detail_thumbnail_root_layout:
playVideo(currentStreamInfo);
break;
case R.id.detail_title_root_layout:
@@ -478,12 +513,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
parallaxScrollRootView = (ParallaxScrollView) rootView.findViewById(R.id.detail_main_content);
//thumbnailRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_thumbnail_root_layout);
thumbnailBackgroundButton = (Button) rootView.findViewById(R.id.detail_thumbnail_background_button);
thumbnailBackgroundButton = rootView.findViewById(R.id.detail_thumbnail_root_layout);
thumbnailImageView = (ImageView) rootView.findViewById(R.id.detail_thumbnail_image_view);
thumbnailPlayButton = (ImageView) rootView.findViewById(R.id.detail_thumbnail_play_button);
contentRootLayoutHiding = (RelativeLayout) rootView.findViewById(R.id.detail_content_root_hiding);
contentRootLayoutHiding = (LinearLayout) rootView.findViewById(R.id.detail_content_root_hiding);
videoTitleRoot = rootView.findViewById(R.id.detail_title_root_layout);
videoTitleTextView = (TextView) rootView.findViewById(R.id.detail_video_title_view);
@@ -493,7 +527,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
detailControlsBackground = (TextView) rootView.findViewById(R.id.detail_controls_background);
detailControlsPopup = (TextView) rootView.findViewById(R.id.detail_controls_popup);
videoDescriptionRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_description_root_layout);
videoDescriptionRootLayout = (LinearLayout) rootView.findViewById(R.id.detail_description_root_layout);
videoUploadDateView = (TextView) rootView.findViewById(R.id.detail_upload_date_view);
videoDescriptionView = (TextView) rootView.findViewById(R.id.detail_description_view);
@@ -505,19 +539,19 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
thumbsDisabledTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_disabled_view);
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
uploaderButton = (Button) rootView.findViewById(R.id.detail_uploader_button);
uploaderTextView = (TextView) rootView.findViewById(R.id.detail_uploader_text_view);
uploaderThumb = (ImageView) rootView.findViewById(R.id.detail_uploader_thumbnail_view);
relatedStreamRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_related_streams_root_layout);
relatedStreamRootLayout = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_root_layout);
nextStreamTitle = (TextView) rootView.findViewById(R.id.detail_next_stream_title);
relatedStreamsView = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_view);
relatedStreamExpandButton = ((ImageButton) rootView.findViewById(R.id.detail_related_streams_expand));
actionBarHandler = new ActionBarHandler(activity);
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
infoItemBuilder = new InfoItemBuilder(activity, rootView.findViewById(android.R.id.content));
infoItemBuilder = new InfoItemBuilder(activity);
setHeightThumbnail();
}
@@ -533,7 +567,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
});
videoTitleRoot.setOnClickListener(this);
uploaderButton.setOnClickListener(this);
uploaderRootLayout.setOnClickListener(this);
thumbnailBackgroundButton.setOnClickListener(this);
detailControlsBackground.setOnClickListener(this);
detailControlsPopup.setOnClickListener(this);
@@ -545,7 +579,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, displayImageOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri, R.string.could_not_load_thumbnails));
ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri, R.string.could_not_load_thumbnails));
}
});
} else thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
@@ -677,33 +711,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
}
try {
Bundle args = new Bundle();
// Sometimes it may be that some information is not available due to changes fo the
// website which was crawled. Then the ui has to understand this and act right.
if (info.audio_streams != null) {
AudioStream audioStream =
info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format);
args.putString(DownloadDialog.AUDIO_URL, audioStream.url);
args.putString(DownloadDialog.FILE_SUFFIX_AUDIO, audioSuffix);
}
if (sortedStreamVideosList != null) {
VideoStream selectedStreamItem = sortedStreamVideosList.get(selectedStreamId);
String videoSuffix = "." + MediaFormat.getSuffixById(selectedStreamItem.format);
args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix);
args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url);
}
args.putString(DownloadDialog.TITLE, info.title);
DownloadDialog downloadDialog = DownloadDialog.newInstance(args);
DownloadDialog downloadDialog = DownloadDialog.newInstance(info, sortedStreamVideosList, selectedStreamId);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
Toast.makeText(activity,
R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show();
Toast.makeText(activity, R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
@@ -756,8 +767,17 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
// Get url from the new top
StackItem peek = stack.peek();
if (peek.getInfo() != null) selectAndHandleInfo(peek.getInfo());
else selectAndLoadVideo(0, peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : "");
if (peek.getInfo() != null) {
final StreamInfo streamInfo = peek.getInfo();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
selectAndHandleInfo(streamInfo);
}
});
} else {
selectAndLoadVideo(0, peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : "");
}
return true;
}
@@ -804,7 +824,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
* If it is, check if the cache contains the info already.</br>
* If the cache doesn't have the info, load from the network.
*
* @param info info to prepare and load, can be null
* @param info info to prepare and load, can be null
* @param scrollToTop whether or not scroll the scrollView to y = 0
*/
public void prepareAndLoad(StreamInfo info, boolean scrollToTop) {
@@ -848,7 +868,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
else parallaxScrollRootView.scrollTo(0, 0);
}
animateView(contentRootLayoutHiding, false, greaterThanThreshold ? 250 : 0, new Runnable() {
animateView(contentRootLayoutHiding, false, greaterThanThreshold ? 250 : 0, 0, new Runnable() {
@Override
public void run() {
handleStreamInfo(infoFinal, false);
@@ -865,7 +885,7 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
}
}
private void handleStreamInfo(@NonNull StreamInfo info, boolean fromNetwork) {
private void handleStreamInfo(@NonNull final StreamInfo info, boolean fromNetwork) {
if (DEBUG) Log.d(TAG, "handleStreamInfo() called with: info = [" + info + "]");
currentStreamInfo = info;
selectVideo(info.service_id, info.webpage_url, info.title);
@@ -879,7 +899,6 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
if (!TextUtils.isEmpty(info.uploader)) uploaderTextView.setText(info.uploader);
uploaderTextView.setVisibility(!TextUtils.isEmpty(info.uploader) ? View.VISIBLE : View.GONE);
uploaderButton.setVisibility(!TextUtils.isEmpty(info.channel_url) ? View.VISIBLE : View.GONE);
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, activity));
@@ -904,14 +923,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
}
if (!TextUtils.isEmpty(info.upload_date)) videoUploadDateView.setText(Localization.localizeDate(info.upload_date, activity));
videoUploadDateView.setVisibility(!TextUtils.isEmpty(info.upload_date) ? View.VISIBLE : View.GONE);
if (!TextUtils.isEmpty(info.description)) { //noinspection deprecation
videoDescriptionView.setText(Build.VERSION.SDK_INT >= 24 ? Html.fromHtml(info.description, 0) : Html.fromHtml(info.description));
}
videoDescriptionView.setVisibility(!TextUtils.isEmpty(info.description) ? View.VISIBLE : View.GONE);
videoDescriptionView.setVisibility(View.GONE);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setVisibility(View.VISIBLE);
@@ -925,9 +938,32 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
toggleExpandRelatedVideos(currentStreamInfo);
wasRelatedStreamsExpanded = false;
}
setTitleToUrl(info.webpage_url, info.title);
setStreamInfoToUrl(info.webpage_url, info);
prepareDescription(info.description);
prepareUploadDate(info.upload_date);
if (autoPlayEnabled) {
playVideo(info);
// Only auto play in the first open
autoPlayEnabled = false;
}
}
private void prepareUploadDate(final String uploadDate) {
// Hide until date is prepared or forever if no date is supplied
videoUploadDateView.setVisibility(View.GONE);
if (!TextUtils.isEmpty(uploadDate)) {
backgroundHandler.sendMessage(Message.obtain(backgroundHandler, BackgroundCallback.MESSAGE_UPLOADER_DATE, uploadDate));
}
}
private void prepareDescription(final String descriptionHtml) {
// Send the unparsed description to the handler as a message
if (!TextUtils.isEmpty(descriptionHtml)) {
backgroundHandler.sendMessage(Message.obtain(backgroundHandler, BackgroundCallback.MESSAGE_DESCRIPTION, descriptionHtml));
}
}
public void playVideo(StreamInfo info) {
@@ -1004,10 +1040,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
int height = isPortrait ? (int) (getResources().getDisplayMetrics().widthPixels / (16.0f / 9.0f))
: (int) (getResources().getDisplayMetrics().heightPixels / 2f);
thumbnailImageView.setScaleType(isPortrait ? ImageView.ScaleType.CENTER_CROP : ImageView.ScaleType.FIT_CENTER);
thumbnailImageView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height));
thumbnailImageView.setLayoutParams(new FrameLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height));
thumbnailImageView.setMinimumHeight(height);
thumbnailBackgroundButton.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height));
thumbnailBackgroundButton.setMinimumHeight(height);
}
public String getShortCount(Long viewCount) {
@@ -1049,14 +1083,11 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
.setStartDelay((long) (duration * .8f) + delay).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator()).start();
}
}
/*//////////////////////////////////////////////////////////////////////////
// OnStreamInfoReceivedListener callbacks
//////////////////////////////////////////////////////////////////////////*/
private void setErrorImage(final int imageResource) {
if (thumbnailImageView == null || activity == null) return;
thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource));
animateView(thumbnailImageView, false, 0, new Runnable() {
animateView(thumbnailImageView, false, 0, 0, new Runnable() {
@Override
public void run() {
animateView(thumbnailImageView, true, 500);
@@ -1072,6 +1103,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
currentStreamInfo = null;
}
/*//////////////////////////////////////////////////////////////////////////
// OnStreamInfoReceivedListener callbacks
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onReceive(StreamInfo info) {
if (DEBUG) Log.d(TAG, "onReceive() called with: info = [" + info + "]");
@@ -1079,15 +1114,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
handleStreamInfo(info, true);
showContentWithAnimation(300, 0, 0);
animateView(loadingProgressBar, false, 200);
if (autoPlayEnabled) {
playVideo(info);
// Only auto play in the first open
autoPlayEnabled = false;
}
StreamInfoCache.getInstance().putInfo(info);
isLoading.set(false);
@@ -1143,4 +1171,69 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
public void onUnrecoverableError(Exception exception) {
activity.finish();
}
/*//////////////////////////////////////////////////////////////////////////
// Background handling
//////////////////////////////////////////////////////////////////////////*/
private static class BackgroundCallback implements Handler.Callback {
private static final int MESSAGE_DESCRIPTION = 1;
public static final int MESSAGE_UPLOADER_DATE = 2;
private final Handler uiHandler;
private final Context context;
BackgroundCallback(Handler uiHandler, Context context) {
this.uiHandler = uiHandler;
this.context = context;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_DESCRIPTION:
handleDescription((String) msg.obj);
return true;
case MESSAGE_UPLOADER_DATE:
handleUploadDate((String) msg.obj);
return true;
}
return false;
}
private void handleUploadDate(String uploadDate) {
String localizedDate = Localization.localizeDate(uploadDate, context);
uiHandler.sendMessage(Message.obtain(uiHandler, MESSAGE_UPLOADER_DATE, localizedDate));
}
private void handleDescription(String description) {
Spanned parsedDescription;
if (TextUtils.isEmpty(description)) {
return;
}
if (Build.VERSION.SDK_INT >= 24) {
parsedDescription = Html.fromHtml(description, 0);
} else {
//noinspection deprecation
parsedDescription = Html.fromHtml(description);
}
uiHandler.sendMessage(Message.obtain(uiHandler, MESSAGE_DESCRIPTION, parsedDescription));
}
}
private class UICallback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case BackgroundCallback.MESSAGE_DESCRIPTION:
videoDescriptionView.setText((Spanned) msg.obj);
videoDescriptionView.setVisibility(View.VISIBLE);
return true;
case BackgroundCallback.MESSAGE_UPLOADER_DATE:
videoUploadDateView.setText((String) msg.obj);
videoUploadDateView.setVisibility(View.VISIBLE);
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,33 @@
package org.schabi.newpipe.fragments.search;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
/**
* Recycler view scroll listener which calls the method {@link #onScrolledDown(RecyclerView)}
* if the view is scrolled below the last item.
*/
public abstract class OnScrollBelowItemsListener extends RecyclerView.OnScrollListener {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//check for scroll down
if (dy > 0) {
int pastVisibleItems, visibleItemCount, totalItemCount;
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
visibleItemCount = recyclerView.getLayoutManager().getChildCount();
totalItemCount = recyclerView.getLayoutManager().getItemCount();
pastVisibleItems = layoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisibleItems) >= totalItemCount) {
onScrolledDown(recyclerView);
}
}
}
/**
* Called when the recycler view is scrolled below the last item.
* @param recyclerView the recycler view
*/
public abstract void onScrolledDown(RecyclerView recyclerView);
}

View File

@@ -5,6 +5,7 @@ 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.v7.app.ActionBar;
@@ -37,6 +38,7 @@ import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.fragments.BaseFragment;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.workers.SearchWorker;
import org.schabi.newpipe.workers.SuggestionWorker;
@@ -45,12 +47,13 @@ import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
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_KEY = "query_key";
private static final String PAGE_NUMBER_KEY = "page_number_key";
private static final String SERVICE_KEY = "service_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";
@@ -66,6 +69,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
private int serviceId = -1;
private String searchQuery = "";
private int pageNumber = 0;
private boolean showSuggestions = true;
private SearchWorker curSearchWorker;
private SuggestionWorker curSuggestionWorker;
@@ -101,7 +105,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
setHasOptionsMenu(true);
if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY_KEY);
serviceId = savedInstanceState.getInt(SERVICE_KEY, 0);
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);
@@ -133,6 +137,8 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
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
@@ -171,7 +177,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
String query = searchEditText != null && !TextUtils.isEmpty(searchEditText.getText().toString())
? searchEditText.getText().toString() : searchQuery;
outState.putString(QUERY_KEY, query);
outState.putInt(SERVICE_KEY, serviceId);
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());
@@ -206,7 +212,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
resultRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
if (infoListAdapter == null) {
infoListAdapter = new InfoListAdapter(getActivity(), getActivity().findViewById(android.R.id.content));
infoListAdapter = new InfoListAdapter(getActivity());
if (savedInstanceState != null) {
//noinspection unchecked
ArrayList<InfoItem> serializable = (ArrayList<InfoItem>) savedInstanceState.getSerializable(INFO_LIST_KEY);
@@ -218,13 +224,13 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
NavigationHelper.openVideoDetailFragment(getFragmentManager(), serviceId, url, title);
}
});
infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
NavigationHelper.openChannelFragment(getFragmentManager(), serviceId, url, title);
}
});
}
@@ -236,33 +242,24 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
protected void initListeners() {
super.initListeners();
resultRecyclerView.clearOnScrollListeners();
resultRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
resultRecyclerView.addOnScrollListener(new OnScrollBelowItemsListener() {
@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);
}
public void onScrolledDown(RecyclerView recyclerView) {
if(!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");
@@ -383,7 +380,7 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
public void onClick(View v) {
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
if (TextUtils.isEmpty(searchEditText.getText())) {
NavigationHelper.openMainActivity(activity);
NavigationHelper.openMainFragment(getFragmentManager());
return;
}
@@ -521,6 +518,11 @@ public class SearchFragment extends BaseFragment implements SuggestionWorker.OnS
}
private void searchSuggestions(String query) {
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);

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

@@ -31,8 +31,7 @@ import de.hdodenhof.circleimageview.CircleImageView;
public class ChannelInfoItemHolder extends InfoItemHolder {
public final CircleImageView itemThumbnailView;
public final TextView itemChannelTitleView;
public final TextView itemSubscriberCountView;
public final TextView itemVideoCountView;
public final TextView itemAdditionalDetailView;
public final TextView itemChannelDescriptionView;
public final View itemRoot;
@@ -42,8 +41,7 @@ public class ChannelInfoItemHolder extends InfoItemHolder {
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);
itemAdditionalDetailView = (TextView) v.findViewById(R.id.itemAdditionalDetails);
itemChannelDescriptionView = (TextView) v.findViewById(R.id.itemChannelDescriptionView);
}

View File

@@ -17,6 +17,8 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
import java.util.Locale;
/**
* Created by Christian Schabesberger on 26.09.16.
* <p>
@@ -54,18 +56,35 @@ public class InfoItemBuilder {
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 =
new DisplayImageOptions.Builder().cacheInMemory(true).build();
/** Base display options */
private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS =
new DisplayImageOptions.Builder()
.cacheInMemory(true)
.build();
/** Display options for stream thumbnails */
private static final DisplayImageOptions DISPLAY_STREAM_THUMBNAIL_OPTIONS =
new DisplayImageOptions.Builder()
.cloneFrom(DISPLAY_IMAGE_OPTIONS)
.showImageOnFail(R.drawable.dummy_thumbnail)
.showImageForEmptyUri(R.drawable.dummy_thumbnail)
.showImageOnLoading(R.drawable.dummy_thumbnail)
.build();
/** Display options for channel thumbnails */
private static final DisplayImageOptions DISPLAY_CHANNEL_THUMBNAIL_OPTIONS =
new DisplayImageOptions.Builder()
.cloneFrom(DISPLAY_IMAGE_OPTIONS)
.showImageOnLoading(R.drawable.buddy_channel_item)
.showImageForEmptyUri(R.drawable.buddy_channel_item)
.showImageOnFail(R.drawable.buddy_channel_item)
.build();
private OnInfoItemSelectedListener onStreamInfoItemSelectedListener;
private OnInfoItemSelectedListener onChannelInfoItemSelectedListener;
public InfoItemBuilder(Context context, View rootView) {
mContext = context;
this.rootView = rootView;
public InfoItemBuilder(Context context) {
viewsS = context.getString(R.string.views);
videosS = context.getString(R.string.videos);
subsS = context.getString(R.string.subscriber);
@@ -73,7 +92,6 @@ 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(
@@ -107,6 +125,7 @@ public class InfoItemBuilder {
public View buildView(ViewGroup parent, final InfoItem info) {
View itemView = null;
InfoItemHolder holder = null;
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (info.infoType()) {
case STREAM:
//long start = System.nanoTime();
@@ -127,6 +146,22 @@ public class InfoItemBuilder {
return itemView;
}
private String getStreamInfoDetailLine(final StreamInfoItem info) {
String viewsAndDate = "";
if(info.view_count >= 0) {
viewsAndDate = shortViewCount(info.view_count);
}
if(!TextUtils.isEmpty(info.upload_date)) {
if(viewsAndDate.isEmpty()) {
viewsAndDate = info.upload_date;
} else {
viewsAndDate += "" + info.upload_date;
}
}
return viewsAndDate;
}
private void buildStreamInfoItem(StreamInfoItemHolder holder, final StreamInfoItem info) {
if (info.infoType() != InfoItem.InfoType.STREAM) {
Log.e("InfoItemBuilder", "Info type not yet supported");
@@ -146,46 +181,59 @@ public class InfoItemBuilder {
holder.itemDurationView.setVisibility(View.GONE);
}
}
if (info.view_count >= 0) {
holder.itemViewCountView.setText(shortViewCount(info.view_count));
} else {
holder.itemViewCountView.setVisibility(View.GONE);
}
if (!TextUtils.isEmpty(info.upload_date)) holder.itemUploadDateView.setText(info.upload_date + "");
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
if (!TextUtils.isEmpty(info.thumbnail_url)) {
imageLoader.displayImage(info.thumbnail_url,
holder.itemThumbnailView, displayImageOptions,
new ImageErrorLoadingListener(mContext, rootView, info.service_id));
}
holder.itemAdditionalDetails.setText(getStreamInfoDetailLine(info));
// Default thumbnail is shown on error, while loading and if the url is empty
imageLoader.displayImage(info.thumbnail_url,
holder.itemThumbnailView,
DISPLAY_STREAM_THUMBNAIL_OPTIONS,
new ImageErrorLoadingListener(holder.itemRoot.getContext(), holder.itemRoot.getRootView(), info.service_id));
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onStreamInfoItemSelectedListener.selected(info.service_id, info.webpage_url, info.getTitle());
if(onStreamInfoItemSelectedListener != null) {
onStreamInfoItemSelectedListener.selected(info.service_id, info.webpage_url, info.getTitle());
}
}
});
}
private String getChannelInfoDetailLine(final ChannelInfoItem info) {
String details = "";
if(info.subscriberCount >= 0) {
details = shortSubscriber(info.subscriberCount);
}
if(info.videoAmount >= 0) {
String formattedVideoAmount = info.videoAmount + " " + videosS;
if(!details.isEmpty()) {
details += "" + formattedVideoAmount;
} else {
details = formattedVideoAmount;
}
}
return details;
}
private void buildChannelInfoItem(ChannelInfoItemHolder holder, final ChannelInfoItem info) {
if (!TextUtils.isEmpty(info.getTitle())) holder.itemChannelTitleView.setText(info.getTitle());
holder.itemSubscriberCountView.setText(shortSubscriber(info.subscriberCount) + "");
holder.itemVideoCountView.setText(info.videoAmount + " " + videosS);
holder.itemAdditionalDetailView.setText(getChannelInfoDetailLine(info));
if (!TextUtils.isEmpty(info.description)) holder.itemChannelDescriptionView.setText(info.description);
holder.itemThumbnailView.setImageResource(R.drawable.buddy_channel_item);
if (!TextUtils.isEmpty(info.thumbnailUrl)) {
imageLoader.displayImage(info.thumbnailUrl,
holder.itemThumbnailView,
displayImageOptions,
new ImageErrorLoadingListener(mContext, rootView, info.serviceId));
}
imageLoader.displayImage(info.thumbnailUrl,
holder.itemThumbnailView,
DISPLAY_CHANNEL_THUMBNAIL_OPTIONS,
new ImageErrorLoadingListener(holder.itemRoot.getContext(), holder.itemRoot.getRootView(), info.serviceId));
holder.itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onChannelInfoItemSelectedListener.selected(info.serviceId, info.getLink(), info.channelName);
if(onStreamInfoItemSelectedListener != null) {
onChannelInfoItemSelectedListener.selected(info.serviceId, info.getLink(), info.channelName);
}
}
});
}
@@ -218,7 +266,10 @@ public class InfoItemBuilder {
}
public static String getDurationString(int duration) {
String output = "";
if(duration < 0) {
duration = 0;
}
String output;
int days = duration / (24 * 60 * 60); /* greater than a day */
duration %= (24 * 60 * 60);
int hours = duration / (60 * 60); /* greater than an hour */
@@ -228,46 +279,12 @@ public class InfoItemBuilder {
//handle days
if (days > 0) {
output = Integer.toString(days) + ":";
}
// handle hours
if (hours > 0 || !output.isEmpty()) {
if (hours > 0) {
if (hours >= 10 || output.isEmpty()) {
output += Integer.toString(hours);
} else {
output += "0" + Integer.toString(hours);
}
} else {
output += "00";
}
output += ":";
}
//handle minutes
if (minutes > 0 || !output.isEmpty()) {
if (minutes > 0) {
if (minutes >= 10 || output.isEmpty()) {
output += Integer.toString(minutes);
} else {
output += "0" + Integer.toString(minutes);
}
} else {
output += "00";
}
output += ":";
}
//handle seconds
if (output.isEmpty()) {
output += "0:";
}
if (seconds >= 10) {
output += Integer.toString(seconds);
output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds);
} else if(hours > 0) {
output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
} else {
output += "0" + Integer.toString(seconds);
output = String.format(Locale.US, "%d:%02d", minutes, seconds);
}
return output;
}
}

View File

@@ -55,8 +55,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
notifyDataSetChanged();
}
public InfoListAdapter(Activity a, View rootView) {
infoItemBuilder = new InfoItemBuilder(a, rootView);
public InfoListAdapter(Activity a) {
infoItemBuilder = new InfoItemBuilder(a);
infoItemList = new ArrayList<>();
}
@@ -78,6 +78,9 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
}
public void clearStreamItemList() {
if(infoItemList.isEmpty()) {
return;
}
infoItemList.clear();
notifyDataSetChanged();
}
@@ -152,7 +155,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
//god damen f*** ANDROID SH**
//god damn f*** ANDROID SH**
if(holder instanceof InfoItemHolder) {
if(header != null) {
i--;

View File

@@ -33,8 +33,7 @@ public class StreamInfoItemHolder extends InfoItemHolder {
public final TextView itemVideoTitleView,
itemUploaderView,
itemDurationView,
itemUploadDateView,
itemViewCountView;
itemAdditionalDetails;
public final View itemRoot;
public StreamInfoItemHolder(View v) {
@@ -44,8 +43,7 @@ public class StreamInfoItemHolder extends InfoItemHolder {
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);
itemAdditionalDetails = (TextView) v.findViewById(R.id.itemAdditionalDetails);
}
@Override

View File

@@ -286,15 +286,13 @@ public class BackgroundPlayer extends Service {
public void onPrepared(boolean playWhenReady) {
super.onPrepared(playWhenReady);
if (simpleExoPlayer.getDuration() < 15000) {
PROGRESS_LOOP_INTERVAL = 1000;
FAST_FORWARD_REWIND_AMOUNT = 2000;
} else if (simpleExoPlayer.getDuration() > 60 * 60 * 1000) {
PROGRESS_LOOP_INTERVAL = 2000;
FAST_FORWARD_REWIND_AMOUNT = 60000;
} else {
PROGRESS_LOOP_INTERVAL = 2000;
FAST_FORWARD_REWIND_AMOUNT = 10000;
}
PROGRESS_LOOP_INTERVAL = 1000;
basePlayerImpl.getPlayer().setVolume(1f);
}
@@ -323,6 +321,7 @@ public class BackgroundPlayer extends Service {
public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) {
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
if (bigNotRemoteView != null) bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
updateNotification(-1);
}
@@ -452,4 +451,4 @@ public class BackgroundPlayer extends Service {
releaseWifiAndCpu();
}
}
}
}

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;
/*//////////////////////////////////////////////////////////////////////////
@@ -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,6 +468,8 @@ 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 && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
@@ -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,11 +524,11 @@ 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, true);
playerImpl.hideControls(300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
}
}

View File

@@ -25,6 +25,7 @@ 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;
@@ -45,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
*
@@ -260,6 +263,7 @@ public class PopupVideoPlayer extends Service {
i.putExtra(Constants.KEY_TITLE, videoTitle);
i.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(i);
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
@@ -408,7 +412,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
@@ -418,7 +422,14 @@ public class PopupVideoPlayer extends Service {
stopSelf();
}
/*//////////////////////////////////////////////////////////////////////////
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) {
hideControls(100, 0);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Broadcast Receiver
//////////////////////////////////////////////////////////////////////////*/
@@ -541,9 +552,10 @@ public class PopupVideoPlayer extends Service {
if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.animateView(playerImpl.getControlsRoot(), false, 0, 0);
playerImpl.animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
playerImpl.animateView(playerImpl.getResizingIndicator(), true, 200, 0);
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);
@@ -553,7 +565,8 @@ public class PopupVideoPlayer extends Service {
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isResizing) return false;
if (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f) playerImpl.animateView(playerImpl.getControlsRoot(), true, 0, 0);
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);
@@ -582,7 +595,7 @@ 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);
}
}
@@ -610,7 +623,7 @@ public class PopupVideoPlayer extends Service {
if (isResizing) {
isResizing = false;
playerImpl.animateView(playerImpl.getResizingIndicator(), false, 100, 0);
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
savePositionAndSize();
@@ -665,7 +678,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;
@@ -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

@@ -1,6 +1,7 @@
package org.schabi.newpipe.report;
import android.content.Context;
import android.support.annotation.NonNull;
import org.acra.collector.CrashReportData;
import org.acra.sender.ReportSender;
@@ -30,9 +31,9 @@ import org.schabi.newpipe.R;
public class AcraReportSender implements ReportSender {
@Override
public void send(Context context, CrashReportData report) throws ReportSenderException {
public void send(@NonNull Context context, @NonNull CrashReportData report) throws ReportSenderException {
ErrorActivity.reportError(context, report,
ErrorActivity.ErrorInfo.make(ErrorActivity.UI_ERROR,"none",
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,"none",
"App crash, UI failure", R.string.app_ui_crash));
}
}

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.report;
import android.content.Context;
import android.support.annotation.NonNull;
import org.acra.config.ACRAConfiguration;
import org.acra.sender.ReportSender;
@@ -28,7 +29,8 @@ import org.schabi.newpipe.report.AcraReportSender;
*/
public class AcraReportSenderFactory implements ReportSenderFactory {
public ReportSender create(Context context, ACRAConfiguration config) {
@NonNull
public ReportSender create(@NonNull Context context, @NonNull ACRAConfiguration config) {
return new AcraReportSender();
}
}

View File

@@ -11,6 +11,8 @@ import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
@@ -71,24 +73,7 @@ public class ErrorActivity extends AppCompatActivity {
// BUNDLE TAGS
public static final String ERROR_INFO = "error_info";
public static final String ERROR_LIST = "error_list";
// MESSAGE ID
public static final int SEARCHED = 0;
public static final int REQUESTED_STREAM = 1;
public static final int GET_SUGGESTIONS = 2;
public static final int SOMETHING_ELSE = 3;
public static final int USER_REPORT = 4;
public static final int LOAD_IMAGE = 5;
public static final int UI_ERROR = 6;
public static final int REQUESTED_CHANNEL = 7;
// MESSAGE STRING
public static final String SEARCHED_STRING = "searched";
public static final String REQUESTED_STREAM_STRING = "requested stream";
public static final String GET_SUGGESTIONS_STRING = "get suggestions";
public static final String SOMETHING_ELSE_STRING = "something";
public static final String USER_REPORT_STRING = "user report";
public static final String LOAD_IMAGE_STRING = "load image";
public static final String UI_ERROR_STRING = "ui error";
public static final String REQUESTED_CHANNEL_STRING = "requested channel";
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
Thread globIpRangeThread;
@@ -105,11 +90,11 @@ public class ErrorActivity extends AppCompatActivity {
private TextView errorMessageView;
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UI_ERROR, "none", "", R.string.app_ui_crash));
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
}
public static void reportError(final Context context, final List<Throwable> el,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
final Class returnActivity, View rootView, final ErrorInfo errorInfo) {
if (rootView != null) {
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
@@ -118,7 +103,7 @@ public class ErrorActivity extends AppCompatActivity {
@Override
public void onClick(View v) {
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
ac.returnActivity = returnAcitivty;
ac.returnActivity = returnActivity;
Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra(ERROR_INFO, errorInfo);
intent.putExtra(ERROR_LIST, elToSl(el));
@@ -128,7 +113,7 @@ public class ErrorActivity extends AppCompatActivity {
}).show();
} else {
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
ac.returnActivity = returnAcitivty;
ac.returnActivity = returnActivity;
Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra(ERROR_INFO, errorInfo);
intent.putExtra(ERROR_LIST, elToSl(el));
@@ -138,34 +123,34 @@ public class ErrorActivity extends AppCompatActivity {
}
public static void reportError(final Context context, final Throwable e,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
final Class returnActivity, View rootView, final ErrorInfo errorInfo) {
List<Throwable> el = null;
if(e != null) {
el = new Vector<>();
el.add(e);
}
reportError(context, el, returnAcitivty, rootView, errorInfo);
reportError(context, el, returnActivity, rootView, errorInfo);
}
// async call
public static void reportError(Handler handler, final Context context, final Throwable e,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
final Class returnActivity, final View rootView, final ErrorInfo errorInfo) {
List<Throwable> el = null;
if(e != null) {
el = new Vector<>();
el.add(e);
}
reportError(handler, context, el, returnAcitivty, rootView, errorInfo);
reportError(handler, context, el, returnActivity, rootView, errorInfo);
}
// async call
public static void reportError(Handler handler, final Context context, final List<Throwable> el,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
final Class returnActivity, final View rootView, final ErrorInfo errorInfo) {
handler.post(new Runnable() {
@Override
public void run() {
reportError(context, el, returnAcitivty, rootView, errorInfo);
reportError(context, el, returnActivity, rootView, errorInfo);
}
});
}
@@ -232,7 +217,7 @@ public class ErrorActivity extends AppCompatActivity {
errorInfo = intent.getParcelableExtra(ERROR_INFO);
errorList = intent.getStringArrayExtra(ERROR_LIST);
//importand add gurumeditaion
// important add guru meditation
addGuruMeditaion();
currentTimeStamp = getCurrentTimeStamp();
@@ -250,7 +235,7 @@ public class ErrorActivity extends AppCompatActivity {
});
reportButton.setEnabled(false);
globIpRangeThread = new Thread(new IpRagneRequester());
globIpRangeThread = new Thread(new IpRangeRequester());
globIpRangeThread.start();
// normal bugreport
@@ -308,17 +293,30 @@ public class ErrorActivity extends AppCompatActivity {
return text;
}
/**
* Get the checked activity.
* @param returnActivity the activity to return to
* @return the casted return activity or null
*/
@Nullable
static Class<? extends Activity> getReturnActivity(Class<?> returnActivity) {
Class<? extends Activity> checkedReturnActivity = null;
if (returnActivity != null){
if (Activity.class.isAssignableFrom(returnActivity)) {
checkedReturnActivity = returnActivity.asSubclass(Activity.class);
} else {
checkedReturnActivity = MainActivity.class;
}
}
return checkedReturnActivity;
}
private void goToReturnActivity() {
if (returnActivity == null) {
Class<? extends Activity> checkedReturnActivity = getReturnActivity(returnActivity);
if (checkedReturnActivity == null) {
super.onBackPressed();
} else {
Intent intent;
if (returnActivity != null &&
returnActivity.isAssignableFrom(Activity.class)) {
intent = new Intent(this, returnActivity);
} else {
intent = new Intent(this, MainActivity.class);
}
Intent intent = new Intent(this, checkedReturnActivity);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
}
@@ -376,26 +374,11 @@ public class ErrorActivity extends AppCompatActivity {
return "";
}
private String getUserActionString(int userAction) {
switch (userAction) {
case REQUESTED_STREAM:
return REQUESTED_STREAM_STRING;
case SEARCHED:
return SEARCHED_STRING;
case GET_SUGGESTIONS:
return GET_SUGGESTIONS_STRING;
case SOMETHING_ELSE:
return SOMETHING_ELSE_STRING;
case USER_REPORT:
return USER_REPORT_STRING;
case LOAD_IMAGE:
return LOAD_IMAGE_STRING;
case UI_ERROR:
return UI_ERROR_STRING;
case REQUESTED_CHANNEL:
return REQUESTED_CHANNEL_STRING;
default:
return "Your description is in another castle.";
private String getUserActionString(UserAction userAction) {
if(userAction == null) {
return "Your description is in another castle.";
} else {
return userAction.getMessage();
}
}
@@ -444,28 +427,28 @@ public class ErrorActivity extends AppCompatActivity {
return new ErrorInfo[size];
}
};
public int userAction;
public String request;
public String serviceName;
public int message;
final public UserAction userAction;
final public String request;
final public String serviceName;
@StringRes
final public int message;
public ErrorInfo() {
private ErrorInfo(UserAction userAction, String serviceName, String request, @StringRes int message) {
this.userAction = userAction;
this.serviceName = serviceName;
this.request = request;
this.message = message;
}
protected ErrorInfo(Parcel in) {
this.userAction = in.readInt();
this.userAction = UserAction.valueOf(in.readString());
this.request = in.readString();
this.serviceName = in.readString();
this.message = in.readInt();
}
public static ErrorInfo make(int userAction, String serviceName, String request, int message) {
ErrorInfo info = new ErrorInfo();
info.userAction = userAction;
info.serviceName = serviceName;
info.request = request;
info.message = message;
return info;
public static ErrorInfo make(UserAction userAction, String serviceName, String request, @StringRes int message) {
return new ErrorInfo(userAction, serviceName, request, message);
}
@Override
@@ -475,14 +458,14 @@ public class ErrorActivity extends AppCompatActivity {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.userAction);
dest.writeString(this.userAction.name());
dest.writeString(this.request);
dest.writeString(this.serviceName);
dest.writeInt(this.message);
}
}
private class IpRagneRequester implements Runnable {
private class IpRangeRequester implements Runnable {
Handler h = new Handler();
public void run() {
String ipRange = "none";
@@ -493,17 +476,16 @@ public class ErrorActivity extends AppCompatActivity {
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
+ "0.0";
} catch(Throwable e) {
Log.d(TAG, "Error while error: could not get iprange");
e.printStackTrace();
Log.w(TAG, "Error while error: could not get iprange", e);
} finally {
h.post(new IpRageReturnRunnable(ipRange));
h.post(new IpRangeReturnRunnable(ipRange));
}
}
}
private class IpRageReturnRunnable implements Runnable {
private class IpRangeReturnRunnable implements Runnable {
String ipRange;
public IpRageReturnRunnable(String ipRange) {
public IpRangeReturnRunnable(String ipRange) {
this.ipRange = ipRange;
}
public void run() {

View File

@@ -0,0 +1,26 @@
package org.schabi.newpipe.report;
/**
* The user actions that can cause an error.
*/
public enum UserAction {
SEARCHED("searched"),
REQUESTED_STREAM("requested stream"),
GET_SUGGESTIONS("get suggestions"),
SOMETHING_ELSE("something"),
USER_REPORT("user report"),
LOAD_IMAGE("load image"),
UI_ERROR("ui error"),
REQUESTED_CHANNEL("requested channel");
private final String message;
UserAction(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}

View File

@@ -72,9 +72,7 @@ public class NewPipeSettings {
public static String getVideoDownloadPath(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final String key = context.getString(R.string.download_path_key);
String downloadPath = prefs.getString(key, Environment.DIRECTORY_MOVIES);
return downloadPath;
return prefs.getString(key, Environment.DIRECTORY_MOVIES);
}
public static File getAudioDownloadFolder(Context context) {
@@ -84,9 +82,7 @@ public class NewPipeSettings {
public static String getAudioDownloadPath(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final String key = context.getString(R.string.download_path_audio_key);
String downloadPath = prefs.getString(key, Environment.DIRECTORY_MUSIC);
return downloadPath;
return prefs.getString(key, Environment.DIRECTORY_MUSIC);
}
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {

View File

@@ -49,19 +49,21 @@ public class SettingsActivity extends AppCompatActivity {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.settings_title);
actionBar.setTitle(R.string.settings);
actionBar.setDisplayShowTitleEnabled(true);
}
getFragmentManager().beginTransaction()
.replace(R.id.fragment_holder, new SettingsFragment())
.commit();
if (savedInstanceBundle == null) {
getFragmentManager().beginTransaction()
.replace(R.id.fragment_holder, new SettingsFragment())
.commit();
}
}
@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

@@ -69,7 +69,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
Log.d("TAG", "onPreferenceTreeClick() called with: preferenceScreen = [" + preferenceScreen + "], preference = [" + preference + "]");
if (MainActivity.DEBUG) 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)
@@ -87,11 +87,11 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d("TAG", "onActivityResult() called with: requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]");
if (MainActivity.DEBUG) Log.d("TAG", "onActivityResult() called with: requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]");
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);
String path = data.getData().getPath();
defaultPreferences.edit().putString(key, path).apply();
updatePreferencesSummary();
} else if (requestCode == REQUEST_INSTALL_ORBOT) {
@@ -101,7 +101,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
}
}
/**
/*
* 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,
@@ -114,7 +114,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d("TAG", "onSharedPreferenceChanged() called with: sharedPreferences = [" + sharedPreferences + "], key = [" + key + "]");
if (MainActivity.DEBUG) Log.d("TAG", "onSharedPreferenceChanged() called with: sharedPreferences = [" + sharedPreferences + "], key = [" + key + "]");
String summary = null;
if (key.equals(USE_TOR_KEY)) {
@@ -131,10 +131,10 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
} 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);
getActivity().recreate();
}
defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
}
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

@@ -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

@@ -3,6 +3,10 @@ package org.schabi.newpipe.util;
import android.content.Context;
import android.content.Intent;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
@@ -10,8 +14,10 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.channel.ChannelFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.search.SearchFragment;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.VideoPlayer;
@@ -36,7 +42,6 @@ public class NavigationHelper {
return mIntent;
}
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, VideoPlayer instance) {
return new Intent(context, targetClazz)
.putExtra(BasePlayer.VIDEO_TITLE, instance.getVideoTitle())
@@ -66,31 +71,69 @@ public class NavigationHelper {
}
/*//////////////////////////////////////////////////////////////////////////
// Through Interface (faster)
// Through FragmentManager
//////////////////////////////////////////////////////////////////////////*/
public static void openChannel(OnItemSelectedListener listener, int serviceId, String url) {
openChannel(listener, serviceId, url, null);
public static void openMainFragment(FragmentManager fragmentManager) {
ImageLoader.getInstance().clearMemoryCache();
fragmentManager.beginTransaction()
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
.replace(R.id.fragment_holder, new MainFragment())
.commit();
}
public static void openChannel(OnItemSelectedListener listener, int serviceId, String url, String name) {
listener.onItemSelected(StreamingService.LinkType.CHANNEL, serviceId, url, name);
public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) {
fragmentManager.beginTransaction()
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
.addToBackStack(null)
.commit();
}
public static void openVideoDetail(OnItemSelectedListener listener, int serviceId, String url) {
openVideoDetail(listener, serviceId, url, null);
public static void openVideoDetailFragment(FragmentManager fragmentManager, int serviceId, String url, String title) {
openVideoDetailFragment(fragmentManager, serviceId, url, title, false);
}
public static void openVideoDetail(OnItemSelectedListener listener, int serviceId, String url, String title) {
listener.onItemSelected(StreamingService.LinkType.STREAM, serviceId, url, title);
public static void openVideoDetailFragment(FragmentManager fragmentManager, int serviceId, String url, String title, boolean autoPlay) {
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_holder);
if (title == null) title = "";
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
VideoDetailFragment detailFragment = (VideoDetailFragment) fragment;
detailFragment.setAutoplay(autoPlay);
detailFragment.selectAndLoadVideo(serviceId, url, title);
return;
}
VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url, title);
instance.setAutoplay(autoPlay);
fragmentManager.beginTransaction()
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
.replace(R.id.fragment_holder, instance)
.addToBackStack(null)
.commit();
}
public static void openChannelFragment(FragmentManager fragmentManager, int serviceId, String url, String name) {
if (name == null) name = "";
fragmentManager.beginTransaction()
.setCustomAnimations(R.anim.custom_fade_in, R.anim.custom_fade_out, R.anim.custom_fade_in, R.anim.custom_fade_out)
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
.addToBackStack(null)
.commit();
}
/*//////////////////////////////////////////////////////////////////////////
// Through Intents
//////////////////////////////////////////////////////////////////////////*/
public static void openByLink(Context context, String url) throws Exception {
context.startActivity(getIntentByLink(context, url));
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);
}
public static void openChannel(Context context, int serviceId, String url) {
@@ -118,12 +161,12 @@ 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);
public static void openByLink(Context context, String url) throws Exception {
Intent intentByLink = getIntentByLink(context, url);
if (intentByLink == null) throw new NullPointerException("getIntentByLink(context = [" + context + "], url = [" + url + "]) returned null");
intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intentByLink);
}
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
@@ -147,8 +190,7 @@ public class NavigationHelper {
case CHANNEL:
return getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
case NONE:
throw new Exception("Url not known to service. service="
+ Integer.toString(serviceId) + " url=" + url);
throw new Exception("Url not known to service. service=" + serviceId + " url=" + url);
}
return null;
}

View File

@@ -12,6 +12,8 @@ import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import static org.schabi.newpipe.report.UserAction.REQUESTED_CHANNEL;
/**
* Extract {@link ChannelInfo} with {@link ChannelExtractor} from the given url of the given service
*
@@ -67,7 +69,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
ChannelExtractor extractor = getService().getChannelExtractorInstance(url, pageNumber);
channelInfo = ChannelInfo.getInfo(extractor);
if (!channelInfo.errors.isEmpty()) handleErrorsDuringExtraction(channelInfo.errors, ErrorActivity.REQUESTED_CHANNEL);
if (!channelInfo.errors.isEmpty()) handleErrorsDuringExtraction(channelInfo.errors, REQUESTED_CHANNEL);
if (callback != null && channelInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
@Override
@@ -93,7 +95,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
}
});
} else if (exception instanceof ParsingException || exception instanceof ExtractionException) {
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.parsing_error));
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_CHANNEL, getServiceName(), url, R.string.parsing_error));
getHandler().post(new Runnable() {
@Override
public void run() {
@@ -101,7 +103,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
}
});
} else {
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.general_error));
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_CHANNEL, getServiceName(), url, R.string.general_error));
getHandler().post(new Runnable() {
@Override
public void run() {

View File

@@ -6,6 +6,7 @@ import android.util.Log;
import android.view.View;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import java.util.List;
@@ -59,14 +60,14 @@ public abstract class ExtractorWorker extends AbstractWorker {
* @param errorUserAction what action was the user performing during the error.
* (One of the {@link ErrorActivity}.REQUEST_* error (message) ids)
*/
protected void handleErrorsDuringExtraction(List<Throwable> errorsList, int errorUserAction){
protected void handleErrorsDuringExtraction(List<Throwable> errorsList, UserAction errorUserAction){
String errorString = "<error id>";
switch (errorUserAction) {
case ErrorActivity.REQUESTED_STREAM:
errorString= ErrorActivity.REQUESTED_STREAM_STRING;
case REQUESTED_STREAM:
errorString= errorUserAction.getMessage();
break;
case ErrorActivity.REQUESTED_CHANNEL:
errorString= ErrorActivity.REQUESTED_CHANNEL_STRING;
case REQUESTED_CHANNEL:
errorString= errorUserAction.getMessage();
break;
}

View File

@@ -13,10 +13,13 @@ 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 org.schabi.newpipe.report.UserAction;
import java.io.IOException;
import java.util.EnumSet;
import static org.schabi.newpipe.report.UserAction.*;
/**
* Return list of results based on a query
*
@@ -106,7 +109,7 @@ public class SearchWorker extends AbstractWorker {
});
} 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));
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(SEARCHED, getServiceName(), query, R.string.parsing_error));
getHandler().post(new Runnable() {
@Override
public void run() {
@@ -115,7 +118,7 @@ public class SearchWorker extends AbstractWorker {
});
} 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));
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(SEARCHED, getServiceName(), query, R.string.general_error));
getHandler().post(new Runnable() {
@Override
public void run() {

View File

@@ -10,9 +10,12 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import java.io.IOException;
import static org.schabi.newpipe.report.UserAction.*;
/**
* Extract {@link StreamInfo} with {@link StreamExtractor} from the given url of the given service
*
@@ -66,7 +69,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
StreamExtractor streamExtractor = getService().getExtractorInstance(url);
streamInfo = StreamInfo.getVideoInfo(streamExtractor);
if (streamInfo != null && !streamInfo.errors.isEmpty()) handleErrorsDuringExtraction(streamInfo.errors, ErrorActivity.REQUESTED_STREAM);
if (streamInfo != null && !streamInfo.errors.isEmpty()) handleErrorsDuringExtraction(streamInfo.errors, REQUESTED_STREAM);
if (callback != null && getHandler() != null && streamInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
@Override
@@ -121,7 +124,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
});
} else if (exception instanceof YoutubeStreamExtractor.DecryptException) {
// custom service related exceptions
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.youtube_signature_decryption_error));
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.youtube_signature_decryption_error));
getHandler().post(new Runnable() {
@Override
public void run() {
@@ -131,9 +134,9 @@ public class StreamExtractorWorker extends ExtractorWorker {
} else if (exception instanceof StreamInfo.StreamExctractException) {
if (!streamInfo.errors.isEmpty()) {
// !!! if this case ever kicks in someone gets kicked out !!!
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
} else {
ErrorActivity.reportError(getHandler(), getContext(), streamInfo.errors, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
ErrorActivity.reportError(getHandler(), getContext(), streamInfo.errors, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
}
getHandler().post(new Runnable() {
@@ -143,7 +146,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
}
});
} else if (exception instanceof ParsingException) {
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.parsing_error));
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.parsing_error));
getHandler().post(new Runnable() {
@Override
public void run() {
@@ -151,7 +154,7 @@ public class StreamExtractorWorker extends ExtractorWorker {
}
});
} else {
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.general_error));
ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(REQUESTED_STREAM, getServiceName(), url, R.string.general_error));
getHandler().post(new Runnable() {
@Override
public void run() {

View File

@@ -11,10 +11,13 @@ 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 org.schabi.newpipe.report.UserAction;
import java.io.IOException;
import java.util.List;
import static org.schabi.newpipe.report.UserAction.*;
/**
* Worker that get suggestions based on the query
*
@@ -79,7 +82,7 @@ public class SuggestionWorker extends AbstractWorker {
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));
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(GET_SUGGESTIONS, getServiceName(), query, R.string.parsing_error));
getHandler().post(new Runnable() {
@Override
public void run() {
@@ -95,7 +98,7 @@ public class SuggestionWorker extends AbstractWorker {
});
} 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));
ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(GET_SUGGESTIONS, getServiceName(), query, R.string.general_error));
getHandler().post(new Runnable() {
@Override
public void run() {

View File

@@ -9,12 +9,14 @@ public interface DownloadDataSource {
/**
* Load all missions
*
* @return a list of download missions
*/
List<DownloadMission> loadMissions();
/**
* Add a downlaod mission to the storage
* Add a download mission to the storage
*
* @param downloadMission the download mission to add
* @return the identifier of the mission
*/
@@ -22,6 +24,7 @@ public interface DownloadDataSource {
/**
* Update a download mission which exists in the storage
*
* @param downloadMission the download mission to update
* @throws IllegalArgumentException if the mission was not added to storage
*/
@@ -30,6 +33,7 @@ public interface DownloadDataSource {
/**
* Delete a download mission
*
* @param downloadMission the mission to delete
*/
void deleteMission(DownloadMission downloadMission);

View File

@@ -1,48 +1,53 @@
package us.shandian.giga.get;
public interface DownloadManager
{
int BLOCK_SIZE = 512 * 1024;
public interface DownloadManager {
int BLOCK_SIZE = 512 * 1024;
/**
* Start a new download mission
* @param url the url to download
* @param location the location
* @param name the name of the file to create
* @param isAudio true if the download is an audio file
* @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission.
/**
* Start a new download mission
*
* @param url the url to download
* @param location the location
* @param name the name of the file to create
* @param isAudio true if the download is an audio file
* @param threads the number of threads maximal used to download chunks of the file. @return the identifier of the mission.
*/
int startMission(String url, String location, String name, boolean isAudio, int threads);
int startMission(String url, String location, String name, boolean isAudio, int threads);
/**
* Resume the execution of a download mission.
* @param id the identifier of the mission to resume.
*/
void resumeMission(int id);
/**
* Pause the execution of a download mission.
* @param id the identifier of the mission to pause.
/**
* Resume the execution of a download mission.
*
* @param id the identifier of the mission to resume.
*/
void pauseMission(int id);
void resumeMission(int id);
/**
* Deletes the mission from the downloaded list but keeps the downloaded file.
* @param id The mission identifier
/**
* Pause the execution of a download mission.
*
* @param id the identifier of the mission to pause.
*/
void deleteMission(int id);
void pauseMission(int id);
/**
* Get the download mission by its identifier
* @param id the identifier of the download mission
* @return the download mission or null if the mission doesn't exist
/**
* Deletes the mission from the downloaded list but keeps the downloaded file.
*
* @param id The mission identifier
*/
DownloadMission getMission(int id);
void deleteMission(int id);
/**
* Get the number of download missions.
* @return the number of download missions.
/**
* Get the download mission by its identifier
*
* @param id the identifier of the download mission
* @return the download mission or null if the mission doesn't exist
*/
int getCount();
DownloadMission getMission(int id);
/**
* Get the number of download missions.
*
* @return the number of download missions.
*/
int getCount();
}

View File

@@ -18,95 +18,96 @@ import java.util.Comparator;
import java.util.List;
import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerImpl implements DownloadManager
{
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
private final DownloadDataSource mDownloadDataSource;
public class DownloadManagerImpl implements DownloadManager {
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
private final DownloadDataSource mDownloadDataSource;
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
/**
* Create a new instance
* @param searchLocations the directories to search for unfinished downloads
*
* @param searchLocations the directories to search for unfinished downloads
* @param downloadDataSource the data source for finished downloads
*/
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
mDownloadDataSource = downloadDataSource;
loadMissions(searchLocations);
}
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
mDownloadDataSource = downloadDataSource;
loadMissions(searchLocations);
}
@Override
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
DownloadMission existingMission = getMissionByLocation(location, name);
if(existingMission != null) {
// Already downloaded or downloading
if(existingMission.finished) {
// Overwrite mission
@Override
public int startMission(String url, String location, String name, boolean isAudio, int threads) {
DownloadMission existingMission = getMissionByLocation(location, name);
if (existingMission != null) {
// Already downloaded or downloading
if (existingMission.finished) {
// Overwrite mission
deleteMission(mMissions.indexOf(existingMission));
} else {
// Rename file (?)
} else {
// Rename file (?)
try {
name = generateUniqueName(location, name);
}catch (Exception e) {
} catch (Exception e) {
Log.e(TAG, "Unable to generate unique name", e);
name = System.currentTimeMillis() + name ;
name = System.currentTimeMillis() + name;
Log.i(TAG, "Using " + name);
}
}
}
}
}
DownloadMission mission = new DownloadMission(name, url, location);
mission.timestamp = System.currentTimeMillis();
mission.threadCount = threads;
mission.addListener(new MissionListener(mission));
new Initializer(mission).start();
return insertMission(mission);
}
DownloadMission mission = new DownloadMission(name, url, location);
mission.timestamp = System.currentTimeMillis();
mission.threadCount = threads;
mission.addListener(new MissionListener(mission));
new Initializer(mission).start();
return insertMission(mission);
}
@Override
public void resumeMission(int i) {
DownloadMission d = getMission(i);
if (!d.running && d.errCode == -1) {
d.start();
}
}
@Override
public void resumeMission(int i) {
DownloadMission d = getMission(i);
if (!d.running && d.errCode == -1) {
d.start();
}
}
@Override
public void pauseMission(int i) {
DownloadMission d = getMission(i);
if (d.running) {
d.pause();
}
}
@Override
public void pauseMission(int i) {
DownloadMission d = getMission(i);
if (d.running) {
d.pause();
}
}
@Override
public void deleteMission(int i) {
DownloadMission mission = getMission(i);
if(mission.finished) {
mDownloadDataSource.deleteMission(mission);
}
mission.delete();
mMissions.remove(i);
}
@Override
public void deleteMission(int i) {
DownloadMission mission = getMission(i);
if (mission.finished) {
mDownloadDataSource.deleteMission(mission);
}
mission.delete();
mMissions.remove(i);
}
private void loadMissions(Iterable<String> searchLocations) {
mMissions.clear();
loadFinishedMissions();
for(String location: searchLocations) {
loadMissions(location);
}
private void loadMissions(Iterable<String> searchLocations) {
mMissions.clear();
loadFinishedMissions();
for (String location : searchLocations) {
loadMissions(location);
}
}
}
/**
* Loads finished missions from the data source
*/
private void loadFinishedMissions() {
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
if(finishedMissions == null) {
/**
* Loads finished missions from the data source
*/
private void loadFinishedMissions() {
List<DownloadMission> finishedMissions = mDownloadDataSource.loadMissions();
if (finishedMissions == null) {
finishedMissions = new ArrayList<>();
}
// Ensure its sorted
@@ -117,251 +118,255 @@ public class DownloadManagerImpl implements DownloadManager
}
});
mMissions.ensureCapacity(mMissions.size() + finishedMissions.size());
for(DownloadMission mission: finishedMissions) {
File downloadedFile = mission.getDownloadedFile();
if(!downloadedFile.isFile()) {
if(DEBUG) {
Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath());
}
mDownloadDataSource.deleteMission(mission);
} else {
mission.length = downloadedFile.length();
mission.finished = true;
mission.running = false;
mMissions.add(mission);
}
}
}
for (DownloadMission mission : finishedMissions) {
File downloadedFile = mission.getDownloadedFile();
if (!downloadedFile.isFile()) {
if (DEBUG) {
Log.d(TAG, "downloaded file removed: " + downloadedFile.getAbsolutePath());
}
mDownloadDataSource.deleteMission(mission);
} else {
mission.length = downloadedFile.length();
mission.finished = true;
mission.running = false;
mMissions.add(mission);
}
}
}
private void loadMissions(String location) {
private void loadMissions(String location) {
File f = new File(location);
File f = new File(location);
if (f.exists() && f.isDirectory()) {
File[] subs = f.listFiles();
if (f.exists() && f.isDirectory()) {
File[] subs = f.listFiles();
if(subs == null) {
if (subs == null) {
Log.e(TAG, "listFiles() returned null");
return;
}
for (File sub : subs) {
if (sub.isFile() && sub.getName().endsWith(".giga")) {
String str = Utility.readFromFile(sub.getAbsolutePath());
if (str != null && !str.trim().equals("")) {
for (File sub : subs) {
if (sub.isFile() && sub.getName().endsWith(".giga")) {
String str = Utility.readFromFile(sub.getAbsolutePath());
if (str != null && !str.trim().equals("")) {
if (DEBUG) {
Log.d(TAG, "loading mission " + sub.getName());
Log.d(TAG, str);
}
if (DEBUG) {
Log.d(TAG, "loading mission " + sub.getName());
Log.d(TAG, str);
}
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
DownloadMission mis = new Gson().fromJson(str, DownloadMission.class);
if (mis.finished) {
if(!sub.delete()) {
if (mis.finished) {
if (!sub.delete()) {
Log.w(TAG, "Unable to delete .giga file: " + sub.getPath());
}
continue;
}
continue;
}
mis.running = false;
mis.recovered = true;
insertMission(mis);
}
}
}
}
}
@Override
public DownloadMission getMission(int i) {
return mMissions.get(i);
}
@Override
public int getCount() {
return mMissions.size();
}
private int insertMission(DownloadMission mission) {
int i = -1;
DownloadMission m = null;
if (mMissions.size() > 0) {
do {
m = mMissions.get(++i);
} while (m.timestamp > mission.timestamp && i < mMissions.size() - 1);
//if (i > 0) i--;
} else {
i = 0;
}
mMissions.add(i, mission);
return i;
}
mis.running = false;
mis.recovered = true;
insertMission(mis);
}
}
}
}
}
@Override
public DownloadMission getMission(int i) {
return mMissions.get(i);
}
@Override
public int getCount() {
return mMissions.size();
}
private int insertMission(DownloadMission mission) {
int i = -1;
DownloadMission m = null;
if (mMissions.size() > 0) {
do {
m = mMissions.get(++i);
} while (m.timestamp > mission.timestamp && i < mMissions.size() - 1);
//if (i > 0) i--;
} else {
i = 0;
}
mMissions.add(i, mission);
return i;
}
/**
* Get a mission by its location and name
* @param location the location
* @param name the name
* @return the mission or null if no such mission exists
*/
private @Nullable DownloadMission getMissionByLocation(String location, String name) {
for(DownloadMission mission: mMissions) {
if(location.equals(mission.location) && name.equals(mission.name)) {
return mission;
}
}
return null;
}
/**
* Splits the filename into name and extension
*
* Dots are ignored if they appear: not at all, at the beginning of the file,
* at the end of the file
*
* @param name the name to split
* @return a string array with a length of 2 containing the name and the extension
* Get a mission by its location and name
*
* @param location the location
* @param name the name
* @return the mission or null if no such mission exists
*/
private static String[] splitName(String name) {
int dotIndex = name.lastIndexOf('.');
if(dotIndex <= 0 || (dotIndex == name.length() - 1)) {
return new String[]{name, ""};
} else {
return new String[]{name.substring(0, dotIndex), name.substring(dotIndex + 1)};
}
}
private
@Nullable
DownloadMission getMissionByLocation(String location, String name) {
for (DownloadMission mission : mMissions) {
if (location.equals(mission.location) && name.equals(mission.name)) {
return mission;
}
}
return null;
}
/**
* Generates a unique file name.
*
* e.g. "myname (1).txt" if the name "myname.txt" exists.
* @param location the location (to check for existing files)
* @param name the name of the file
/**
* Splits the filename into name and extension
* <p>
* Dots are ignored if they appear: not at all, at the beginning of the file,
* at the end of the file
*
* @param name the name to split
* @return a string array with a length of 2 containing the name and the extension
*/
private static String[] splitName(String name) {
int dotIndex = name.lastIndexOf('.');
if (dotIndex <= 0 || (dotIndex == name.length() - 1)) {
return new String[]{name, ""};
} else {
return new String[]{name.substring(0, dotIndex), name.substring(dotIndex + 1)};
}
}
/**
* Generates a unique file name.
* <p>
* e.g. "myname (1).txt" if the name "myname.txt" exists.
*
* @param location the location (to check for existing files)
* @param name the name of the file
* @return the unique file name
* @throws IllegalArgumentException if the location is not a directory
* @throws SecurityException if the location is not readable
* @throws IllegalArgumentException if the location is not a directory
* @throws SecurityException if the location is not readable
*/
private static String generateUniqueName(String location, String name) {
if(location == null) throw new NullPointerException("location is null");
if(name == null) throw new NullPointerException("name is null");
File destination = new File(location);
if(!destination.isDirectory()) {
throw new IllegalArgumentException("location is not a directory: " + location);
}
final String[] nameParts = splitName(name);
String[] existingName = destination.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(nameParts[0]);
}
});
Arrays.sort(existingName);
String newName;
int downloadIndex = 0;
do {
newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1];
++downloadIndex;
if(downloadIndex == 1000) { // Probably an error on our side
throw new RuntimeException("Too many existing files");
}
} while (Arrays.binarySearch(existingName, newName) >= 0);
return newName;
}
private static String generateUniqueName(String location, String name) {
if (location == null) throw new NullPointerException("location is null");
if (name == null) throw new NullPointerException("name is null");
File destination = new File(location);
if (!destination.isDirectory()) {
throw new IllegalArgumentException("location is not a directory: " + location);
}
final String[] nameParts = splitName(name);
String[] existingName = destination.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(nameParts[0]);
}
});
Arrays.sort(existingName);
String newName;
int downloadIndex = 0;
do {
newName = nameParts[0] + " (" + downloadIndex + ")." + nameParts[1];
++downloadIndex;
if (downloadIndex == 1000) { // Probably an error on our side
throw new RuntimeException("Too many existing files");
}
} while (Arrays.binarySearch(existingName, newName) >= 0);
return newName;
}
private class Initializer extends Thread {
private DownloadMission mission;
public Initializer(DownloadMission mission) {
this.mission = mission;
}
@Override
public void run() {
try {
URL url = new URL(mission.url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
mission.length = conn.getContentLength();
if (mission.length <= 0) {
mission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
//mission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
return;
}
// Open again
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + (mission.length - 10) + "-" + mission.length);
if (conn.getResponseCode() != 206) {
// Fallback to single thread if no partial content support
mission.fallback = true;
if (DEBUG) {
Log.d(TAG, "falling back");
}
}
if (DEBUG) {
Log.d(TAG, "response = " + conn.getResponseCode());
}
mission.blocks = mission.length / BLOCK_SIZE;
if (mission.threadCount > mission.blocks) {
mission.threadCount = (int) mission.blocks;
}
if (mission.threadCount <= 0) {
mission.threadCount = 1;
}
if (mission.blocks * BLOCK_SIZE < mission.length) {
mission.blocks++;
}
private class Initializer extends Thread {
private DownloadMission mission;
new File(mission.location).mkdirs();
new File(mission.location + "/" + mission.name).createNewFile();
RandomAccessFile af = new RandomAccessFile(mission.location + "/" + mission.name, "rw");
af.setLength(mission.length);
af.close();
mission.start();
} catch (Exception e) {
// TODO Notify
throw new RuntimeException(e);
}
}
}
public Initializer(DownloadMission mission) {
this.mission = mission;
}
/**
* Waits for mission to finish to add it to the {@link #mDownloadDataSource}
*/
private class MissionListener implements DownloadMission.MissionListener {
private final DownloadMission mMission;
@Override
public void run() {
try {
URL url = new URL(mission.url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
mission.length = conn.getContentLength();
private MissionListener(DownloadMission mission) {
if(mission == null) throw new NullPointerException("mission is null");
// Could the mission be passed in onFinish()?
mMission = mission;
}
if (mission.length <= 0) {
mission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
//mission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
return;
}
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
}
// Open again
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + (mission.length - 10) + "-" + mission.length);
@Override
public void onFinish(DownloadMission downloadMission) {
mDownloadDataSource.addMission(mMission);
}
if (conn.getResponseCode() != 206) {
// Fallback to single thread if no partial content support
mission.fallback = true;
@Override
public void onError(DownloadMission downloadMission, int errCode) {
}
}
if (DEBUG) {
Log.d(TAG, "falling back");
}
}
if (DEBUG) {
Log.d(TAG, "response = " + conn.getResponseCode());
}
mission.blocks = mission.length / BLOCK_SIZE;
if (mission.threadCount > mission.blocks) {
mission.threadCount = (int) mission.blocks;
}
if (mission.threadCount <= 0) {
mission.threadCount = 1;
}
if (mission.blocks * BLOCK_SIZE < mission.length) {
mission.blocks++;
}
new File(mission.location).mkdirs();
new File(mission.location + "/" + mission.name).createNewFile();
RandomAccessFile af = new RandomAccessFile(mission.location + "/" + mission.name, "rw");
af.setLength(mission.length);
af.close();
mission.start();
} catch (Exception e) {
// TODO Notify
throw new RuntimeException(e);
}
}
}
/**
* Waits for mission to finish to add it to the {@link #mDownloadDataSource}
*/
private class MissionListener implements DownloadMission.MissionListener {
private final DownloadMission mMission;
private MissionListener(DownloadMission mission) {
if (mission == null) throw new NullPointerException("mission is null");
// Could the mission be passed in onFinish()?
mMission = mission;
}
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
}
@Override
public void onFinish(DownloadMission downloadMission) {
mDownloadDataSource.addMission(mMission);
}
@Override
public void onError(DownloadMission downloadMission, int errCode) {
}
}
}

View File

@@ -18,311 +18,315 @@ import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadMission
{
private static final String TAG = DownloadMission.class.getSimpleName();
public class DownloadMission {
private static final String TAG = DownloadMission.class.getSimpleName();
public interface MissionListener {
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
void onProgressUpdate(DownloadMission downloadMission, long done, long total);
void onFinish(DownloadMission downloadMission);
void onError(DownloadMission downloadMission, int errCode);
}
public static final int ERROR_SERVER_UNSUPPORTED = 206;
public static final int ERROR_UNKNOWN = 233;
public interface MissionListener {
HashMap<MissionListener, Handler> handlerStore = new HashMap<>();
/**
* The filename
*/
public String name;
void onProgressUpdate(DownloadMission downloadMission, long done, long total);
/**
* The url of the file to download
*/
public String url;
void onFinish(DownloadMission downloadMission);
/**
* The directory to store the download
*/
public String location;
void onError(DownloadMission downloadMission, int errCode);
}
/**
* Number of blocks the size of {@link DownloadManager#BLOCK_SIZE}
*/
public long blocks;
public static final int ERROR_SERVER_UNSUPPORTED = 206;
public static final int ERROR_UNKNOWN = 233;
/**
* Number of bytes
*/
public long length;
/**
* Number of bytes downloaded
*/
public long done;
public int threadCount = 3;
public int finishCount;
private List<Long> threadPositions = new ArrayList<Long>();
public final Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
public boolean running;
public boolean finished;
public boolean fallback;
public int errCode = -1;
public long timestamp;
public transient boolean recovered;
private transient ArrayList<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>();
private transient boolean mWritingToFile;
private static final int NO_IDENTIFIER = -1;
private long db_identifier = NO_IDENTIFIER;
public DownloadMission() {
}
public DownloadMission(String name, String url, String location) {
if(name == null) throw new NullPointerException("name is null");
if(name.isEmpty()) throw new IllegalArgumentException("name is empty");
if(url == null) throw new NullPointerException("url is null");
if(url.isEmpty()) throw new IllegalArgumentException("url is empty");
if(location == null) throw new NullPointerException("location is null");
if(location.isEmpty()) throw new IllegalArgumentException("location is empty");
this.url = url;
this.name = name;
this.location = location;
}
private void checkBlock(long block) {
if(block < 0 || block >= blocks) {
throw new IllegalArgumentException("illegal block identifier");
}
}
/**
* Check if a block is reserved
* @param block the block identifier
* @return true if the block is reserved and false if otherwise
/**
* The filename
*/
public boolean isBlockPreserved(long block) {
checkBlock(block);
return blockState.containsKey(block) ? blockState.get(block) : false;
}
public void preserveBlock(long block) {
checkBlock(block);
synchronized (blockState) {
blockState.put(block, true);
}
}
public String name;
/**
* Set the download position of the file
* @param threadId the identifier of the thread
* @param position the download position of the thread
/**
* The url of the file to download
*/
public void setPosition(int threadId, long position) {
threadPositions.set(threadId, position);
}
public String url;
/**
* Get the position of a thread
* @param threadId the identifier of the thread
* @return the position for the thread
/**
* The directory to store the download
*/
public long getPosition(int threadId) {
return threadPositions.get(threadId);
}
public synchronized void notifyProgress(long deltaLen) {
if (!running) return;
if (recovered) {
recovered = false;
}
done += deltaLen;
if (done > length) {
done = length;
}
if (done != length) {
writeThisToFile();
}
for (WeakReference<MissionListener> ref: mListeners) {
final MissionListener listener = ref.get();
if (listener != null) {
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onProgressUpdate(DownloadMission.this, done, length);
}
});
}
}
}
public String location;
/**
* Called by a download thread when it finished.
*/
public synchronized void notifyFinished() {
if (errCode > 0) return;
finishCount++;
if (finishCount == threadCount) {
onFinish();
}
}
/**
* Called when all parts are downloaded
*/
private void onFinish() {
if (errCode > 0) return;
if (DEBUG) {
Log.d(TAG, "onFinish");
}
running = false;
finished = true;
deleteThisFromFile();
for (WeakReference<MissionListener> ref : mListeners) {
final MissionListener listener = ref.get();
if (listener != null) {
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onFinish(DownloadMission.this);
}
});
}
}
}
public synchronized void notifyError(int err) {
errCode = err;
writeThisToFile();
for (WeakReference<MissionListener> ref : mListeners) {
final MissionListener listener = ref.get();
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onError(DownloadMission.this, errCode);
}
});
}
}
public synchronized void addListener(MissionListener listener) {
Handler handler = new Handler(Looper.getMainLooper());
MissionListener.handlerStore.put(listener, handler);
mListeners.add(new WeakReference<MissionListener>(listener));
}
public synchronized void removeListener(MissionListener listener) {
for (Iterator<WeakReference<MissionListener>> iterator = mListeners.iterator();
iterator.hasNext(); ) {
WeakReference<MissionListener> weakRef = iterator.next();
if (listener!=null && listener == weakRef.get())
{
iterator.remove();
}
}
}
/**
* Start downloading with multiple threads.
*/
public void start() {
if (!running && !finished) {
running = true;
if (!fallback) {
for (int i = 0; i < threadCount; i++) {
if (threadPositions.size() <= i && !recovered) {
threadPositions.add((long) i);
}
new Thread(new DownloadRunnable(this, i)).start();
}
} else {
// In fallback mode, resuming is not supported.
threadCount = 1;
done = 0;
blocks = 0;
new Thread(new DownloadRunnableFallback(this)).start();
}
}
}
public void pause() {
if (running) {
running = false;
recovered = true;
// TODO: Notify & Write state to info file
// if (err)
}
}
/**
* Removes the file and the meta file
*/
public void delete() {
deleteThisFromFile();
new File(location, name).delete();
}
/**
* Write this {@link DownloadMission} to the meta file asynchronously
* if no thread is already running.
*/
public void writeThisToFile() {
if (!mWritingToFile) {
mWritingToFile = true;
new Thread() {
@Override
public void run() {
doWriteThisToFile();
mWritingToFile = false;
}
}.start();
}
}
/**
* Write this {@link DownloadMission} to the meta file.
*/
private void doWriteThisToFile() {
synchronized (blockState) {
Utility.writeToFile(getMetaFilename(), new Gson().toJson(this));
}
}
private void deleteThisFromFile() {
new File(getMetaFilename()).delete();
}
/**
* Get the path of the meta file
* @return the path to the meta file
/**
* Number of blocks the size of {@link DownloadManager#BLOCK_SIZE}
*/
private String getMetaFilename() {
return location + "/" + name + ".giga";
}
public long blocks;
public File getDownloadedFile() {
return new File(location, name);
}
/**
* Number of bytes
*/
public long length;
/**
* Number of bytes downloaded
*/
public long done;
public int threadCount = 3;
public int finishCount;
private List<Long> threadPositions = new ArrayList<Long>();
public final Map<Long, Boolean> blockState = new HashMap<Long, Boolean>();
public boolean running;
public boolean finished;
public boolean fallback;
public int errCode = -1;
public long timestamp;
public transient boolean recovered;
private transient ArrayList<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>();
private transient boolean mWritingToFile;
private static final int NO_IDENTIFIER = -1;
private long db_identifier = NO_IDENTIFIER;
public DownloadMission() {
}
public DownloadMission(String name, String url, String location) {
if (name == null) throw new NullPointerException("name is null");
if (name.isEmpty()) throw new IllegalArgumentException("name is empty");
if (url == null) throw new NullPointerException("url is null");
if (url.isEmpty()) throw new IllegalArgumentException("url is empty");
if (location == null) throw new NullPointerException("location is null");
if (location.isEmpty()) throw new IllegalArgumentException("location is empty");
this.url = url;
this.name = name;
this.location = location;
}
private void checkBlock(long block) {
if (block < 0 || block >= blocks) {
throw new IllegalArgumentException("illegal block identifier");
}
}
/**
* Check if a block is reserved
*
* @param block the block identifier
* @return true if the block is reserved and false if otherwise
*/
public boolean isBlockPreserved(long block) {
checkBlock(block);
return blockState.containsKey(block) ? blockState.get(block) : false;
}
public void preserveBlock(long block) {
checkBlock(block);
synchronized (blockState) {
blockState.put(block, true);
}
}
/**
* Set the download position of the file
*
* @param threadId the identifier of the thread
* @param position the download position of the thread
*/
public void setPosition(int threadId, long position) {
threadPositions.set(threadId, position);
}
/**
* Get the position of a thread
*
* @param threadId the identifier of the thread
* @return the position for the thread
*/
public long getPosition(int threadId) {
return threadPositions.get(threadId);
}
public synchronized void notifyProgress(long deltaLen) {
if (!running) return;
if (recovered) {
recovered = false;
}
done += deltaLen;
if (done > length) {
done = length;
}
if (done != length) {
writeThisToFile();
}
for (WeakReference<MissionListener> ref : mListeners) {
final MissionListener listener = ref.get();
if (listener != null) {
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onProgressUpdate(DownloadMission.this, done, length);
}
});
}
}
}
/**
* Called by a download thread when it finished.
*/
public synchronized void notifyFinished() {
if (errCode > 0) return;
finishCount++;
if (finishCount == threadCount) {
onFinish();
}
}
/**
* Called when all parts are downloaded
*/
private void onFinish() {
if (errCode > 0) return;
if (DEBUG) {
Log.d(TAG, "onFinish");
}
running = false;
finished = true;
deleteThisFromFile();
for (WeakReference<MissionListener> ref : mListeners) {
final MissionListener listener = ref.get();
if (listener != null) {
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onFinish(DownloadMission.this);
}
});
}
}
}
public synchronized void notifyError(int err) {
errCode = err;
writeThisToFile();
for (WeakReference<MissionListener> ref : mListeners) {
final MissionListener listener = ref.get();
MissionListener.handlerStore.get(listener).post(new Runnable() {
@Override
public void run() {
listener.onError(DownloadMission.this, errCode);
}
});
}
}
public synchronized void addListener(MissionListener listener) {
Handler handler = new Handler(Looper.getMainLooper());
MissionListener.handlerStore.put(listener, handler);
mListeners.add(new WeakReference<MissionListener>(listener));
}
public synchronized void removeListener(MissionListener listener) {
for (Iterator<WeakReference<MissionListener>> iterator = mListeners.iterator();
iterator.hasNext(); ) {
WeakReference<MissionListener> weakRef = iterator.next();
if (listener != null && listener == weakRef.get()) {
iterator.remove();
}
}
}
/**
* Start downloading with multiple threads.
*/
public void start() {
if (!running && !finished) {
running = true;
if (!fallback) {
for (int i = 0; i < threadCount; i++) {
if (threadPositions.size() <= i && !recovered) {
threadPositions.add((long) i);
}
new Thread(new DownloadRunnable(this, i)).start();
}
} else {
// In fallback mode, resuming is not supported.
threadCount = 1;
done = 0;
blocks = 0;
new Thread(new DownloadRunnableFallback(this)).start();
}
}
}
public void pause() {
if (running) {
running = false;
recovered = true;
// TODO: Notify & Write state to info file
// if (err)
}
}
/**
* Removes the file and the meta file
*/
public void delete() {
deleteThisFromFile();
new File(location, name).delete();
}
/**
* Write this {@link DownloadMission} to the meta file asynchronously
* if no thread is already running.
*/
public void writeThisToFile() {
if (!mWritingToFile) {
mWritingToFile = true;
new Thread() {
@Override
public void run() {
doWriteThisToFile();
mWritingToFile = false;
}
}.start();
}
}
/**
* Write this {@link DownloadMission} to the meta file.
*/
private void doWriteThisToFile() {
synchronized (blockState) {
Utility.writeToFile(getMetaFilename(), new Gson().toJson(this));
}
}
private void deleteThisFromFile() {
new File(getMetaFilename()).delete();
}
/**
* Get the path of the meta file
*
* @return the path to the meta file
*/
private String getMetaFilename() {
return location + "/" + name + ".giga";
}
public File getDownloadedFile() {
return new File(location, name);
}
}

View File

@@ -13,166 +13,165 @@ import static org.schabi.newpipe.BuildConfig.DEBUG;
* Runnable to download blocks of a file until the file is completely downloaded,
* an error occurs or the process is stopped.
*/
public class DownloadRunnable implements Runnable
{
private static final String TAG = DownloadRunnable.class.getSimpleName();
private final DownloadMission mMission;
private final int mId;
public DownloadRunnable(DownloadMission mission, int id) {
if(mission == null) throw new NullPointerException("mission is null");
mMission = mission;
mId = id;
}
@Override
public void run() {
boolean retry = mMission.recovered;
long position = mMission.getPosition(mId);
if (DEBUG) {
Log.d(TAG, mId + ":default pos " + position);
Log.d(TAG, mId + ":recovered: " + mMission.recovered);
}
while (mMission.errCode == -1 && mMission.running && position < mMission.blocks) {
if (Thread.currentThread().isInterrupted()) {
mMission.pause();
return;
}
if (DEBUG && retry) {
Log.d(TAG, mId + ":retry is true. Resuming at " + position);
}
// Wait for an unblocked position
while (!retry && position < mMission.blocks && mMission.isBlockPreserved(position)) {
if (DEBUG) {
Log.d(TAG, mId + ":position " + position + " preserved, passing");
}
position++;
}
retry = false;
if (position >= mMission.blocks) {
break;
}
if (DEBUG) {
Log.d(TAG, mId + ":preserving position " + position);
}
mMission.preserveBlock(position);
mMission.setPosition(mId, position);
long start = position * DownloadManager.BLOCK_SIZE;
long end = start + DownloadManager.BLOCK_SIZE - 1;
if (end >= mMission.length) {
end = mMission.length - 1;
}
HttpURLConnection conn = null;
int total = 0;
try {
URL url = new URL(mMission.url);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
if (DEBUG) {
Log.d(TAG, mId + ":" + conn.getRequestProperty("Range"));
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
}
// A server may be ignoring the range request
if (conn.getResponseCode() != 206) {
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
if (DEBUG) {
Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode());
}
break;
}
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
f.seek(start);
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
byte[] buf = new byte[512];
while (start < end && mMission.running) {
int len = ipt.read(buf, 0, 512);
if (len == -1) {
break;
} else {
start += len;
total += len;
f.write(buf, 0, len);
notifyProgress(len);
}
}
if (DEBUG && mMission.running) {
Log.d(TAG, mId + ":position " + position + " finished, total length " + total);
}
f.close();
ipt.close();
// TODO We should save progress for each thread
} catch (Exception e) {
// TODO Retry count limit & notify error
retry = true;
notifyProgress(-total);
if (DEBUG) {
Log.d(TAG, mId + ":position " + position + " retrying", e);
}
}
}
if (DEBUG) {
Log.d(TAG, "thread " + mId + " exited main loop");
}
if (mMission.errCode == -1 && mMission.running) {
if (DEBUG) {
Log.d(TAG, "no error has happened, notifying");
}
notifyFinished();
}
if (DEBUG && !mMission.running) {
Log.d(TAG, "The mission has been paused. Passing.");
}
}
private void notifyProgress(final long len) {
synchronized (mMission) {
mMission.notifyProgress(len);
}
}
private void notifyError(final int err) {
synchronized (mMission) {
mMission.notifyError(err);
mMission.pause();
}
}
private void notifyFinished() {
synchronized (mMission) {
mMission.notifyFinished();
}
}
public class DownloadRunnable implements Runnable {
private static final String TAG = DownloadRunnable.class.getSimpleName();
private final DownloadMission mMission;
private final int mId;
public DownloadRunnable(DownloadMission mission, int id) {
if (mission == null) throw new NullPointerException("mission is null");
mMission = mission;
mId = id;
}
@Override
public void run() {
boolean retry = mMission.recovered;
long position = mMission.getPosition(mId);
if (DEBUG) {
Log.d(TAG, mId + ":default pos " + position);
Log.d(TAG, mId + ":recovered: " + mMission.recovered);
}
while (mMission.errCode == -1 && mMission.running && position < mMission.blocks) {
if (Thread.currentThread().isInterrupted()) {
mMission.pause();
return;
}
if (DEBUG && retry) {
Log.d(TAG, mId + ":retry is true. Resuming at " + position);
}
// Wait for an unblocked position
while (!retry && position < mMission.blocks && mMission.isBlockPreserved(position)) {
if (DEBUG) {
Log.d(TAG, mId + ":position " + position + " preserved, passing");
}
position++;
}
retry = false;
if (position >= mMission.blocks) {
break;
}
if (DEBUG) {
Log.d(TAG, mId + ":preserving position " + position);
}
mMission.preserveBlock(position);
mMission.setPosition(mId, position);
long start = position * DownloadManager.BLOCK_SIZE;
long end = start + DownloadManager.BLOCK_SIZE - 1;
if (end >= mMission.length) {
end = mMission.length - 1;
}
HttpURLConnection conn = null;
int total = 0;
try {
URL url = new URL(mMission.url);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
if (DEBUG) {
Log.d(TAG, mId + ":" + conn.getRequestProperty("Range"));
Log.d(TAG, mId + ":Content-Length=" + conn.getContentLength() + " Code:" + conn.getResponseCode());
}
// A server may be ignoring the range request
if (conn.getResponseCode() != 206) {
mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED;
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
if (DEBUG) {
Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode());
}
break;
}
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
f.seek(start);
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
byte[] buf = new byte[512];
while (start < end && mMission.running) {
int len = ipt.read(buf, 0, 512);
if (len == -1) {
break;
} else {
start += len;
total += len;
f.write(buf, 0, len);
notifyProgress(len);
}
}
if (DEBUG && mMission.running) {
Log.d(TAG, mId + ":position " + position + " finished, total length " + total);
}
f.close();
ipt.close();
// TODO We should save progress for each thread
} catch (Exception e) {
// TODO Retry count limit & notify error
retry = true;
notifyProgress(-total);
if (DEBUG) {
Log.d(TAG, mId + ":position " + position + " retrying", e);
}
}
}
if (DEBUG) {
Log.d(TAG, "thread " + mId + " exited main loop");
}
if (mMission.errCode == -1 && mMission.running) {
if (DEBUG) {
Log.d(TAG, "no error has happened, notifying");
}
notifyFinished();
}
if (DEBUG && !mMission.running) {
Log.d(TAG, "The mission has been paused. Passing.");
}
}
private void notifyProgress(final long len) {
synchronized (mMission) {
mMission.notifyProgress(len);
}
}
private void notifyError(final int err) {
synchronized (mMission) {
mMission.notifyError(err);
mMission.pause();
}
}
private void notifyFinished() {
synchronized (mMission) {
mMission.notifyFinished();
}
}
}

View File

@@ -6,70 +6,69 @@ import java.net.HttpURLConnection;
import java.net.URL;
// Single-threaded fallback mode
public class DownloadRunnableFallback implements Runnable
{
private final DownloadMission mMission;
//private int mId;
public DownloadRunnableFallback(DownloadMission mission) {
if(mission == null) throw new NullPointerException("mission is null");
//mId = id;
mMission = mission;
}
public class DownloadRunnableFallback implements Runnable {
private final DownloadMission mMission;
//private int mId;
@Override
public void run() {
try {
URL url = new URL(mMission.url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() != 200 && conn.getResponseCode() != 206) {
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
} else {
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
f.seek(0);
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
byte[] buf = new byte[512];
int len = 0;
while ((len = ipt.read(buf, 0, 512)) != -1 && mMission.running) {
f.write(buf, 0, len);
notifyProgress(len);
if (Thread.interrupted()) {
break;
}
}
f.close();
ipt.close();
}
} catch (Exception e) {
notifyError(DownloadMission.ERROR_UNKNOWN);
}
if (mMission.errCode == -1 && mMission.running) {
notifyFinished();
}
}
private void notifyProgress(final long len) {
synchronized (mMission) {
mMission.notifyProgress(len);
}
}
private void notifyError(final int err) {
synchronized (mMission) {
mMission.notifyError(err);
mMission.pause();
}
}
public DownloadRunnableFallback(DownloadMission mission) {
if (mission == null) throw new NullPointerException("mission is null");
//mId = id;
mMission = mission;
}
private void notifyFinished() {
synchronized (mMission) {
mMission.notifyFinished();
}
}
@Override
public void run() {
try {
URL url = new URL(mMission.url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() != 200 && conn.getResponseCode() != 206) {
notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED);
} else {
RandomAccessFile f = new RandomAccessFile(mMission.location + "/" + mMission.name, "rw");
f.seek(0);
BufferedInputStream ipt = new BufferedInputStream(conn.getInputStream());
byte[] buf = new byte[512];
int len = 0;
while ((len = ipt.read(buf, 0, 512)) != -1 && mMission.running) {
f.write(buf, 0, len);
notifyProgress(len);
if (Thread.interrupted()) {
break;
}
}
f.close();
ipt.close();
}
} catch (Exception e) {
notifyError(DownloadMission.ERROR_UNKNOWN);
}
if (mMission.errCode == -1 && mMission.running) {
notifyFinished();
}
}
private void notifyProgress(final long len) {
synchronized (mMission) {
mMission.notifyProgress(len);
}
}
private void notifyError(final int err) {
synchronized (mMission) {
mMission.notifyError(err);
mMission.pause();
}
}
private void notifyFinished() {
synchronized (mMission) {
mMission.notifyFinished();
}
}
}

View File

@@ -64,6 +64,7 @@ public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper {
/**
* Returns all values of the download mission as ContentValues.
*
* @param downloadMission the download mission
* @return the content values
*/
@@ -88,7 +89,7 @@ public class DownloadMissionSQLiteHelper extends SQLiteOpenHelper {
}
public static DownloadMission getMissionFromCursor(Cursor cursor) {
if(cursor == null) throw new NullPointerException("cursor is null");
if (cursor == null) throw new NullPointerException("cursor is null");
int pos;
String name = cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME));
String location = cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION));

View File

@@ -37,7 +37,7 @@ public class SQLiteDownloadDataSource implements DownloadDataSource {
null, null, null, DownloadMissionSQLiteHelper.KEY_TIMESTAMP);
int count = cursor.getCount();
if(count == 0) return new ArrayList<>();
if (count == 0) return new ArrayList<>();
result = new ArrayList<>(count);
while (cursor.moveToNext()) {
result.add(DownloadMissionSQLiteHelper.getMissionFromCursor(cursor));
@@ -47,7 +47,7 @@ public class SQLiteDownloadDataSource implements DownloadDataSource {
@Override
public void addMission(DownloadMission downloadMission) {
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
if (downloadMission == null) throw new NullPointerException("downloadMission is null");
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
database.insert(MISSIONS_TABLE_NAME, null, values);
@@ -55,25 +55,25 @@ public class SQLiteDownloadDataSource implements DownloadDataSource {
@Override
public void updateMission(DownloadMission downloadMission) {
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
if (downloadMission == null) throw new NullPointerException("downloadMission is null");
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
ContentValues values = DownloadMissionSQLiteHelper.getValuesOfMission(downloadMission);
String whereClause = KEY_LOCATION+ " = ? AND " +
String whereClause = KEY_LOCATION + " = ? AND " +
KEY_NAME + " = ?";
int rowsAffected = database.update(MISSIONS_TABLE_NAME, values,
whereClause, new String[]{downloadMission.location, downloadMission.name});
if(rowsAffected != 1) {
if (rowsAffected != 1) {
Log.e(TAG, "Expected 1 row to be affected by update but got " + rowsAffected);
}
}
@Override
public void deleteMission(DownloadMission downloadMission) {
if(downloadMission == null) throw new NullPointerException("downloadMission is null");
if (downloadMission == null) throw new NullPointerException("downloadMission is null");
SQLiteDatabase database = downloadMissionSQLiteHelper.getWritableDatabase();
database.delete(MISSIONS_TABLE_NAME,
KEY_LOCATION + " = ? AND " +
KEY_NAME + " = ?",
KEY_NAME + " = ?",
new String[]{downloadMission.location, downloadMission.name});
}
}

View File

@@ -6,7 +6,6 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -16,6 +15,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import android.widget.Toast;
@@ -34,237 +34,227 @@ import us.shandian.giga.get.sqlite.SQLiteDownloadDataSource;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerService extends Service
{
public class DownloadManagerService extends Service {
private static final String TAG = DownloadManagerService.class.getSimpleName();
private static final String TAG = DownloadManagerService.class.getSimpleName();
/**
* Message code of update messages stored as {@link Message#what}.
*/
private static final int UPDATE_MESSAGE = 0;
private static final int NOTIFICATION_ID = 1000;
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location";
private static final String EXTRA_IS_AUDIO = "DownloadManagerService.extra.is_audio";
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
/**
* Message code of update messages stored as {@link Message#what}.
*/
private static final int UPDATE_MESSAGE = 0;
private static final int NOTIFICATION_ID = 1000;
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location";
private static final String EXTRA_IS_AUDIO = "DownloadManagerService.extra.is_audio";
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
private DMBinder mBinder;
private DownloadManager mManager;
private Notification mNotification;
private Handler mHandler;
private long mLastTimeStamp = System.currentTimeMillis();
private DownloadDataSource mDataSource;
private DMBinder mBinder;
private DownloadManager mManager;
private Notification mNotification;
private Handler mHandler;
private long mLastTimeStamp = System.currentTimeMillis();
private DownloadDataSource mDataSource;
private MissionListener missionListener = new MissionListener();
private MissionListener missionListener = new MissionListener();
private void notifyMediaScanner(DownloadMission mission) {
Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name);
// notify media scanner on downloaded media file ...
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
}
private void notifyMediaScanner(DownloadMission mission) {
Uri uri = Uri.parse("file://" + mission.location + "/" + mission.name);
// notify media scanner on downloaded media file ...
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
}
@Override
public void onCreate() {
super.onCreate();
@Override
public void onCreate() {
super.onCreate();
if (DEBUG) {
Log.d(TAG, "onCreate");
}
if (DEBUG) {
Log.d(TAG, "onCreate");
}
mBinder = new DMBinder();
if(mDataSource == null) {
mDataSource = new SQLiteDownloadDataSource(this);
}
if (mManager == null) {
ArrayList<String> paths = new ArrayList<>(2);
paths.add(NewPipeSettings.getVideoDownloadPath(this));
paths.add(NewPipeSettings.getAudioDownloadPath(this));
mManager = new DownloadManagerImpl(paths, mDataSource);
if (DEBUG) {
Log.d(TAG, "mManager == null");
Log.d(TAG, "Download directory: " + paths);
}
}
mBinder = new DMBinder();
if (mDataSource == null) {
mDataSource = new SQLiteDownloadDataSource(this);
}
if (mManager == null) {
ArrayList<String> paths = new ArrayList<>(2);
paths.add(NewPipeSettings.getVideoDownloadPath(this));
paths.add(NewPipeSettings.getAudioDownloadPath(this));
mManager = new DownloadManagerImpl(paths, mDataSource);
if (DEBUG) {
Log.d(TAG, "mManager == null");
Log.d(TAG, "Download directory: " + paths);
}
}
Intent i = new Intent();
i.setAction(Intent.ACTION_MAIN);
i.setClass(this, DownloadActivity.class);
Intent openDownloadListIntent = new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN);
Drawable icon = this.getResources().getDrawable(R.mipmap.ic_launcher);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
openDownloadListIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Builder builder = new Builder(this)
.setContentIntent(PendingIntent.getActivity(this, 0, i, 0))
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
.setContentTitle(getString(R.string.msg_running))
.setContentText(getString(R.string.msg_running_detail));
Drawable icon = ContextCompat.getDrawable(this, R.mipmap.ic_launcher);
PendingIntent pendingIntent =
PendingIntent.getActivity(
this,
0,
new Intent(this, DownloadActivity.class)
.setAction(DownloadActivity.INTENT_LIST),
PendingIntent.FLAG_UPDATE_CURRENT
);
Builder builder = new Builder(this)
.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
.setContentTitle(getString(R.string.msg_running))
.setContentText(getString(R.string.msg_running_detail));
builder.setContentIntent(pendingIntent);
mNotification = builder.build();
mNotification = builder.build();
HandlerThread thread = new HandlerThread("ServiceMessenger");
thread.start();
HandlerThread thread = new HandlerThread("ServiceMessenger");
thread.start();
mHandler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_MESSAGE: {
int runningCount = 0;
mHandler = new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_MESSAGE: {
int runningCount = 0;
for (int i = 0; i < mManager.getCount(); i++) {
if (mManager.getMission(i).running) {
runningCount++;
}
}
updateState(runningCount);
break;
}
}
}
};
}
for (int i = 0; i < mManager.getCount(); i++) {
if (mManager.getMission(i).running) {
runningCount++;
}
}
updateState(runningCount);
break;
}
}
}
};
private void startMissionAsync(final String url, final String location, final String name,
final boolean isAudio, final int threads) {
mHandler.post(new Runnable() {
@Override
public void run() {
int missionId = mManager.startMission(url, location, name, isAudio, threads);
mBinder.onMissionAdded(mManager.getMission(missionId));
}
});
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) {
Log.d(TAG, "Starting");
}
Log.i(TAG, "Got intent: " + intent);
String action = intent.getAction();
if(action != null && action.equals(Intent.ACTION_RUN)) {
String name = intent.getStringExtra(EXTRA_NAME);
String location = intent.getStringExtra(EXTRA_LOCATION);
int threads = intent.getIntExtra(EXTRA_THREADS, 1);
boolean isAudio = intent.getBooleanExtra(EXTRA_IS_AUDIO, false);
String url = intent.getDataString();
startMissionAsync(url, location, name, isAudio, threads);
}
return START_NOT_STICKY;
}
private void startMissionAsync(final String url, final String location, final String name,
final boolean isAudio, final int threads) {
mHandler.post(new Runnable() {
@Override
public void run() {
int missionId = mManager.startMission(url, location, name, isAudio, threads);
mBinder.onMissionAdded(mManager.getMission(missionId));
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (DEBUG) {
Log.d(TAG, "Destroying");
}
for (int i = 0; i < mManager.getCount(); i++) {
mManager.pauseMission(i);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) {
Log.d(TAG, "Starting");
}
Log.i(TAG, "Got intent: " + intent);
String action = intent.getAction();
if (action != null && action.equals(Intent.ACTION_RUN)) {
String name = intent.getStringExtra(EXTRA_NAME);
String location = intent.getStringExtra(EXTRA_LOCATION);
int threads = intent.getIntExtra(EXTRA_THREADS, 1);
boolean isAudio = intent.getBooleanExtra(EXTRA_IS_AUDIO, false);
String url = intent.getDataString();
startMissionAsync(url, location, name, isAudio, threads);
}
return START_NOT_STICKY;
}
stopForeground(true);
}
@Override
public IBinder onBind(Intent intent) {
int permissionCheck;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onDestroy() {
super.onDestroy();
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show();
}
if (DEBUG) {
Log.d(TAG, "Destroying");
}
return mBinder;
}
for (int i = 0; i < mManager.getCount(); i++) {
mManager.pauseMission(i);
}
private void postUpdateMessage() {
mHandler.sendEmptyMessage(UPDATE_MESSAGE);
}
private void updateState(int runningCount) {
if (runningCount == 0) {
stopForeground(true);
} else {
startForeground(NOTIFICATION_ID, mNotification);
}
}
stopForeground(true);
}
public static void startMission(Context context, String url, String location, String name, boolean isAudio, int threads) {
Intent intent = new Intent(context, DownloadManagerService.class);
intent.setAction(Intent.ACTION_RUN);
intent.setData(Uri.parse(url));
intent.putExtra(EXTRA_NAME, name);
intent.putExtra(EXTRA_LOCATION, location);
intent.putExtra(EXTRA_IS_AUDIO, isAudio);
intent.putExtra(EXTRA_THREADS, threads);
context.startService(intent);
}
@Override
public IBinder onBind(Intent intent) {
int permissionCheck;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if (permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show();
}
}
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show();
}
return mBinder;
}
private void postUpdateMessage() {
mHandler.sendEmptyMessage(UPDATE_MESSAGE);
}
private void updateState(int runningCount) {
if (runningCount == 0) {
stopForeground(true);
} else {
startForeground(NOTIFICATION_ID, mNotification);
}
}
public static void startMission(Context context, String url, String location, String name, boolean isAudio, int threads) {
Intent intent = new Intent(context, DownloadManagerService.class);
intent.setAction(Intent.ACTION_RUN);
intent.setData(Uri.parse(url));
intent.putExtra(EXTRA_NAME, name);
intent.putExtra(EXTRA_LOCATION, location);
intent.putExtra(EXTRA_IS_AUDIO, isAudio);
intent.putExtra(EXTRA_THREADS, threads);
context.startService(intent);
}
class MissionListener implements DownloadMission.MissionListener {
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
long now = System.currentTimeMillis();
long delta = now - mLastTimeStamp;
if (delta > 2000) {
postUpdateMessage();
mLastTimeStamp = now;
}
}
private class MissionListener implements DownloadMission.MissionListener {
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
long now = System.currentTimeMillis();
long delta = now - mLastTimeStamp;
if (delta > 2000) {
postUpdateMessage();
mLastTimeStamp = now;
}
}
@Override
public void onFinish(DownloadMission downloadMission) {
postUpdateMessage();
notifyMediaScanner(downloadMission);
}
@Override
public void onFinish(DownloadMission downloadMission) {
postUpdateMessage();
notifyMediaScanner(downloadMission);
}
@Override
public void onError(DownloadMission downloadMission, int errCode) {
postUpdateMessage();
}
}
@Override
public void onError(DownloadMission downloadMission, int errCode) {
postUpdateMessage();
}
}
// Wrapper of DownloadManager
public class DMBinder extends Binder {
public DownloadManager getDownloadManager() {
return mManager;
}
public void onMissionAdded(DownloadMission mission) {
mission.addListener(missionListener);
postUpdateMessage();
}
public void onMissionRemoved(DownloadMission mission) {
mission.removeListener(missionListener);
postUpdateMessage();
}
}
// Wrapper of DownloadManager
public class DMBinder extends Binder {
public DownloadManager getDownloadManager() {
return mManager;
}
public void onMissionAdded(DownloadMission mission) {
mission.addListener(missionListener);
postUpdateMessage();
}
public void onMissionRemoved(DownloadMission mission) {
mission.removeListener(missionListener);
postUpdateMessage();
}
}
}

View File

@@ -7,6 +7,8 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.support.v4.content.FileProvider;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -18,14 +20,13 @@ import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.support.v7.widget.RecyclerView;
import org.schabi.newpipe.R;
import java.io.File;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.service.DownloadManagerService;
@@ -35,341 +36,340 @@ import us.shandian.giga.util.Utility;
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder>
{
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
private static final String TAG = "MissionAdapter";
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder> {
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
private static final String TAG = "MissionAdapter";
static {
ALGORITHMS.put(R.id.md5, "MD5");
ALGORITHMS.put(R.id.sha1, "SHA1");
}
private Context mContext;
private LayoutInflater mInflater;
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private int mLayout;
public MissionAdapter(Context context, DownloadManagerService.DMBinder binder, DownloadManager manager, boolean isLinear) {
mContext = context;
mManager = manager;
mBinder = binder;
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
}
static {
ALGORITHMS.put(R.id.md5, "MD5");
ALGORITHMS.put(R.id.sha1, "SHA1");
}
private Context mContext;
private LayoutInflater mInflater;
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private int mLayout;
public MissionAdapter(Context context, DownloadManagerService.DMBinder binder, DownloadManager manager, boolean isLinear) {
mContext = context;
mManager = manager;
mBinder = binder;
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
}
@Override
public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false));
h.menu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buildPopup(h);
}
});
@Override
public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false));
h.menu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
buildPopup(h);
}
});
/*h.itemView.setOnClickListener(new View.OnClickListener() {
@Override
@Override
public void onClick(View v) {
showDetail(h);
}
});*/
return h;
}
@Override
public void onViewRecycled(MissionAdapter.ViewHolder h) {
super.onViewRecycled(h);
h.mission.removeListener(h.observer);
h.mission = null;
h.observer = null;
h.progress = null;
h.position = -1;
h.lastTimeStamp = -1;
h.lastDone = -1;
h.colorId = 0;
}
return h;
}
@Override
public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) {
DownloadMission ms = mManager.getMission(pos);
h.mission = ms;
h.position = pos;
Utility.FileType type = Utility.getFileType(ms.name);
h.icon.setImageResource(Utility.getIconForFileType(type));
h.name.setText(ms.name);
h.size.setText(Utility.formatBytes(ms.length));
h.progress = new ProgressDrawable(mContext, Utility.getBackgroundForFileType(type), Utility.getForegroundForFileType(type));
h.bkg.setBackgroundDrawable(h.progress);
h.observer = new MissionObserver(this, h);
ms.addListener(h.observer);
updateProgress(h);
}
@Override
public void onViewRecycled(MissionAdapter.ViewHolder h) {
super.onViewRecycled(h);
h.mission.removeListener(h.observer);
h.mission = null;
h.observer = null;
h.progress = null;
h.position = -1;
h.lastTimeStamp = -1;
h.lastDone = -1;
h.colorId = 0;
}
@Override
public int getItemCount() {
return mManager.getCount();
}
@Override
public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) {
DownloadMission ms = mManager.getMission(pos);
h.mission = ms;
h.position = pos;
@Override
public long getItemId(int position) {
return position;
}
private void updateProgress(ViewHolder h) {
updateProgress(h, false);
}
private void updateProgress(ViewHolder h, boolean finished) {
if (h.mission == null) return;
long now = System.currentTimeMillis();
if (h.lastTimeStamp == -1) {
h.lastTimeStamp = now;
}
if (h.lastDone == -1) {
h.lastDone = h.mission.done;
}
long deltaTime = now - h.lastTimeStamp;
long deltaDone = h.mission.done - h.lastDone;
if (deltaTime == 0 || deltaTime > 1000 || finished) {
if (h.mission.errCode > 0) {
h.status.setText(R.string.msg_error);
} else {
float progress = (float) h.mission.done / h.mission.length;
h.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
h.progress.setProgress(progress);
}
}
if (deltaTime > 1000 && deltaDone > 0) {
float speed = (float) deltaDone / deltaTime;
String speedStr = Utility.formatSpeed(speed * 1000);
String sizeStr = Utility.formatBytes(h.mission.length);
h.size.setText(sizeStr + " " + speedStr);
h.lastTimeStamp = now;
h.lastDone = h.mission.done;
}
}
Utility.FileType type = Utility.getFileType(ms.name);
private void buildPopup(final ViewHolder h) {
PopupMenu popup = new PopupMenu(mContext, h.menu);
popup.inflate(R.menu.mission);
Menu menu = popup.getMenu();
MenuItem start = menu.findItem(R.id.start);
MenuItem pause = menu.findItem(R.id.pause);
MenuItem view = menu.findItem(R.id.view);
MenuItem delete = menu.findItem(R.id.delete);
MenuItem checksum = menu.findItem(R.id.checksum);
// Set to false first
start.setVisible(false);
pause.setVisible(false);
view.setVisible(false);
delete.setVisible(false);
checksum.setVisible(false);
if (!h.mission.finished) {
if (!h.mission.running) {
if (h.mission.errCode == -1) {
start.setVisible(true);
}
delete.setVisible(true);
} else {
pause.setVisible(true);
}
} else {
view.setVisible(true);
delete.setVisible(true);
checksum.setVisible(true);
}
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.start:
mManager.resumeMission(h.position);
mBinder.onMissionAdded(mManager.getMission(h.position));
return true;
case R.id.pause:
mManager.pauseMission(h.position);
mBinder.onMissionRemoved(mManager.getMission(h.position));
h.lastTimeStamp = -1;
h.lastDone = -1;
return true;
case R.id.view:
File f = new File(h.mission.location, h.mission.name);
String ext = Utility.getFileExt(h.mission.name);
h.icon.setImageResource(Utility.getIconForFileType(type));
h.name.setText(ms.name);
h.size.setText(Utility.formatBytes(ms.length));
Log.d(TAG, "Viewing file: " + f.getAbsolutePath() + " ext: " + ext);
h.progress = new ProgressDrawable(mContext, Utility.getBackgroundForFileType(type), Utility.getForegroundForFileType(type));
ViewCompat.setBackground(h.bkg, h.progress);
if (ext == null) {
Log.w(TAG, "Can't view file because it has no extension: " +
h.mission.name);
return false;
}
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
Log.v(TAG, "Mime: " + mime + " package: " + mContext.getApplicationContext().getPackageName() + ".provider");
if (f.exists()) {
viewFileWithFileProvider(f, mime);
} else {
Log.w(TAG, "File doesn't exist");
}
return true;
case R.id.delete:
mManager.deleteMission(h.position);
notifyDataSetChanged();
return true;
case R.id.md5:
case R.id.sha1:
DownloadMission mission = mManager.getMission(h.position);
new ChecksumTask().execute(mission.location + "/" + mission.name, ALGORITHMS.get(id));
return true;
default:
return false;
}
}
});
popup.show();
}
h.observer = new MissionObserver(this, h);
ms.addListener(h.observer);
private void viewFile(File file, String mimetype) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), mimetype);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
}
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.v(TAG, "Starting intent: " + intent);
mContext.startActivity(intent);
}
updateProgress(h);
}
private void viewFileWithFileProvider(File file, String mimetype) {
String ourPackage = mContext.getApplicationContext().getPackageName();
Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimetype);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
}
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.v(TAG, "Starting intent: " + intent);
mContext.startActivity(intent);
}
private class ChecksumTask extends AsyncTask<String, Void, String> {
ProgressDialog prog;
@Override
public int getItemCount() {
return mManager.getCount();
}
@Override
protected void onPreExecute() {
super.onPreExecute();
// Create dialog
prog = new ProgressDialog(mContext);
prog.setCancelable(false);
prog.setMessage(mContext.getString(R.string.msg_wait));
prog.show();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
protected String doInBackground(String... params) {
return Utility.checksum(params[0], params[1]);
}
private void updateProgress(ViewHolder h) {
updateProgress(h, false);
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
prog.dismiss();
Utility.copyToClipboard(mContext, result);
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
public DownloadMission mission;
public int position;
private void updateProgress(ViewHolder h, boolean finished) {
if (h.mission == null) return;
public TextView status;
public ImageView icon;
public TextView name;
public TextView size;
public View bkg;
public ImageView menu;
public ProgressDrawable progress;
public MissionObserver observer;
long now = System.currentTimeMillis();
public long lastTimeStamp = -1;
public long lastDone = -1;
public int colorId;
public ViewHolder(View v) {
super(v);
status = Utility.findViewById(v, R.id.item_status);
icon = Utility.findViewById(v, R.id.item_icon);
name = Utility.findViewById(v, R.id.item_name);
size = Utility.findViewById(v, R.id.item_size);
bkg = Utility.findViewById(v, R.id.item_bkg);
menu = Utility.findViewById(v, R.id.item_more);
}
}
static class MissionObserver implements DownloadMission.MissionListener {
private MissionAdapter mAdapter;
private ViewHolder mHolder;
public MissionObserver(MissionAdapter adapter, ViewHolder holder) {
mAdapter = adapter;
mHolder = holder;
}
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
mAdapter.updateProgress(mHolder);
}
if (h.lastTimeStamp == -1) {
h.lastTimeStamp = now;
}
@Override
public void onFinish(DownloadMission downloadMission) {
//mAdapter.mManager.deleteMission(mHolder.position);
// TODO Notification
//mAdapter.notifyDataSetChanged();
if (mHolder.mission != null) {
mHolder.size.setText(Utility.formatBytes(mHolder.mission.length));
mAdapter.updateProgress(mHolder, true);
}
}
if (h.lastDone == -1) {
h.lastDone = h.mission.done;
}
@Override
public void onError(DownloadMission downloadMission, int errCode) {
mAdapter.updateProgress(mHolder);
}
}
long deltaTime = now - h.lastTimeStamp;
long deltaDone = h.mission.done - h.lastDone;
if (deltaTime == 0 || deltaTime > 1000 || finished) {
if (h.mission.errCode > 0) {
h.status.setText(R.string.msg_error);
} else {
float progress = (float) h.mission.done / h.mission.length;
h.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
h.progress.setProgress(progress);
}
}
if (deltaTime > 1000 && deltaDone > 0) {
float speed = (float) deltaDone / deltaTime;
String speedStr = Utility.formatSpeed(speed * 1000);
String sizeStr = Utility.formatBytes(h.mission.length);
h.size.setText(sizeStr + " " + speedStr);
h.lastTimeStamp = now;
h.lastDone = h.mission.done;
}
}
private void buildPopup(final ViewHolder h) {
PopupMenu popup = new PopupMenu(mContext, h.menu);
popup.inflate(R.menu.mission);
Menu menu = popup.getMenu();
MenuItem start = menu.findItem(R.id.start);
MenuItem pause = menu.findItem(R.id.pause);
MenuItem view = menu.findItem(R.id.view);
MenuItem delete = menu.findItem(R.id.delete);
MenuItem checksum = menu.findItem(R.id.checksum);
// Set to false first
start.setVisible(false);
pause.setVisible(false);
view.setVisible(false);
delete.setVisible(false);
checksum.setVisible(false);
if (!h.mission.finished) {
if (!h.mission.running) {
if (h.mission.errCode == -1) {
start.setVisible(true);
}
delete.setVisible(true);
} else {
pause.setVisible(true);
}
} else {
view.setVisible(true);
delete.setVisible(true);
checksum.setVisible(true);
}
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.start:
mManager.resumeMission(h.position);
mBinder.onMissionAdded(mManager.getMission(h.position));
return true;
case R.id.pause:
mManager.pauseMission(h.position);
mBinder.onMissionRemoved(mManager.getMission(h.position));
h.lastTimeStamp = -1;
h.lastDone = -1;
return true;
case R.id.view:
File f = new File(h.mission.location, h.mission.name);
String ext = Utility.getFileExt(h.mission.name);
Log.d(TAG, "Viewing file: " + f.getAbsolutePath() + " ext: " + ext);
if (ext == null) {
Log.w(TAG, "Can't view file because it has no extension: " +
h.mission.name);
return false;
}
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
Log.v(TAG, "Mime: " + mime + " package: " + mContext.getApplicationContext().getPackageName() + ".provider");
if (f.exists()) {
viewFileWithFileProvider(f, mime);
} else {
Log.w(TAG, "File doesn't exist");
}
return true;
case R.id.delete:
mManager.deleteMission(h.position);
notifyDataSetChanged();
return true;
case R.id.md5:
case R.id.sha1:
DownloadMission mission = mManager.getMission(h.position);
new ChecksumTask().execute(mission.location + "/" + mission.name, ALGORITHMS.get(id));
return true;
default:
return false;
}
}
});
popup.show();
}
private void viewFile(File file, String mimetype) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), mimetype);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
}
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.v(TAG, "Starting intent: " + intent);
mContext.startActivity(intent);
}
private void viewFileWithFileProvider(File file, String mimetype) {
String ourPackage = mContext.getApplicationContext().getPackageName();
Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimetype);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
}
//mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.v(TAG, "Starting intent: " + intent);
mContext.startActivity(intent);
}
static class ViewHolder extends RecyclerView.ViewHolder {
public DownloadMission mission;
public int position;
public TextView status;
public ImageView icon;
public TextView name;
public TextView size;
public View bkg;
public ImageView menu;
public ProgressDrawable progress;
public MissionObserver observer;
public long lastTimeStamp = -1;
public long lastDone = -1;
public int colorId;
public ViewHolder(View v) {
super(v);
status = (TextView) v.findViewById(R.id.item_status);
icon = (ImageView) v.findViewById(R.id.item_icon);
name = (TextView) v.findViewById(R.id.item_name);
size = (TextView) v.findViewById(R.id.item_size);
bkg = v.findViewById(R.id.item_bkg);
menu = (ImageView) v.findViewById(R.id.item_more);
}
}
static class MissionObserver implements DownloadMission.MissionListener {
private MissionAdapter mAdapter;
private ViewHolder mHolder;
public MissionObserver(MissionAdapter adapter, ViewHolder holder) {
mAdapter = adapter;
mHolder = holder;
}
@Override
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
mAdapter.updateProgress(mHolder);
}
@Override
public void onFinish(DownloadMission downloadMission) {
//mAdapter.mManager.deleteMission(mHolder.position);
// TODO Notification
//mAdapter.notifyDataSetChanged();
if (mHolder.mission != null) {
mHolder.size.setText(Utility.formatBytes(mHolder.mission.length));
mAdapter.updateProgress(mHolder, true);
}
}
@Override
public void onError(DownloadMission downloadMission, int errCode) {
mAdapter.updateProgress(mHolder);
}
}
private class ChecksumTask extends AsyncTask<String, Void, String> {
ProgressDialog prog;
@Override
protected void onPreExecute() {
super.onPreExecute();
// Create dialog
prog = new ProgressDialog(mContext);
prog.setCancelable(false);
prog.setMessage(mContext.getString(R.string.msg_wait));
prog.show();
}
@Override
protected String doInBackground(String... params) {
return Utility.checksum(params[0], params[1]);
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
prog.dismiss();
Utility.copyToClipboard(mContext, result);
}
}
}

View File

@@ -6,53 +6,55 @@ import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
public class ProgressDrawable extends Drawable
{
private float mProgress;
private int mBackgroundColor, mForegroundColor;
public ProgressDrawable(Context context, int background, int foreground) {
this(context.getResources().getColor(background), context.getResources().getColor(foreground));
}
public ProgressDrawable(int background, int foreground) {
mBackgroundColor = background;
mForegroundColor = foreground;
}
public void setProgress(float progress) {
mProgress = progress;
invalidateSelf();
}
public class ProgressDrawable extends Drawable {
private float mProgress;
private int mBackgroundColor, mForegroundColor;
@Override
public void draw(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
Paint paint = new Paint();
paint.setColor(mBackgroundColor);
canvas.drawRect(0, 0, width, height, paint);
paint.setColor(mForegroundColor);
canvas.drawRect(0, 0, (int) (mProgress * width), height, paint);
}
public ProgressDrawable(Context context, @ColorRes int background, @ColorRes int foreground) {
this(ContextCompat.getColor(context, background), ContextCompat.getColor(context, foreground));
}
@Override
public void setAlpha(int alpha) {
// Unsupported
}
public ProgressDrawable(int background, int foreground) {
mBackgroundColor = background;
mForegroundColor = foreground;
}
@Override
public void setColorFilter(ColorFilter filter) {
// Unsupported
}
public void setProgress(float progress) {
mProgress = progress;
invalidateSelf();
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
public void draw(@NonNull Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
Paint paint = new Paint();
paint.setColor(mBackgroundColor);
canvas.drawRect(0, 0, width, height, paint);
paint.setColor(mForegroundColor);
canvas.drawRect(0, 0, (int) (mProgress * width), height, paint);
}
@Override
public void setAlpha(int alpha) {
// Unsupported
}
@Override
public void setColorFilter(ColorFilter filter) {
// Unsupported
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}

View File

@@ -1,26 +1,23 @@
package us.shandian.giga.ui.common;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import org.schabi.newpipe.R;
import us.shandian.giga.util.Utility;
public abstract class ToolbarActivity extends ActionBarActivity
{
protected Toolbar mToolbar;
public abstract class ToolbarActivity extends ActionBarActivity {
protected Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResource());
mToolbar = Utility.findViewById(this, R.id.toolbar);
setSupportActionBar(mToolbar);
}
protected abstract int getLayoutResource();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResource());
mToolbar = (Toolbar) this.findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
}
protected abstract int getLayoutResource();
}

View File

@@ -3,11 +3,10 @@ package us.shandian.giga.ui.fragment;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.service.DownloadManagerService;
public class AllMissionsFragment extends MissionsFragment
{
public class AllMissionsFragment extends MissionsFragment {
@Override
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
return binder.getDownloadManager();
}
@Override
protected DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder) {
return binder.getDownloadManager();
}
}

View File

@@ -10,139 +10,141 @@ import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.adapter.MissionAdapter;
import us.shandian.giga.util.Utility;
public abstract class MissionsFragment extends Fragment
{
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
private SharedPreferences mPrefs;
private boolean mLinear;
private MenuItem mSwitch;
private RecyclerView mList;
private MissionAdapter mAdapter;
private GridLayoutManager mGridManager;
private LinearLayoutManager mLinearManager;
private Context mActivity;
private ServiceConnection mConnection = new ServiceConnection() {
public abstract class MissionsFragment extends Fragment {
private DownloadManager mManager;
private DownloadManagerService.DMBinder mBinder;
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
mBinder = (DownloadManagerService.DMBinder) binder;
mManager = setupDownloadManager(mBinder);
updateList();
}
private SharedPreferences mPrefs;
private boolean mLinear;
private MenuItem mSwitch;
@Override
public void onServiceDisconnected(ComponentName name) {
// What to do?
}
private RecyclerView mList;
private MissionAdapter mAdapter;
private GridLayoutManager mGridManager;
private LinearLayoutManager mLinearManager;
private Context mActivity;
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.missions, container, false);
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
mBinder = (DownloadManagerService.DMBinder) binder;
mManager = setupDownloadManager(mBinder);
updateList();
}
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mLinear = mPrefs.getBoolean("linear", false);
@Override
public void onServiceDisconnected(ComponentName name) {
// What to do?
}
// Bind the service
Intent i = new Intent();
i.setClass(getActivity(), DownloadManagerService.class);
getActivity().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
// Views
mList = Utility.findViewById(v, R.id.mission_recycler);
// Init
mGridManager = new GridLayoutManager(getActivity(), 2);
mLinearManager = new LinearLayoutManager(getActivity());
mList.setLayoutManager(mGridManager);
setHasOptionsMenu(true);
return v;
}
/** Added in API level 23. */
};
@Override
public void onAttach(Context activity) {
super.onAttach(activity);
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.missions, container, false);
// Bug: in api< 23 this is never called
// so mActivity=null
// so app crashes with nullpointer exception
mActivity = activity;
}
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mLinear = mPrefs.getBoolean("linear", false);
/** deprecated in API level 23,
* but must remain to allow compatibility with api<23 */
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Bind the service
Intent i = new Intent();
i.setClass(getActivity(), DownloadManagerService.class);
getActivity().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
mActivity = activity;
}
// Views
mList = (RecyclerView) v.findViewById(R.id.mission_recycler);
@Override
public void onDestroyView() {
super.onDestroyView();
getActivity().unbindService(mConnection);
}
// Init
mGridManager = new GridLayoutManager(getActivity(), 2);
mLinearManager = new LinearLayoutManager(getActivity());
mList.setLayoutManager(mGridManager);
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.switch_mode:
setHasOptionsMenu(true);
return v;
}
/**
* Added in API level 23.
*/
@Override
public void onAttach(Context activity) {
super.onAttach(activity);
// Bug: in api< 23 this is never called
// so mActivity=null
// so app crashes with nullpointer exception
mActivity = activity;
}
/**
* deprecated in API level 23,
* but must remain to allow compatibility with api<23
*/
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
@Override
public void onDestroyView() {
super.onDestroyView();
getActivity().unbindService(mConnection);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
/*switch (item.getItemId()) {
case R.id.switch_mode:
mLinear = !mLinear;
updateList();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}*/
}
public void notifyChange() {
mAdapter.notifyDataSetChanged();
}
private void updateList() {
mAdapter = new MissionAdapter(mActivity, mBinder, mManager, mLinear);
if (mLinear) {
mList.setLayoutManager(mLinearManager);
} else {
mList.setLayoutManager(mGridManager);
}
mList.setAdapter(mAdapter);
if (mSwitch != null) {
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
}
mPrefs.edit().putBoolean("linear", mLinear).commit();
}
protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder);
public void notifyChange() {
mAdapter.notifyDataSetChanged();
}
private void updateList() {
mAdapter = new MissionAdapter(mActivity, mBinder, mManager, mLinear);
if (mLinear) {
mList.setLayoutManager(mLinearManager);
} else {
mList.setLayoutManager(mGridManager);
}
mList.setAdapter(mAdapter);
if (mSwitch != null) {
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
}
mPrefs.edit().putBoolean("linear", mLinear).commit();
}
protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder);
}

View File

@@ -1,85 +0,0 @@
package us.shandian.giga.util;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.Environment;
import java.io.File;
import java.io.PrintWriter;
//todo: replace this by using the internal crash handler of newpipe
public class CrashHandler implements Thread.UncaughtExceptionHandler
{
public static final String CRASH_DIR = Environment.getExternalStorageDirectory().getPath() + "/GigaCrash/";
public static final String CRASH_LOG = CRASH_DIR + "last_crash.log";
public static final String CRASH_TAG = CRASH_DIR + ".crashed";
private static String ANDROID = Build.VERSION.RELEASE;
private static String MODEL = Build.MODEL;
private static String MANUFACTURER = Build.MANUFACTURER;
public static String VERSION = "Unknown";
private Thread.UncaughtExceptionHandler mPrevious;
public static void init(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
VERSION = info.versionName + info.versionCode;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void register() {
new CrashHandler();
}
private CrashHandler() {
mPrevious = Thread.currentThread().getUncaughtExceptionHandler();
Thread.currentThread().setUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
File f = new File(CRASH_LOG);
if (f.exists()) {
f.delete();
} else {
try {
new File(CRASH_DIR).mkdirs();
f.createNewFile();
} catch (Exception e) {
return;
}
}
PrintWriter p;
try {
p = new PrintWriter(f);
} catch (Exception e) {
return;
}
p.write("Android Version: " + ANDROID + "\n");
p.write("Device Model: " + MODEL + "\n");
p.write("Device Manufacturer: " + MANUFACTURER + "\n");
p.write("App Version: " + VERSION + "\n");
p.write("*********************\n");
throwable.printStackTrace(p);
p.close();
try {
new File(CRASH_TAG).createNewFile();
} catch (Exception e) {
return;
}
if (mPrevious != null) {
mPrevious.uncaughtException(thread, throwable);
}
}
}

View File

@@ -1,256 +1,221 @@
package us.shandian.giga.util;
import android.app.Activity;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.widget.Toast;
import org.schabi.newpipe.R;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.R;
public class Utility {
import com.nononsenseapps.filepicker.FilePickerActivity;
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
public enum FileType {
VIDEO,
MUSIC,
UNKNOWN
}
public class Utility
{
public static enum FileType {
VIDEO,
MUSIC,
UNKNOWN
}
public static String formatBytes(long bytes) {
if (bytes < 1024) {
return String.format("%d B", bytes);
} else if (bytes < 1024 * 1024) {
return String.format("%.2f kB", (float) bytes / 1024);
} else if (bytes < 1024 * 1024 * 1024) {
return String.format("%.2f MB", (float) bytes / 1024 / 1024);
} else {
return String.format("%.2f GB", (float) bytes / 1024 / 1024 / 1024);
}
}
public static String formatSpeed(float speed) {
if (speed < 1024) {
return String.format("%.2f B/s", speed);
} else if (speed < 1024 * 1024) {
return String.format("%.2f kB/s", speed / 1024);
} else if (speed < 1024 * 1024 * 1024) {
return String.format("%.2f MB/s", speed / 1024 / 1024);
} else {
return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024);
}
}
public static void writeToFile(String fileName, String content) {
try {
writeToFile(fileName, content.getBytes("UTF-8"));
} catch (Exception e) {
}
}
public static void writeToFile(String fileName, byte[] content) {
File f = new File(fileName);
if (!f.exists()) {
try {
f.createNewFile();
} catch (Exception e) {
}
}
try {
FileOutputStream opt = new FileOutputStream(f, false);
opt.write(content, 0, content.length);
opt.close();
} catch (Exception e) {
}
}
public static String readFromFile(String file) {
try {
File f = new File(file);
if (!f.exists() || !f.canRead()) {
return null;
}
BufferedInputStream ipt = new BufferedInputStream(new FileInputStream(f));
byte[] buf = new byte[512];
StringBuilder sb = new StringBuilder();
while (ipt.available() > 0) {
int len = ipt.read(buf, 0, 512);
sb.append(new String(buf, 0, len, "UTF-8"));
}
ipt.close();
return sb.toString();
} catch (Exception e) {
return null;
}
}
public static <T> T findViewById(View v, int id) {
return (T) v.findViewById(id);
}
public static <T> T findViewById(Activity activity, int id) {
return (T) activity.findViewById(id);
}
public static String getFileExt(String url) {
if (url.indexOf("?")>-1) {
url = url.substring(0,url.indexOf("?"));
}
if (url.lastIndexOf(".") == -1) {
return null;
} else {
String ext = url.substring(url.lastIndexOf(".") );
if (ext.indexOf("%")>-1) {
ext = ext.substring(0,ext.indexOf("%"));
}
if (ext.indexOf("/")>-1) {
ext = ext.substring(0,ext.indexOf("/"));
}
return ext.toLowerCase();
public static String formatBytes(long bytes) {
if (bytes < 1024) {
return String.format("%d B", bytes);
} else if (bytes < 1024 * 1024) {
return String.format("%.2f kB", (float) bytes / 1024);
} else if (bytes < 1024 * 1024 * 1024) {
return String.format("%.2f MB", (float) bytes / 1024 / 1024);
} else {
return String.format("%.2f GB", (float) bytes / 1024 / 1024 / 1024);
}
}
}
}
public static String formatSpeed(float speed) {
if (speed < 1024) {
return String.format("%.2f B/s", speed);
} else if (speed < 1024 * 1024) {
return String.format("%.2f kB/s", speed / 1024);
} else if (speed < 1024 * 1024 * 1024) {
return String.format("%.2f MB/s", speed / 1024 / 1024);
} else {
return String.format("%.2f GB/s", speed / 1024 / 1024 / 1024);
}
}
public static FileType getFileType(String file) {
if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac") || file.endsWith(".m4a")) {
return FileType.MUSIC;
} else if (file.endsWith(".mp4") || file.endsWith(".mpeg") || file.endsWith(".rm") || file.endsWith(".rmvb")
|| file.endsWith(".flv") || file.endsWith(".webp") || file.endsWith(".webm")) {
return FileType.VIDEO;
} else {
return FileType.UNKNOWN;
}
}
public static void writeToFile(String fileName, String content) {
try {
writeToFile(fileName, content.getBytes("UTF-8"));
} catch (Exception e) {
public static Boolean isMusicFile(String file)
{
return Utility.getFileType(file) == FileType.MUSIC;
}
}
}
public static Boolean isVideoFile(String file)
{
return Utility.getFileType(file) == FileType.VIDEO;
}
public static int getBackgroundForFileType(FileType type) {
switch (type) {
case MUSIC:
return R.color.audio_left_to_load_color;
case VIDEO:
return R.color.video_left_to_load_color;
default:
return R.color.gray;
}
}
public static int getForegroundForFileType(FileType type) {
switch (type) {
case MUSIC:
return R.color.audio_already_load_color;
case VIDEO:
return R.color.video_already_load_color;
default:
return R.color.gray;
}
}
public static void writeToFile(String fileName, byte[] content) {
File f = new File(fileName);
public static int getIconForFileType(FileType type) {
switch(type) {
case MUSIC:
return R.drawable.music;
case VIDEO:
return R.drawable.video;
default:
return R.drawable.video;
}
}
if (!f.exists()) {
try {
f.createNewFile();
} catch (Exception e) {
public static boolean isDirectoryAvailble(String path) {
File dir = new File(path);
return dir.exists() && dir.isDirectory();
}
}
}
public static boolean isDownloadDirectoryAvailble(Context context) {
return isDirectoryAvailble(NewPipeSettings.getVideoDownloadPath(context));
}
try {
FileOutputStream opt = new FileOutputStream(f, false);
opt.write(content, 0, content.length);
opt.close();
} catch (Exception e) {
public static void showDirectoryChooser(Activity activity) {
Intent i = new Intent(activity, FilePickerActivity.class);
i.setAction(Intent.ACTION_GET_CONTENT);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
i.putExtra(FilePickerActivity.EXTRA_MODE, AbstractFilePickerFragment.MODE_DIR);
activity.startActivityForResult(i, 233);
}
public static void copyToClipboard(Context context, String str) {
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText("text", str));
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
}
public static String checksum(String path, String algorithm) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
FileInputStream i = null;
try {
i = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
byte[] buf = new byte[1024];
int len = 0;
try {
while ((len = i.read(buf)) != -1) {
md.update(buf, 0, len);
}
} catch (IOException e) {
}
byte[] digest = md.digest();
// HEX
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
}
}
public static String readFromFile(String file) {
try {
File f = new File(file);
if (!f.exists() || !f.canRead()) {
return null;
}
BufferedInputStream ipt = new BufferedInputStream(new FileInputStream(f));
byte[] buf = new byte[512];
StringBuilder sb = new StringBuilder();
while (ipt.available() > 0) {
int len = ipt.read(buf, 0, 512);
sb.append(new String(buf, 0, len, "UTF-8"));
}
ipt.close();
return sb.toString();
} catch (Exception e) {
return null;
}
}
@Nullable
public static String getFileExt(String url) {
int index;
if ((index = url.indexOf("?")) > -1) {
url = url.substring(0, index);
}
index = url.lastIndexOf(".");
if (index == -1) {
return null;
} else {
String ext = url.substring(index);
if ((index = ext.indexOf("%")) > -1) {
ext = ext.substring(0, index);
}
if ((index = ext.indexOf("/")) > -1) {
ext = ext.substring(0, index);
}
return ext.toLowerCase();
}
}
public static FileType getFileType(String file) {
if (file.endsWith(".mp3") || file.endsWith(".wav") || file.endsWith(".flac") || file.endsWith(".m4a")) {
return FileType.MUSIC;
} else if (file.endsWith(".mp4") || file.endsWith(".mpeg") || file.endsWith(".rm") || file.endsWith(".rmvb")
|| file.endsWith(".flv") || file.endsWith(".webp") || file.endsWith(".webm")) {
return FileType.VIDEO;
} else {
return FileType.UNKNOWN;
}
}
@ColorRes
public static int getBackgroundForFileType(FileType type) {
switch (type) {
case MUSIC:
return R.color.audio_left_to_load_color;
case VIDEO:
return R.color.video_left_to_load_color;
default:
return R.color.gray;
}
}
@ColorRes
public static int getForegroundForFileType(FileType type) {
switch (type) {
case MUSIC:
return R.color.audio_already_load_color;
case VIDEO:
return R.color.video_already_load_color;
default:
return R.color.gray;
}
}
@DrawableRes
public static int getIconForFileType(FileType type) {
switch (type) {
case MUSIC:
return R.drawable.music;
case VIDEO:
return R.drawable.video;
default:
return R.drawable.video;
}
}
public static void copyToClipboard(Context context, String str) {
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText("text", str));
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
}
public static String checksum(String path, String algorithm) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
FileInputStream i = null;
try {
i = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
byte[] buf = new byte[1024];
int len = 0;
try {
while ((len = i.read(buf)) != -1) {
md.update(buf, 0, len);
}
} catch (IOException e) {
}
byte[] digest = md.digest();
// HEX
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromAlpha="0.0"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:toAlpha="1.0"/>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromAlpha="1.0"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:toAlpha="0.0"/>

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: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 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: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@@ -15,4 +15,4 @@
<include layout="@layout/toolbar_layout"/>
</RelativeLayout>
</FrameLayout>

View File

@@ -5,8 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:gravity="center"
android:keepScreenOn="true">
android:gravity="center">
<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
android:id="@+id/aspectRatioLayout"

View File

@@ -4,11 +4,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/video_item_search_height"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:orientation="vertical"
android:padding="12dp">
android:padding="@dimen/video_item_search_padding">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/itemThumbnailView"
@@ -22,55 +21,43 @@
android:src="@drawable/buddy_channel_item"
tools:ignore="RtlHardcoded"/>
<RelativeLayout
<TextView
android:id="@+id/itemChannelTitleView"
android:layout_width="match_parent"
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/itemThumbnailView"
android:orientation="vertical"
tools:ignore="RtlHardcoded">
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
tools:text="Channel Title, Lorem ipsum"/>
<TextView
android:id="@+id/itemChannelTitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/channel_item_detail_title_text_size"
tools:text="Channel Title, Lorem ipsum"/>
<TextView
android:id="@+id/itemAdditionalDetails"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/itemThumbnailView"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="10M subscribers • 1000 videos"/>
<TextView
android:id="@+id/itemChannelDescriptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/itemSubscriberCountView"
android:layout_below="@+id/itemChannelTitleView"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit"/>
<TextView
android:id="@+id/itemSubscriberCountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="10M subscribers • "/>
<TextView
android:id="@+id/itemChannelDescriptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/itemAdditionalDetails"
android:layout_marginBottom="@dimen/video_item_detail_description_to_details_margin"
android:layout_toRightOf="@id/itemThumbnailView"
android:ellipsize="end"
android:lines="2"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit"/>
<TextView
android:id="@+id/itemVideoCountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/itemSubscriberCountView"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="1000 videos"/>
</RelativeLayout>
</RelativeLayout>

View File

@@ -32,12 +32,11 @@
<RadioGroup
android:id="@+id/video_audio_group"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/file_name"
android:layout_marginBottom="12dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="6dp"
android:gravity="left"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
@@ -56,11 +55,22 @@
android:text="@string/audio"/>
</RadioGroup>
<Spinner
android:id="@+id/quality_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="150dp"
android:layout_below="@+id/video_audio_group"
android:layout_marginBottom="12dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
tools:listitem="@layout/resolutions_spinner_item"/>
<TextView
android:id="@+id/threads_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/video_audio_group"
android:layout_below="@+id/quality_spinner"
android:layout_marginBottom="6dp"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/error_message_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@string/general_error"
android:gravity="center"
android:textSize="16sp"
@@ -22,8 +22,6 @@
android:id="@+id/error_button_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/error_message_view"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:text="@string/retry"
android:textAlignment="center"
@@ -32,4 +30,4 @@
android:textSize="16sp"
android:theme="@style/RedButton"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".fragments.channel.ChannelFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/channel_streams_view"
@@ -11,26 +11,11 @@
android:layout_height="match_parent"
android:background="?android:windowBackground"
android:scrollbars="vertical"
tools:listitem="@layout/stream_item"/>
tools:listitem="@layout/stream_item" />
<ProgressBar
android:id="@+id/loading_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="wrap_content"
layout="@layout/loading_error_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="50dp"
android:visibility="gone"
tools:visibility="visible"/>
</RelativeLayout>
android:layout_gravity="center" />
</FrameLayout>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@@ -14,26 +13,11 @@
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/stream_item"/>
tools:listitem="@layout/stream_item" />
<ProgressBar
android:id="@+id/loading_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="wrap_content"
layout="@layout/loading_error_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:visibility="gone"
tools:visibility="visible"/>
</RelativeLayout>
android:layout_gravity="center_vertical" />
</FrameLayout>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_item_detail"
@@ -16,105 +15,92 @@
app:parallax_factor="1.9">
<!--WRAPPER-->
<RelativeLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- THUMBNAIL -->
<RelativeLayout
<FrameLayout
android:id="@+id/detail_thumbnail_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black">
android:background="@android:color/black"
android:clickable="true"
android:foreground="?attr/selectableItemBackground">
<ImageView
android:id="@+id/detail_thumbnail_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="@android:color/transparent"
android:contentDescription="@string/detail_thumbnail_view_description"
android:scaleType="centerCrop"
tools:ignore="RtlHardcoded"
tools:layout_height="200dp"
tools:src="@drawable/dummy_thumbnail"/>
tools:src="@drawable/dummy_thumbnail" />
<ImageView
android:id="@+id/detail_thumbnail_play_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:src="@drawable/new_play_arrow"
android:visibility="invisible"
tools:ignore="ContentDescription"
tools:visibility="visible"/>
tools:visibility="visible" />
<Button
android:id="@+id/detail_thumbnail_background_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"/>
</RelativeLayout>
</FrameLayout>
<!-- CONTENT -->
<RelativeLayout
<LinearLayout
android:id="@+id/detail_content_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/detail_thumbnail_root_layout"
android:background="?android:windowBackground">
android:background="?android:windowBackground"
android:orientation="vertical">
<!-- TITLE -->
<RelativeLayout
<FrameLayout
android:id="@+id/detail_title_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="12dp">
android:paddingRight="12dp">
<TextView
android:id="@+id/detail_video_title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/detail_toggle_description_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="20dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingBottom="2dp"
android:paddingTop="6dp"
android:paddingBottom="8dp"
android:paddingTop="12dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_title_text_size"
tools:ignore="RtlHardcoded"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero."/>
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />
<ImageView
android:id="@+id/detail_toggle_description_view"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_gravity="center_vertical|right"
android:layout_marginLeft="5dp"
android:layout_marginTop="6dp"
android:src="@drawable/arrow_down"
tools:ignore="ContentDescription,RtlHardcoded"/>
</RelativeLayout>
tools:ignore="ContentDescription,RtlHardcoded" />
</FrameLayout>
<!--HIDING ROOT-->
<RelativeLayout
<LinearLayout
android:id="@+id/detail_content_root_hiding"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/detail_title_root_layout"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
@@ -122,197 +108,156 @@
<RelativeLayout
android:id="@+id/detail_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="55dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="6dp"
android:baselineAligned="false"
android:orientation="horizontal"
android:paddingTop="6dp">
android:orientation="horizontal">
<!-- VIEW & THUMBS -->
<LinearLayout
android:id="@+id/detail_views_thumbs_root"
<TextView
android:id="@+id/detail_view_count_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerHorizontal="true"
android:orientation="vertical"
android:paddingTop="6dp"
tools:ignore="RtlHardcoded">
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_views_text_size"
tools:ignore="RtlHardcoded"
tools:text="2,816,821,505 views" />
<TextView
android:id="@+id/detail_view_count_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_views_text_size"
tools:ignore="RtlHardcoded"
tools:text="2,816,821,505 views"/>
<ImageView
android:id="@+id/detail_thumbs_up_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="?attr/thumbs_up" />
<LinearLayout
android:id="@+id/detail_thumbs_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:gravity="center_vertical"
android:minHeight="30dp"
android:orientation="horizontal">
<TextView
android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_up_img_view"
android:gravity="left|center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="12M" />
<ImageView
android:id="@+id/detail_thumbs_up_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:contentDescription="@string/detail_likes_img_view_description"
android:src="?attr/thumbs_up"/>
<ImageView
android:id="@+id/detail_thumbs_down_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="15dp"
android:layout_toRightOf="@id/detail_thumbs_up_count_view"
android:contentDescription="@string/detail_dislikes_img_view_description"
android:src="?attr/thumbs_down"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:gravity="left|center_vertical"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="12M"/>
<TextView
android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:layout_toRightOf="@id/detail_thumbs_down_img_view"
android:gravity="left|center_vertical"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="10K" />
<ImageView
android:id="@+id/detail_thumbs_down_img_view"
android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height"
android:layout_marginLeft="15dp"
android:contentDescription="@string/detail_dislikes_img_view_description"
android:src="?attr/thumbs_down"
tools:ignore="RtlHardcoded"/>
<TextView
android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/video_item_detail_like_margin"
android:gravity="left|center_vertical"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_likes_text_size"
tools:ignore="RtlHardcoded"
tools:text="10K"/>
<TextView
android:id="@+id/detail_thumbs_disabled_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="15dp"
android:gravity="left|center_vertical"
android:text="@string/disabled"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_likes_text_size"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded"/>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/detail_thumbs_disabled_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_below="@id/detail_view_count_view"
android:layout_marginLeft="15dp"
android:gravity="left|center_vertical"
android:text="@string/disabled"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_likes_text_size"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="RtlHardcoded" />
<!-- CONTROLS -->
<LinearLayout
android:id="@+id/detail_controls_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<TextView
android:id="@+id/detail_controls_popup"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_alignParentRight="true"
android:layout_centerHorizontal="true"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<TextView
android:id="@+id/detail_controls_background"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/play_audio"
android:drawableTop="?attr/audio"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_background_title"
android:textSize="12sp"/>
<TextView
android:id="@+id/detail_controls_popup"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/open_in_popup_mode"
android:drawableTop="?attr/popup"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_popup_title"
android:textSize="12sp"/>
</LinearLayout>
android:layout_alignParentTop="true"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/open_in_popup_mode"
android:drawableTop="?attr/popup"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_popup_title"
android:textSize="12sp" />
<TextView
android:id="@+id/detail_controls_background"
android:layout_width="80dp"
android:layout_height="55dp"
android:layout_alignParentTop="true"
android:layout_gravity="center_vertical"
android:layout_toLeftOf="@id/detail_controls_popup"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/play_audio"
android:drawableTop="?attr/audio"
android:gravity="center"
android:paddingBottom="6dp"
android:paddingTop="6dp"
android:text="@string/controls_background_title"
android:textSize="12sp" />
</RelativeLayout>
<!--UPLOADER-->
<FrameLayout
<LinearLayout
android:id="@+id/detail_uploader_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/detail_root">
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="8dp">
<RelativeLayout
android:id="@+id/detail_uploader_layout"
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/detail_uploader_thumbnail_view"
android:layout_width="@dimen/video_item_detail_uploader_image_size"
android:layout_height="@dimen/video_item_detail_uploader_image_size"
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/detail_uploader_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="8dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/detail_uploader_thumbnail_view"
android:layout_width="@dimen/video_item_detail_uploader_image_size"
android:layout_height="@dimen/video_item_detail_uploader_image_size"
android:layout_alignParentLeft="true"
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded"/>
<TextView
android:id="@+id/detail_uploader_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_uploader_text_size"
android:textStyle="bold"
tools:ignore="RtlHardcoded"
tools:text="Uploader"/>
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_gravity="bottom"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separatorColor"/>
<Button
android:id="@+id/detail_uploader_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
tools:visibility="gone"/>
android:layout_marginLeft="15dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_detail_uploader_text_size"
android:textStyle="bold"
tools:ignore="RtlHardcoded"
tools:text="Uploader" />
<!--<Button
android:id="@+id/detail_uploader_subscribe"
@@ -326,15 +271,22 @@
android:drawableLeft="@drawable/ic_rss_feed_white_24dp"
tools:ignore="RtlHardcoded"
android:visibility="gone"/>-->
</FrameLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separatorColor" />
<!--DESCRIPTIONS-->
<RelativeLayout
<LinearLayout
android:id="@+id/detail_description_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detail_uploader_root_layout"
android:layout_marginTop="5dp"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
@@ -347,106 +299,74 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_upload_date_text_size"
android:textStyle="bold"
tools:text="Upload date"/>
tools:text="Published on Oct 2, 2009" />
<TextView
android:id="@+id/detail_description_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/detail_upload_date_view"
android:layout_marginBottom="8dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="3dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_description_text_size"
tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum."/>
tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum." />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_alignParentBottom="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/separatorColor"/>
android:background="?attr/separatorColor" />
</RelativeLayout>
</LinearLayout>
<!--NEXT AND RELATED VIDEOS-->
<RelativeLayout
<LinearLayout
android:id="@+id/detail_related_streams_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/detail_description_root_layout"
android:layout_centerHorizontal="true"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginTop="14dp">
android:layout_marginTop="14dp"
android:orientation="vertical">
<TextView
android:id="@+id/detail_next_stream_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginLeft="12dp"
android:text="@string/next_video_title"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/video_item_detail_next_text_size"
tools:ignore="RtlHardcoded"/>
tools:ignore="RtlHardcoded" />
<LinearLayout
android:id="@+id/detail_related_streams_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/detail_next_stream_title"
android:layout_marginTop="2dp"
android:orientation="vertical"
tools:minHeight="50dp"/>
tools:minHeight="50dp" />
<ImageButton
android:id="@+id/detail_related_streams_expand"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/detail_related_streams_view"
android:background="?attr/selectableItemBackground"
android:paddingBottom="10dp"
android:paddingTop="4dp"
android:src="?attr/expand"
android:textAlignment="center"
android:textAllCaps="true"
tools:ignore="ContentDescription"/>
tools:ignore="ContentDescription" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
<!-- LOADING BAR -->
<ProgressBar
android:id="@+id/loading_progress_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/detail_title_root_layout"
android:layout_centerHorizontal="true"
android:layout_marginTop="15dp"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"/>
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/detail_title_root_layout"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:visibility="gone"
tools:visibility="visible"/>
</RelativeLayout>
</RelativeLayout>
<!-- LOADING BAR, ERROR & RETRY -->
<include layout="@layout/loading_error_retry" />
</LinearLayout>
</LinearLayout>
</com.nirhart.parallaxscroll.views.ParallaxScrollView>
</RelativeLayout>
</FrameLayout>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<ProgressBar
android:id="@+id/loading_progress_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible" />
<!--ERROR PANEL-->
<include
android:id="@+id/error_panel"
layout="@layout/error_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/video_item_detail_error_panel_margin"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>

View File

@@ -76,6 +76,20 @@
tools:ignore="RtlHardcoded"
tools:progress="52"/>
<TextView
android:id="@+id/notificationTime"
style="@android:style/TextAppearance.StatusBar.EventContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="2dp"
android:layout_alignTop="@+id/notificationProgressBar"
android:layout_toRightOf="@+id/notificationCover"
android:ellipsize="end"
android:maxLines="1"
android:textSize="12sp"
tools:text="Duis posuere"/>
<RelativeLayout
android:id="@+id/notificationControls"
android:layout_width="match_parent"
@@ -88,17 +102,14 @@
<ImageButton
android:id="@+id/notificationRepeat"
android:layout_width="45dp"
android:layout_height="match_parent"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginLeft="8dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="#00000000"
android:clickable="true"
android:paddingBottom="4dp"
android:paddingLeft="11dp"
android:paddingRight="11dp"
android:paddingTop="4dp"
android:scaleType="fitCenter"
android:scaleType="fitXY"
android:src="@drawable/ic_repeat_white"
tools:ignore="ContentDescription"/>
@@ -242,4 +253,4 @@
android:layout_alignParentLeft="true" />
</RelativeLayout>
</RelativeLayout>-->
</RelativeLayout>-->

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp">
<ImageView
android:id="@+id/wo_sound_icon"
android:layout_width="20dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="4dp"
android:scaleType="fitCenter"
android:src="?attr/volume_off"
tools:ignore="ContentDescription,RtlHardcoded"/>
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_toRightOf="@+id/wo_sound_icon"
android:ellipsize="end"
android:gravity="left"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
tools:ignore="RtlHardcoded"
tools:text="MPEG-4 1080p60 very long res"/>
</RelativeLayout>

View File

@@ -4,11 +4,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/video_item_search_height"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:orientation="vertical"
android:padding="12dp">
android:padding="@dimen/video_item_search_padding">
<ImageView
android:id="@+id/itemThumbnailView"
@@ -42,55 +41,40 @@
tools:ignore="RtlHardcoded"
tools:text="1:09:10"/>
<RelativeLayout
<TextView
android:id="@+id/itemVideoTitleView"
android:layout_width="match_parent"
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/itemThumbnailView"
android:orientation="vertical"
tools:ignore="RtlHardcoded">
android:ellipsize="end"
android:lines="2"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
<TextView
android:id="@+id/itemVideoTitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/itemUploaderView"
android:layout_alignParentTop="true"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsumLorem ipsum"/>
<TextView
android:id="@+id/itemUploaderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/itemAdditionalDetails"
android:layout_toRightOf="@id/itemThumbnailView"
android:lines="1"
android:layout_marginBottom="@dimen/video_item_detail_description_to_details_margin"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader"/>
<TextView
android:id="@+id/itemUploaderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/itemUploadDateView"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_uploader_text_size"
tools:text="Uploader"/>
<TextView
android:id="@+id/itemUploadDateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="2 years ago • "/>
<TextView
android:id="@+id/itemViewCountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/itemUploadDateView"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="10M views"/>
</RelativeLayout>
<TextView
android:id="@+id/itemAdditionalDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/itemThumbnailView"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
tools:text="2 years ago • 10M views"/>
</RelativeLayout>

View File

@@ -37,7 +37,7 @@
android:layout_gravity="right|center_vertical"
tools:ignore="RtlHardcoded">
<FrameLayout
<View
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center"

View File

@@ -1,11 +0,0 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/switch_mode"
android:title="@string/switch_mode"
android:icon="@drawable/list"
app:showAsAction="always"/>
</menu>

View File

@@ -8,20 +8,20 @@
android:title="@string/download"
app:showAsAction="always"/>
<item
android:id="@+id/menu_item_share"
android:icon="?attr/share"
android:title="@string/share"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_play_with_kodi"
android:icon="?attr/cast"
android:title="@string/play_with_kodi_title"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/menu_item_share"
android:icon="?attr/share"
android:title="@string/share"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/menu_item_openInBrowser"
android:title="@string/open_in_browser"
app:showAsAction="never"/>
</menu>
</menu>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="background_player_name">مشغل NewPipe في الخلفية</string>
<string name="background_player_playing_toast">جاري التشغيل في الخلفية</string>
<string name="cancel">إلغاء</string>
<string name="choose_browser">إختر متصفح</string>
@@ -26,7 +25,6 @@
<string name="kore_not_found">تطبيق Kore غير موجود. هل تريد تثبيته؟</string>
<string name="light_theme_title">مضيء</string>
<string name="list_thumbnail_view_description">صور معاينة الفيديو</string>
<string name="loading">جاري التحميل</string>
<string name="m4a_description">m4a — جودة أفضل</string>
<string name="network_error">خطأ في الشبكة</string>
<string name="next_video_title">الفيديو التالي</string>
@@ -38,9 +36,7 @@
<string name="screen_rotation">تدوير</string>
<string name="search">بحث</string>
<string name="search_language_title">لغة المحتوى المفضل</string>
<string name="search_page">صفحة البحث:</string>
<string name="settings">الإعدادات</string>
<string name="settings_activity_title">الإعدادات</string>
<string name="settings_category_appearance_title">المظهر</string>
<string name="settings_category_other_title">تعريب JetSub مدونة درويديات</string>
<string name="settings_category_video_audio_title">الفيديو والصوتيات</string>
@@ -49,7 +45,6 @@
<string name="show_next_and_similar_title">عرض التالي والفيديوهات المشابهة</string>
<string name="show_play_with_kodi_summary">عرض خيار لتشغيل الفيديو بواسطة Kodi Media Center.</string>
<string name="show_play_with_kodi_title">عرض خيار التشغيل بواسطة Kodi.</string>
<string name="similar_videos_btn_text">الفيديوهات المشابهة</string>
<string name="theme_title">الثيم</string>
<string name="upload_date_text">تم الرفع في %1$s</string>
<string name="url_not_supported_toast">الرابط غير مدعوم</string>

View File

@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><string name="main_bg_subtitle">Calca na gueta pa entamar</string>
<string name="background_player_name">Reproductor de fondu NewPipe</string>
<resources>
<string name="main_bg_subtitle">Calca na gueta pa entamar</string>
<string name="view_count_text">%1$s visiones</string>
<string name="upload_date_text">Espublizáu\'l %1$s</string>
<string name="no_player_found">Nun s\'alcontró un reproductor de fluxos. ¿Quies instalar VLC?</string>
@@ -8,7 +8,6 @@
<string name="cancel">Encaboxar</string>
<string name="open_in_browser">Abrir nel restolador</string>
<string name="share">Compartir</string>
<string name="loading">Cargando</string>
<string name="download">Baxar</string>
<string name="search">Guetar</string>
<string name="settings">Axustes</string>
@@ -16,7 +15,6 @@
<string name="share_dialog_title">Compartir con</string>
<string name="choose_browser">Escoyer restolador</string>
<string name="screen_rotation">rotación</string>
<string name="settings_activity_title">Axustes</string>
<string name="use_external_video_player_title">Usar reproductor de videu esternu</string>
<string name="use_external_audio_player_title">Usar reproductor d\'audiu esternu</string>
@@ -45,7 +43,6 @@
<string name="next_video_title">Videu siguiente</string>
<string name="show_next_and_similar_title">Amosar vídeos siguientes y asemeyaos</string>
<string name="url_not_supported_toast">URL non sofitada</string>
<string name="similar_videos_btn_text">Vídeos asemeyaos</string>
<string name="search_language_title">Llingua de conteníu preferíu</string>
<string name="settings_category_video_audio_title">Videu y audiu</string>
<string name="settings_category_appearance_title">Aspeutu</string>
@@ -58,7 +55,6 @@
<string name="duration_live">en direuto</string>
<string name="downloads">Descargues</string>
<string name="downloads_title">Descargues</string>
<string name="settings_title">Axustes</string>
<string name="error_report_title">Informe de fallu</string>
<string name="general_error">Fallu</string>
@@ -78,8 +74,6 @@
<string name="error_snackbar_action">INFORMAR</string>
<string name="what_device_headline">Información:</string>
<string name="what_happened_headline">Qué asocedió:</string>
<string name="info_searched_lbl">Guetóse:</string>
<string name="info_requested_stream_lbl">Tresmisión solicitada:</string>
<string name="your_comment">El to comentariu (n\'inglés):</string>
<string name="error_details_headline">Detalles:</string>
@@ -95,23 +89,10 @@
<string name="err_dir_create">Nun pue crease\'l direutoriu de descarga «%1$s»</string>
<string name="info_dir_created">Creose\'l direutoriu de descarga «%1$s»</string>
<string name="enable_background_audio">Reproducir de fondu</string>
<string name="video">Videu</string>
<string name="audio">Audiu</string>
<string name="text">Testu</string>
<string name="logging">Rexistru</string>
<string name="logging_normal">Normal</string>
<string name="logging_verbose">Detalláu</string>
<string name="retry">Retentar</string>
<string name="off">[desactivao]</string>
<string name="error_drm_not_supported">Conteníu protexíu non sofitáu en niveles d\'API pembaxo de 18</string>
<string name="error_drm_unsupported_scheme">Esti preséu nun sofita l\'esquema DRM riquíu</string>
<string name="error_drm_unknown">Asocedió un fallu de DRM desconocíu</string>
<string name="error_no_decoder">Esti preséu nun apurre un descodificador pa <xliff:g id="mime_type">%1$s</xliff:g></string>
<string name="error_no_secure_decoder">Esti preséu nun apurre un descodificador seguru pa <xliff:g id="mime_type">%1$s</xliff:g></string>
<string name="storage_permission_denied">Ñegóse l\'accesu al almacenamientu</string>
<string name="use_exoplayer_title">Usar ExoPlayer</string>
<string name="use_exoplayer_summary">Esperimental</string>
<string name="start">Aniciar</string>
<string name="pause">Posar</string>
@@ -121,7 +102,6 @@
<string name="add">Misión nueva</string>
<string name="finish">Val</string>
<string name="msg_url">URL de descarga</string>
<string name="msg_name">Nome de ficheru</string>
<string name="msg_threads">Filos</string>
<string name="msg_error">Fallu</string>
@@ -137,14 +117,9 @@
<string name="autoplay_by_calling_app_title">Auto-reproducir al llamar dende otra aplicación</string>
<string name="detail_uploader_thumbnail_view_description">Miniatura del xubidor</string>
<string name="detail_dislikes_img_view_description">Despréstames</string>
<string name="error_querying_decoders">Nun puen consultase los descodificadores del preséu</string>
<string name="error_instantiating_decoder">Nun pue instanciase\'l descodificador <xliff:g id="decoder_name">%1$s</xliff:g></string>
<string name="switch_mode">Conmutar ente llistáu y rexáu</string>
<string name="msg_fetch_filename">Dir en cata del nome de ficheru</string>
<string name="msg_running">NewPipe baxando</string>
<string name="search_page">"Guetar páxina: "</string>
<string name="could_not_load_image">Nun pudo cargase la imaxe</string>
<string name="app_ui_crash">Cascó l\'aplicación/IU</string>
<string name="info_labels"/>
@@ -170,15 +145,9 @@
<string name="short_thousand">M</string>
<string name="short_million">Mill</string>
<string name="short_billion">MMill</string>
<string name="restart_title">Reaniciar</string>
<string name="msg_restart">Tienes de reaniciar l\'aplicación p\'aplicar el tema.
¿Quies reaniciala agora?</string>
<string name="msg_popup_permission">Precísase esti permisu pa
abrir en ventanu emerxente</string>
<string name="action_settings">Axustes</string>
<string name="reCaptchaActivity">reCAPTCHA</string>
<string name="reCaptcha_title">Prueba reCAPTCHA</string>
<string name="recaptcha_request_toast">Prueba reCAPTCHA solicitada</string>
@@ -193,4 +162,16 @@ abrir en ventanu emerxente</string>
<string name="refresh">Refrescar</string>
<string name="clear">Llimpiar</string>
</resources>
<string name="use_external_video_player_summary">Delles resoluciones NUN tendrán audiu al habilitar esta opción</string>
<string name="popup_remember_size_pos_title">Tamañu y posición del ventanu emerxente</string>
<string name="popup_remember_size_pos_summary">Recuerda la cabera posición y resolución afitada nel ventanu emerxente</string>
<string name="player_gesture_controls_title">Controles per xestos del reproductor</string>
<string name="player_gesture_controls_summary">Usa xestos pa controlar el brilléu y volume del reproductor</string>
<string name="show_search_suggestions_title">Suxerencies de gueta</string>
<string name="show_search_suggestions_summary">Amuesa suxerencies al guetar</string>
<string name="settings_category_popup_title">Ventanu emerxente</string>
<string name="popup_resizing_indicator_title">Redimensionáu</string>
<string name="use_old_player_summary">Compilación vieya del reproductor Mediaframework.</string>
</resources>

View File

@@ -0,0 +1,181 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="main_bg_subtitle">শুরু করতে অনুসন্ধান এ আলতো চাপ</string>
<string name="view_count_text">%1$s বার দেখা হয়েছে</string>
<string name="upload_date_text">প্রকাশকাল %1$s</string>
<string name="no_player_found">কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। তুমি কি VLC ইনস্টল করতে চাও?</string>
<string name="install">ইনস্টল</string>
<string name="cancel">বাদ দাও</string>
<!-- <string name="fdroid_vlc_url" translatable="false">https://f-droid.org/repository/browse/?fdfilter=vlc&amp;fdid=org.videolan.vlc</string> -->
<string name="open_in_browser">ব্রাউজারে খোলো</string>
<string name="open_in_popup_mode">পপআপ মোডে খোলো</string>
<string name="share">শেয়ার</string>
<string name="download">ডাউনলোড</string>
<string name="search">খোঁজ</string>
<string name="settings">সেটিং</string>
<string name="did_you_mean">তুমি কি বলতে চাচ্ছ %1$s ?</string>
<string name="share_dialog_title">শেয়ার কর</string>
<string name="choose_browser">ব্রাউজার পছন্দ কর</string>
<string name="screen_rotation">রোটেশন</string>
<string name="use_external_video_player_title">বাহ্যিক ভিডিও প্লেয়ার ব্যবহার করো</string>
<string name="use_external_audio_player_title">বাহ্যিক অডিও প্লেয়ার ব্যবহার করো</string>
<string name="popup_mode_share_menu_title">NewPipe পপআপ মোড</string>
<string name="controls_background_title">ব্যাকগ্রাউন্ড</string>
<string name="controls_popup_title">পপআপ</string>
<string name="download_path_title">ভিডিও ডাউনলোড করার পাথ</string>
<string name="download_path_summary">ডাউনলোড করা ভিডিও সঞ্চয় করার পাথ।</string>
<string name="download_path_dialog_title">ভিডিওগুলির জন্য ডাউনলোডের পাথ প্রবেশ করাও</string>
<string name="download_path_audio_title">অডিও ডাউনলোড পাথ</string>
<string name="download_path_audio_summary">ডাউনলোড করা অডিও সঞ্চয় করার পাথ</string>
<string name="download_path_audio_dialog_title">অডিও ফাইলগুলির জন্য ডাউনলোডের পাথ প্রবেশ করাও।</string>
<string name="autoplay_by_calling_app_title">স্বয়ংক্রিয়ভাবে প্লে করো যখন অন্য অ্যাপ্লিকেশন থেকে চালু করা হয়</string>
<string name="autoplay_by_calling_app_summary">স্বয়ংক্রিয়ভাবে একটি ভিডিও প্লে করো যখন NewPipe অন্য অ্যাপ্লিকেশন থেকে চালু করা হয়।</string>
<string name="default_resolution_title">ডিফল্ট রেজোল্যুশন</string>
<string name="default_popup_resolution_title">ডিফল্ট পপআপ রেজোল্যুশন</string>
<string name="show_higher_resolutions_title">উচ্চ রেজোল্যুশন দেখাও</string>
<string name="show_higher_resolutions_summary">শুধুমাত্র কিছু ডিভাইস 2k / 4k ভিডিও চালানোয় সমর্থন</string>
<string name="play_with_kodi_title">Kodi এর মাধ্যমে চালাও</string>
<string name="kore_not_found">Kore অ্যাপ্লিকেশন খুঁজে পাওয়া যায়নি। Kore ইনস্টল করবে?</string>
<!-- <string name="fdroid_kore_url" translatable="false">https://f-droid.org/repository/browse/?fdfilter=Kore&amp;fdid=org.xbmc.kore</string> -->
<string name="show_play_with_kodi_title">দেখাও \"Kodi এর মাধ্যমে চালাও \" বিকল্প</string>
<string name="show_play_with_kodi_summary">Kodi মিডিয়া সেন্টারে এর মাধ্যমে ভিডিও প্লে করার জন্য একটি বিকল্প প্রদর্শন কর।</string>
<string name="play_audio">অডিও</string>
<string name="default_audio_format_title">ডিফল্ট অডিও ফরম্যাট</string>
<string name="preferred_video_format_title">পছন্দসই ভিডিও ফরম্যাট</string>
<string name="webm_description">WebM — বিনামূল্য/স্বাধীন ফরম্যাট</string>
<string name="m4a_description">m4a — ভালো মানের</string>
<string name="theme_title">থিম</string>
<string name="light_theme_title">উজ্জ্বল</string>
<string name="dark_theme_title">অন্ধকার</string>
<string name="black_theme_title">কালো</string>
<string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string>
<string name="popup_remember_size_pos_summary">শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো</string>
<string name="download_dialog_title">ডাউনলোড</string>
<string name="next_video_title">পরবর্তী ভিডিও</string>
<string name="show_next_and_similar_title">পরবর্তী এবং অনুরূপ ভিডিওগুলি দেখাও</string>
<string name="url_not_supported_toast">URL সমর্থিত নয়</string>
<string name="search_language_title">কন্টেন্ট এর জন্য পছন্দসই ভাষা</string>
<string name="settings_category_video_audio_title">ভিডিও এবং অডিও</string>
<string name="settings_category_popup_title">পপআপ</string>
<string name="settings_category_appearance_title">অ্যাপিয়ারেন্স</string>
<string name="settings_category_other_title">অন্যান্য</string>
<!-- <string name="background_player_time_text" translatable="false">%1$s - NewPipe</string> -->
<string name="background_player_playing_toast">ব্যাকগ্রাউন্ডে চলছে</string>
<string name="popup_playing_toast">পপআপ মোডে চলছে</string>
<!-- <string name="c3s_url" translatable="false">https://www.c3s.cc/</string> -->
<string name="play_btn_text">চালাও</string>
<string name="content">কন্টেন্ট</string>
<string name="show_age_restricted_content_title">বয়স সীমাবদ্ধ কন্টেন্ট দেখাও</string>
<string name="video_is_age_restricted">ভিডিওটিকে বয়স সীমিত করা হয়েছে। প্রথমে সেটিংসে বয়স সীমাবদ্ধ ভিডিওগুলি সক্ষম করো।</string>
<string name="duration_live">লাইভ</string>
<string name="downloads">ডাউনলোডগুলি</string>
<string name="downloads_title">ডাউনলোডগুলি</string>
<string name="error_report_title">ত্রুটি প্রতিবেদন</string>
<string name="all">সবগুলি</string>
<string name="channel">চ্যানেল</string>
<string name="yes">হ্যাঁ</string>
<string name="later">পরবর্তীতে</string>
<string name="disabled">নিস্ক্রীয়</string>
<string name="filter">ফিল্টার</string>
<string name="refresh">রিফ্রেশ</string>
<string name="clear">পরিষ্কার</string>
<string name="popup_resizing_indicator_title">আকার পরিবর্তন</string>
<!-- error strings -->
<string name="general_error">ত্রুটি</string>
<string name="network_error">নেটওয়ার্ক ত্রুটি</string>
<string name="could_not_load_thumbnails">সব থাম্বনেইল লোড করা যায়নি</string>
<string name="youtube_signature_decryption_error">ভিডিও URL স্বাক্ষর ডিক্রিপ্ট করা যায়নি।</string>
<string name="parsing_error">ওয়েবসাইট বিশ্লেষন করা যায়নি।</string>
<string name="light_parsing_error">ওয়েবসাইট সম্পুর্নভাবে বিশ্লেষন করা যায়নি।</string>
<string name="content_not_available">কন্টেন্ট উপলব্ধ নয়।</string>
<string name="blocked_by_gema">GEMA কর্তৃক ব্লক করা হয়েছে।</string>
<string name="could_not_setup_download_menu">ডাউনলোড মেনু সেটআপ করা যায়নি।</string>
<string name="live_streams_not_supported">এটি একটি লাইভ স্ট্রিম। যা এখনও সমর্থিত নয়।</string>
<string name="could_not_get_stream">কোনও স্ট্রিম পাওয়া যায়নি।</string>
<string name="could_not_load_image">চিত্র লোড করা যায়নি</string>
<string name="app_ui_crash">অ্যাপ / UI ক্র্যাশ করেছে</string>
<!-- error activity -->
<string name="sorry_string">দুঃখিত, এটা ঘটা উচিত ছিল না।</string>
<!-- <string name="guru_meditation" translatable="false">Guru Meditation.</string> -->
<string name="error_report_button_text">মেইলের মাধ্যমে ত্রুটি প্রতিবেদন করো</string>
<string name="error_snackbar_message">দুঃখিত, কিছু ত্রুটি ঘটেছে।</string>
<string name="error_snackbar_action">প্রতিবেদন</string>
<string name="what_device_headline">তথ্য:</string>
<string name="what_happened_headline">কি হয়েছিল:</string>
<!-- <string name="info_labels">What:\\nRequest:\\nContent Lang:\\nService:\\nGMT Time:\\nPackage:\\nVersion:\\nOS version:\\nGlob. IP range:</string> -->
<string name="your_comment">তোমার মন্তব্য (ইংরেজিতে):</string>
<string name="error_details_headline">বর্ণনা:</string>
<!-- Content descriptions (for better accessibility) -->
<string name="list_thumbnail_view_description">ভিডিও প্রাকদর্শন থাম্বনেইল</string>
<string name="detail_thumbnail_view_description">ভিডিও প্রাকদর্শন থাম্বনেইল</string>
<string name="detail_uploader_thumbnail_view_description">আপলোডারের ইউজারপিক থাম্বনেইল</string>
<string name="detail_likes_img_view_description">পছন্দ হয়েছে</string>
<string name="detail_dislikes_img_view_description">অপছন্দ হয়েছে</string>
<string name="use_tor_title">টর ব্যবহার করো</string>
<string name="use_tor_summary">(পরীক্ষামূলক) গোপনীয়তা বর্ধিত করতে টর এর মাধ্যমে ডাউনলোড ট্রাফিক জোরপুর্বক পাঠাও (ভিডিওগুলি স্ট্রিমিং এ সমর্থিত নয়)।</string>
<string name="report_error">একটি ত্রুটি রিপোর্ট করো</string>
<string name="user_report">ব্যবহারকারীর প্রতিবেদন</string>
<string name="err_dir_create">\'%1$s\' ডাউনলোড ডিরেক্টরি তৈরি করতে পারছে না</string>
<string name="info_dir_created">\'%1$s\' ডাউনলোড ডিরেক্টরি তৈরি করা হয়েছে</string>
<string name="video">ভিডিও</string>
<string name="audio">অডিও</string>
<string name="retry">পুনরায় চেষ্টা করো</string>
<string name="storage_permission_denied">স্টোরেজ অ্যাক্সেস করার অনুমতি অস্বীকার করা হয়েছে</string>
<string name="use_old_player_title">পুরানো প্লেয়ার ব্যবহার করো</string>
<string name="use_old_player_summary">মিডিয়াফ্রেমওয়ার্ক প্লেয়ারের পুরানো বিল্ড।</string>
<string name="videos">ভিডিওগুলি</string>
<string name="subscriber">গ্রাহক</string>
<string name="subscriber_plural">গ্রাহকরা</string>
<string name="subscribe">সাবস্ক্রাইব</string>
<string name="views">প্রদর্শন</string>
<string name="short_thousand">K</string>
<string name="short_million">M</string>
<string name="short_billion">B</string>
<!-- Missions -->
<string name="start">শুরু</string>
<string name="pause">বিরতি</string>
<string name="view">প্রদর্শন</string>
<string name="delete">ডিলেট</string>
<string name="checksum">চেকসাম</string>
<!-- Fragment -->
<string name="add">নতুন মিশন</string>
<string name="finish">ঠিক আছে</string>
<!-- Msg -->
<string name="msg_name">ফাইলের নাম</string>
<string name="msg_threads">থ্রেড</string>
<string name="msg_error">ত্রুটি</string>
<string name="msg_server_unsupported">সার্ভার অসমর্থিত</string>
<string name="msg_exists">ফাইল ইতিমধ্যেই বিদ্যমান</string>
<string name="msg_url_malform">বিকৃত URL অথবা ইন্টারনেট নেই</string>
<string name="msg_running">NewPipe ডাউনলোড হচ্ছে</string>
<string name="msg_running_detail">বিস্তারিত জানার জন্য আলতো চাপ</string>
<string name="msg_wait">অনুগ্রহপূর্বক অপেক্ষা করো…</string>
<string name="msg_copied">ক্লিপবোর্ডে অনুলিপি করা হয়েছে।</string>
<string name="no_available_dir">অনুগ্রহ করে একটি উপলব্ধ ডাউনলোড ডিরেক্টরি নির্বাচন করো।</string>
<string name="msg_popup_permission">এই অনুমতিটি পপআপ মোডে খুলতে প্রয়োজন</string>
<!-- Checksum types -->
<!-- <string name="md5" translatable="false">MD5</string> -->
<!-- <string name="sha1" translatable="false">SHA1</string> -->
<string name="reCaptchaActivity">রিক্যাপচা</string>
<string name="reCaptcha_title">reCAPTCHA চ্যালেঞ্জ</string>
<string name="recaptcha_request_toast">reCAPTCHA চ্যালেঞ্জ অনুরোধ করা হয়েছে</string>
<!-- End of GigaGet's Strings -->
<string name="info_labels">কি:\\nঅনুরোধ:\\nকন্টেন্ট ভাষা:\\nসার্ভিস:\\nসময়(GMT এ):\\nপ্যাকেজ:\\nসংস্করণ:\\nওএস সংস্করণ:\\nআইপি পরিসর:</string>
</resources>

View File

@@ -1,20 +1,18 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><string name="upload_date_text">Publikováno %1$s</string>
<resources>
<string name="upload_date_text">Publikováno %1$s</string>
<string name="no_player_found">Nenalezen žádný přehrávač. Nainstalovat VLC?</string>
<string name="install">Instalovat</string>
<string name="cancel">Zrušit</string>
<string name="open_in_browser">Otevřít v prohlížeči</string>
<string name="share">Sdílet</string>
<string name="loading">Načítám</string>
<string name="download">Stáhnout</string>
<string name="search">Vyhledat</string>
<string name="settings">Nastavení</string>
<string name="did_you_mean">Měli jste na mysli: %1$s?</string>
<string name="search_page">"Vyhledat stránku: "</string>
<string name="share_dialog_title">Sdílet s</string>
<string name="choose_browser">Vybrat prohlížeč</string>
<string name="screen_rotation">otočení</string>
<string name="settings_activity_title">Nastavení</string>
<string name="use_external_video_player_title">Použít externí video přehrávač</string>
<string name="use_external_audio_player_title">Použít externí audio přehrávač</string>
@@ -26,7 +24,6 @@
<string name="play_with_kodi_title">Přehrát pomocí Kodi</string>
<string name="kore_not_found">Aplikace Kore nenalezena. Nainstalovat Kore?</string>
<string name="view_count_text">%1$s zhlédnutí</string>
<string name="background_player_name">NewPipe Přehrávač na pozadí</string>
<string name="download_path_title">Umístění pro stažené video</string>
<string name="download_path_summary">Cesta, kam se uloží stažené video.</string>
<string name="download_path_dialog_title">Zadejte umístění pro stažená videa</string>
@@ -45,7 +42,6 @@
<string name="next_video_title">Následující video</string>
<string name="show_next_and_similar_title">Zobrazit následující a podobná videa</string>
<string name="url_not_supported_toast">URL není podporováno</string>
<string name="similar_videos_btn_text">Podobná videa</string>
<string name="search_language_title">Preferovaný jazyk obsahu</string>
<string name="settings_category_video_audio_title">Video a audio</string>
<string name="settings_category_appearance_title">Vzhled</string>
@@ -70,7 +66,7 @@
<string name="err_dir_create">Nebylo možné vytvořit složku pro stažené soubory \'%1$s\'</string>
<string name="info_dir_created">Vytvořena složka pro stažené soubory \'%1$s\'</string>
<string name="autoplay_by_calling_app_title">Automaticky přehrávat při otevření z jiné aplikace</string>
<string name="autoplay_by_calling_app_title">Automaticky přehrávat při otevření z jiné aplikace</string>
<string name="autoplay_by_calling_app_summary">Automaticky přehrát video, když je NewPipe otevřen z jiné aplikace.</string>
<string name="content">Obsah</string>
<string name="show_age_restricted_content_title">Zobrazovat věkově omezený obsah</string>
@@ -87,27 +83,19 @@
<string name="msg_exists">Soubor již existuje</string>
<string name="msg_url_malform">Špatné URL nebo připojení k Internetu není k disposici</string>
<string name="msg_error">Chyba</string>
<string name="msg_url">Stáhnout URL</string>
<string name="msg_name">Jméno souboru</string>
<string name="msg_threads">Vlákna</string>
<string name="pause">Zastavit</string>
<string name="delete">Smazat</string>
<string name="use_exoplayer_title">Použít ExoPlayer</string>
<string name="use_exoplayer_summary">Experimentální</string>
<string name="start">Začít</string>
<string name="error_drm_unknown">Neznámá chyba DRM</string>
<string name="retry">Zkusit znovu</string>
<string name="text">Text</string>
<string name="enable_background_audio">Hrát na pozadí</string>
<string name="video">Video</string>
<string name="audio">Audio</string>
<string name="report_error">Nahlásit chybu</string>
<string name="info_searched_lbl">Hledáno:</string>
<string name="error_details_headline">Detaily:</string>
<string name="info_requested_stream_lbl">Vyžádaný stream:</string>
<string name="what_happened_headline">Co se stalo:</string>
<string name="error_snackbar_action">NAHLÁSIT</string>
<string name="sorry_string">Omlouváme se, tohle se nemělo stát.</string>
@@ -118,49 +106,29 @@
<string name="live_streams_not_supported">Tento stream je vysílán živě, funkce ještě není podporována.</string>
<string name="could_not_get_stream">Nepodařilo se dostat žádný stream.</string>
<string name="could_not_setup_download_menu">Nepodařilo se nastavit menu stahování.</string>
<string name="settings_title">Nastavení</string>
<string name="error_report_title">Nahlásit chybu</string>
<string name="downloads">Stažené soubory</string>
<string name="downloads_title">Stažené soubory</string>
<string name="what_device_headline">Info:</string>
<string name="your_comment">Vaše poznámky (Anglicky):</string>
<string name="logging">Logování</string>
<string name="logging_normal">Normální</string>
<string name="error_drm_not_supported">Chráněný obsah není podporován na API úrovni menší než 18</string>
<string name="error_drm_unsupported_scheme">Toto zařízení nepodporuje potřebné DRM schéma</string>
<string name="error_no_decoder">Toto zařízení nedodává dekodér pro <xliff:g id="mime_type">
%1$s</xliff:g></string>
<string name="error_no_secure_decoder">Toto zařízení nedodává bezpečný dekodér pro
<xliff:g id="mime_type">
%1$s</xliff:g></string>
<string name="storage_permission_denied">Oprávnění přístupu do Úložiště bylo zamítnuto</string>
<string name="view">Shlédnout</string>
<string name="add">Nová mise</string>
<string name="finish">Hotovo</string>
<string name="switch_mode">Přepnout mezi listem a mřížkou</string>
<string name="msg_fetch_filename">Získej jméno souboru</string>
<string name="action_settings">Nastavení</string>
<string name="reCaptchaActivity">reCAPTCHA</string>
<string name="reCaptcha_title">Výzva reCAPTCHA</string>
<string name="recaptcha_request_toast">Požadována výzva reCAPTCHA</string>
<string name="black_theme_title">Černé</string>
<string name="off">[vypnuto]</string>
<string name="checksum">Checksum</string>
<string name="no_available_dir">Prosím vyberte dostupnou složku pro stažení souborů.</string>
<string name="title_activity_channel">Aktivita kanálu</string>
<string name="user_report">Hlášení uživatele</string>
<string name="logging_verbose">Podrobné</string>
<string name="error_querying_decoders">Nelze zjistit dekodéry zařízení</string>
<string name="error_instantiating_decoder">Nelze doložit dekodér <xliff:g id="decoder_name">
%1$s</xliff:g></string>
<string name="info_labels">Co:\\nŽádost:\\nJazyk obsahu:\\nSlužba:\\nČas GMT:\\nBalíček:\\nVerze:\\nVerze OS:\\nGlobální rozsah IP:</string>
<string name="all">Vše</string>
<string name="channel">Kanál</string>
@@ -172,11 +140,6 @@
<string name="subscribe">Odebírat</string>
<string name="views">zobrazení</string>
<string name="short_thousand">k</string>
<string name="restart_title">Restart</string>
<string name="msg_restart">Pro aplikaci tématu je potřeba restartovat aplikaci.
Chcete restartovat ihned?</string>
<string name="open_in_popup_mode">Otevřít ve vyskakovacím okně</string>
<string name="short_million">M</string>
@@ -184,4 +147,10 @@ Chcete restartovat ihned?</string>
otevření ve vyskakovacím okně</string>
<string name="use_old_player_title">Použít starý přehrávač</string>
<string name="use_external_video_player_summary">Některé formáty rozlišení NEBUDOU obsahovat zvukovou stopu po zapnutí této funkce</string>
<string name="show_higher_resolutions_title">Zobrazit vyšší rozlišení</string>
<string name="show_higher_resolutions_summary">Pouze některá zařízení podporují přehrávání videí ve 2K/4K</string>
<string name="preferred_video_format_title">Preferovaný video formát</string>
<string name="popup_remember_size_pos_title">Zapamatovat si velikost a pozici vyskakovacího okna</string>
<string name="popup_remember_size_pos_summary">Zapamatovat si poslední nastavení velikosti a pozice vyskakovacího okna</string>
</resources>

View File

@@ -1,9 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources>
<string name="view_count_text">%1$s Aufrufe</string>
<string name="upload_date_text">Veröffentlicht am %1$s</string>
<string name="no_player_found">Keinen Streamplayer gefunden. Möchtest du VLC installieren?</string>
<string name="install">Jetzt installieren</string>
<string name="install">Installieren</string>
<string name="cancel">Abbrechen</string>
<string name="open_in_browser">In Browser öffnen</string>
<string name="share">Teilen</string>
@@ -11,32 +11,25 @@
<string name="search">Suchen</string>
<string name="settings">Einstellungen</string>
<string name="did_you_mean">Meintest du: %1$s ?</string>
<string name="search_page">Suchseite: </string>
<string name="share_dialog_title">Teilen mit</string>
<string name="choose_browser">Browser</string>
<string name="screen_rotation">Rotation</string>
<string name="settings_activity_title">Einstellungen</string>
<string name="download_path_title">Downloadverzeichnis für Videos</string>
<string name="download_path_summary">Verzeichnis in dem heruntergeladene Videos gespeichert werden.</string>
<string name="download_path_summary">Verzeichnis in dem heruntergeladene Videos gespeichert werden</string>
<string name="download_path_dialog_title">Downloadverzeichnis für Videos eingeben</string>
<string name="default_resolution_title">Standardauflösung</string>
<string name="play_with_kodi_title">Mit Kodi abspielen</string>
<string name="kore_not_found">Kore App wurde nicht gefunden. Möchten sie Kore jetzt installieren?</string>
<string name="show_play_with_kodi_title">Zeige \"Mit Kodi abspielen\" Option</string>
<string name="show_play_with_kodi_summary">Zeigt eine Option an, über die man Videos mit dem Kodi Mediacenter abspielen kann.</string>
<string name="show_play_with_kodi_summary">Zeigt eine Option an, über die man Videos mit dem Kodi Mediacenter abspielen kann</string>
<string name="play_audio">Audio</string>
<string name="default_audio_format_title">Bevorzugtes Audioformat</string>
<string name="webm_description">WebM — freies Format</string>
<string name="m4a_description">m4a — bessere Qualität</string>
<string name="download_dialog_title">Herunterladen</string>
<string-array name="downloadOptions">
<item>Video</item>
<item>Audio</item>
</string-array>
<string name="next_video_title">Nächstes Video</string>
<string name="show_next_and_similar_title">Zeige nächstes und ähnliche Videos</string>
<string name="url_not_supported_toast">URL wird nicht unterstützt</string>
<string name="similar_videos_btn_text">Ähnliche Videos</string>
<string name="settings_category_video_audio_title">Video &amp; Audio</string>
<string name="search_language_title">Bevorzugte Sprache des Inhalts</string>
<string name="list_thumbnail_view_description">Video-Vorschaubild</string>
@@ -44,7 +37,6 @@
<string name="detail_uploader_thumbnail_view_description">Nutzerbild</string>
<string name="detail_dislikes_img_view_description">Gefällt nicht</string>
<string name="detail_likes_img_view_description">Gefällt</string>
<string name="loading">Lade</string>
<string name="use_external_video_player_title">Benutze externen Videoabspieler</string>
<string name="use_external_audio_player_title">Benutze externen Audioabspieler</string>
<string name="background_player_playing_toast">Spiele im Hintergrund ab</string>
@@ -52,12 +44,11 @@
<string name="use_tor_title">Benutze TOR</string>
<string name="use_tor_summary">(Experimentell) Erzwinge das Herunterladen durch TOR für verbesserte Privatsphäre (Videostream noch nicht unterstützt).</string>
<string name="background_player_name">NewPipe Hintergrundwiedergabe</string>
<string name="network_error">Netzwerkfehler</string>
<string name="download_path_audio_title">Downloadverzeichnis für Musik</string>
<string name="download_path_audio_summary">Verzeichnis zum Speichern heruntergeladener Audiodateien</string>
<string name="download_path_audio_dialog_title">Pfad für heruntergeladene Audiodateien eingeben.</string>
<string name="download_path_audio_dialog_title">Pfad für heruntergeladene Audiodateien eingeben</string>
<string name="theme_title">Aussehen</string>
<string name="dark_theme_title">Dunkel</string>
@@ -90,34 +81,16 @@
<string name="info_labels">Was:\\nAnfrage:\\nSprache des Inhalts:\\nDienst:\\nZeit (GMT):\\nPacket:\\nVersion:\\nOS-Version:\\nGlob. IP-Bereich:</string>
<string name="error_details_headline">Details:</string>
<string name="enable_background_audio">Im Hintergrund abspielen</string>
<string name="video">Video</string>
<string name="audio">Audio</string>
<string name="text">Text</string>
<string name="logging_normal">Normal</string>
<string name="logging_verbose">Ausführlich</string>
<string name="retry">Wiederholen</string>
<string name="off">[aus]</string>
<string name="error_drm_unsupported_scheme">Dieses Gerät unterstützt das erforderliche DRM-Schema nicht</string>
<string name="error_drm_unknown">Ein unbekannter DRM-Fehler ist aufgetreten</string>
<string name="error_querying_decoders">Konnte Dekodierer des Gerätes nicht abrufen</string>
<string name="error_instantiating_decoder">Konnte Dekodierer <xliff:g id="decoder_name">%1$s</xliff:g> nicht instantiieren</string>
<string name="storage_permission_denied">Zugriff auf den Massenspeicher wurde verweigert</string>
<string name="use_exoplayer_title">Benutze ExoPlayer</string>
<string name="use_exoplayer_summary">Experimentell</string>
<string name="sorry_string">Entschuldigung. Dies sollte nicht passieren.</string>
<string name="sorry_string">Entschuldigung. Dies hätte nicht passieren sollen.</string>
<string name="error_snackbar_message">Entschuldigung. Es sind einige Fehler aufgetreten.</string>
<string name="info_searched_lbl">Gesucht:</string>
<string name="info_requested_stream_lbl">Angeforderter Stream:</string>
<string name="your_comment">Dein Kommentar (auf englisch):</string>
<string name="logging">Protokollierung</string>
<string name="error_no_decoder">Dieses Gerät stellt keinen Dekodierer für <xliff:g id="mime_type">%1$s</xliff:g> bereit</string>
<string name="error_no_secure_decoder">Dieses Gerät stellt keinen abgesicherten Dekodierer für <xliff:g id="mime_type">%1$s</xliff:g> bereit</string>
<string name="could_not_get_stream">Konnte keinen Stream holen.</string>
<string name="error_drm_not_supported">Geschützte Inhalte werden von API-Ebenen unterhalb von 18 nicht unterstützt</string>
<string name="autoplay_by_calling_app_title">Bei Aufruf aus einer anderen App automatisch abspielen</string>
<string name="autoplay_by_calling_app_summary">Spielt ein Video automatisch ab, wenn NewPipe von einer anderen App aufgerufen wurde.</string>
<string name="autoplay_by_calling_app_summary">Spielt ein Video automatisch ab, wenn NewPipe von einer anderen App aufgerufen wurde</string>
<string name="report_error">Einen Fehler melden</string>
<string name="user_report">Anwenderbericht</string>
@@ -126,13 +99,11 @@
<string name="main_bg_subtitle">„Suchen“ antippen und loslegen</string>
<string name="downloads">Downloads</string>
<string name="downloads_title">Downloads</string>
<string name="settings_title">Einstellungen</string>
<string name="error_report_title">Fehlerbericht</string>
<string name="delete">Löschen</string>
<string name="checksum">Prüfsumme</string>
<string name="switch_mode">Zwischen Liste und Gitter umschalten</string>
<string name="videos">Videos</string>
<string name="subscriber">Abonnent</string>
@@ -141,7 +112,6 @@
<string name="short_million">Mio.</string>
<string name="short_billion">Mrd.</string>
<string name="msg_url">Download-URL</string>
<string name="msg_name">Dateiname</string>
<string name="msg_error">Fehler</string>
<string name="msg_exists">Datei existiert bereits</string>
@@ -149,9 +119,9 @@
<string name="msg_copied">In Zwischenablage kopiert.</string>
<string name="no_available_dir">Bitte wählen Sie ein verfügbares Downloadverzeichnis.</string>
<string name="start">Start</string>
<string name="start">Starten</string>
<string name="pause">Pause</string>
<string name="view">Anzeigen</string>
<string name="view">Ansehen</string>
<string name="add">Neue Mission</string>
<string name="finish">Okay</string>
<string name="msg_server_unsupported">Server nicht unterstützt</string>
@@ -160,15 +130,12 @@
<string name="msg_threads">Threads</string>
<string name="msg_running">NewPipe lädt herunter</string>
<string name="msg_running_detail">Für Details antippen</string>
<string name="action_settings">Einstellungen</string>
<string name="msg_url_malform">Beschädigte URL oder Internet nicht erreichbar</string>
<string name="msg_fetch_filename">Vorgeschlagener Dateiname</string>
<string name="title_activity_channel">Kanalaktivität</string>
<string name="msg_url_malform">Ungültige URL oder Internet nicht verfügbar</string>
<string name="reCaptchaActivity">reCAPTCHA</string>
<string name="black_theme_title">Schwarz</string>
<string name="reCaptcha_title">reCAPTCHA Herausforderung</string>
<string name="reCaptcha_title">reCAPTCHA-Aufgabe</string>
<string name="recaptcha_request_toast">reCAPTCHA Herausforderung angefordert</string>
<string name="later">Später</string>
@@ -176,11 +143,6 @@
<string name="yes">Ja</string>
<string name="all">Alle</string>
<string name="channel">Kanal</string>
<string name="restart_title">Neu starten</string>
<string name="msg_restart">Um die Änderung des Aussehens zu ändern, müssen Sie die App neu starten.
Möchten Sie jetzt neu starten?</string>
<string name="subscribe">Abonnieren</string>
<string name="disabled">Deaktiviert</string>
@@ -195,11 +157,26 @@ Möchten Sie jetzt neu starten?</string>
<string name="msg_popup_permission">Diese Berechtigung ist für das
Öffnen im Popup-Modus erforderlich</string>
<string name="use_old_player_summary">Mediaframework Player der vorherigen Version.</string>
<string name="use_old_player_summary">Alter eingebauter Mediaframework-Player.</string>
<string name="default_popup_resolution_title">Standardauflösung des Popups</string>
<string name="show_higher_resolutions_title">Zeige höhere Auflösungen an</string>
<string name="show_higher_resolutions_summary">Nur einige Geräte unterstützen das Abspielen von 2k-/4k-Videos</string>
<string name="show_higher_resolutions_summary">Nur einige Geräte unterstützen das Abspielen von 2K-/4K-Videos</string>
<string name="controls_background_title">Hintergrund</string>
<string name="controls_popup_title">Popup</string>
<string name="popup_remember_size_pos_title">Größe und Position des Popups merken</string>
<string name="use_external_video_player_summary">Manche Auflösungen werden KEINE Tonspur haben, wenn diese Option eingeschaltet ist</string>
<string name="popup_remember_size_pos_summary">Letzte Größe und Position des Popups merken</string>
<string name="player_gesture_controls_title">Gestensteuerung</string>
<string name="player_gesture_controls_summary">Gesten benutzen, um Helligkeit und Lautstärke des Players zu steuern</string>
<string name="show_search_suggestions_title">Suchvorschläge</string>
<string name="show_search_suggestions_summary">Beim Suchen Vorschläge anzeigen</string>
<string name="settings_category_popup_title">Hinweis</string>
<string name="filter">Filter</string>
<string name="refresh">Aktualisieren</string>
<string name="clear">Löschen</string>
<string name="popup_resizing_indicator_title">Größenänderung</string>
<string name="best_resolution">Beste Auflösung</string>
</resources>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="background_player_name">NewPipe Background Player</string>
<resources>
<string name="view_count_text">%1$s προβολές</string>
<string name="upload_date_text">Ανέβηκε στις %1$s</string>
<string name="no_player_found">Δεν βρέθηκε πρόγραμμα αναπαραγωγής. Εγκατάσταση του VLC;</string>
@@ -7,16 +7,13 @@
<string name="cancel">Ακύρωση</string>
<string name="open_in_browser">Άνοιγμα στον browser</string>
<string name="share">Κοινοποίηση</string>
<string name="loading">Φορτώνει</string>
<string name="download">Λήψη</string>
<string name="search">Αναζήτηση</string>
<string name="settings">Ρυθμίσεις</string>
<string name="did_you_mean">"Μήπως εννοείτε: "</string>
<string name="search_page">"Αναζήτηση σελίδας: "</string>
<string name="did_you_mean">"Μήπως εννοείτε: %1$s ?"</string>
<string name="share_dialog_title">Κοινοποίηση με</string>
<string name="choose_browser">Επιλέξτε browser</string>
<string name="screen_rotation">περιστροφή</string>
<string name="settings_activity_title">Ρυθμίσεις</string>
<string name="use_external_video_player_title">Χρήση εξωτερικού video player</string>
<string name="use_external_audio_player_title">Χρήση εξωτερικού audio player</string>
@@ -45,7 +42,6 @@
<string name="next_video_title">Επόμενο video</string>
<string name="show_next_and_similar_title">Προβολή επόμενου και σχετικών video</string>
<string name="url_not_supported_toast">Δεν υποστηρίζεται η διεύθυνση URL</string>
<string name="similar_videos_btn_text">Σχετικά video</string>
<string name="search_language_title">Προτιμώμενη γλώσσα περιεχομένου</string>
<string name="settings_category_video_audio_title">Video &amp; Ήχος</string>
<string name="settings_category_appearance_title">Εμφάνιση</string>

View File

@@ -1,21 +1,18 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="background_player_name">NewPipe-fonludilo</string>
<resources>
<string name="view_count_text">%1$s vidoj</string>
<string name="upload_date_text">Eldonita je %1$s</string>
<string name="install">Instali</string>
<string name="cancel">Nuligi</string>
<string name="open_in_browser">Malfermi per retumilo</string>
<string name="share">Konigi</string>
<string name="loading">Ŝargado</string>
<string name="download">Elŝuti</string>
<string name="search">Serĉi</string>
<string name="settings">Agordoj</string>
<string name="did_you_mean">Ĉu vi intencis: %1$s?</string>
<string name="search_page">"Serĉpaĝo: "</string>
<string name="share_dialog_title">Konigi kun</string>
<string name="choose_browser">Elekti retumilon</string>
<string name="screen_rotation">turno</string>
<string name="settings_activity_title">Agordoj</string>
<string name="use_external_video_player_title">Uzi eksteran videoludilon</string>
<string name="use_external_audio_player_title">Uzi eksteran sonludilon</string>
@@ -33,7 +30,6 @@
<string name="download_dialog_title">Elŝuti</string>
<string name="next_video_title">Sekva video</string>
<string name="url_not_supported_toast">Ligilo ne subtenita</string>
<string name="similar_videos_btn_text">Similaj videoj</string>
<string name="search_language_title">Preferata enhavlingvo</string>
<string name="settings_category_video_audio_title">Video kaj sono</string>
<string name="settings_category_appearance_title">Apero</string>
@@ -59,7 +55,7 @@
<string name="detail_uploader_thumbnail_view_description">Miniaturo de la bildo de la alŝutinto</string>
<string name="err_dir_create">La elŝutujo \'%1$s\' ne kreeblas</string>
<string name="info_dir_created">Elŝutujo \'%1$s\' kreita</string>
<string name="download_path_title">Elŝutujo por videoj</string>
<string name="download_path_title">Elŝutujo por videoj</string>
<string name="download_path_audio_title">Elŝutujo por muziko</string>
<string name="use_tor_summary">(Eksperimenta) Devigi elŝuttrafikon tra Tor por pli bona privateco (elsendfluaj videoj estas ankoraŭ ne subtenitaj).</string>
@@ -80,8 +76,5 @@
<string name="report_error">Raporti eraron</string>
<string name="video">Video</string>
<string name="text">Teksto</string>
<string name="logging_normal">Normala</string>
<string name="retry">Reprovi</string>
<string name="use_exoplayer_summary">Eksperimenta</string>
</resources>

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