Compare commits

...

889 Commits
v0.24.1 ... dev

Author SHA1 Message Date
Stypox e37336eef2
Merge pull request #10918 from Stypox/non-transitive-r
Migrate to non-transitive R classes
2024-05-08 10:35:08 +02:00
Siddhesh Naik 879d7a24f0
Fix github worklow for Android tests (#11014)
- The github workflow fails when running android tests.
- The workflow is trying to launch an x86 emulator on aarch-64 (macos-latest) host.
- The macos-latest system seem to be used originally as it supports
  hardware acceleration.
- This is no longer recomended, and ubuntu-latest host can handle the
  same and be faster than macos-latest.

Doc: https://github.com/marketplace/actions/android-emulator-runner#running-hardware-accelerated-emulators-on-linux-runners
2024-04-29 02:45:18 +05:30
Stypox 9e4ac2eacb
Merge pull request #11003 from ashutosh001/dev
Update README.md
2024-04-26 11:05:54 +02:00
ashutosh001 d9d6fff48f
Update README.md 2024-04-26 06:23:46 +05:30
Stypox 9828586762
Fix indentation for ktlint 2024-04-23 20:16:04 +02:00
Hosted Weblate 8caaa6d297
Merge branch 'origin/dev' into Weblate. 2024-04-23 19:27:20 +02:00
Stypox 83ca6b9468
Update NewPipeExtractor to v0.24.0 2024-04-23 19:25:13 +02:00
Stypox 24e65ef018
Merge branch 'dev' 2024-04-23 19:23:20 +02:00
Stypox a69bbab732
Merge pull request from GHSA-wxrm-jhpf-vp6v
Fix preferences import vulnerability
2024-04-23 19:22:17 +02:00
Stypox a557ac3c7b
Merge pull request #10929 from TeamNewPipe/release-0.27.0
Release v0.27.0 (997)
2024-04-23 19:21:12 +02:00
Stypox d61b4b89ea
Merge pull request #10992 from Stypox/fix-download-nnfp
Fix free storage space check for all APIs
2024-04-23 18:42:57 +02:00
Stypox b8daf16b92
Update app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2024-04-23 18:39:56 +02:00
Stypox caa3812e13
Ignore all errors when getting free storage space
It's not a critical check that needs to be perfomed, so in case something does not work on some device/version, let's just ignore the error.
2024-04-23 18:05:31 +02:00
Hosted Weblate 23a087c498
Translated using Weblate (Romanian)
Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Croatian)

Currently translated at 99.5% (735 of 738 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (French (Louisiana))

Currently translated at 0.2% (2 of 738 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (738 of 738 strings)

Added translation using Weblate (French (Louisiana))

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (French)

Currently translated at 99.7% (736 of 738 strings)

Added translation using Weblate (Arabic (Tunisian))

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Kannada)

Currently translated at 5.8% (43 of 738 strings)

Translated using Weblate (Kannada)

Currently translated at 5.1% (4 of 78 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Russian)

Currently translated at 99.8% (737 of 738 strings)

Translated using Weblate (German)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (German)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 99.0% (731 of 738 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (736 of 738 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (736 of 738 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 50.0% (39 of 78 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Japanese)

Currently translated at 99.4% (734 of 738 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Slovak)

Currently translated at 21.7% (17 of 78 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 62.8% (49 of 78 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Polish)

Currently translated at 61.5% (48 of 78 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 23.0% (18 of 78 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Flavian <3zorro.1@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jonatan Nyberg <jonatan@autistici.org>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Ray <ray@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Sergio Marques <so.boston.android@gmail.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Tấn Lực Trương <september122022ios16@gmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: abhijithkjg <abhijithkj2001@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kaajjo <claymanoff@gmail.com>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: yosrinajar <yosron3@gmail.com>
Co-authored-by: zeineb-b <zeinebbouhejba21@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/kn/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2024-04-23 18:00:52 +02:00
Stypox c3c39a7b24
Fix free storage space check for all APIs
See https://stackoverflow.com/q/31171838
See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html
2024-04-23 12:16:06 +02:00
Stypox 00770fc634
Update NewPipeExtractor 2024-04-20 13:11:08 +02:00
Stypox 5bf77160f7
Merge pull request #10952 from bg172/release-0.27.0
Add an intuitive prefix for the duration of lists in the UI
2024-04-11 09:27:54 +02:00
Stypox d9da84c412
Merge pull request #10957 from Stypox/fix-feed-npe
Fix NPE if avatarUrl is null when reloading feed
2024-04-11 09:26:11 +02:00
Audric V b3a6318672
Merge pull request #10959 from Stypox/fix-comment-replies-state
Fix not saving comment replies state on config change
2024-04-10 16:24:31 +02:00
Stypox 67b41b970d
Fix not saving comment replies state on config change 2024-04-10 10:52:47 +02:00
Stypox 3738e30949
Fix NPE when avatarUrl is empty 2024-04-09 20:18:21 +02:00
Stypox 0ba73b11c1
Update NewPipeExtractor 2024-04-08 00:03:37 +02:00
bg1722 13baaa31cd add an intuitive prefix for the duration of lists on UI, and avoid using the new prefix for single videos 2024-04-06 07:58:05 +02:00
TobiGr f0db2aa43c Improve documentation 2024-04-04 15:49:12 +02:00
Stypox f704721b59
Release v0.27.0 (997) 2024-04-01 14:23:48 +02:00
Stypox 7abf0f4886
Update NewPipeExtractor to YT comments fix PR
https://github.com/TeamNewPipe/NewPipeExtractor/pull/1163
2024-04-01 14:23:04 +02:00
Stypox c915b6e68b
Add changelog for v0.27.0 (997) 2024-04-01 14:16:51 +02:00
Hosted Weblate 0b28c688c6
Translated using Weblate (Estonian)
Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Vietnamese)

Currently translated at 46.7% (36 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (736 of 738 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (738 of 738 strings)

Translated using Weblate (German)

Currently translated at 99.7% (736 of 738 strings)

Translated using Weblate (Korean)

Currently translated at 31.1% (24 of 77 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Korean)

Currently translated at 98.9% (726 of 734 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (734 of 734 strings)

Translated using Weblate (German)

Currently translated at 99.7% (732 of 734 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (German)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Hungarian)

Currently translated at 16.8% (13 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (730 of 730 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (730 of 730 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Apious <apious@kakao.com>
Co-authored-by: Eduardo Malaspina <vaio0@swismail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Michael Moroni <michaelmoroni@disroot.org>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Pi-Cla <pirateclip@protonmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Tim Trek <T.Trek@byom.de>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Tấn Lực Trương <september122022ios16@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translation: NewPipe/Metadata
2024-04-01 13:38:40 +02:00
Stypox 2756ef6d2f
Show notification when failing to import settings 2024-03-30 18:53:45 +01:00
Stypox 7da1d30010
Expose all import/export errors to the user 2024-03-30 18:47:20 +01:00
Stypox 8e192acb63
Add test zips and extensive tests for ImportExportManager
Now all possible combinations of files in the zip (present or not) are checked at the same time
2024-03-30 18:42:11 +01:00
Stypox d8423499dc
Use JSON for settings imports/exports 2024-03-30 16:58:12 +01:00
TobiGr 974167fcb8 Add comment that empty constructors are needed for IcePick
See 5e7ad6ffd1 and https://github.com/TeamNewPipe/NewPipe/pull/10781#discussion_r1545351144
2024-03-30 16:19:02 +01:00
Stypox 6afdbd6fd3
Add test: vulnerable settings should fail importing 2024-03-30 16:12:41 +01:00
Stypox d8668ed226
Show snackbar error when settings import fails 2024-03-30 16:12:41 +01:00
Stypox d75a6eaa41
Fix vulnerability with whitelist-aware ObjectInputStream
Only a few specific classes are now allowed.
2024-03-30 16:12:35 +01:00
Stypox 235fb92638
Make checkstyle accept javadocs with long links 2024-03-30 15:49:06 +01:00
Stypox ea18b4ea1f
Move import export manager to separate folder 2024-03-30 15:49:05 +01:00
Stypox 58f5ec0181
Merge pull request #9580 from pratyaksh1610/branch-8232
Moved player notification setting to notification section
2024-03-30 15:38:33 +01:00
pratyaksh1610 e42c9abdde
moved player notification to notification section 2024-03-30 15:23:46 +01:00
Stypox 5e7ad6ffd1
Fix fragments without empty constructor 2024-03-30 15:15:31 +01:00
Stypox 4c8238874e
Merge pull request #8221 from GGAutomaton/feature-7870
Sort bookmarked playlists
2024-03-30 15:02:37 +01:00
Stypox 38d4887901
Undo some unneeded changes to LocalPlaylistManager 2024-03-30 14:46:13 +01:00
Stypox c9051d33c1
Fix warnings and allow moving only up and down even in grid 2024-03-30 14:39:40 +01:00
Stypox 3cc0205def
Fix inconsistencies when removing playlist
Remove checkDisplayIndexModified because it was causing more problems than it solved. Now when adding new playlists they won't necessarily appear at the top, but will get sorted alphabetically along with the other playlists with index -1. This will be the case until any playlist is sorted, at which point all indices are assigned and newly added playlists will appear at the top again.
2024-03-30 14:14:31 +01:00
Stypox 90979e2a81
Fix PlaylistLocalItemTest 2024-03-29 20:58:07 +01:00
Stypox e66e1b542c
Also sort playlist duplicates by display index 2024-03-29 20:55:24 +01:00
Stypox 92e9c3e42e
Fix DatabaseMigrationTest
Complete removal of unneeded index, and remove default value for `remote_playlists.display_index`.
2024-03-29 20:43:55 +01:00
Stypox 4591c09637
Apply review 2024-03-29 18:08:37 +01:00
Stypox e1ce3fef1b
Merge branch 'dev' into pr8221 2024-03-29 18:08:31 +01:00
Stypox 3c0a200f7b
Merge pull request #6045 from bg172/showOverallDurationInPlaylist
show overall duration of videos in playlist
2024-03-29 14:32:29 +01:00
bg1722 bef5907ec3
show OverallDuration in Playlist
earlier only overall amount of videos was shown. Now overall duration is shown there too - as formatted by existing Localization.concatenateStrings() and Localization.getDurationString().

show all videos OverallDuration in local Playlist too

refactor to make implementation in LocalPlaylistFragment and PlaylistFragment more obviously similar

unfortunately could not refactor upto BaseLocalListFragment

revert the changes for online Playlists

because they are paginated and may be infinite i.e. correct count may come only from the service->extractor chain which unfortunately does not give overall duration yet

next try to improve user-experience with online Playlist

just show that duration is longer (">") than the calculated value in case there is more page(s)

even more improve user-experience for online Playlist

by adding the duration of next items as soon as they are made visible

make showing of playlists duration configurable, disabled by default

adjusted duration to be handled as long because it comes as long from extractor

no idea why I handled it as int earlier

Revert "make showing of playlists duration configurable, disabled by default", refactor

This reverts commit bc1ba17a20d3dd1763210f81d7ca67c5f1734a3d.

Fix rebase

Apply review

Rename video -> stream

Remove unused settings keys
2024-03-29 14:11:27 +01:00
Stypox f0beb662aa
Merge pull request #10790 from TeamNewPipe/update-check-consent
Ask for consent before checking for updates
2024-03-29 11:42:57 +01:00
Stypox 92402685f8
Improve new version checks before running 2024-03-29 11:14:30 +01:00
Stypox 3703fed1a5
update_app_key default value should be false 2024-03-29 11:08:33 +01:00
Stypox f4fb960c62
Migrate to non-transitive R classes 2024-03-29 00:17:13 +01:00
Tobi a3bbbf03b4 Ask for consent before starting update checks
NewPipe is contacting its servers without asking for the users' consent. This is categorized as "tracking" by F-Droid (see https://github.com/TeamNewPipe/NewPipe/discussions/10785).

This commit disables checking for udpates by default and adds a dialog asking for the user's consent to automatically check for updates if the app version is eligible for them. After upgrading to a version containing this commit the user is asked directly on the first app start. On fresh installs however, showing it on the first app start contributes to a bad onboarding an welcoming experience. Therefore, the dialog is shown at the second app start.

Co-authored-by: Stypox <stypox@pm.me>
2024-03-28 23:42:00 +01:00
TobiGr 1d3a69a29f Move text from manual_update_title to check_for_updates 2024-03-28 23:08:02 +01:00
Stypox 10c57b15da
Merge pull request #10781 from Profpatsch/BaseDescriptionFragment-assert-member-is-initialized
BaseDescriptionFragment: Assert member is initialized
2024-03-28 22:48:00 +01:00
Stypox b85f7a6747
Some more slight improvements 2024-03-28 22:46:30 +01:00
Stypox 3f94e7b638
Merge pull request #10912 from Stypox/download-fixes
Download fixes
2024-03-28 19:14:20 +01:00
Stypox 2af95cc1d4
Merge pull request #9236 from vincetzr/Option-to-reset-settings
Option to reset settings
2024-03-28 19:00:54 +01:00
Stypox cefdefdfd2
11111th commit 2024-03-28 18:51:36 +01:00
Stypox 37f7fa7ef4
Merge branch 'dev' into pr9236 2024-03-28 18:43:29 +01:00
Stypox e687eb5631
Merge pull request #8242 from dtcxzyw/trim-search-string
Trim search string and remove duplicate records from the database
2024-03-28 18:34:59 +01:00
Stypox 88c3af7647
Merge pull request #9975 from Marius1501/landscape_card_mode_improve
Changed the landscape layout of list card item
2024-03-28 15:01:41 +01:00
ge78fug ddd6c8cbf1
Changed the landscape layout of list card item
Make layout-land/list_stream_card_item a symlink to layout/list_stream_item
2024-03-28 14:46:18 +01:00
Stypox 81220f90d6
Merge pull request #10909 from Stypox/fix-getAudioTrackType-null
Fix not considering nullability when comparing getAudioTrackType
2024-03-28 13:47:12 +01:00
Stypox e0268a91ad
Merge pull request #10717 from Stypox/cache-key-type
Calculate cache key based on info type instead of item type
2024-03-28 13:34:51 +01:00
Stypox 29e4135aaa
Try to fix PR labeler
Reference: https://github.com/actions/labeler?tab=readme-ov-file#permissions
2024-03-28 12:03:10 +01:00
Stypox 5d9adce40d
Fix NPE, since dismissing a dialog still calls onViewCreated() 2024-03-28 11:35:21 +01:00
Stypox d3afde8789
Remove unused DownloadDialog.onDismissListener 2024-03-28 11:21:33 +01:00
Stypox d8a5d5545d
Fix choosing audio format to mux with video-only download 2024-03-28 11:09:56 +01:00
Stypox bed3516687
Fix non-desugared method being used
Search for "Not supported at all minSDK levels" here: https://developer.android.com/studio/write/java8-support-table
2024-03-27 17:30:23 +01:00
Stypox 3a014d8d46
Fix not considering nullability when comparing getAudioTrackType 2024-03-27 16:05:17 +01:00
Stypox 58ae7fbccb
Merge pull request #10724 from Roshanjossey/patch-1
use GitHub markdown to emphasise warning in Readme
2024-03-27 10:27:50 +01:00
Stypox b06a9618d4
use GitHub markdown to emphasise warning in all READMEs 2024-03-27 10:22:52 +01:00
Stypox 434c4a5cbc
Merge pull request #10908 from TeamNewPipe/node-20
Update workflows to use Node 20
2024-03-27 09:47:48 +01:00
TobiGr c34d30dc17 [CI] Update sonar job to use cache@v4
Updates deprecated Node 16 to 20
2024-03-26 22:48:47 +01:00
TobiGr 0d4c1bee3f [CI] Update gradle/wrapper-validation-action to v2
Updates deprecated Node 16 to 20
2024-03-26 22:32:29 +01:00
Tobi 34a25d0be3
Merge pull request #10907 from TeamNewPipe/weblate
Update translations
2024-03-26 21:42:40 +01:00
Mohammed Anas 3134f5e747
Don't add "question" label to question discussions (#10906)
They already have a "Questions" category of their own anyway.
2024-03-26 20:28:28 +00:00
Hosted Weblate 1732584e5e Translated using Weblate (Danish)
Currently translated at 100.0% (729 of 729 strings)

Co-authored-by: cat <158170307+cultcats@users.noreply.github.com>
2024-03-26 21:19:18 +01:00
Tobi f50cafbac1
Merge pull request #10905 from mhmdanas/fix-question-discussion-form
Fix GitHub question discussion form
2024-03-26 21:04:02 +01:00
Mohammed Anas bc7c3f48ad
Fix GitHub question discussion form
The name and description fields are both invalid here.
2024-03-26 19:53:45 +00:00
Tobi b760419fd5
Merge pull request #10896 from TeamNewPipe/storage-message
Add separate message when download is rejected due to insufficient storage
2024-03-26 18:56:18 +01:00
Tobi 5cf3c58d0e
Merge pull request #10732 from Profpatsch/dont-write-media-format
Don't write defaultFormat setting, use default value
2024-03-25 10:36:35 +01:00
TobiGr 206d1b6db4 Add separate message when download is rejected due to insufficient storage 2024-03-21 11:56:42 +01:00
CloudyRowly 2e318b8b03
Added "free memory" check before downloading [Android N / API 24+] (#10505)
Added "free space" check before downloading eliminating bugs related to out-of-memory on Android N / API 24+
2024-03-21 09:18:55 +01:00
Isira Seneviratne 5bdb6f18d6 Use hexToByteArray() extension 2024-03-20 14:00:13 +01:00
Isira Seneviratne 2e53a99361 Convert isReleaseApk to lazy value 2024-03-20 14:00:13 +01:00
Isira Seneviratne bec18e13d3 Improve app signature check 2024-03-20 14:00:13 +01:00
Tobi 7edd471ec5
Merge pull request #10890 from TeamNewPipe/weblate
Update translations
2024-03-18 10:58:28 +01:00
Hosted Weblate e6a4a3fa4f
Translated using Weblate (Danish)
Currently translated at 96.4% (703 of 729 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Danish)

Currently translated at 96.4% (703 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Danish)

Currently translated at 88.3% (644 of 729 strings)

Translated using Weblate (Danish)

Currently translated at 88.3% (644 of 729 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Georgian)

Currently translated at 92.2% (71 of 77 strings)

Translated using Weblate (Uzbek (latin))

Currently translated at 62.6% (457 of 729 strings)

Translated using Weblate (Santali)

Currently translated at 12.6% (92 of 729 strings)

Translated using Weblate (French)

Currently translated at 89.6% (69 of 77 strings)

Translated using Weblate (Japanese)

Currently translated at 11.6% (9 of 77 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.1% (4 of 77 strings)

Translated using Weblate (Bengali)

Currently translated at 20.7% (16 of 77 strings)

Translated using Weblate (German)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Bengali (India))

Currently translated at 40.7% (297 of 729 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 85.5% (624 of 729 strings)

Translated using Weblate (Tamil)

Currently translated at 46.6% (340 of 729 strings)

Translated using Weblate (Bengali (Bangladesh))

Currently translated at 55.1% (402 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 98.7% (76 of 77 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.4% (725 of 729 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 99.4% (725 of 729 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 2.5% (2 of 77 strings)

Translated using Weblate (Malay)

Currently translated at 48.6% (355 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 99.3% (724 of 729 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Finnish)

Currently translated at 98.3% (717 of 729 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 22.0% (17 of 77 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 95.4% (696 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Vietnamese)

Currently translated at 42.8% (33 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Esperanto)

Currently translated at 70.7% (516 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 94.6% (690 of 729 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (French)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Santali)

Currently translated at 10.0% (73 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 42.8% (33 of 77 strings)

Translated using Weblate (German)

Currently translated at 81.8% (63 of 77 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Kannada)

Currently translated at 5.4% (40 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Croatian)

Currently translated at 88.7% (647 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 20.7% (16 of 77 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (French)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Catalan)

Currently translated at 86.5% (631 of 729 strings)

Translated using Weblate (Telugu)

Currently translated at 58.9% (430 of 729 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Tigrinya)

Currently translated at 8.9% (65 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 98.7% (76 of 77 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Slovak)

Currently translated at 99.7% (727 of 729 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 85.5% (624 of 729 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Swedish)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (728 of 729 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (729 of 729 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Serbian)

Currently translated at 18.1% (14 of 77 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (77 of 77 strings)

Translated using Weblate (Greek)

Currently translated at 24.6% (19 of 77 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (German)

Currently translated at 100.0% (728 of 728 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Kannada)

Currently translated at 5.5% (40 of 726 strings)

Translated using Weblate (Sinhala)

Currently translated at 2.6% (2 of 76 strings)

Translated using Weblate (Sinhala)

Currently translated at 4.1% (30 of 726 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.2% (4 of 76 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Turkish)

Currently translated at 99.8% (725 of 726 strings)

Translated using Weblate (Slovak)

Currently translated at 98.4% (715 of 726 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (French)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Italian)

Currently translated at 99.5% (723 of 726 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (German)

Currently translated at 100.0% (726 of 726 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 21.0% (16 of 76 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Romanian)

Currently translated at 99.8% (724 of 725 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (725 of 725 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Alexthegib <traducoes@skiff.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andrey F <firsan777@mail.ru>
Co-authored-by: Angelk90 <angelo.k90@hotmail.it>
Co-authored-by: Chethan <76928501+ch3thanhs@users.noreply.github.com>
Co-authored-by: Danr <mdp43140@gmail.com>
Co-authored-by: David Svane <davidcygnus@users.noreply.hosted.weblate.org>
Co-authored-by: Deleted User <noreply+77891@weblate.org>
Co-authored-by: DuninduH <mateyh37@gmail.com>
Co-authored-by: Eric <zxmegaxqug@hldrive.com>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Heidhou chazanouvha <Heidhou@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihfandi <ihfandicahyo@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jan Layola <gilajan@protonmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Juan Martinez <jjml.nipon@gmail.com>
Co-authored-by: KarmaKat <lloydwestbury@gmail.com>
Co-authored-by: Kuko <kuko7@protonmail.ch>
Co-authored-by: LiJu09 <lisojuraj@gmail.com>
Co-authored-by: Martin Constantino–Bodin <martin.bodin@ens-lyon.org>
Co-authored-by: Mehmet <mehmetyalcin.0103@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nils Van Zuijlen <nils.van-zuijlen@mailo.com>
Co-authored-by: Nista <42772160+Nista11@users.noreply.github.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: P.O <rasmusson.mikael@protonmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Pi-Cla <pirateclip@protonmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Ray <ray@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Scrambled777 <weblate.scrambled777@simplelogin.com>
Co-authored-by: Sergio Marques <so.boston.android@gmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Subbarayudu <raidu.g6@gmail.com>
Co-authored-by: Subham Jena <subhamjena8465@gmail.com>
Co-authored-by: T1z3n <info@njbraun.de>
Co-authored-by: Terry Louwers <t.louwers@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: Xəyyam Qocayev <xxmn77@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: cat <158170307+cultcats@users.noreply.github.com>
Co-authored-by: ds-z <drazen.sostaric01@gmail.com>
Co-authored-by: dyare darbani <darbanidyare@gmail.com>
Co-authored-by: fsbat0 <fsbat0@users.noreply.hosted.weblate.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: hshbuk <hsh.bukchin@gmail.com>
Co-authored-by: jspast <joao.pastorello@protonmail.com>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Co-authored-by: Åzze <laitinen.jere222@gmail.com>
Co-authored-by: Çağla Pickaxe <caglapickaxe@gmail.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bg/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/el/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ka/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/si/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2024-03-18 09:59:34 +01:00
Tobi de2a139340
Merge pull request #10889 from eltociear/patch-1
Fix typo in TextLinkifier.java
2024-03-18 08:38:16 +01:00
Ikko Eltociear Ashimine 9d6ac67c46
Update TextLinkifier.java
minor fix
2024-03-18 14:43:16 +09:00
Audric V 6f7b905983
Merge pull request #10740 from Goooler/gha
Update GitHub action dependencies in workflows
2024-02-05 23:44:45 +01:00
Audric V bcd4626008
Merge pull request #10817 from Isira-Seneviratne/Jsoup
Update jsoup to 1.17.2
2024-02-05 14:37:43 +01:00
Isira Seneviratne 27730a20d6 Update Jsoup to 1.17.2 2024-02-05 10:52:08 +05:30
Audric V 4aa0190175
Merge pull request #10795 from TeamNewPipe/matrix_room_URL_change
Update Matrix chat URL to new link
2024-01-28 14:14:30 +01:00
opusforlife2 6dd62335e9
Update Matrix room URL to new link 2024-01-27 16:36:13 +00:00
Profpatsch 32d2606a65 BaseDescriptionFragment: Assert member is initialized
`streamInfo` and `channelInfo` have to be initialized, since the only
way to construct the class it to pass them. So we can remove the null
check boilerplate and make some of the accessors `NonNull`.
2024-01-23 14:28:37 +01:00
Zongle Wang 2051334bba
Bump GH actions
Old ones are deprecated.
2024-01-08 11:55:57 +08:00
Profpatsch 575e809004 Don't write defaultFormat setting, use default value
Nowhere else does this (write a setting if it’s not set).

It took me a while to see that this code does not do what it intends,
because `defaultFormat` is already the default value in the first
`context.getString`, so calling `getMediaFormatFromKey` again is the
exact same call (“do you know the definition of insanity…”) and will
return `null` again …

So let’s drop the setting write and just rely on the default values.
2024-01-06 17:24:53 +01:00
Roshan Jossy 66e8e2a696
use GitHub markdown to emphasise warning in Readme 2024-01-01 15:07:37 +01:00
Stypox 55373c95d9
Update NewPipeExtractor to include MediaCCC channel fix 2023-12-30 23:49:09 +01:00
Stypox 04bdc1cc0b
Base cache key on info type instead of item type
It didn't really made sense to consider two cache keys as equal based on the type of items contained within that list.
2023-12-30 23:46:16 +01:00
Stypox 1d8850d1b2
Merge pull request #10712 from Stypox/notification-actions-api-33-2
[Android 13+] Restore support of custom notification actions
2023-12-30 21:55:44 +01:00
Stypox f98548698a
Android 33 -> Android 13
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2023-12-30 21:55:32 +01:00
Stypox 4b1824e8c1
Allow play/pausing from notification when buffering
This change is in line with a recent change in how the play/pause button behaves in the player ui: if the buffering indicator is shown, it's still possible to toggle play/pause, to allow e.g. pausing videos before they even start.
This change was needed because on Android 13+ notification actions can't be null, and thus the buffering hourglass action wasn't shown.
2023-12-29 16:18:26 +01:00
Stypox 17e88f1749
Do not update notification actions if nothing changed
This should avoid costly updates of the media session.
2023-12-29 16:16:45 +01:00
Stypox 5edafca05a
Implement notification actions via MediaSessionConnector on Android 13+ 2023-12-29 15:54:15 +01:00
Stypox 2c4c283099
Extract NotificationActionData from NotificationUtil 2023-12-29 15:54:15 +01:00
Stypox 9fb8125655
Allow each notification slot to contain any possible action 2023-12-29 15:54:15 +01:00
Stypox aab6580195
Extract NotificationSlot from NotificationActionsPreference 2023-12-29 12:31:59 +01:00
Stypox 30f0db1d28
Customize only 2 notification actions on Android 13+ 2023-12-29 12:13:08 +01:00
Stypox 5a4dae2070
Fix settings_notification.xml indentation 2023-12-29 11:37:17 +01:00
Stypox 8345f348f6
Merge pull request #10091 from TeamNewPipe/feat/playlist_description
Add playlist description to playlist fragment
2023-12-29 10:58:13 +01:00
Stypox 9220e32463
Fix FeedDAOTest 2023-12-29 10:54:31 +01:00
Stypox 845e72bf4a
Merge branch 'master' into dev 2023-12-29 10:48:37 +01:00
Tobi 49429ff40a
Merge pull request #10700 from TeamNewPipe/newpipe_0.26.1
Newpipe 0.26.1
2023-12-26 18:26:48 +01:00
TobiGr 3df21ad25e Bump version to 0.26.1 (996) 2023-12-26 16:59:02 +01:00
TobiGr d0f4600be4 Add changelog for NewPipe 0.26.1 2023-12-26 16:58:49 +01:00
TobiGr 0fa2e76c3e Fix NPE when ChannelTabLHFactory not implemented for a service
Fixes #10698
2023-12-26 16:55:52 +01:00
Stypox 9ff1b5230f
Improve TextEllipsizer class 2023-12-23 18:04:05 +01:00
TobiGr 65eb631711
Ellipsize playlist description if it is longer than 5 lines
The description can be expanded / collapsed via a "show more" / "show less" button.
2023-12-23 12:33:52 +01:00
TobiGr 6c99557553
Add playlist description to PlaylistFragment 2023-12-23 12:13:34 +01:00
Stypox 2b4357fa87
Merge pull request #10530 from TacoTheDank/bumpMiscLibraries
Update miscellaneous libraries
2023-12-23 12:06:42 +01:00
Stypox cda4b3faaa
Update AGP and Gradle 2023-12-23 12:01:50 +01:00
Stypox 5d09a88335
Update more libraries 2023-12-23 11:58:58 +01:00
TacoTheDank edd4f6b9f3
Update Studio and desugaring versions 2023-12-23 11:47:57 +01:00
TacoTheDank 1e7e2109d2
Exclude RxJava file from META-INF 2023-12-23 11:47:57 +01:00
TacoTheDank b31d3831e6
Change Converters to class to fix build 2023-12-23 11:47:57 +01:00
TacoTheDank 0f81a0504c
Use 'tasks.register' for Gradle tasks 2023-12-23 11:47:57 +01:00
TacoTheDank 4a7fda95ae
Update miscellaneous libraries 2023-12-23 11:47:57 +01:00
Stypox ee3455e1e5
Merge pull request #10086 from TacoTheDank/bumpAndroidX
Update some AndroidX libraries and compileSdk to 34
2023-12-23 11:46:28 +01:00
Isira Seneviratne f9fc1cd817
Store/retrieve parcelable arrays as lists instead. 2023-12-23 11:38:40 +01:00
TacoTheDank 76f1e588f7
Utilize BundleCompat and IntentCompat methods 2023-12-23 11:38:40 +01:00
Isira Seneviratne f3b458c803
Bump compileSdk to 34 2023-12-23 11:38:32 +01:00
TacoTheDank 00566ed4d4
Fix AndroidX Work deprecation 2023-12-23 11:36:33 +01:00
TacoTheDank e4a07411b8
Update some AndroidX libraries 2023-12-23 11:36:32 +01:00
Stypox 2c1bb2706f
Merge pull request #10018 from Stypox/comment-replies
Add support for comment replies
2023-12-23 11:01:07 +01:00
Stypox aa84d6fc8f
Add @NonNull annotations 2023-12-22 18:52:42 +01:00
Stypox d76e9b0bd8
Fix scrolling to correct comment after closing replies 2023-12-22 18:52:42 +01:00
TobiGr b4016c91c1
scroll last comment into view 2023-12-22 11:57:55 +01:00
TobiGr 5f32d001cc
Expand DetailFragment again when exiting the CommentRepliesFragment 2023-12-22 11:57:55 +01:00
Stypox 8c9287d0c8
Revert relying on source ListInfo, use commentsInfoItem.getUrl() instead
This reverts commit bb01da3691ff1d5c3dccd41b7ca1a5deb1b5676f. This commit was not needed
2023-12-22 11:57:55 +01:00
Stypox 3f37e27852
Add some documentation and javadocs
Also further simplify CommentRepliesInfo and RelatedItemsInfo
2023-12-22 11:57:55 +01:00
Stypox f41ab8b086
Add comment replies fragment header 2023-12-22 11:57:55 +01:00
Stypox ad68f784ae
Extract some utility methods from CommentInfoItemHolder 2023-12-22 11:48:07 +01:00
Stypox 4b6392df54
Set comment replies fragment title 2023-12-22 11:48:07 +01:00
Stypox 94ea329b50
Always show comment replies in list mode 2023-12-22 11:48:07 +01:00
Stypox 591ed2e01f
Fix some code smells 2023-12-22 11:48:07 +01:00
Stypox 78cf9aaa7d
Save and restore state in CommentRepliesFragment 2023-12-22 11:48:07 +01:00
Stypox f9494a294f
Implement CommentRepliesFragment 2023-12-22 11:48:07 +01:00
Stypox 0dd4553700
Load more items even if initial related items are empty 2023-12-22 11:48:07 +01:00
Stypox 4f7b36cd70
Keep source list info in InfoItemBuilder
Also remove some unused code
2023-12-22 11:48:07 +01:00
Stypox 5d350aec87
Move RelatedItemInfo next to fragment using it 2023-12-22 11:48:07 +01:00
Stypox 059db6fb31
Add replies button to comments 2023-12-22 11:48:05 +01:00
Stypox 4c709b2c4d
Use start/end instead of left/right in comment layout 2023-12-22 11:46:03 +01:00
Stypox 8f4cd032b7
Remove mini variant and move upload date to top in comments 2023-12-22 11:46:03 +01:00
Stypox 67629938d6
Merge pull request #10470 from TeamNewPipe/release-0.26.0
Release 0.26.0
2023-12-21 22:40:05 +01:00
Hosted Weblate 9aff49bd88
Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Danish)

Currently translated at 88.2% (640 of 725 strings)

Translated using Weblate (Serbian)

Currently translated at 17.1% (13 of 76 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Filipino)

Currently translated at 32.1% (233 of 725 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Romanian)

Currently translated at 99.4% (721 of 725 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Indonesian)

Currently translated at 97.3% (74 of 76 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (French)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Polish)

Currently translated at 61.8% (47 of 76 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 28.9% (22 of 76 strings)

Translated using Weblate (German)

Currently translated at 81.5% (62 of 76 strings)

Merge branch 'origin/dev' into Weblate.

Translated using Weblate (Bengali)

Currently translated at 78.7% (571 of 725 strings)

Translated using Weblate (Basque)

Currently translated at 95.0% (689 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 43.4% (33 of 76 strings)

Translated using Weblate (Lithuanian)

Currently translated at 92.9% (674 of 725 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 30.2% (23 of 76 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (724 of 725 strings)

Translated using Weblate (Interlingua)

Currently translated at 32.0% (232 of 725 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.6% (686 of 725 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.6% (686 of 725 strings)

Translated using Weblate (Romanian)

Currently translated at 89.3% (648 of 725 strings)

Translated using Weblate (German)

Currently translated at 81.5% (62 of 76 strings)

Translated using Weblate (German)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Filipino)

Currently translated at 32.1% (233 of 725 strings)

Translated using Weblate (Filipino)

Currently translated at 32.1% (233 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Swedish)

Currently translated at 72.3% (55 of 76 strings)

Translated using Weblate (Esperanto)

Currently translated at 3.9% (3 of 76 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (723 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 71.0% (515 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 2.6% (2 of 76 strings)

Translated using Weblate (Tigrinya)

Currently translated at 8.4% (61 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 43.4% (33 of 76 strings)

Translated using Weblate (German)

Currently translated at 81.5% (62 of 76 strings)

Translated using Weblate (Finnish)

Currently translated at 98.3% (713 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 70.0% (508 of 725 strings)

Added translation using Weblate (English (Old))

Added translation using Weblate (Aymara)

Added translation using Weblate (English (Middle))

Added translation using Weblate (Arabic (Najdi))

Added translation using Weblate (German (Low))

Added translation using Weblate (Sicilian)

Added translation using Weblate (Kashmiri)

Added translation using Weblate (Burmese)

Translated using Weblate (Tigrinya)

Currently translated at 3.5% (26 of 725 strings)

Translated using Weblate (Georgian)

Currently translated at 91.1% (661 of 725 strings)

Translated using Weblate (French)

Currently translated at 98.8% (717 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 42.1% (32 of 76 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.3% (227 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 35.5% (27 of 76 strings)

Translated using Weblate (Esperanto)

Currently translated at 69.2% (502 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 69.2% (502 of 725 strings)

Translated using Weblate (German)

Currently translated at 81.5% (62 of 76 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Bulgarian)

Currently translated at 63.8% (463 of 725 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 66.3% (481 of 725 strings)

Translated using Weblate (French)

Currently translated at 98.6% (715 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 31.5% (24 of 76 strings)

Translated using Weblate (Vietnamese)

Currently translated at 97.3% (706 of 725 strings)

Translated using Weblate (Swedish)

Currently translated at 98.4% (714 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Vietnamese)

Currently translated at 94.6% (686 of 725 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 21.0% (16 of 76 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (French)

Currently translated at 98.2% (712 of 725 strings)

Deleted translation using Weblate (English (Middle))

Co-authored-by: /dev/urandom <dev.urandom@posteo.org>
Co-authored-by: A <ogloppi@mailbox.org>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Aitor Salaberria <trslbrr@gmail.com>
Co-authored-by: Alexthegib <traducoes@skiff.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ans Virlis <tddakk@yahoo.com>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Cem TÜRKER <cemburbut@gmail.com>
Co-authored-by: Danr <mdp43140@gmail.com>
Co-authored-by: David Svane <davidcygnus@users.noreply.hosted.weblate.org>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Erik Matson <erik@nextleveltranslation.com>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Giorgi Taba K'obakhidze <t@gtk.ge>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihfandi <ihfandicahyo@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jener Gomes <jenerg1@gmail.com>
Co-authored-by: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>
Co-authored-by: LiftedStarfish <liftedstarfish@protonmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Napstaguy04 <brokenscreen3@gmail.com>
Co-authored-by: Nista <42772160+Nista11@users.noreply.github.com>
Co-authored-by: P.O <rasmusson.mikael@protonmail.com>
Co-authored-by: PiryusQ <piryusq@gmail.com>
Co-authored-by: Ray <ray@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Co-authored-by: Stypox <stypox@pm.me>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Tmpod <tom@tmpod.dev>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Translator <kvb@tuta.io>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: ferarilalon <ferarilalongpt@gmail.com>
Co-authored-by: fsbat0 <fsbat@duck.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: notlin4 <iamnotlin4@gmail.com>
Co-authored-by: searinminecraft <114207889+searinminecraft@users.noreply.github.com>
Co-authored-by: sum1tookshoe <gamingwithshoe@gmail.com>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: İbrahim Dinç <woltytherespectful@gmail.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/eo/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2023-12-21 22:37:00 +01:00
Stypox 5b999a88f8
Merge pull request #10673 from Stypox/transaction-too-large
Fix transaction too large in channel tab fragments
2023-12-21 22:30:22 +01:00
Stypox 482531836f
Merge pull request #10670 from Stypox/feed-oom
Fix OutOfMemory when fetching feed
2023-12-21 22:29:46 +01:00
Stypox b3c82f54df
Merge pull request #10671 from Stypox/channel-main-tab-lag
Fix application lagging with many main page tabs
2023-12-21 22:28:59 +01:00
Stypox 77fa4bbe2f
Update NewPipeExtractor to v0.23.1 2023-12-21 22:28:09 +01:00
Stypox 495c9850b4
Fix transaction too large for channel tab fragments 2023-12-20 23:57:43 +01:00
Stypox c0f8d145f8
Fix lag with many channels on main page
Disable loading all tabs at once, since there can be many of them, and use default strategy of only keeping in memory the two tabs adjacent to the current tab.
2023-12-20 22:47:57 +01:00
Stypox 80f33daeeb
Fix OutOfMemory when fetching feed
Reduced memory footprint of FeedUpdateInfo objects. Those objects might stay around for a while and accumulate (up to BUFFER_COUNT_BEFORE_INSERT = 20 at the moment), so in order not to fill up the memory it's better to keep as little data as possible.
Previously ChannelInfo data was stored, causing ReadyChannelTabLinkHandler objects to be also stored uselessly (and those channel tabs contain prefetched JSON data which used ~700KB of memory).
2023-12-20 20:22:45 +01:00
Stypox a16dcb63b5
Merge pull request #10645 from Stypox/fix-fragment-manager
Fix crashes due to wrong root fragment manager
2023-12-20 12:03:24 +01:00
Stypox b871b5d2dd
Fix crashes due to wrong root fragment manager 2023-12-10 16:06:07 +01:00
Stypox e876647af5
Update NewPipeExtractor to v0.23.0 2023-12-10 15:58:45 +01:00
Stypox 8d59812827
Fix channel avatar not loading correctly sometimes
The fix just involves removing some really outdated code (6 years ago) added in 33e29be7db (diff-38bd2cf1b92659b499c08e1cf6ac9ef384c7e13381b906f2f98c57cbb758756dR778) (blame: 9318bb5306/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java (L778)).
What that code did was setting the 'buddy' image to the uploader avatar as a placeholder, and then setting the actual image if it existed and after it had loaded.
That code remained there up until now, but now it doesn't make sense anymore, since Picasso already takes care of setting placeholders.
The problem is, starting from #10066 the actual uploader image is set before (not after) those lines of code, making them do the wrong thing, i.e. always overwrite the currently set image.
But then why did the channel avatar image work normally sometimes?
My guess is that since Picasso loads images in the background, when opening a video from scratch setting the placeholder still happened before Picasso finished loading the image.
However when the image is already cached it's loaded much faster and therefore setting the placeholder happens after, effectively hiding the loaded image.
2023-12-10 15:12:38 +01:00
TobiGr e39ac885de
Update new version check to match new API structure
See TeamNewPipe/web-api#17
2023-12-10 15:12:38 +01:00
TobiGr e6965622bd
Fix crash with disabled thumbnails when trying to play a stream 2023-12-10 15:12:38 +01:00
TobiGr 0d8d3479e1
NewPipe 0.26.0 (995) 2023-12-10 15:12:38 +01:00
Stypox 35c1dfd145
Update changelog for NewPipe 0.26.0 (995) 2023-12-10 15:05:29 +01:00
Hosted Weblate 096115def7
Translated using Weblate (Bengali)
Currently translated at 78.7% (571 of 725 strings)

Translated using Weblate (Basque)

Currently translated at 95.0% (689 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 43.4% (33 of 76 strings)

Translated using Weblate (Lithuanian)

Currently translated at 92.9% (674 of 725 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 30.2% (23 of 76 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (724 of 725 strings)

Translated using Weblate (Interlingua)

Currently translated at 32.0% (232 of 725 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.6% (686 of 725 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.6% (686 of 725 strings)

Translated using Weblate (Romanian)

Currently translated at 89.3% (648 of 725 strings)

Translated using Weblate (German)

Currently translated at 81.5% (62 of 76 strings)

Translated using Weblate (German)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Filipino)

Currently translated at 32.1% (233 of 725 strings)

Translated using Weblate (Filipino)

Currently translated at 32.1% (233 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Swedish)

Currently translated at 72.3% (55 of 76 strings)

Translated using Weblate (Esperanto)

Currently translated at 3.9% (3 of 76 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (723 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 71.0% (515 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 2.6% (2 of 76 strings)

Translated using Weblate (Tigrinya)

Currently translated at 8.4% (61 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 43.4% (33 of 76 strings)

Translated using Weblate (German)

Currently translated at 81.5% (62 of 76 strings)

Translated using Weblate (Finnish)

Currently translated at 98.3% (713 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 70.0% (508 of 725 strings)

Added translation using Weblate (English (Old))

Added translation using Weblate (Aymara)

Added translation using Weblate (English (Middle))

Added translation using Weblate (Arabic (Najdi))

Added translation using Weblate (German (Low))

Added translation using Weblate (Sicilian)

Added translation using Weblate (Kashmiri)

Added translation using Weblate (Burmese)

Translated using Weblate (Tigrinya)

Currently translated at 3.5% (26 of 725 strings)

Translated using Weblate (Georgian)

Currently translated at 91.1% (661 of 725 strings)

Translated using Weblate (French)

Currently translated at 98.8% (717 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 42.1% (32 of 76 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.3% (227 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 35.5% (27 of 76 strings)

Translated using Weblate (Esperanto)

Currently translated at 69.2% (502 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 69.2% (502 of 725 strings)

Translated using Weblate (German)

Currently translated at 81.5% (62 of 76 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Bulgarian)

Currently translated at 63.8% (463 of 725 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Esperanto)

Currently translated at 66.3% (481 of 725 strings)

Translated using Weblate (French)

Currently translated at 98.6% (715 of 725 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 31.5% (24 of 76 strings)

Translated using Weblate (Vietnamese)

Currently translated at 97.3% (706 of 725 strings)

Translated using Weblate (Swedish)

Currently translated at 98.4% (714 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Vietnamese)

Currently translated at 94.6% (686 of 725 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 21.0% (16 of 76 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (French)

Currently translated at 98.2% (712 of 725 strings)

Deleted translation using Weblate (English (Middle))

Co-authored-by: /dev/urandom <dev.urandom@posteo.org>
Co-authored-by: A <ogloppi@mailbox.org>
Co-authored-by: Aitor Salaberria <trslbrr@gmail.com>
Co-authored-by: Alexthegib <traducoes@skiff.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ans Virlis <tddakk@yahoo.com>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: Cem TÜRKER <cemburbut@gmail.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Erik Matson <erik@nextleveltranslation.com>
Co-authored-by: Giorgi Taba K'obakhidze <t@gtk.ge>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jener Gomes <jenerg1@gmail.com>
Co-authored-by: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>
Co-authored-by: LiftedStarfish <liftedstarfish@protonmail.com>
Co-authored-by: Napstaguy04 <brokenscreen3@gmail.com>
Co-authored-by: P.O <rasmusson.mikael@protonmail.com>
Co-authored-by: PiryusQ <piryusq@gmail.com>
Co-authored-by: Ray <ray@users.noreply.hosted.weblate.org>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Translator <kvb@tuta.io>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: ferarilalon <ferarilalongpt@gmail.com>
Co-authored-by: fsbat0 <fsbat@duck.com>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: notlin4 <iamnotlin4@gmail.com>
Co-authored-by: sum1tookshoe <gamingwithshoe@gmail.com>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: İbrahim Dinç <woltytherespectful@gmail.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/eo/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2023-12-10 14:50:31 +01:00
Stypox e784af3e2d
Merge pull request #10446 from AudricV/dl_improve_video_audio_stream_selection
Improve audio stream selection for video-only streams in the downloader
2023-12-07 16:48:57 +01:00
Stypox ce30108efc
Improve javadoc for `getAudioStreamFor` 2023-12-07 16:40:32 +01:00
Stypox edbd623e21
Fix Matrix channel link
#newpipe:matrix.org is unofficial, #newpipe:libera.chat is the official one
2023-12-07 16:11:59 +01:00
Stypox 7cfd537755
Merge pull request #10494 from TobiGr/fix-new-streams
Fix notifying about old "new" streams
2023-12-07 14:12:54 +01:00
opusforlife2 ddd6d03e0b
Add Matrix room link to ReadMe (#10632) 2023-12-06 18:34:43 +00:00
Tobi b4a0e08d9d
Update app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt
Co-authored-by: Stypox <stypox@pm.me>
2023-11-23 17:12:16 +01:00
Stypox 545f9ae5f3
Merge pull request #10489 from sqproman/missing_quotation_replace_char_crash
Quote filename replacement characters to fix crashes when downloading streams with special characters
2023-11-16 20:19:01 +01:00
Stypox be4a5a5f3e
Merge pull request #10576 from AudricV/fix-npe-feed-new-items-button
Fix crash when setting the masking of the new feed items button if the context is null
2023-11-16 09:02:24 +01:00
Stypox 3dc593fe88
Merge pull request #10577 from AudricV/fix-npe-play-queue-audio-track-menu
Fix crash when building the play queue audio track menu if the player is null
2023-11-16 09:01:11 +01:00
Stypox e8ed18f1cf
Merge pull request #10578 from AudricV/try-fix-player-service-foreground-start
Restore player service start handling before player UI separation and fix some issues in this service
2023-11-16 08:59:56 +01:00
Stypox bf8890b0df
Merge pull request #10579 from AudricV/exclude-hls-opus-streams-for-playback
Remove OPUS HLS streams from playable streams
2023-11-16 08:30:49 +01:00
AudricV e5fda35c51
Remove OPUS HLS streams from playable streams
This format is not supported by ExoPlayer when returned as HLS streams, so we
can't play streams using this format and this delivery method.

Also improve the Javadoc of ListHelper.getPlayableStreams.
2023-11-15 23:37:22 +01:00
AudricV 84d50da009
Restore player service start handling before player UI separation
This behavior was present before 0.24.0 and the player UI separation and
avoided crashes for which their exception contained
"Context.startForegroundService() did not then call Service.startForeground()".

Some player nullability checks have been also added, and the player service is
now stopped when it has been started from a media button and there is nothing
to play.
2023-11-15 23:21:20 +01:00
AudricV 2cf7764714
Fix crash when building the play queue audio track menu if the player is null
As the player can be null in some cases, we have to make sure that the player
is not null, by using Optionals on the player itself instead of its methods
returning Optionals.

If the player is null, the play queue audio track menu will now be hidden.
2023-11-15 21:45:56 +01:00
AudricV 9fab0ec94f
Fix crash when setting the masking of the new feed items button if the context is null
As the fragment context can be null in some cases, we have to make sure that
the context is not null before calling
DeviceUtils.hasAnimationsAnimatorDurationEnabled.

If the context is null, the button will now not be hidden automatically.
2023-11-15 19:04:45 +01:00
Stypox 6d694518fe
Merge pull request #10491 from TeamNewPipe/readme
[README] Remove Bitcoin and Bountysource donation options
2023-10-27 05:41:59 +02:00
TobiGr 5265b767cb Fix notifiying about old "new" streams
Add tests for `FeedDAO.unlinkStreamsOlderThan(:offsetDateTime) `
Closes #10237
2023-10-14 18:33:21 +02:00
TobiGr d10a93fe4f [README] Remove Bountysource badge 2023-10-13 17:30:13 +02:00
TobiGr 995986ecc7 [README] Remove Bitcoin and Bountysource donation options 2023-10-13 17:30:13 +02:00
akko 6d0bb02544 adds quotation to create filename util replacement char 2023-10-13 11:28:09 +07:00
TobiGr 6f51c47dc9
Deleted translation using Weblate (Sicilian) 2023-10-07 17:39:49 +02:00
TobiGr 626daf89c1
Deleted translation using Weblate (Kashmiri) 2023-10-07 17:39:44 +02:00
Hosted Weblate b18ccffeb4
Added translation using Weblate (English (Middle))
Deleted translation using Weblate (German (Low))

Deleted translation using Weblate (English (Old))

Deleted translation using Weblate (English (Middle))

Deleted translation using Weblate (Burmese)

Deleted translation using Weblate (Aymara)

Deleted translation using Weblate (Arabic (Najdi))

Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Weblate <noreply@weblate.org>
2023-10-07 15:39:43 +00:00
Hosted Weblate 2ab2185e0a
Translated using Weblate (Odia)
Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Serbian)

Currently translated at 17.1% (13 of 76 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Finnish)

Currently translated at 98.0% (711 of 725 strings)

Translated using Weblate (French)

Currently translated at 98.2% (712 of 725 strings)

Translated using Weblate (Indonesian)

Currently translated at 94.7% (72 of 76 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Hebrew)

Currently translated at 51.3% (39 of 76 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (76 of 76 strings)

Translated using Weblate (Polish)

Currently translated at 61.8% (47 of 76 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (76 of 76 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Åzze <laitinen.jere222@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translation: NewPipe/Metadata
2023-10-07 17:21:24 +02:00
TobiGr be47609405 Add changelog for NewPipe 0.26.0 (995) 2023-10-05 14:49:35 +02:00
Hosted Weblate 5dee7a5262
Translated using Weblate (Portuguese)
Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Thai)

Currently translated at 29.3% (213 of 725 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 95.0% (689 of 725 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (723 of 725 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (German)

Currently translated at 100.0% (725 of 725 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Portuguese)

Currently translated at 95.2% (685 of 719 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 99.1% (713 of 719 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (German)

Currently translated at 81.3% (61 of 75 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (French)

Currently translated at 98.4% (708 of 719 strings)

Translated using Weblate (German)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 99.3% (714 of 719 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.8% (718 of 719 strings)

Translated using Weblate (French)

Currently translated at 97.7% (703 of 719 strings)

Translated using Weblate (German)

Currently translated at 99.4% (715 of 719 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Czech)

Currently translated at 95.5% (687 of 719 strings)

Translated using Weblate (Greek)

Currently translated at 99.8% (718 of 719 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (719 of 719 strings)

Translated using Weblate (German)

Currently translated at 99.3% (714 of 719 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alexthegib <traducoes@skiff.com>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Sergio Marques <so.boston.android@gmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: TobiGr <tobigr@mail.de>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bowornsin <bowornsin@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translation: NewPipe/Metadata
2023-10-05 03:09:31 +02:00
Stypox bff7ada2d1
Merge pull request #8248 from dtcxzyw/fix-readd-to-playlist
Fix inconsistency between user interaction and database commit order when re-adding videos to the playlist
2023-10-03 17:45:44 +02:00
Tobi ed33d1d4f7
Fix images in README.sr.md 2023-10-02 16:21:27 +02:00
Tobi 64e64f72f7
Update README.asm.md 2023-10-02 16:18:54 +02:00
TobiGr d3c783832a Fix screenshot URLs in multiple READMEs 2023-10-02 16:16:53 +02:00
TobiGr d963b69d5c Fix links to README.sr.md 2023-10-02 16:06:33 +02:00
Tobi 49ce9ba387
Fix links to other READMEs in README.sr.md 2023-10-02 16:00:25 +02:00
NEXI d63a6d3f75
Create Serbian README (#10465) 2023-10-02 15:58:26 +02:00
TobiGr 3d5a8af52b Fix inconsistency when LocalPlaylist is used as MainFragment tab 2023-10-02 02:56:30 +02:00
Yingwei Zheng 1cf670dad9 Fix inconsistency between user interaction and database commit order when re-adding videos to the playlist 2023-10-02 02:56:30 +02:00
Tobi b50e3c07d2 Use PR labeler fork
This updates some libs
2023-10-02 02:15:25 +02:00
Tobi fe7d1692c3 Fix PR labeler permissions
Although the permission to modify PRs is granted to the entire workflow, the job still reports that it does not the permission to do so:
GITHUB_TOKEN Permissions
  Contents: read
  Metadata: read
  PullRequests: read
This adds the permission to the job directly
2023-09-27 10:06:34 +02:00
TobiGr 0758cd6980 Fix wrongly formatted string ressources
There were multiple substitutions specified in non-positional format in the ressources video_details_list_item and share_playlist_content_details
2023-09-26 11:22:22 +02:00
Siddhesh Naik e80b6b3057
Add playlist name and video name to playlist sharing content (#10427)
- Currently, only a list of videos separated by newline are added in
  the share content.
- This makes it difficult to identify a specific video in a list of
  Urls.
- Used string resources for the sharing content formats.
- Added a confirmation dialog for users to choose between sharing
  playlist formats.
- Added Playlist name as the header and corresponding video name for
  each video url in following format.

Playlist
- Music1: https://media-url1
- Music2: https://media-url2
- Music3: https://media-url3


Co-authored-by: TobiGr <tobigr@users.noreply.github.com>
2023-09-26 11:11:33 +02:00
Tobi 9c86afe40d
Merge pull request #10453 from TeamNewPipe/pr-labeler
Add content: read permission to PR size labeler workflow
2023-09-26 10:42:01 +02:00
Tobi db4619f5a4
Add content: read permission to PR size labeler workflow 2023-09-26 10:40:17 +02:00
Tobi f90d74ca31
Merge pull request #10447 from TeamNewPipe/pr-labeler
Add write permission to PR labeler workflow
2023-09-24 20:27:58 +02:00
Tobi 609f0a2eee
Add write permission to PR labeler workflow 2023-09-24 20:24:57 +02:00
AudricV 77bbbc88f8
Use ListHelper to get secondary audio streams for video-only streams
Instead of searching for the first audio stream matching a compatible media
format, this change makes SecondaryStreamHelper.getAudioStreamFor use methods
isLimitingDataUsage, getAudioFormatComparator and getAudioIndexByHighestRank of
ListHelper to get an audio stream which can be muxed into a video-only stream,
if available.

This allows users to download videos with the highest audio quality available
if no resolution limit on mobile data usage has been set.

The order of formats used to search a compatible audio stream has been kept.
2023-09-24 18:23:45 +02:00
AudricV cdb79ef78a
Make isLimitingDataUsage method of ListHelper package-private and fix some typos in the class 2023-09-24 18:23:44 +02:00
Tobi 1630e309fb
Merge pull request #9987 from Edwardsoen/add_high_resolution_to_default_option
Include a high-resolution option in the default resolution settings.
2023-09-24 17:54:18 +02:00
Stypox 2d4f56f57c
Merge pull request #10170 from TeamNewPipe/actions/pr-size-labeler
Add workflow "PR size labeler" to label PRs based on the number of changed lines
2023-09-24 09:34:33 +02:00
TobiGr d622993483
Add workflow "PR size labeler" to label PRs based on the number of changed lines
This should help reviewers to determine which PRs to review.
2023-09-24 09:33:44 +02:00
Tobi c68a6ee0ed
Merge pull request #10436 from TeamNewPipe/fix/license-restore
Fix restoring software license dialog
2023-09-23 14:11:09 +02:00
TobiGr 94c1438913 Use "done" button to close license dialogs.
Rename string res "recaptcha_done_button" to "done".
2023-09-23 13:56:49 +02:00
TobiGr e206a26a85 Restore license dialog buttons to open the SoftwareComponent's website
Do not keep the active License but the active SoftwareComponent.
2023-09-23 13:49:09 +02:00
TobiGr 242e20316b [AboutFragment / LicenseFragment] Fix license restore after rotation
Do not restore last opened license after a rotation change when the license was closed earlier.

This commit adds onCancelListener and onDismissListener to the AlertDialogs which are used to display the licenses.
2023-09-23 13:49:09 +02:00
TobiGr 279fd2399d Remove translations without default value 2023-09-22 21:44:58 +02:00
kuragehime a5fcb41ab0 Translated using Weblate (ryu (generated) (ryu))
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Marian Hanzel cb4f656673 Translated using Weblate (Slovak)
Currently translated at 18.6% (14 of 75 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
2023-09-22 21:38:28 +02:00
ShareASmile b9e5ee6759 Translated using Weblate (Punjabi)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
ShareASmile 1084b7c3ad Translated using Weblate (Hindi)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Yaron Shahrabani 39c06c5461 Translated using Weblate (Hebrew)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Jeff Huang b9c7f8769b Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Eric dc45adf7f2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Agnieszka C a69af42f7f Translated using Weblate (Polish)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Ihor Hordiichuk 1a5dfae7a0 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Linerly d41b5d80ad Translated using Weblate (Indonesian)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Rex_sa f0bcb3ba28 Translated using Weblate (Arabic)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Marian Hanzel 7da35bf71d Translated using Weblate (Slovak)
Currently translated at 99.7% (711 of 713 strings)
2023-09-22 21:38:28 +02:00
Vasilis K 03c339dd4b Translated using Weblate (Greek)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
kuragehime 11c74bd26b Translated using Weblate (Japanese)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
gallegonovato 0a292cf893 Translated using Weblate (Spanish)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
TobiGr ac6811867f Translated using Weblate (German)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
VfBFan 0c9df501e8 Translated using Weblate (German)
Currently translated at 100.0% (713 of 713 strings)
2023-09-22 21:38:28 +02:00
Hosted Weblate 4c4f9b45d9 Translated using Weblate (Kazakh)
Currently translated at 0.5% (4 of 702 strings)

Translated using Weblate (Kazakh)

Currently translated at 6.6% (5 of 75 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (702 of 702 strings)

Merge branch 'origin/dev' into Weblate.

Translated using Weblate (Spanish)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Greek)

Currently translated at 99.8% (701 of 702 strings)

Translated using Weblate (Japanese)

Currently translated at 98.2% (690 of 702 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (German)

Currently translated at 80.0% (60 of 75 strings)

Translated using Weblate (German)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (702 of 702 strings)

Translated using Weblate (Greek)

Currently translated at 97.8% (687 of 702 strings)

Translated using Weblate (Serbian)

Currently translated at 98.5% (692 of 702 strings)

Translated using Weblate (German)

Currently translated at 99.5% (699 of 702 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.8% (685 of 686 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.7% (684 of 686 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 16.0% (12 of 75 strings)

Translated using Weblate (Galician)

Currently translated at 99.5% (683 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 16.0% (12 of 75 strings)

Translated using Weblate (Bengali)

Currently translated at 83.8% (575 of 686 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (686 of 686 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Aidos <goldenbit.kz@yandex.kz>
Co-authored-by: Daniel Rozario <rozario@tuta.io>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Subham Jena <subhamjena8465@gmail.com>
Co-authored-by: Tibor Botfai (gidano) <gidano@gmail.com>
Co-authored-by: TobiGr <tobigr@mail.de>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: WB <dln0@proton.me>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: nexi <nexiphotographer@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: zmni <zmni@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/kk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sr/
Translation: NewPipe/Metadata
2023-09-22 21:38:28 +02:00
Audric V 5a921c9f10
Merge pull request #10440 from Stypox/remove-deobfuscation-exception
Update extractor and remove DeobfuscateException
2023-09-22 16:29:13 +02:00
Stypox bdc2aa2b39
Update extractor and remove DeobfuscateException
In ErrorInfo it was treated separately from other ParsingExceptions, including by showing a customized message, which has now been removed.
2023-09-22 10:43:37 +02:00
Stypox b508dd69be
Merge pull request #10062 from Stypox/multiple-images
Allow selecting image quality among multiple images
2023-09-22 10:20:19 +02:00
Stypox f8b756c8bc
Make question mark localizable 2023-09-22 10:14:45 +02:00
Stypox 027b829c38
Use @DrawableRes in PicassoHelper 2023-09-22 10:14:45 +02:00
Stypox 0a2d6d1d62
Add test for ImageStrategy 2023-09-22 10:14:45 +02:00
Stypox 1b485ddb5a
Allow using CHECKSTYLE:OFF comments 2023-09-22 10:14:45 +02:00
Stypox 0085ca6416
Fix loading images from db even if disabled 2023-09-22 10:14:44 +02:00
Stypox 87dca0f7ec
Separate imageListToDbUrl from choosePreferredImage
imageListToDbUrl should be used if the URL is going to be saved to the database, to avoid saving nothing in case at the moment of saving the user preference is to not show images.
2023-09-22 10:14:44 +02:00
Stypox 37af2c87e8
Fix possible NPE in PlayQueueNavigator 2023-09-22 10:14:44 +02:00
Stypox bf908f0b7d
Add documentation and fix SonarCloud issue 2023-09-22 10:14:44 +02:00
Stypox 8d463b9577
Further improve image resolution strategy
Now using multiple comparison steps instead of magic values
2023-09-22 10:14:44 +02:00
Stypox 4f7d206736
Display all thumbnails in description tab 2023-09-22 10:14:44 +02:00
Stypox 35073c780d
Implement better image selection strategy 2023-09-22 10:14:44 +02:00
Stypox 0a8f28b1c6
Add image quality preference 2023-09-22 10:14:43 +02:00
Stypox af2375948d
Support obtaining multiple images from the extractor 2023-09-22 09:57:33 +02:00
TobiGr df2e0be08d Add summary to reset preference 2023-09-21 16:01:07 +02:00
TobiGr ff1aca272e Remove strange change 2023-09-21 16:01:07 +02:00
TobiGr f2e352832a Create new settings category: Backup and restore
Following settings have been move to the new category:
- import database (from ContenttSettings)
- export database (from ContenttSettings)
- reset settings (from DebugSettings)
2023-09-21 16:01:07 +02:00
vincetzr ad0855ac83 Update app/src/main/res/xml/debug_settings.xml
Ensuring title to be fully displayed on small devices.

Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2023-09-21 16:01:07 +02:00
Vincent Tanumihardja d7ef9b1f0c Added strings to strings.xml to allow translation. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 40a3e1b18a Revert committed file change of settings key. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 25a73090f5 Revert changes made to dev. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja a239a26b17 Revert changes made to dev. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 06d256294f Revert changes made to dev. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 81ad50e82a Added delete xml method inside the yes dialogue. 2023-09-21 16:01:07 +02:00
Zhidong Piao 23de9bf93e clear shared preference xmls 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 5c46412faa Changed alert dialogue. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 076e9eee01 Added alert dialogue and restarts the app when resetting settings. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 2103a04092 Cleaned up xml files. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 58517d1d27 Added reset button but working as intended for theme. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja aa1847189b Added reset button but slightly working as intended. 2023-09-21 16:01:07 +02:00
Vincent Tanumihardja 5d101e7b88 Added reset button but not working as intended. 2023-09-21 16:01:07 +02:00
Tobi e2de83188a
Merge pull request #10199 from TeamNewPipe/feat/player/accessibility
Improved accessibility of player interfaces
2023-09-21 15:57:38 +02:00
TobiGr 2a1b506d98 Improved accessibility of player interfaces 2023-09-21 12:20:00 +02:00
Stypox b798ff5c92
Merge pull request #10435 from TeamNewPipe/imp/codequality
Improve codequality
2023-09-20 21:24:44 +02:00
Tobi 673aa0a87b
Merge pull request #10428 from Isira-Seneviratne/AGP_8.1
Bump AGP to 8.1.1
2023-09-20 20:20:36 +02:00
TobiGr 779ea19222 Fix doc formatting 2023-09-20 19:44:23 +02:00
Isira Seneviratne a1f2b7f8e8 Switch to Files.createDirectories() 2023-09-20 19:44:23 +02:00
Isira Seneviratne fcb855cea9 Bump AGP to 8.1.1 2023-09-20 19:44:23 +02:00
Tobi 50fb48f66d
Merge pull request #10244 from TacoTheDank/bumpMaterial
Update Google Material library
2023-09-20 18:19:53 +02:00
TobiGr 0acc3532c9 Remove useless override 2023-09-20 15:42:09 +02:00
TobiGr 8bf2d996ea Reorder the modifiers to comply with the Java Language Specification. 2023-09-20 15:41:57 +02:00
TobiGr 748c2babe9 Add comments and annotations 2023-09-20 15:41:21 +02:00
Tobi 6859f73c54
Merge pull request #10224 from TacoTheDank/moreKotlinMath
Replace MathUtils.clamp with Kotlin coerceIn
2023-09-20 11:04:54 +02:00
TacoTheDank b1faed586d Replace MathUtils.clamp with Kotlin coerceIn 2023-09-19 16:32:37 -04:00
TacoTheDank 6c848b4766 Update Google Material library 2023-09-19 16:30:09 -04:00
Stypox 725c18eada
Merge pull request #10165 from TeamNewPipe/fix/media-format
Fix downloads of streams with missing MediaFormat
2023-09-19 15:54:12 +02:00
Stypox 992bb5d7be
Simplify retrieveMediaFormatFromContentTypeHeader
Also check for nullity
2023-09-19 15:33:49 +02:00
Stypox 9e353f1cdc
Merge pull request #10394 from TeamNewPipe/fix/memory-leaks
Fix memory leaks and add documentation
2023-09-19 14:17:23 +02:00
TobiGr 8f83e39970 Fix three memory leaks
Add documentation to BaseFragment.initViews(View, Bundle) and BaseFragment.initListeners()
2023-09-19 00:13:16 +02:00
Stypox 0eae9e7cdc
Merge pull request #9182 from Theta-Dev/channel-tabs
Add support for channel tabs
2023-09-18 23:46:13 +02:00
TobiGr 031b893196
Remove unused content not supported TextView 2023-09-18 23:22:32 +02:00
TobiGr 64da7a06c0
Fix previous ActionBar title visible for a few miliseconds when opening ChannelFragment 2023-09-18 23:22:32 +02:00
TobiGr 57eaa1bbe1
Apply review
Co-Authored-By:  Audric V <74829229+AudricV@users.noreply.github.com>
2023-09-18 23:22:32 +02:00
TobiGr 109d06b4bb
Deduplicate code to initialize ClickListeners on playlist controls
Add the separate utility class PlayButtonHelper to handle the initialization of the listeners.
The ClickListeners on playlist controls had different behaviours. This commit fixes that.

The commit also refactors the way how the app determines whether it is started for the first time. The previous version was not clean and recent in this PR caused it to fail.
2023-09-18 23:22:32 +02:00
AudricV 0d9910cbbe
Fix SubscriptionManagerTest tests
The breakage of these tests is related to the channel tabs changes.

The testRememberRecentStreams test method has been removed, as it doesn't seem
to be relevant anymore to managing subscriptions.
2023-09-18 23:22:32 +02:00
AudricV 8fbc8ffc7c
Remove unneeded German translation 2023-09-18 23:22:32 +02:00
AudricV f2ee3859ab
Hide the upload date element on the About tab
This empty element should be always hidden for this tab, as there is no upload
date available for channels.
2023-09-18 23:22:32 +02:00
AudricV 89dc44be61
Always show the About tab and support having no description 2023-09-18 23:22:32 +02:00
TobiGr 6ab8716e69
Extract actual feed loading code into separate method
Increase readability
2023-09-18 23:22:32 +02:00
TobiGr 5c7c382323
Add missing `@Override` annotations to setupMetadata() implementations 2023-09-18 23:22:32 +02:00
Stypox 78b4b9441e
Update NewPipeExtractor and adapt imports 2023-09-18 23:22:32 +02:00
Stypox 9e55014a13
Fix wrongly themed channel header
Since it is embedded in the app bar and has red as background color, it should be themed in the same way as the toolbar.
2023-09-18 23:22:32 +02:00
Stypox 6f23b56b06
Use consistent name for livestreams tab in settings keys 2023-09-18 23:22:32 +02:00
Stypox 1519527356
Fix loading feed when a channel tab is empty 2023-09-18 23:22:32 +02:00
Stypox 6b3a178f2a
Show snackbar with feed loading errors 2023-09-18 23:22:32 +02:00
Stypox 604419dd1f
Make channel banner placeholder match YouTube's size
YouTube's "Desktop Max" thumbnails are 2560x423, while our previous placeholder banner was 2550x427. The extractor actually returns a lower resolution "Desktop Max" banner at 1060x175, but the ratio wrt 2560x423 is off by ~0.1%

The PNG was optimized with OptiPNG
2023-09-18 23:22:32 +02:00
Stypox c48e702a50
Improve placeholder channel banner handling
Now the placeholder gets hidden if there is no banner url or the user disabled images, to save space
2023-09-18 23:22:32 +02:00
Stypox 1061bce4f3
Add avatar and bannner URLs to channel About tab 2023-09-18 23:22:32 +02:00
Stypox 013d513450
Add space above channel description (About tab) 2023-09-18 23:22:32 +02:00
ThetaDev dca32efadf
add channel banner placeholder 2023-09-18 23:22:32 +02:00
ThetaDev 28d952a643
feat: filter fetched channel tabs 2023-09-18 23:22:32 +02:00
ThetaDev a2a717bd49
update NPE 2023-09-18 23:22:32 +02:00
ThetaDev 753a92055c
feat: add playlist controls to channel tab 2023-09-18 23:22:32 +02:00
Stypox 371f986773
Fix some code smells 2023-09-18 23:22:32 +02:00
Stypox a1e8b9be4e
Fix channel tabs in main page setting title themselves 2023-09-18 23:22:32 +02:00
Stypox c076a0f771
Channels are now an Info
The previous "main" tab is now just a normal tab returned in getTabs().
Various part of the code that used to handle channels as ListInfo now either take the first (playable, i.e. with streams) tab (e.g. the ChannelTabPlayQueue), or take all of them combined (e.g. the feed).
2023-09-18 23:22:32 +02:00
ThetaDev dfbd39e898
fix: limit channel header height 2023-09-18 23:22:32 +02:00
ThetaDev b5893f3fa3
fix: notification menu option disappears when switching tabs 2023-09-18 23:22:32 +02:00
Stypox e3614cb932
Move channel header to collapsible app bar 2023-09-18 23:22:32 +02:00
ThetaDev 193c3e5b3d
fix: NPE in ChannelFragment::onSaveInstanceState 2023-09-18 23:22:32 +02:00
ThetaDev c03c344f49
refactor: rename ChannelInfo to ChannelAbout
fix: localize about tab name
2023-09-18 23:22:32 +02:00
ThetaDev 25e3031830
cleanup: remove empty constructor from ChannelFragment 2023-09-18 23:22:31 +02:00
ThetaDev b7911a8fd8
remove fragment_channel_info 2023-09-18 23:22:31 +02:00
ThetaDev 88384dc35e
update extractor 2023-09-18 23:22:31 +02:00
ThetaDev 39b4ed082c
refactor: common code from ChannelInfo/Description -> BaseInfoFragment 2023-09-18 23:22:31 +02:00
ThetaDev d87aa23ae0
update NewPipeExtractor 2023-09-18 23:22:31 +02:00
ThetaDev be548dcb52
fix: channel tab title not being set 2023-09-18 23:22:31 +02:00
ThetaDev 4357a34339
fix: ChannelFragment: save last tab 2023-09-18 23:22:31 +02:00
ThetaDev 2c03ba204e
refactor: adjustments to updated tab extractor API 2023-09-18 23:22:31 +02:00
ThetaDev 2c98d079de
fix: cache channel data 2023-09-18 23:22:31 +02:00
ThetaDev 16cd47fa2e
fix: missing album tab key 2023-09-18 23:22:31 +02:00
ThetaDev 74a8bfba93
feat: add album tab 2023-09-18 23:22:31 +02:00
ThetaDev c929f00456
fix: remember selected channel tab on screen rotation 2023-09-18 23:22:31 +02:00
ThetaDev bb062f07f9
feat: add option to hide channel tabs 2023-09-18 23:22:31 +02:00
ThetaDev c3d1e75a8f
fix: scrollable channel description 2023-09-18 23:22:31 +02:00
ThetaDev 506e3724a6
fix: add progress spinners 2023-09-18 23:22:31 +02:00
ThetaDev 4859ab67d4
feat: prettier channel info page 2023-09-18 23:22:31 +02:00
ThetaDev 6d84d19520
fix: handle unsupported content 2023-09-18 23:22:31 +02:00
ThetaDev 8627efd0a1
fix: get notified menu option on all tabs 2023-09-18 23:22:31 +02:00
ThetaDev 6d13cf5e71
feat: add channel tabs 2023-09-18 23:22:31 +02:00
Rishab Aggarwal 7e2ab0d384
Improved downloading experience (#10407)
* added LoadingDialog for improving download experience

* [LoadingDialog] Apply some review comments and make title customizable.

* removed permission handling from loading Dialog

* fix checks

* remove <p> Tag from first sentence

---------

Co-authored-by: rishabaggarwal <Rishabaggarwal@sharechat.com>
Co-authored-by: TobiGr <tobigr@users.noreply.github.com>
2023-09-18 05:24:03 +05:30
TobiGr 19640d5e7c Add documentation to increase maintainablilty
Rename a variable
2023-09-18 01:45:53 +02:00
Edward d1a82a85cd Include a high-resolution option in the default resolution settings. 2023-09-18 01:15:22 +02:00
Isira Seneviratne b1ab261890
Merge pull request #10248 from Isira-Seneviratne/NIO_downloads
Improve the download helpers using the Java 7 NIO API.
2023-09-17 21:09:02 +05:30
Tobi 038278283a
Merge pull request #10234 from TacoTheDank/bumpMisc
Update miscellaneous libraries
2023-09-17 16:35:40 +02:00
TacoTheDank c74bd11a6f Update miscellaneous libraries 2023-09-17 15:43:43 +02:00
TobiGr f2c2f1735e Replace RuntimeException with IOException
The RuntimeException was not explicitly declared and thus not caught at every call of this constructor. This change ensures that this possible exception is handled by the dedicated error handlers.
2023-09-17 15:31:19 +02:00
TobiGr 4e41e12bd2 Small code and doc improvements
Remove unnecessary else-branch and use Collections.isEmpty().
Add doc comment for splitFilename()
2023-09-17 15:31:19 +02:00
Isira Seneviratne 6df808f266 Use Path in the download helper classes. 2023-09-17 14:50:26 +02:00
Isira Seneviratne 2cb973f150 Use desugar_jdk_libs_nio. 2023-09-17 14:50:26 +02:00
Tobi b5463cf5e1
Merge pull request #10406 from ShareASmile/fix-language-selector
Fix language Picker Selecting Wrong Languages
2023-09-09 00:52:07 +02:00
K Gill 862546205a
fix wrongly placed uchinaguchi in language selector settings caus..
..ing language picker to select wrong languages
2023-09-08 16:33:17 +05:30
Audric V 7c1790bbfd
Merge pull request #10396 from AudricV/one-stream-main-player-queue-button
Show play queue button in main player when there is one stream
2023-08-31 15:06:06 +02:00
AudricV 2d16a06bc4
Show play queue button in main player when there is one stream 2023-08-30 19:45:53 +02:00
Tobi 25cf917969
Merge pull request #10377 from TeamNewPipe/image-minimizer
Image minizier: replace Number.toFixed(0) with Math.floor()
2023-08-25 00:54:55 +02:00
Tobi d09c650afd
Merge pull request #10376 from TeamNewPipe/fix-string-formats
Fix string formats
2023-08-24 21:14:03 +02:00
AudricV 2b833c5250 Fix audio_track_name string formats 2023-08-24 20:41:11 +02:00
TobiGr 510db568eb Image minizier: replace Number.toFixed(0) with Math.floor()
Number.toFixed returns a string, Math.floor a number
2023-08-24 11:53:35 +02:00
Hosted Weblate e4003c842b
Translated using Weblate (Arabic)
Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Malayalam)

Currently translated at 6.6% (5 of 75 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (German)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (German)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (684 of 686 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Albanian)

Currently translated at 82.7% (568 of 686 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (French)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (686 of 686 strings)

Added translation using Weblate (Burmese)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Slovak)

Currently translated at 98.9% (679 of 686 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (German)

Currently translated at 100.0% (686 of 686 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Croatian)

Currently translated at 91.8% (628 of 684 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (French)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 98.6% (675 of 684 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Urdu)

Currently translated at 74.2% (508 of 684 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.2% (679 of 684 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Galician)

Currently translated at 99.5% (681 of 684 strings)

Translated using Weblate (Finnish)

Currently translated at 89.9% (615 of 684 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (684 of 684 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Alexthegib <jcwkgxc@nightorb.com>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: C. Rüdinger <Mail-an-CR@web.de>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jani Kinnunen <janikinnunen340@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Joel A <joeax910@student.liu.se>
Co-authored-by: Jorge Pelaez <jorpelae@yahoo.co.jp>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Milan Šalka <salka.milan@googlemail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Sergio Marques <so.boston.android@gmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Shifa Graphics <shifagraphix@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: W L <wl@mailhole.de>
Co-authored-by: WB <dln0@proton.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: tellmeY18 <vysakh_b190622ec@nitc.ac.in>
Co-authored-by: thami simo <simo.azad@gmail.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ml/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translation: NewPipe/Metadata
2023-08-23 21:29:25 +02:00
TobiGr 68957d3880 Fix grammar in JDoc 2023-08-22 16:23:22 +02:00
Tobi e6747066ae
Merge pull request #10360 from TeamNewPipe/improvement/tabSelected
Simplify `MainActivity.tabSelected(MenuItem)`
2023-08-22 10:57:14 +02:00
TobiGr 62f0abee47 Simplify MainActivity.tabSelected(MenuItem)
Rename variables and skip iterations if kiosk was found.
2023-08-19 21:58:44 +02:00
TobiGr 9118ecd68f Remove unnecessary debug warning and use JDoc instead 2023-08-17 16:51:31 +02:00
TobiGr 15fd47c7f2 Apply review 2023-08-16 22:18:53 +02:00
Yingwei Zheng ef40ac7bb3 Fix a typo 2023-08-16 22:02:12 +02:00
Yingwei Zheng 881d04ba1e Refactor database migration test and string trimming 2023-08-16 22:02:12 +02:00
TobiGr 4af5b5f6f2 Fix database migration and string trimming
Co-authored-by:  Yingwei Zheng <dtcxzyw@qq.com>
2023-08-16 22:02:12 +02:00
TobiGr 90f0809029 Trim search string and remove duplicate records from the database
Co-authored-by:  Yingwei Zheng <dtcxzyw@qq.com>
2023-08-16 21:26:35 +02:00
TobiGr db5ed48dbb Fix some sonar warnings and make some smaller improvements 2023-08-14 23:09:50 +02:00
TobiGr ba84e7eead Display "Unknown quality" if quality is unknown and not MediaFormat name 2023-08-14 23:06:32 +02:00
TobiGr e51067177e Add tests for new methods retrieving MediaFormats
Fix failing tests
2023-08-14 23:06:32 +02:00
TobiGr f3859ed710 Retrieve MediaFormat for streams that could not be extracted by the extractor 2023-08-14 23:06:32 +02:00
TobiGr 0db12e5561 Rename StreamSizeWrapper to StreamInfoWrapper 2023-08-14 22:48:39 +02:00
Stypox ac5f991c0c
Merge pull request #10331 from TeamNewPipe/improve_bug_template
Make "latest release" link more obvious to bug reporters
2023-08-12 19:46:15 +02:00
opusforlife2 4a0ff3f7ef
Make latest release link more obvious to bug reporters 2023-08-12 15:08:03 +00:00
Stypox 601b1ef742
Merge pull request #10313 from mhmdanas/fix-capitalization-of-night-theme
Make capitalization of "Night theme" setting consistent with others
2023-08-07 23:07:48 +02:00
triallax d957725805 Make capitalization of "Night theme" setting consistent with others
Been a pet peeve of mine for some time.
2023-08-07 20:40:33 +01:00
Stypox 4201723d10
Merge pull request #10304 from TeamNewPipe/fix/media.ccc.de
Adjust empty state message for ListInfoFragments depending on Info stream type
2023-08-06 10:07:33 +02:00
Stypox bef79e77aa
Update app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java 2023-08-06 10:07:13 +02:00
TobiGr 32f74273f0 Adjust empty state message for ListInfoFragments depending on Info stream type
Show "no streams" for SoundClound.
Show "no live streams" for MeidaCCCLiveStreamKiosk.
Otherwise show "no videos"
2023-08-05 03:50:28 +02:00
TobiGr c69bcaafbb Fixed some Sonar warnings 2023-08-03 12:02:08 +02:00
Tobi 50d7d1b7b3
Merge pull request #10275 from J-Stutzmann/fix/audio-focus
Fix player audio focus not respecting mute
2023-08-03 11:55:40 +02:00
J-Stutzmann c06d61a83c Made audio-focus calls respect mute state. 2023-08-02 23:44:23 -04:00
TobiGr bc4f0c699f Ignore false positive inspection 2023-08-02 20:44:30 +02:00
Stypox 1e8efa7165
Merge pull request #10283 from TeamNewPipe/release/0.25.2
Release v0.25.2 (994)
2023-08-02 20:14:14 +02:00
Stypox d4019f4b54
Update NewPipeExtractor to v0.22.7 2023-08-02 20:12:55 +02:00
TobiGr 3f0f66f106 Bump version to 0.25.2 (994) 2023-08-02 20:02:22 +02:00
Hosted Weblate 8f644e8aaf
Translated using Weblate (Urdu)
Currently translated at 5.3% (4 of 75 strings)

Translated using Weblate (Urdu)

Currently translated at 73.2% (501 of 684 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Polish)

Currently translated at 61.3% (46 of 75 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (75 of 75 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (684 of 684 strings)

Translated using Weblate (German)

Currently translated at 100.0% (684 of 684 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Shifa Graphics <shifagraphix@gmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: rickeesingh <rickeesingh231@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ur/
Translation: NewPipe/Metadata
2023-08-02 19:57:41 +02:00
TobiGr 27f77518fe Update NewPipe Extractor to 39a911db9f 2023-07-31 23:59:28 +02:00
Hosted Weblate b56f3b3324 Translated using Weblate (Punjabi)
Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 93.2% (69 of 74 strings)

Translated using Weblate (Toki Pona)

Currently translated at 6.5% (45 of 683 strings)

Translated using Weblate (Toki Pona)

Currently translated at 2.7% (2 of 74 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Added translation using Weblate (Toki Pona)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 96.3% (658 of 683 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 8.3% (57 of 683 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: John Donne <akheron@zaclys.net>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Translator <kvb@tuta.io>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: William <Electroboss@users.noreply.hosted.weblate.org>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tok/
Translation: NewPipe/Metadata
2023-07-31 23:46:11 +02:00
TobiGr 0195655479 Add changelog for NewPipe 0.25.2 (994) 2023-07-31 23:43:41 +02:00
Tobi 3c91ec33ae
Merge pull request #10122 from TeamNewPipe/fix/media-tunneling
Disable media tunneling by default on known unsupported devices
2023-07-31 23:30:24 +02:00
Tobi 6b3f51e5ea
Merge pull request #10281 from TeamNewPipe/okio
Update com.squareup.okio:okio to 3.4.0
2023-07-31 23:29:39 +02:00
TobiGr d6a1170ddb Replace settings migration with automatic check for device blacklist version 2023-07-31 23:00:54 +02:00
TobiGr 428a7d418b Update com.squareup.okio:okio to 3.4.0
Use okio 3.4.0 explicity to fix vulnerability introduced through okhttp3 (3.3.0).
See https://www.cve.org/CVERecord?id=CVE-2023-3635 for more details on the vulnerability.
2023-07-31 21:53:49 +02:00
TobiGr 40d102fcb5 Disable media tunneling by default on new devices
Sony BRAVIA_VH1, BRAVIA_VH2, BRAVIA_ATV2, BRAVIA_ATV3_4K
Phillips 4K (O)LED TV (PH7M_EU_5596)
Panasonic 4KTV-JUP (TX_50JXW834)
Bouygtel4K (HMB9213NW)
2023-07-29 22:08:51 +02:00
TobiGr 1db73370a7 Ensure that imports handle disabling media tunneling correctly
Store in preferences whether media tunneling was disabled automatically.
Show info in ExoPlayer settings if media tunneling was disabled autmatically.
2023-07-29 22:08:51 +02:00
TobiGr 8b63b437d8 Disable media tunneling if the device is known for not supporting it
Revert removing the Utils related to media tunneling.
2023-07-29 14:13:03 +02:00
TobiGr 78e577d260 Make some constants private and annotate params 2023-07-29 14:13:03 +02:00
Tobi 96a7cc2971
Merge pull request #10250 from kuragehimekurara1/dev
Added Uchinaguchi translation and README
2023-07-24 19:36:32 +02:00
TobiGr 9eedbae879 Fix ryu translation syntax 2023-07-24 19:33:27 +02:00
kuragehime 38d3b3c7ef Added Uchinaguchi (ryu) to language selector 2023-07-24 19:31:31 +02:00
kuragehime e4d3b74f1b Added Uchinaguchi translation 2023-07-24 19:31:31 +02:00
TobiGr 54f3003a6f Added Uchinaguchi README
Co-authored-by:  kuragehime <kuragehime641@gmail.com>
2023-07-24 19:31:31 +02:00
TobiGr cbc7b8ce18 Fix wrongly formatted string resource `audio_track_name`
Closes #10243
2023-07-24 19:14:00 +02:00
Hosted Weblate ec7d01b794
Translated using Weblate (Swedish)
Currently translated at 72.9% (54 of 74 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (German)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Dutch)

Currently translated at 64.8% (48 of 74 strings)

Translated using Weblate (German)

Currently translated at 81.0% (60 of 74 strings)

Translated using Weblate (Albanian)

Currently translated at 81.0% (552 of 681 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (French)

Currently translated at 93.2% (69 of 74 strings)

Translated using Weblate (Vietnamese)

Currently translated at 33.7% (25 of 74 strings)

Translated using Weblate (Bulgarian)

Currently translated at 66.5% (453 of 681 strings)

Translated using Weblate (Finnish)

Currently translated at 87.3% (595 of 681 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (German)

Currently translated at 81.0% (60 of 74 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Japanese)

Currently translated at 12.1% (9 of 74 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Vietnamese)

Currently translated at 32.4% (24 of 74 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Romanian)

Currently translated at 96.0% (654 of 681 strings)

Translated using Weblate (Japanese)

Currently translated at 99.7% (679 of 681 strings)

Translated using Weblate (Korean)

Currently translated at 12.1% (9 of 74 strings)

Translated using Weblate (Korean)

Currently translated at 12.1% (9 of 74 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (681 of 681 strings)

Translated using Weblate (German)

Currently translated at 79.7% (59 of 74 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (681 of 681 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: AhHyeon An <toto1444@gmail.com>
Co-authored-by: AioiLight <info@aioilight.space>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Anonymous <deni76@tutanota.com>
Co-authored-by: Anxhelo Lushka <anxhelo1995@gmail.com>
Co-authored-by: Arsi Kiikka <arsikiikka20@gmail.com>
Co-authored-by: David Braz <davidbrazps2@gmail.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Etienne Barrier <etienne.barrier@gmail.com>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: MJ Kim <faith@users.noreply.hosted.weblate.org>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: zmni <zmni@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translation: NewPipe/Metadata
2023-07-24 19:04:23 +02:00
Tobi 3edd4c012d
Merge pull request #10195 from AudricV/player_refactor-renderers-activation-or-deactivation
Refactor Player.useVideoSource logic and improve its comments
2023-07-22 14:12:35 +02:00
Tobi 3243f97ff2
Merge pull request #10233 from TeamNewPipe/actions/mimmizer-pr
Minimize images in PR descriptions
2023-07-20 23:18:03 +02:00
Isira Seneviratne c658f28b02
Merge pull request #10078 from Isira-Seneviratne/Improve_feed_notifications
Improve new stream notifications
2023-07-20 06:39:19 +05:30
AudricV 5ab3a4a9e0
Refactor Player.useVideoSource logic and improve its comments
- don't check for isAudioOnly == !videoEnabled, as this prevents enabling again
video and text tracks renderers in some cases;
- when reloading play queue manager if that's needed, disable or enable video
and text tracks renderers, as they may need to be enabled again in some cases
like starting a video in main player, opening play queue, switching to
background player on it and switching back to main player;
- disable or enable video renderers also for streams with AUDIO_STREAM
StreamType, as doing so doesn't raise any issue and simplifies code;
- reword and move some comments to make them easier to understand.
2023-07-19 22:52:18 +02:00
Isira Seneviratne cb00c57009 Set channel icon for stream notifications 2023-07-19 05:52:59 +05:30
Tobi cd2884d412
Merge pull request #10235 from TacoTheDank/bumpRoom
Update AndroidX Room library
2023-07-18 22:35:30 +02:00
Tobi 471137093a
Merge pull request #9719 from Marius1501/tabs_on_bottom
Added bottom main-tabs feature
2023-07-18 22:03:47 +02:00
Tobi 57064479c8
Merge pull request #8456 from SydneyDrone/database_tests
Add database test for SubscriptionManager
2023-07-18 08:55:35 +02:00
Stypox 528bd502b4 Improve SubscriptionManager tests
- fix checkstyle errors
- tests do not run in order, so each one has to do its own assertions separately from what others did
- the uid of an entity in the database needn't be the same of the one created in-memory, since the uid gets assigned upon inserting in the database
- some database functions return a `Completable` that doesn't do anything until it is subscribed to or awaited, so I added `.awaitBlocking()` where needed
- the data of an entity in-memory does not get updated automatically when the corresponding entity in the database is changed, so some tests have been removed
- `manager.insertSubscription` only inserts recent streams, so they need to have a date set on them (I also made related items hardcoded and not dependent on what the channel is currently doing)
2023-07-18 08:36:29 +02:00
SydneyDrone 90bc1905f5 Create SubscriptionManagerTest.java 2023-07-18 08:36:29 +02:00
TacoTheDank a01e59e9db Update AndroidX Room library 2023-07-17 21:09:44 -04:00
TacoTheDank 3f944c1bb2 Fix MigrationTestHelper deprecation 2023-07-17 21:09:21 -04:00
Tobi 43ef852117
Merge pull request #10230 from TeamNewPipe/fix/offline-stream-chooser
[Download] Fix audio stream selection
2023-07-17 23:47:21 +02:00
TobiGr bf22515bcd Fix README 2023-07-17 22:18:08 +02:00
Tobi 4c17c7b45b
Merge pull request #10240 from TeamNewPipe/readmes
Add translated READMEs
2023-07-17 22:08:31 +02:00
TobiGr ec21200787 Add links to all READMEs 2023-07-17 21:38:26 +02:00
TobiGr 25fea73704 Add Russian README
Taken from #8711 with updated screenshots

Co-authored-by:  Vik <63919734+ViktorOn@users.noreply.github.com>
2023-07-17 21:38:26 +02:00
TobiGr 2377d85efb Add German README
Taken from #8712 with updated screenshots and applied reviews.

Co-authored-by: Nico Haas <nico.haas@aisec.fraunhofer.de>
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authered-by: TobiGr <tobigr@users.noreply.github.com>
2023-07-17 21:38:26 +02:00
TobiGr ddef550637 Add French README
Taken from #9296 with updated screenshots

Co-authored-by: eze-kiel <hugoblanc@fastmail.com>
2023-07-17 21:38:26 +02:00
Tobi 2f0ed7f3b7
Merge pull request #10232 from Stypox/leakcanary-fix
Fix LeakCanary startup in debug builds and fix a memory leak
2023-07-17 19:42:24 +02:00
TobiGr b6bdd359d6 Add Assamese README
Taken from #9618 with updated screenshots

Co-authored-by:  Abhilash <121420261+WirelessAlien@users.noreply.github.com>
2023-07-17 18:54:38 +02:00
TobiGr a4453bc699 Add Punjabi README
Taken from #9621 with updated screenshots.

Co-authored-by:  K Gill <60492161+ShareASmile@users.noreply.github.com>
2023-07-17 18:54:38 +02:00
TobiGr 3e87c40856 Add Italian README
Taken from #10157

Co-authored-by:  Mirko Di <84203046+mirk0dex@users.noreply.github.com>
2023-07-17 18:54:38 +02:00
Tobi d80e531a2e
Merge pull request #9421 from seojun0924/patch-2
Update README.ko.md
2023-07-17 16:28:48 +02:00
Tobi d25e84a461
Merge pull request #9897 from rogerjs93/patch-1
Update README.es.md
2023-07-17 01:40:17 +02:00
TobiGr 05cc520665 Fix pure logo
The previous version was not properly vertically aligned. The default alignement from the logo is used now.
2023-07-17 01:28:55 +02:00
TobiGr eeec6fd002 Replace null check with use of NotificationManagerCompat.from 2023-07-17 01:28:55 +02:00
Isira Seneviratne 795bc82c7f Show number of new streams in the collapsed summary notification. 2023-07-17 01:28:55 +02:00
Isira Seneviratne 7742c40ac0 Create individual stream notifications for convenience on Android 7.0 and later. 2023-07-17 01:28:55 +02:00
TobiGr d9e2ada369 Minimize images in PR descriptions 2023-07-15 02:33:53 +02:00
Stypox 5d6158ea76
No need to manually mark fragment as destroyed for LeakCanary
It already does so automatically.
2023-07-14 20:48:05 +02:00
Stypox 00257e969e
Fix PlayerService leakead by Binder instance
Also see https://stackoverflow.com/q/63787707
2023-07-14 18:34:20 +02:00
Stypox 135f0f7249
Make all leak canary libs debugImplementation-only 2023-07-14 18:32:30 +02:00
Stypox fdd8b76add
Fix DebugApp doing unneeded AppWatcher.manualInstall 2023-07-14 18:32:04 +02:00
TobiGr 6b7ffbba4c [Download] Fix audio stream selection
Closes #10180
2023-07-14 17:06:12 +02:00
Tobi 8cfba4003d
Merge pull request #10229 from Koitharu/bugfix/feed_crash
Fix crash after feed update
2023-07-14 15:31:12 +02:00
Koitharu 01b46edf1a
Fix crash after feed update 2023-07-14 11:41:52 +03:00
Stypox f8599d17c2
Merge pull request #10085 from TacoTheDank/bumpLeakCanary
Update LeakCanary library
2023-07-12 19:05:38 +02:00
Stypox 5c7a9a52f5
Merge pull request #10223 from TacoTheDank/cleanAlertDialogs
Clean up AlertDialogs
2023-07-12 19:02:57 +02:00
TacoTheDank c1f0a945c0 Clean up AlertDialogs 2023-07-11 21:54:10 -04:00
TacoTheDank db7de05f2b Update LeakCanary library 2023-07-11 20:32:29 -04:00
Stypox e33bb676f9
Merge pull request #10219 from TeamNewPipe/PR-template-wiki-link
Add link to wiki page for APK download
2023-07-08 23:01:48 +02:00
Tobi 30724dbc50
Add link to wiki page for APK download 2023-07-08 22:49:10 +02:00
Stypox e765343162
Merge pull request #10166 from TeamNewPipe/fix/image-workflow
Add support for new GitHub assets URLs in image minimizer workflow
2023-07-08 22:18:16 +02:00
Tobi 62ce0b0408
Merge pull request #10213 from Stypox/update-screenshots
Update screenshots
2023-07-08 12:33:59 +02:00
Stypox 3bbc606694
Update screenshots in translated READMEs 2023-07-07 20:57:42 +02:00
Stypox 56eec9fed1
Add separation between tablet and phone images 2023-07-07 20:57:42 +02:00
Stypox ea0d798ea0
Update README screenshots 2023-07-07 20:57:41 +02:00
Stypox 5716d51112
Update screenshots 2023-07-07 20:57:41 +02:00
Tobi d845a158f0
Merge pull request #10200 from TeamNewPipe/fix/acra
Update ACRA and Checkstyle to fix a dependency vulnerability
2023-07-02 00:16:54 +02:00
TobiGr 1a2fbd8122 Update acra and checkstyle fixing vulnerability in dependency com.google.guava
See https://app.snyk.io/org/thescrabi/project/27dc214e-7f4f-47bb-a77c-443201491254
2023-07-01 13:30:37 +02:00
TobiGr 8bdeed8f28 Add support for new GitHub assetes URLs in image minimizer workflow 2023-06-15 16:03:30 +02:00
Tobi 3c87462203
Merge pull request #10164 from TeamNewPipe/weblate
Update weblate & fix conflicts
2023-06-14 12:55:16 +02:00
TobiGr 3622438a9d Translated using Weblate (Arabic)
Currently translated at 55.4% (41 of 74 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Malayalam)

Currently translated at 83.7% (572 of 683 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Tigrinya)

Currently translated at 2.1% (15 of 683 strings)

Translated using Weblate (Tigrinya)

Currently translated at 4.0% (3 of 74 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 22.2% (152 of 683 strings)

Translated using Weblate (Turkish)

Currently translated at 98.8% (675 of 683 strings)

Added translation using Weblate (Tigrinya)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (683 of 683 strings)

Added translation using Weblate (Kannada)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 13.4% (92 of 683 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Korean)

Currently translated at 12.1% (9 of 74 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (683 of 683 strings)

Added translation using Weblate (English (Middle))

Added translation using Weblate (English (Old))

Added translation using Weblate (Sicilian)

Added translation using Weblate (Aymara)

Added translation using Weblate (Arabic (Najdi))

Added translation using Weblate (Kashmiri)

Added translation using Weblate (German (Low))

Translated using Weblate (Korean)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Latvian)

Currently translated at 93.7% (640 of 683 strings)

Translated using Weblate (Dutch)

Currently translated at 64.8% (48 of 74 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 95.9% (71 of 74 strings)

Translated using Weblate (German)

Currently translated at 74.3% (55 of 74 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (683 of 683 strings)

Deleted translation using Weblate (Kashmiri)

Deleted translation using Weblate (Arabic (Najdi))

Deleted translation using Weblate (Aymara)

Deleted translation using Weblate (Sicilian)

Deleted translation using Weblate (English (Old))

Deleted translation using Weblate (English (Middle))

Deleted translation using Weblate (German (Low))

Translated using Weblate (Belarusian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Slovak)

Currently translated at 17.5% (13 of 74 strings)

Translated using Weblate (French)

Currently translated at 90.5% (67 of 74 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.2% (678 of 683 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Italian)

Currently translated at 97.2% (664 of 683 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (French)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (German)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (German)

Currently translated at 100.0% (683 of 683 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: AhHyeon An <toto1444@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Alexthegib <jcwkgxc@nightorb.com>
Co-authored-by: Arnis Jaundzeikars <sangsatori@theradiant.space>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: Domokun <domokun@asdasd.nl>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Surfoo <surfooo@gmail.com>
Co-authored-by: TobiGr <tobigr@mail.de>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: ai <woldu@duck.com>
Co-authored-by: atilluF <110931720+atilluF@users.noreply.github.com>
Co-authored-by: ctntt <pavlov_mainstreamed@slmail.me>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: gbpu <gui.beppu@gmail.com>
Co-authored-by: gymka <gymka@archlinux.lt>
Co-authored-by: jeffin-v <jeffin@posteo.net>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: thami simo <simo.azad@gmail.com>
Co-authored-by: tryvseu <tryvseu@tuta.io>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 이정희 <daemul72@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ti/
Translation: NewPipe/Metadata

Translated using Weblate (Hungarian)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (683 of 683 strings)

Translated using Weblate (Galician)

Currently translated at 99.7% (681 of 683 strings)

Translated using Weblate (Hungarian)

Currently translated at 17.5% (13 of 74 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/

Translated using Weblate (Hindi)

Currently translated at 100.0% (74 of 74 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/

Translated using Weblate (N’Ko)

Currently translated at 98.5% (673 of 683 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (683 of 683 strings)
2023-06-14 12:01:36 +02:00
Isira Seneviratne 1848892ff8
Merge pull request #10076 from Isira-Seneviratne/Remove_unused_resources
Remove unused resources
2023-06-09 12:49:45 +05:30
Tobi 72c6ed2804
Merge pull request #10066 from Stypox/swap-subchannel-avatar
Fix uploader and subchannel avatars being swapped and disable loading thumbnail message failure on content details page
2023-06-05 23:24:12 +02:00
Tobi 42de2c7033
Merge pull request #10141 from quarthex/add-peertube.stream
handle links to the PeerTube instance “peertube.stream”
2023-06-05 23:20:38 +02:00
Romain 6bcc8691fa
handle links to the PeerTube instance “peertube.stream”
also sort the lines, because it will quickly become a mess otherwise
2023-06-05 20:34:55 +02:00
Isira Seneviratne 6cf13ed8fb
Merge pull request #10087 from TacoTheDank/bumpLibraries
Update some libraries
2023-06-02 08:49:12 +05:30
Isira Seneviratne ad75db40df
Merge pull request #10088 from TacoTheDank/organizeProguard
Organize the proguard file
2023-06-02 06:00:22 +05:30
TacoTheDank 4e3bf3c2f9 Update some libraries 2023-05-30 12:22:05 -04:00
Stypox 1925687f18
Merge pull request #10089 from TacoTheDank/fixUnresolvedExtractor
Fix unresolved extractor
2023-05-26 11:55:41 +02:00
Stypox 577301c4eb
Proper filename for questions discussion template 2023-05-26 11:52:26 +02:00
Tobi c87b42de1c
Merge pull request #10120 from Stypox/move-questions
Move questions to Discussions
2023-05-26 11:15:01 +02:00
Stypox c8e8915c2e
Move questions to Discussions 2023-05-26 11:05:32 +02:00
Stypox 17cdedfa85
Merge pull request #10119 from Stypox/rewrite-announcement
Add rewrite announcement to readme
2023-05-26 10:35:37 +02:00
Stypox 677bb4070f
Add rewrite announcement to readme 2023-05-26 10:34:33 +02:00
TacoTheDank fe82029dc7 Fix unresolved extractor 2023-05-12 01:12:12 -04:00
TacoTheDank 0ab9961908 Organize the proguard file 2023-05-12 01:09:08 -04:00
Isira Seneviratne ecbf5d5ead Remove unused resources. 2023-05-08 06:27:41 +05:30
Tobi df430badbc
Merge pull request #10042 from MBKaba/patch-1
add language ߒߞߏ (nqo)
2023-05-06 23:28:55 +02:00
Tobi 8639972a54
Merge pull request #10074 from TeamNewPipe/weblate
Update translations
2023-05-06 23:03:54 +02:00
TobiGr 41038f452d Remove translations of previously deleted string brightness_gesture_control_summary 2023-05-06 02:01:54 +02:00
TobiGr 2f31ea8864 Remove translations of previously deleted string brightness_gesture_control_title 2023-05-06 01:58:33 +02:00
TobiGr e831059162 Remove translations of previously deleted string volume_gesture_control_summary 2023-05-06 01:55:36 +02:00
TobiGr e109e8cf1c Remove translations of previously deleted string volume_gesture_control_title 2023-05-06 01:52:31 +02:00
TobiGr f1524b6aba Remove translations of previously deleted string feed_toggle_show_played_items 2023-05-06 01:48:16 +02:00
TobiGr 51ee6f87e0 Remove translations of previously deleted string feed_toggle_hide_played_items 2023-05-06 01:48:08 +02:00
TobiGr 0bb3e7cb86 Translated using Weblate (Belarusian)
Currently translated at 6.7% (5 of 74 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 12.9% (86 of 664 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 99.3% (660 of 664 strings)

Translated using Weblate (Estonian)

Currently translated at 99.6% (662 of 664 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (German)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 0.0% (0 of 74 strings)

Translated using Weblate (N’Ko)

Currently translated at 8.9% (59 of 661 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.6% (659 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Added translation using Weblate (N’Ko)

Translated using Weblate (Bambara)

Currently translated at 0.1% (1 of 661 strings)

Translated using Weblate (Bambara)

Currently translated at 1.3% (1 of 74 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (74 of 74 strings)

Added translation using Weblate (Bambara)

Merge branch 'origin/dev' into Weblate.

Translated using Weblate (Estonian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alexthegib <jcwkgxc@nightorb.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Bakary Kaba <mbkaba@live.fr>
Co-authored-by: Bdd55oo <giggzuv9z.eofjx@aleeas.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hoàng Lâm Lê <work.lehoanglam@gmail.com>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: mono <monodevx@gmail.com>
Co-authored-by: random r <epsilin@yopmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/be/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bm/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nqo/
Translation: NewPipe/Metadata

Translated using Weblate (German)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (French)

Currently translated at 99.3% (660 of 664 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Catalan)

Currently translated at 93.2% (619 of 664 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 41.4% (275 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 4.0% (3 of 74 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nqo/

Translated using Weblate (Dutch)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Turkish)

Currently translated at 99.3% (660 of 664 strings)

Translated using Weblate (Georgian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 67.9% (451 of 664 strings)

Translated using Weblate (French)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (French)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Odia)

Currently translated at 98.9% (657 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 84.6% (562 of 664 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Turkish)

Currently translated at 99.5% (661 of 664 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (French)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (German)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 100.0% (664 of 664 strings)

Translated using Weblate (N’Ko)

Currently translated at 5.4% (4 of 74 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nqo/

Translated using Weblate (N’Ko)

Currently translated at 9.4% (7 of 74 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nqo/
2023-05-06 01:47:58 +02:00
Tobi 4bf063645a
Merge pull request #10067 from Stypox/update-gradle-again
Update Android Gradle Plugin to 8.0.1
2023-05-04 10:33:57 +00:00
Stypox 9866eab60f
Update Android Gradle Plugin to 8.0.1 2023-05-03 11:18:12 +02:00
Stypox 10c42de2f1
Fix uploader and subchannel avatars swapped 2023-05-03 10:35:28 +02:00
Stypox e1fd25fb71
Merge pull request #10046 from Stypox/ktlint-java17
Fix ktlint formatter after upgrade to Java 17
2023-05-02 13:43:44 +02:00
Stypox 2315b082ff
Merge pull request #9937 from Theta-Dev/alang-selector
Add support for multiple audio tracks
2023-05-02 10:07:21 +02:00
AudricV 023f6166ab
Add Open in browser button to audio external players dialog
This change makes the dialog consistent with the video one.
2023-05-02 00:18:46 +02:00
AudricV d89a3c6c4d
Remove "default" from audio track already present message
We don't know if, on muxed video streams we get for all services which support
multiple audio languages, that the audio language returned is the original one
or not, even if it should be the case.

In order to avoid saying potential false information, this word has been
removed from the string resource (ID and value) and the corresponding layout ID
in the download dialog.
2023-05-02 00:11:09 +02:00
ThetaDev fb00ee8cf9
[YouTube] Improve download speed (#9948) 2023-05-01 19:26:42 +02:00
ThetaDev 22671ca16c fix: audio stream cache key, code fmt 2023-05-01 00:04:04 +02:00
ThetaDev 4e837e838d
fix docs in app/src/main/java/org/schabi/newpipe/util/Localization.java
Co-authored-by: Audric V. <74829229+AudricV@users.noreply.github.com>
2023-05-01 00:02:37 +02:00
Tobi ed1781133c
Merge pull request from GHSA-r3gv-6fw7-hc52
Fix CI command injection vulnerability
2023-04-27 22:51:54 +02:00
MBKaba 60fc662a26
Update settings_keys.xml
Add ߒߞߏ (nqo) language code to <string-array name="app_language_code">

Add ߒߞߏ (nqo) language name to <string-array name="app_language_name">
2023-04-27 12:21:59 +00:00
Stypox 43b0167a3a
Fix CI command injection vulnerability
See https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions
2023-04-26 16:01:20 +02:00
Stypox 8519897089
Fix ktlint formatter after upgrade to Java 17
See https://github.com/pinterest/ktlint/issues/1195 and https://github.com/ScoopInstaller/Extras/issues/10313. Note that although this should have been fixed in the latest version of ktlint (we are using an old one), the fix doesn't seems to have worked for me.
2023-04-25 18:37:04 +02:00
MBKaba 60a5d02018
add language ߒߞߏ (nqo)
The translation is already done:
https://hosted.weblate.org/projects/newpipe/strings/nqo/
2023-04-24 10:44:26 +00:00
ThetaDev c377ffbce8 Merge branch 'dev' of github.com:TeamNewPipe/NewPipe into alang-selector 2023-04-21 23:32:33 +02:00
ThetaDev b567d428ad fix: small codestyle fixes 2023-04-21 23:15:37 +02:00
Stypox da30e539df
Merge pull request #9748 from TeamNewPipe/feat/av1-tags
Add whitelist to only retrieve supported YouTube itags/streams
2023-04-20 16:42:31 +02:00
Isira Seneviratne f74d794b2a
Merge pull request #10035 from Stypox/update-gradle
Upgrade Gradle and AGP from 7.x to 8.x and use Java 17
2023-04-20 19:55:31 +05:30
Stypox 69ef4a987e
Update CI Java version 2023-04-20 15:42:33 +02:00
Stypox 78e1e0508e
Fix gradle build for Java 17 and 19 2023-04-20 11:19:21 +02:00
Stypox 6d98ad7abc
Further upgrade gradle to 8.1 2023-04-20 10:50:48 +02:00
Stypox 70b3ba310a
Upgrade to Gradle 8.0 2023-04-20 10:36:23 +02:00
ThetaDev 2edc223e77 Merge branch 'dev' into alang-selector 2023-04-17 23:01:07 +02:00
TobiGr e18a6b09f8 Apply new itag filter only to YouTube streams 2023-04-17 13:10:29 +02:00
TobiGr f8c3ec4be7 Use a whitelist to filter all streams retrieved by the extractor.
NewPipe Extractor now extracts all YouTube Itags and therefore only those which can be handled by the player need to be retrieved from the list of all available streams.
2023-04-17 13:00:11 +02:00
Stypox ba3afd1e35
Merge pull request #10021 from Isira-Seneviratne/PendingIntentCompat
Switch to AndroidX's PendingIntentCompat.
2023-04-14 14:20:47 +02:00
Isira Seneviratne 20f0011921 Fix Sonar failure. 2023-04-13 10:53:46 +05:30
Isira Seneviratne acebabd028 Use AndroidX's PendingIntentCompat class. 2023-04-13 10:53:46 +05:30
Stypox 6243f34946
Merge pull request #8875 from AudricV/exoplayer-settings
Add an ExoPlayer settings page
2023-04-10 17:40:33 +02:00
AudricV 787758a436
[Android 6+] Add ability to always use ExoPlayer's MediaVideoCodecRenderer setOutputSurface workaround
As some devices not present in ExoPlayer's list may not implement
MediaCodec.setOutputSurface(Surface) properly, this workaround could be useful
on these devices.

It forces ExoPlayer to fall back on releasing and re-instantiating video codec
instances, which is always used on Android 5 and lower due to addition of this
method in Android 6.

To do so, a CustomMediaCodecVideoRenderer, based on ExoPlayer's
MediaVideoCodecRenderer which always return true for the
codecNeedsSetOutputSurfaceWorkaround method has been added, which is used in
CustomRenderersFactory, a class based on DefaultRenderersFactory which always
returns our CustomMediaCodecVideoRenderer as the video renderers.

CustomRenderersFactory replaces DefaultRenderersFactory in the player, in the
case this setting is enabled.
2023-04-10 17:39:27 +02:00
AudricV a02b92fd59
Update playback load interval size setting description
- Remove redundant player restart requirement note, as it is written on the
ExoPlayer settings description page;
- Add precision about the setting effect/limitation, as it only applies on
progressive contents/media sources and not on every content/media source;
- Remove translations of this description, to ensure that they will be updated
by translators.
2023-04-10 17:39:25 +02:00
AudricV a6ff85a208
Move media tunneling setting to ExoPlayer settings and make this setting available on release builds
Media tunneling may be not supported by more devices than the ones we
whitelisted before.

As a matter of fact, the list of devices on which media tunneling is disabled
could be not maintainable in the future, especially if the list of devices
grows more and more.

A preferable solution is to allow users to configure this setting themselves,
allowing them to not wait for their device(s) to be whitelisted in a future
NewPipe update.

This solution has been applied in this commit and works on every build type.

The corresponding preference in the debug settings has been of course removed
and the code used to prevent media tunneling activation on specific devices has
been removed.
2023-04-10 17:37:30 +02:00
AudricV 41da8fc05f
Add ability to use ExoPlayer's decoder fallback option
This option could help to avoid decoder initialization issues, which falls back
to lower-priority decoders if decoder initialization fails. This may result in
poor playback performance than when using primary decoders.

It is disabled by default, but can be enabled in ExoPlayer settings.
2023-04-10 17:37:30 +02:00
AudricV a4a9957a15
Add ExoPlayerSettingsFragment and move playback load interval size setting into it
This fragment has been added into SettingsResourceRegistry, to allow searches
in its options.

It has been placed at the place of the previous playback load interval size
setting (so in Video and Audio settings).
2023-04-10 17:37:30 +02:00
Stypox 29318c64ed
Merge pull request #10004 from TeamNewPipe/fix/apk-jsoup
Remove jsoup files from APK
2023-04-10 17:22:20 +02:00
Isira Seneviratne 74bd28cbd9 Update AndroidX Core to 1.10.0. 2023-04-09 18:45:57 +05:30
ThetaDev 365bb2d0e4 Merge branch 'dev' of github.com:TeamNewPipe/NewPipe into alang-selector 2023-04-05 14:06:14 +02:00
TobiGr c08538d25d Remove jsoup files from APK
Two jsoup files slipped into the META-INF dir of the APK for some reason. README.md and CHANGES are removed automatically now.
2023-04-04 17:42:34 +02:00
Stypox 140ea8642c
Merge pull request #10002 from Stypox/readme-notice
Add notice to README to not open feature PRs
2023-04-04 14:58:27 +02:00
Stypox 445d364193
Merge pull request #9708 from Marius1501/switch_setting_brightness_volume
Created a setting to switch the sides of volume and brightness
2023-04-04 14:19:08 +02:00
Stypox 4bb45c001d Fix settings migration 2023-04-04 11:06:20 +02:00
Stypox 7350b1f32e Add notice to README to not open feature PRs 2023-04-04 10:54:00 +02:00
Stypox 4a33ee6045
Merge pull request #10001 from Stypox/remove-exoplayer-from-changelogs
Remove "ExoPlayer settings" from 0.25.1 changelogs
2023-04-04 10:09:45 +02:00
Stypox 704e9bd7b6 Fix checkstyle 2023-04-04 10:02:01 +02:00
ge78fug d2735607b8 Changed the default of the switches 2023-04-04 09:57:06 +02:00
Marius Wagner 3c72992c39 Update app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
Co-authored-by: Stypox <stypox@pm.me>
2023-04-04 09:57:06 +02:00
ge78fug 7689d1d15c Added the migration 2023-04-04 09:57:06 +02:00
ge78fug 65d8589e7a Changed the naming 2023-04-04 09:57:06 +02:00
ge78fug 32cec6c9a7 Changed the naming 2023-04-04 09:57:06 +02:00
Marius Wagner 72ca52a29b Made the requested changes 2023-04-04 09:57:06 +02:00
ge78fug 2ded8c7cc1 Made two list options 2023-04-04 09:57:06 +02:00
ge78fug 759a9080a8 Fixed a bug 2023-04-04 09:57:06 +02:00
ge78fug 2ba649949f Updated the gesture-switch-toggle 2023-04-04 09:57:06 +02:00
ge78fug c8d54ec6c7 Changed to val 2023-04-04 09:57:06 +02:00
Marius Wagner 96e9242431 Update app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt
Co-authored-by: Stypox <stypox@pm.me>
2023-04-04 09:57:06 +02:00
ge78fug 3c74cb3439 Created a setting to switch the sides of volume and brightness 2023-04-04 09:57:06 +02:00
Stypox 7a8116b2cf Remove "ExoPlayer settings" from 0.25.1 changelogs
Also remove "hi" changelog completely, as it was not really translated
2023-04-04 09:47:51 +02:00
ThetaDev d010384c88 Merge branch 'dev' of github.com:TeamNewPipe/NewPipe into alang-selector 2023-04-03 22:13:16 +02:00
Stypox 07111d86d4
Merge pull request #9869 from TeamNewPipe/release-0.25.1
Release v0.25.1 (993)
2023-04-03 14:50:23 +02:00
Hosted Weblate ec974a2b3d Translated using Weblate (Estonian)
Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
2023-04-03 14:49:41 +02:00
Stypox 02906e8132
Merge pull request #9812 from TeamNewPipe/revert-8894-WindowCompat
Revert "Use WindowCompat."
2023-04-03 14:23:17 +02:00
Stypox 6f428d0c6b
Merge pull request #9890 from Redirion/bumpexo184
Bump ExoPlayer to 2.18.5
2023-04-03 14:11:15 +02:00
TobiGr 41da2bfb00 Bump NewPipe Extractor to 0.22.6 2023-04-02 23:02:19 +02:00
TobiGr 746b1f7eb2 Merge branch 'dev' into release-0.25.1 2023-04-02 22:54:00 +02:00
Hosted Weblate 03fd286956
Translated using Weblate (Odia)
Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Swedish)

Currently translated at 59.4% (44 of 74 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 18.9% (14 of 74 strings)

Translated using Weblate (Danish)

Currently translated at 98.3% (650 of 661 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Korean)

Currently translated at 99.8% (660 of 661 strings)

Translated using Weblate (French)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (German)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Indonesian)

Currently translated at 74.3% (55 of 74 strings)

Translated using Weblate (Czech)

Currently translated at 98.6% (73 of 74 strings)

Translated using Weblate (Ukrainian)

Currently translated at 98.6% (73 of 74 strings)

Translated using Weblate (Basque)

Currently translated at 43.2% (32 of 74 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 85.6% (566 of 661 strings)

Translated using Weblate (Belarusian)

Currently translated at 92.7% (613 of 661 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Latvian)

Currently translated at 89.7% (593 of 661 strings)

Translated using Weblate (Malay)

Currently translated at 54.3% (359 of 661 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 98.3% (650 of 661 strings)

Translated using Weblate (Vietnamese)

Currently translated at 97.7% (646 of 661 strings)

Translated using Weblate (Lithuanian)

Currently translated at 97.4% (644 of 661 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Korean)

Currently translated at 99.8% (660 of 661 strings)

Translated using Weblate (Hungarian)

Currently translated at 97.4% (644 of 661 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (English)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Assamese)

Currently translated at 14.8% (98 of 661 strings)

Translated using Weblate (Georgian)

Currently translated at 96.6% (639 of 661 strings)

Translated using Weblate (Bosnian)

Currently translated at 17.8% (118 of 661 strings)

Translated using Weblate (Gujarati)

Currently translated at 9.9% (66 of 661 strings)

Translated using Weblate (Marathi)

Currently translated at 8.4% (56 of 661 strings)

Translated using Weblate (Odia)

Currently translated at 99.8% (660 of 661 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Bengali)

Currently translated at 87.7% (580 of 661 strings)

Translated using Weblate (Bengali (India))

Currently translated at 45.9% (304 of 661 strings)

Translated using Weblate (Filipino)

Currently translated at 34.9% (231 of 661 strings)

Translated using Weblate (Danish)

Currently translated at 97.5% (645 of 661 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Belarusian)

Currently translated at 91.2% (603 of 661 strings)

Translated using Weblate (Belarusian)

Currently translated at 91.2% (603 of 661 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Albanian)

Currently translated at 83.9% (555 of 661 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 85.3% (564 of 661 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Catalan)

Currently translated at 93.3% (617 of 661 strings)

Translated using Weblate (Bulgarian)

Currently translated at 68.9% (456 of 661 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Finnish)

Currently translated at 90.0% (595 of 661 strings)

Translated using Weblate (Croatian)

Currently translated at 96.2% (636 of 661 strings)

Translated using Weblate (Vietnamese)

Currently translated at 97.7% (646 of 661 strings)

Translated using Weblate (Hebrew)

Currently translated at 98.7% (653 of 661 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Asturian)

Currently translated at 71.5% (473 of 661 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Dutch)

Currently translated at 98.3% (650 of 661 strings)

Translated using Weblate (French)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (German)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (English)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Korean)

Currently translated at 10.8% (8 of 74 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Belarusian)

Currently translated at 84.2% (557 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 95.9% (71 of 74 strings)

Translated using Weblate (Belarusian)

Currently translated at 78.9% (522 of 661 strings)

Translated using Weblate (Persian)

Currently translated at 99.3% (657 of 661 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Filipino)

Currently translated at 34.9% (231 of 661 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (French)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Russian)

Currently translated at 89.1% (66 of 74 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Sinhala)

Currently translated at 3.4% (23 of 661 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 64.8% (48 of 74 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Polish)

Currently translated at 60.8% (45 of 74 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (74 of 74 strings)

Translated using Weblate (Russian)

Currently translated at 78.3% (58 of 74 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 18.9% (14 of 74 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (661 of 661 strings)

Translated using Weblate (German)

Currently translated at 100.0% (661 of 661 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Alexthegib <jcwkgxc@nightorb.com>
Co-authored-by: Alfred Makne Poulsen <alfred@omj.dk>
Co-authored-by: Cyndaquissshhh <iversonbriones123@gmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Gontzal Manuel Pujana Onaindia <thadahdenyse@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Maday <royalcoolness7898@gmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Ray <ray.cfu@protonmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Rui Martins <martins.ro@gmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Sean Minnaert <sean.minnaert@gmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Simon Nilsson <Observeramera@pm.me>
Co-authored-by: Subham Jena <subhamjena8465@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Translator <kvb@tuta.io>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: WB <dln0@proton.me>
Co-authored-by: Xəyyam Qocayev <xxmn77@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: fincent <fincentpm@protonmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: jc <jcwkgxc@nightorb.com>
Co-authored-by: komiratsu192 <502badgateway@duck.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: tndsG <tharushtnds@gmail.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 모르것다 <jjs4809@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/eu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2023-04-02 22:48:15 +02:00
ThetaDev 39a5c8bdfb fix: reset video stream sizes on audio track selection 2023-03-29 13:39:29 +02:00
Audric V fb1b1c5be1
Merge pull request #9968 from AudricV/fix-open-in-browser-without-browser
Use a system chooser when opening links in browser in the case there is no browser available
2023-03-26 12:41:42 +02:00
AudricV 1a8aa8b17e
Use a system chooser when opening links in browser in the case there is no browser available
This change makes the app using the behavior when there is no default browser
on Android 11 and lower, by opening a system chooser when there is no browser
available (on all Android versions).

Also catch any exception when the system chooser cannot be opened and show the
"No app on your device can open this" toast in this case, as an
`ActivityNotFoundException` could be thrown if no app is available to open a
given web link.
2023-03-26 00:04:38 +01:00
Robin 2317864422 Bump ExoPlayer to 2.18.5 2023-03-23 15:03:07 +01:00
ThetaDev 694418d30d fix: update stream sizes when audio track changed 2023-03-21 16:58:36 +01:00
ThetaDev ed06f559ae feat: add track selection to downloader 2023-03-20 21:41:28 +01:00
ThetaDev fdd3b03fe5 fix: audio stream format selection 2023-03-19 23:47:33 +01:00
ThetaDev dbd6e4d11f fix: sonarcloud lint 2023-03-19 22:55:37 +01:00
ThetaDev 61a14765f3 fix: ListHelper tests 2023-03-19 22:31:31 +01:00
ThetaDev 9b8ffdd2aa fix: improve track name localization 2023-03-19 21:20:21 +01:00
ThetaDev ef0a4cf8b2 feat: add external audio playback language selector 2023-03-19 21:05:48 +01:00
ThetaDev 7aed2eed8a feat: add prefer original option, improve audio stream ordering 2023-03-19 20:40:27 +01:00
ThetaDev 87a88e4df7 feat: localized audio track names 2023-03-19 15:45:52 +01:00
ThetaDev 366c39d4c6 feat: add language selector to audio player 2023-03-19 01:15:36 +01:00
ThetaDev 77649d388c fix: reduce complexity 2023-03-18 16:29:22 +01:00
ThetaDev dba53d23aa fix: remove todo 2023-03-18 16:14:07 +01:00
ThetaDev 208887d538 feat: improve audio track sorting, add prefer_descriptive_audio option 2023-03-18 14:50:19 +01:00
Isira Seneviratne 0cd1a86aa5
Merge pull request #9872 from TeamNewPipe/fix-lint
Remove wrong annotation
2023-03-18 15:00:34 +05:30
ThetaDev de7872d8f2 feat: add audio language selector 2023-03-17 21:51:40 +01:00
Roger Salas bb1f5d8f38
Update README.es.md 2023-03-08 19:27:13 +02:00
Robin 7c39421297 bump ExoPlayer to 2.18.4 2023-03-06 16:49:43 +01:00
TobiGr d06cc862c8 Remove wrong annotation 2023-03-03 11:58:44 +01:00
Stypox c5cf2f4514
Release v0.25.1 (993) 2023-03-01 10:52:05 +01:00
Stypox 3f8e44dc66
Update NewPipeExtractor 2023-03-01 10:51:17 +01:00
Stypox d33229a3b8
Add changelog for v0.25.1 (993) 2023-03-01 10:45:25 +01:00
Hosted Weblate bb57f9cc9d
Merge branch 'origin/dev' into Weblate.
Translated using Weblate (Hindi)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.8% (654 of 655 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (654 of 655 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (German)

Currently translated at 100.0% (655 of 655 strings)

Translated using Weblate (Russian)

Currently translated at 75.3% (55 of 73 strings)

Translated using Weblate (Belarusian)

Currently translated at 74.4% (487 of 654 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 99.8% (653 of 654 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.2% (649 of 654 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Slovenian)

Currently translated at 64.2% (420 of 654 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (German)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (English)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Belarusian)

Currently translated at 6.8% (5 of 73 strings)

Translated using Weblate (Belarusian)

Currently translated at 74.3% (486 of 654 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (German)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Vietnamese)

Currently translated at 98.4% (644 of 654 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (German)

Currently translated at 100.0% (654 of 654 strings)

Merge branch 'origin/dev' into Weblate.

Translated using Weblate (Bengali)

Currently translated at 21.9% (16 of 73 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 69.8% (51 of 73 strings)

Translated using Weblate (Portuguese)

Currently translated at 69.8% (51 of 73 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Dutch)

Currently translated at 65.7% (48 of 73 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 28.7% (21 of 73 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (653 of 654 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Basque)

Currently translated at 45.2% (33 of 73 strings)

Translated using Weblate (German)

Currently translated at 73.9% (54 of 73 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (653 of 654 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (French)

Currently translated at 99.6% (652 of 654 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (German)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 15.0% (11 of 73 strings)

Translated using Weblate (German)

Currently translated at 73.9% (54 of 73 strings)

Translated using Weblate (Thai)

Currently translated at 32.0% (209 of 652 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 15.0% (11 of 73 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 64.3% (47 of 73 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Indonesian)

Currently translated at 76.7% (56 of 73 strings)

Translated using Weblate (Polish)

Currently translated at 60.2% (44 of 73 strings)

Translated using Weblate (Hindi)

Currently translated at 21.9% (16 of 73 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 17.8% (13 of 73 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (652 of 652 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Aitor Salaberria <trslbrr@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Florian <flo.site@zaclys.net>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Issa1553 <fairfull.playing@gmail.com>
Co-authored-by: JS Ahn <freirepublik@gmail.com>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Jonatan Nyberg <jonatan@autistici.org>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Mateus <mateusbernardo@protonmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Phahim Hasan <phahimhasanrakib@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Ray <ray.cfu@protonmail.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: S3aBreeze <S3aBreeze@users.noreply.hosted.weblate.org>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Sierzh <my-email@tut.by>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: ThePsychoBuck <Thepsychobuck@protonmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bowornsin <bowornsin@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: komiratsu19273240ad76c354986 <2011945@naver.com>
Co-authored-by: petlyh <88139840+petlyh@users.noreply.github.com>
Co-authored-by: phneutral26 <github@phileric.anonaddy.com>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Štefan Baebler <stefan.baebler@gmail.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/be/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/eu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2023-03-01 10:17:40 +01:00
Stypox 23a20712da
Merge pull request #9707 from Jared234/1473_remove_duplicates_from_playlist
Remove duplicates from playlist feature
2023-02-28 22:14:01 +01:00
Stypox 43f46e29ad
Update app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java 2023-02-28 21:40:11 +01:00
Stypox 7617f8cdc7
Update app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java 2023-02-28 21:35:57 +01:00
Stypox 2e3490bce2
Merge pull request #9747 from Jared234/9126_remove_partially_watched_from_feed
Added option to remove partially watched videos from the 'Whats new' feed
2023-02-28 19:10:11 +01:00
Jared Fantaye 1dd0930b83 Fixed some small issues 2023-02-28 17:30:17 +01:00
Jared Fantaye 265de55a07 Merge remote-tracking branch 'origin/1473_remove_duplicates_from_playlist' into 1473_remove_duplicates_from_playlist 2023-02-28 16:44:13 +01:00
Jared Fantaye d8ed2c8503 Refactoring removeDuplicates function and preventing concurrent calls. 2023-02-28 16:43:58 +01:00
Stypox 73aebc1110
Merge pull request #9847 from Redirion/exo183
Update to ExoPlayer 2.18.3
2023-02-26 19:16:25 +01:00
Stypox 3cb76e4c34
Merge pull request #9746 from NyanCatTW1/issue9745
Add an option to Ignore hardware media button events
2023-02-26 16:35:12 +01:00
Stypox 65680b2ccf
Only update main tabs position when it changes 2023-02-26 15:58:09 +01:00
Stypox c8ffe65acf
Simplify code to set tab layout position 2023-02-26 15:42:49 +01:00
Stypox a4767fc48a
Listen to ignore hardware buttons pref changes 2023-02-26 14:28:57 +01:00
Nyan Cat 42d861688e
Implement Ignore hardware media button events option 2023-02-26 14:02:50 +01:00
Stypox 2ee4c6e289
Merge pull request #9728 from mahendranv/channel_card
Larger channel cards in search results
2023-02-26 13:43:09 +01:00
Stypox 097c2368f4
Merge pull request #8180 from Trust04zh/fix-4053-8176
Make UI behavior for playback information display more consistent
2023-02-26 13:22:13 +01:00
Stypox 80e0c6ab0e
Merge pull request #9755 from Jared234/9458_faulty_playlist_thumbnail_update
Fixed a bug that caused erroneous updates of the playlist thumbnails
2023-02-26 13:13:12 +01:00
Jared Fantaye 9067c770a7 Made some small code improvements 2023-02-25 22:14:49 +01:00
Stypox f1a071b668
Merge pull request #9858 from Stypox/fit-more-grid-columns
Reduce the size of thumbnails on big screens to fit more grid columns
2023-02-25 20:42:36 +01:00
Stypox 8e888ebdf7
Reduce the size of thumbnails on big screens to fit more grid columns
Reverts part of #9310, which introduced bigger grid thumbnail sizes on big screens, because some users reported not being happy about having too few grid columns. See https://github.com/TeamNewPipe/NewPipe/pull/9310#discussion_r1070670806 .
2023-02-25 15:03:58 +01:00
Stypox 612122997b
Merge pull request #9769 from pratyaksh1610/branch-9765
Fix progress bar scaling on thumbnail in playlists card view
2023-02-25 14:50:07 +01:00
Stypox 4b050c0dd8
Merge pull request #9850 from Stypox/fix-api33-links-again3
[Android 11+] Correctly open URLs in browser and fix opening downloads and external players
2023-02-25 14:33:41 +01:00
Stypox be4f3d9d62
Improve javadocs in ShareUtils 2023-02-25 13:14:31 +01:00
Stypox 24ff6a4313
Rename videoURL to streamUrl 2023-02-25 13:14:31 +01:00
Stypox c2968a3ff2
Use non-deprecated resolveActivity method on API 33+
But such method is not available before API 33
2023-02-25 13:14:31 +01:00
Stypox 671dd4afd3
Merge pull request #9777 from pratyaksh1610/branch-9774
[Bug] Crash fix when click on empty comment
2023-02-25 09:44:31 +01:00
Stypox 600ebdae18
Correctly open urls in browser on Android 11+
- Fix misconfiguration in manifest ('http|https|market' is not valid)
- Split ShareUtils functions taking a boolean parameter into pairs of functions with better names and less runtime checks
- Move all Kore-related functions to KoreUtils
- Remove the toast_no_player string
2023-02-25 09:13:59 +01:00
Robin 5560cea470 Update to ExoPlayer 2.18.3 2023-02-23 12:46:05 +01:00
Stypox 39c500f33c
Revert "Use WindowCompat." 2023-02-14 08:27:04 +01:00
pratyaksh1610 624ad6a47c
Prevent NPEs when comment text is null 2023-02-14 08:18:13 +01:00
Jared Fantaye 68ea99d6e6 Made some small code improvements 2023-02-09 23:17:36 +01:00
Jared Fantaye bc29f40d69 Implemented the suggested changes 2023-02-09 21:18:21 +01:00
Jared234 42fb13f17a
Merge branch 'dev' into 1473_remove_duplicates_from_playlist 2023-02-09 20:47:10 +01:00
Jared Fantaye d5b54c85ed Made some small adjustments to the database query 2023-02-09 20:41:22 +01:00
pratyaksh1610 f0307b1b48 fix progress bar scaling in card view 2023-02-09 21:38:02 +05:30
Mahendran 75292e099c Larger channel cards in search results
- Thumbnail larger (100dp) than the usual (92dp) throughout the app
- Description lint count is 8 (normally 3)
2023-02-09 06:15:22 +05:30
Stypox e0cb2892b8
Merge branch 'master' into dev 2023-02-08 22:48:14 +01:00
Stypox 831f36e18e
Merge pull request #9711 from TeamNewPipe/release-0.25.0
Release v0.25.0 (992)
2023-02-08 22:47:32 +01:00
Stypox d2f8f31d1f
Update NewPipeExtractor again, because of JitPack problems 2023-02-08 22:37:17 +01:00
Stypox 8d43499e5b
Update NewPipeExtractor again 2023-02-08 22:27:49 +01:00
Hosted Weblate 63375627e9
Translated using Weblate (Bengali)
Currently translated at 21.9% (16 of 73 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 69.8% (51 of 73 strings)

Translated using Weblate (Portuguese)

Currently translated at 69.8% (51 of 73 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Dutch)

Currently translated at 65.7% (48 of 73 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 28.7% (21 of 73 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (653 of 654 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Basque)

Currently translated at 45.2% (33 of 73 strings)

Translated using Weblate (German)

Currently translated at 73.9% (54 of 73 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (653 of 654 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (French)

Currently translated at 99.6% (652 of 654 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (German)

Currently translated at 100.0% (654 of 654 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 15.0% (11 of 73 strings)

Translated using Weblate (German)

Currently translated at 73.9% (54 of 73 strings)

Translated using Weblate (Thai)

Currently translated at 32.0% (209 of 652 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 15.0% (11 of 73 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 64.3% (47 of 73 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Indonesian)

Currently translated at 76.7% (56 of 73 strings)

Translated using Weblate (Polish)

Currently translated at 60.2% (44 of 73 strings)

Translated using Weblate (Hindi)

Currently translated at 21.9% (16 of 73 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (73 of 73 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 17.8% (13 of 73 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (652 of 652 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Aitor Salaberria <trslbrr@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Florian <flo.site@zaclys.net>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Issa1553 <fairfull.playing@gmail.com>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Jonatan Nyberg <jonatan@autistici.org>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Mateus <mateusbernardo@protonmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Phahim Hasan <phahimhasanrakib@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Ray <ray.cfu@protonmail.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: S3aBreeze <S3aBreeze@users.noreply.hosted.weblate.org>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bowornsin <bowornsin@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: petlyh <88139840+petlyh@users.noreply.github.com>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/eu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2023-02-08 22:23:39 +01:00
Stypox 4903786b14
Merge pull request #9758 from Stypox/fix-api33-links-again
Fix opening links on Android 12+
2023-02-08 22:20:22 +01:00
Stypox 4cc653fdf1
Fix opening links on Android 12+ 2023-02-07 22:39:12 +01:00
Stypox 4c5c2a3d79
Merge pull request #9693 from Redirion/accelerometerfix
Orientation is locked if there is no sensor for it
2023-02-07 20:07:45 +01:00
ge78fug 26b29ca78d Made the requested color changes 2023-02-07 18:46:40 +01:00
Stypox c85af7861a
Merge pull request #9742 from TeamNewPipe/revert-9553-exo182
Revert PR #9553 "Update ExoPlayer to 2.18.2"
2023-02-07 10:48:18 +01:00
Stypox dc1ecc19ed
Merge pull request #9743 from TeamNewPipe/update-desugar-2.0.2
Update core library desugaring from 2.0.0 to 2.0.2
2023-02-07 10:44:15 +01:00
Trust_04zh e947e86eae
Make positions in list depend on watch history, remove confusing animations
The following is the list of all commits squashed together:

Regain function for option `Positions in lists`

use option `Resume playback` to control display of progress info in VideoDetailFragment, remove this (extra) function from option `Positions in lists`.
remove extra check for live streams, live streams updates just as non-live streams.

fix #8176 by eliminating exit delay

Regain function for option `Positions in lists`

update code with developer's comments

 apply static import to methods in util class DependentPreferenceHelper

Regain function for option `Positions in lists`

use option `Resume playback` to control display of progress info in VideoDetailFragment, remove this (extra) function from option `Positions in lists`.
remove extra check for live streams, live streams updates just as non-live streams.

fix behavior for displaying progress bar when autoplay off but video resume on

not to retrieve unnecessary states when position in lists disabled

fix mistake in code

simplify conditional logic

update doc comment and remove unused method

Fix not showing duration if position indicators disabled

Positions in lists only depends on watch history
2023-02-07 09:48:18 +01:00
Jared Fantaye 5d3955854e Fixed the merge conflict 2023-02-05 21:21:02 +01:00
Jared234 3ff4b713e8
Merge branch 'dev' into 9458_faulty_playlist_thumbnail_update 2023-02-05 20:45:44 +01:00
Jared Fantaye 68097568d5 Fixed the bug by replacing the thumbnail_url with the thumbnail_stream_id 2023-02-05 20:32:34 +01:00
Jared Fantaye cd8d57040c Implemented the feature using multiple checkboxes 2023-02-04 18:48:27 +01:00
TobiGr 812efca08e Update core libraray desugaring libs from 2.0.0 to 2.0.2 2023-02-03 18:42:12 +01:00
Stypox 1db1a00581 Add snippet to ensure baseline.profm file is sorted
Thanks to obfusk, see https://issuetracker.google.com/issues/231837768 and #6486
2023-02-03 18:40:48 +01:00
Tobi e0ba872b66 Revert "Update ExoPlayer to 2.18.2"
This commit reverts 1bb166a
2023-02-03 18:33:35 +01:00
Jared Fantaye 9c82441c19 Implemented the feature and fixed some small issues 2023-02-01 23:10:31 +01:00
ge78fug 38db0cc713 Changed the color 2023-01-31 16:07:57 +01:00
Marius Wagner ee217eb9b7
Merge branch 'TeamNewPipe:dev' into tabs_on_bottom 2023-01-31 13:37:47 +01:00
Jared Fantaye 3d36eb5baf Fixed a small commit mistake 2023-01-30 22:39:16 +01:00
Jared Fantaye d2d324f2dd First draft of the new feature 2023-01-30 22:37:24 +01:00
Marius Wagner 2b37721a6e
Update app/src/main/res/values/strings.xml
Co-authored-by: Stypox <stypox@pm.me>
2023-01-29 19:37:46 +01:00
Stypox 353db0bc6c
Merge pull request #9726 from Stypox/fix-api30+-links
Fix opening URLs in browser on API 30+
2023-01-29 18:08:25 +01:00
Stypox d1aed94d27
Fix opening urls in browser on API 30+
See https://medium.com/androiddevelopers/package-visibility-in-android-11-cc857f221cd9 and https://github.com/TeamNewPipe/NewPipe/issues/9615
2023-01-29 11:38:34 +01:00
Stypox 281cdf65da
Merge pull request #9725 from AudricV/yt_support-live-links
[YouTube] Add support for live links
2023-01-29 11:03:23 +01:00
AudricV 5af5c90492
[YouTube] Add support for live links
The addition of this support requires an extractor update.
2023-01-29 10:59:27 +01:00
Stypox ca421c28a1
Merge pull request #9538 from Jared234/4186_warning_duplicates_in_playlist
Handle duplicate streams in the "Add to playlist" dialog
2023-01-29 10:36:31 +01:00
Stypox 711345eff7
Improve playlist duplicate indicator layout 2023-01-29 10:32:44 +01:00
Stypox 102975aeb3
Improve handling playlist duplicate indicator 2023-01-29 10:32:32 +01:00
Stypox cd12503f99
Merge pull request #9631 from TeamNewPipe/update-npe
Update NewPipeExtractor and properly linkify comments
2023-01-28 22:40:19 +01:00
Stypox 1e724eba6c
Merge pull request #9706 from Jared234/9131_bug_background_player
Fixed a bug that caused the background player to stop working
2023-01-28 21:56:00 +01:00
Jared Fantaye c70ce791db Added the duplicate indicator explanation & removed some unnecessary functions 2023-01-27 15:37:33 +01:00
ge78fug 0821f6463a Added bottom main-tabs feature 2023-01-25 19:25:57 +01:00
Tobi 444ac5fe95
Merge pull request #9709 from Stypox/reproducible-build
Fix reproducible builds
2023-01-24 22:05:22 +01:00
Stypox b9228df32c
Release v0.25.0 (992) 2023-01-22 08:59:21 +01:00
Stypox b6bf0ffc40
Add changelog for v0.25.0 (992) 2023-01-22 08:56:29 +01:00
Hosted Weblate 34e6e70be9
Translated using Weblate (Azerbaijani)
Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Lithuanian)

Currently translated at 99.3% (648 of 652 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Assamese)

Currently translated at 15.0% (98 of 652 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Slovenian)

Currently translated at 2.7% (2 of 72 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Lithuanian)

Currently translated at 99.3% (648 of 652 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Japanese)

Currently translated at 99.8% (651 of 652 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (652 of 652 strings)

Translated using Weblate (German)

Currently translated at 100.0% (652 of 652 strings)

Co-authored-by: Abhilash <dev.abhilash.s@gmail.com>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ahmad0a <Ahmad3p@protonmail.com>
Co-authored-by: AioiLight <info@aioilight.space>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: ErnestasKaralius <ernis.karalius@gmail.com>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Francesco Saltori <francescosaltori@gmail.com>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: HudobniVolk <hudobni.volk@tuta.io>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Pieter van der Razemond <pietervanderrazemond@mailbox.org>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: RSoulwin <aapshergill1@gmail.com>
Co-authored-by: Ray <ray.cfu@protonmail.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sl/
Translation: NewPipe/Metadata
2023-01-22 05:12:39 +01:00
Jared Fantaye 5b3f8a3d30 Replaced the equals method 2023-01-21 14:56:55 +01:00
Sıla fceec71ad3
Corrected language names 2023-01-21 12:13:15 +00:00
Stypox a69f74f51b
Add snippet to ensure baseline.profm file is sorted
Thanks to obfusk, see https://issuetracker.google.com/issues/231837768 and #6486
2023-01-20 18:39:16 +01:00
Jared Fantaye e26c038565 Made some small adjustments 2023-01-20 11:55:50 +01:00
Jared Fantaye 52e39c3402 Fixed tests 2023-01-20 11:12:32 +01:00
Stypox f2af168986
Merge pull request #9691 from Marius1501/change_the_chapter_icon
Changed the chapter icon
2023-01-20 08:16:45 +01:00
ge78fug 6e1ffb4e52 Centered the icon 2023-01-19 23:24:25 +01:00
ge78fug f88c1e1e8b Changed the position 2023-01-19 21:15:09 +01:00
Jared Fantaye ddda80a577 Fixed the bug 2023-01-17 22:31:22 +01:00
Tobi d758e50634
Merge pull request #9696 from Stypox/fix-pref-npe
Fix NPEs after OnSharedPreferenceChangeListener changes
2023-01-17 13:01:09 +01:00
ge78fug a6021730cd Removed format_list_numbered 2023-01-17 10:50:13 +01:00
TobiGr e9fcad4787 Fix SonarLint 2023-01-16 23:20:50 +01:00
TobiGr 640d4b0280 Fix more NPEs after OnSharedPreferenceChangeListener changes 2023-01-16 23:05:29 +01:00
Stypox b9378a7c1f
Fix NPEs after OnSharedPreferenceChangeListener changes
Apps targeting {@link android.os.Build.VERSION_CODES#R} on devices running OS versions {@link android.os.Build.VERSION_CODES#R Android R} or later, will receive a {@code null} value when preferences are cleared.
2023-01-16 22:30:28 +01:00
ge78fug abb6b4282d Chenged the chapter icon 2023-01-16 15:13:34 +01:00
Robin 9ecd5dff09 Orientation is locked if there is no sensor for it 2023-01-16 13:56:45 +01:00
Stypox aa41fec466
Merge pull request #9686 from Isira-Seneviratne/Update_desugar
Update desugar_jdk_libs to 2.0.0.
2023-01-16 12:59:22 +01:00
Tobi e4641cd427
Update translations (#9688)
* Translated using Weblate (Hebrew)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (German)

Currently translated at 100.0% (650 of 650 strings)

Added translation using Weblate (Assamese)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (650 of 650 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (German)

Currently translated at 72.2% (52 of 72 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Catalan)

Currently translated at 95.5% (620 of 649 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 10.6% (69 of 649 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Arabic)

Currently translated at 51.3% (37 of 72 strings)

Translated using Weblate (Bengali)

Currently translated at 89.9% (584 of 649 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Thai)

Currently translated at 32.2% (209 of 649 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (647 of 649 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (German)

Currently translated at 66.6% (48 of 72 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (French)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (German)

Currently translated at 100.0% (649 of 649 strings)

Translated using Weblate (Hindi)

Currently translated at 19.4% (14 of 72 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (648 of 648 strings)

Co-authored-by: Abhilash <dev.abhilash.s@gmail.com>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ahmad0a <Ahmad3p@protonmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: ERYpTION <eryption.x6tf8@simplelogin.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Nikodem Zawirski <nikon96@gmail.com>
Co-authored-by: Oymate <dhruboadittya96@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: RSoulwin <aapshergill1@gmail.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Ricardo <contatorms7@tutamail.com>
Co-authored-by: SalusVF <salus.vf@gmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Translator <kvb@tuta.io>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bowornsin <bowornsin@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: tryvseu <tryvseu@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translation: NewPipe/Metadata

* Translated using Weblate (Slovenian)

Currently translated at 63.6% (414 of 650 strings)

* Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (650 of 650 strings)

* Translated using Weblate (Assamese)

Currently translated at 3.6% (24 of 650 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Abhilash <dev.abhilash.s@gmail.com>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ahmad0a <Ahmad3p@protonmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: ERYpTION <eryption.x6tf8@simplelogin.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Eric <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: GnuPGを使うべきだ <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Nikodem Zawirski <nikon96@gmail.com>
Co-authored-by: Oymate <dhruboadittya96@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: RSoulwin <aapshergill1@gmail.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Ricardo <contatorms7@tutamail.com>
Co-authored-by: SalusVF <salus.vf@gmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Translator <kvb@tuta.io>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bowornsin <bowornsin@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: random r <epsilin@yopmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: tryvseu <tryvseu@tuta.io>
Co-authored-by: HudobniVolk <hudobni.volk@tuta.io>
2023-01-15 21:53:52 +01:00
GET100PERCENT dba24ec1f9
Added Odia language to language selector (#9651) 2023-01-15 21:24:01 +01:00
Stypox abe6dfb99c
Merge pull request #9671 from Stypox/fix-toast-crash-api33
Fix popup enablement toast crash on API 33
2023-01-15 21:05:05 +01:00
Stypox d08d7cf31f
Merge pull request #9310 from mahendranv/fr_larger_thumbs
FR: Full width thumbnails aka card view mode
2023-01-15 20:25:45 +01:00
Stypox 6e73c489de
Improve ellipsizing comments 2023-01-15 19:28:01 +01:00
Stypox 489df0ed7d
Update NewPipeExtractor and properly linkify comments 2023-01-15 19:27:56 +01:00
Mahendran 7924bb5b6b Thumbnails used in NewPipe are small (list/grid) mode. This PR facilitates full width thumbnails and dubbed as card mode. 2023-01-15 22:32:03 +05:30
Stypox c47d1af5e3
Merge pull request #9555 from Marius1501/make_the_channel_images_bigger
Made the channel-images in the grid list bigger
2023-01-15 15:16:09 +01:00
Stypox 51af961e0d
Merge pull request #8894 from Isira-Seneviratne/WindowCompat
Use WindowCompat.
2023-01-15 15:14:05 +01:00
Stypox 86997794ab
Merge pull request #9678 from Marius1501/change_whats_new_icon
Changed the What's New icon
2023-01-15 15:12:26 +01:00
Stypox 2db29187f4
Merge pull request #7725 from AudricV/add-long-press-actions-on-hashtags-and-links-in-descriptions
Add long press action on hashtags and web links in descriptions
2023-01-15 14:06:32 +01:00
Stypox 22c201be39
Create text subpackage in util 2023-01-15 11:51:07 +01:00
AudricV cdd5e89b86
Add ability to copy hashtags, URLs and timestamps in descriptions on long-press
This commit adds the ability to copy to clipboard hashtags, URLs and timestamps
when long-pressing them.

Some changes in our TextView class related to text setting have been required
and metadata items are now using a NewPipeTextView instead of a standard
TextView.

Six new classes have been added:

- a custom LinkMovementMethod class;
- a custom ClickableSpan class, LongPressClickableSpan, in order to set a long
  press event;
- a class to avoid code duplication in CommentTextOnTouchListener, TouchUtils;
- three implementations of LongPressClickableSpan used when linkifying text:
  - HashtagLongPressClickableSpan for hashtags;
  - TimestampLongPressClickableSpan for timestamps;
  - UrlLongPressClickableSpan for URLs.
2023-01-15 11:40:27 +01:00
ge78fug 764b6aa2b1
Made the channel-images in the grid list bigger
Also improved the handling of additional information (expanded description, video count, subscriber count)
2023-01-15 10:50:20 +01:00
Isira Seneviratne f766ef2033 Replace the system UI visibility flags with WindowCompat calls. 2023-01-15 05:44:45 +05:30
Stypox ef4a6238c8
See if playlists already contain a stream from db 2023-01-14 18:01:48 +01:00
Jared Fantaye b3554a6a49
Added the number of duplicates to the toast text. 2023-01-14 18:01:48 +01:00
Jared Fantaye 5fb7b3266b
Removed the duplicate dialog and added another toast option 2023-01-14 18:01:48 +01:00
Jared Fantaye 8b6e110635
Fixed the functionality, improved performance & general code cleanup 2023-01-14 18:01:47 +01:00
Jared Fantaye f5a1f915be
Continued working on a way to show that items are already in a playlist 2023-01-14 18:01:47 +01:00
Jared Fantaye ac15339911
Started working on a way to show that items are already in a playlist 2023-01-14 18:01:47 +01:00
Jared Fantaye fdfeac081a
Implemented a warning before adding duplicate to playlist. 2023-01-14 18:01:46 +01:00
ge78fug 31396a632f Chenged the name of the icon 2023-01-14 09:21:37 +01:00
Isira Seneviratne 223150aa42 Update desugar_jdk_libs to 2.0.0. 2023-01-14 11:00:00 +05:30
Jared Fantaye 135fc08212 Implemented the "remove duplicates" feature. 2023-01-13 21:35:22 +01:00
ge78fug 5e3caf68a5 Chenged the What's New icon 2023-01-13 16:33:45 +01:00
Stypox 262b3a2945
Merge pull request #9664 from Marius1501/whats_new_section_to_default_tabs
Added the "What's New"-section to the default tabs
2023-01-13 13:25:02 +01:00
Stypox e44d09208c
Merge pull request #9642 from Jared234/8582_empty_playlists_not_shown
Fixed a bug that prevented the display of multiple empty playlists
2023-01-13 13:20:42 +01:00
Stypox 0546c9b9fc
Merge pull request #9445 from Jared234/9122_remove_watched_bug
Fixed a bug that incorrectly removed videos from a playlist when using the "Remove Viewed" dialog
2023-01-12 23:45:48 +01:00
Jared Fantaye 38c4a1ed85
Fixed the "Remove Watched" bug
Reverted changes and fixed bug in a different way
2023-01-12 23:44:26 +01:00
Stypox fd8e92cf77
Merge pull request #9523 from Jared234/9468_permanently_set_thumbnail
Allow the user to permanently set a thumbnail
2023-01-12 23:27:50 +01:00
Stypox 062570cc47
Merge pull request #8886 from Isira-Seneviratne/Remove_Runnable_variables
Remove Runnable variables for Handlers.
2023-01-12 15:34:12 +01:00
Isira Seneviratne 9514316be3
Remove Runnable variables for Handlers. 2023-01-12 15:30:19 +01:00
Stypox a15a5adacc
Merge pull request #9619 from Redirion/avoidreflectionifpossible
Check availability of Samsung DeX only on Samsung devices
2023-01-12 12:06:19 +01:00
Stypox b6e6d39985
Fix toast crash on API 33
You shouldn't call getView() on toasts.
Also simplified some duplicate code.
2023-01-12 11:39:25 +01:00
Stypox 48ae830262
Merge pull request #9653 from petlyh/fix-popup-crash
Ask for permission when enqueuing in a popup
2023-01-12 11:24:21 +01:00
Stypox 03f5dd71a5
Merge pull request #9499 from pratyaksh1610/branch-9466
Added Language suffix for subtitle downloads
2023-01-11 19:46:51 +01:00
Stypox 2afbe58722
UX improvements: keep user edits & do not reset cursor 2023-01-11 19:45:55 +01:00
ge78fug 0a64eac778 Added the "What's New"-section to the default tabs 2023-01-11 16:06:11 +01:00
Stypox ad605e2c5a
Actually there is no need to use flatMap
`null` values returned in the lambda are converted to empty `Optional`s in the `map` method: https://developer.android.com/reference/java/util/Optional#map(java.util.function.Function%3C?%20super%20T,%20?%20extends%20U%3E)
2023-01-11 15:26:46 +01:00
Stypox eed44b3231
Merge pull request #9135 from devlearner/routeractivity-screen-rotate
Improve screen rotation handling in Open action menu
2023-01-11 15:20:47 +01:00
Stypox 944e295ae7
Use Optional for simpler code 2023-01-11 15:14:18 +01:00
devlearner 28109fef38
Improve showing of toast
We provide visual feedback via a toast to the user that, well, they're supposed to wait; but with the benefit of the cache openAddToPlaylistDialog() may return (almost) immediately, which would render the toast otiose (if not a bit confusing). This commit improves that by cancelling the toast once the wait's over

... (by 'abusing' RxJava's ambWith();
ref on compose() and Transformer: https://blog.danlew.net/2015/03/02/dont-break-the-chain/
and for me, first time laying my hands at RxJava so kindly bear with me; open for suggestions)
2023-01-11 14:53:48 +01:00
devlearner 40442f3f82
Utilize Lifecycle observer
I thought it would have required an extra dependency; apparently that doesn't seem to be the case...
2023-01-11 14:53:48 +01:00
devlearner 61da167b4f
Oops, added back missing return; 2023-01-11 14:53:48 +01:00
devlearner c744f6756b
Fix Sonar reported code smell 2023-01-11 14:53:48 +01:00
devlearner de7057ac3a
Skip REORDER_TO_FRONT trick which doesn't seem to work on newer Androids
probably due to background restrictions on Android 10+
2023-01-11 14:53:48 +01:00
devlearner 585bfff11d
Utilize a retained fragment to safekeep network requests in flight
pending result for openAddToPlaylistDialog() and openDownloadDialog()
Despite marked deprecated, setRetainInstance(true) is probably our best bet (since a ViewModel is probably too overkill for our present purpose)
2023-01-11 14:53:48 +01:00
devlearner 0f9c20c986
Improve (un)registering FragmentLifecycleCallbacks
to avoid adding it multiple times and ensure proper cleanup
2023-01-11 14:53:48 +01:00
devlearner f860392ae9
Address LayoutParams.FLAG_NOT_TOUCHABLE restriction on Andriod 12+ 2023-01-11 14:53:48 +01:00
devlearner 391830558e
Ensure our transparent activity doesn't block touch events to underlying windows
so we won't hold up UI while fetching media info for Add to Playlist or Download actions
lest user might think it freezes when in fact a network request is underway
2023-01-11 14:53:48 +01:00
devlearner c1f37d8591
Also show toast in openDownloadDialog()
and lengthened a bit to inform user to wait...
2023-01-11 14:53:48 +01:00
devlearner b175774ad8
Try to amicably handle DialogFragment in FragmentManager when recreated from orientation change
- Handle finish() call instead of passing around callbacks to setOnDismissListener()
- Don't start over again if returning to DialogFragment before orientation change
2023-01-11 14:53:48 +01:00
devlearner 73e32889b6
Don't finish() to allow recreate
when orientation change is on foot
2023-01-11 14:53:48 +01:00
devlearner 400ee808e0
Set up theme/locale before super.create()
This seems to solve a bug where the Open action menu dialog does not appear the first time on cold start on older Android (8.0).
This is also the order of things in MainActivity and probably good practice.
2023-01-11 14:53:48 +01:00
Stypox 87976693f8
Merge pull request #9285 from Isira-Seneviratne/Optional_cleanup
Clean up Optional-related code.
2023-01-11 14:52:25 +01:00
Stypox 9c7ed80662
Use Optional.map correctly and other improvements 2023-01-11 14:47:53 +01:00
Jared Fantaye eb3363d4dd Created the first draft. 2023-01-10 20:55:18 +01:00
petlyh edff696ecc
Ask for permission when enqueuing in a popup 2023-01-10 14:16:19 +01:00
Jared Fantaye 9c19e9813a Fixed a bug that caused multiple empty playlists to be not shown. 2023-01-08 11:53:42 +01:00
Jared Fantaye 2679a4bf1e Removed the "Unset Thumbnail" item if you can't use this feature 2023-01-04 16:21:16 +01:00
Isira Seneviratne e8216b2e80 Apply code review suggestions. 2023-01-04 06:10:14 +05:30
Isira Seneviratne e3062d7c66 Use Optional chaining. 2023-01-04 05:16:21 +05:30
Isira Seneviratne fd55d85bbf Remove SimplifyOptionalCallChains. 2023-01-04 05:16:21 +05:30
Robin f10d591462 Samsung DeX should only be checked on Samsung devices
Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
2023-01-03 15:12:02 +01:00
pratyaksh1610 3e15c77a05 move string to donottranslate.xml and fix nits 2023-01-03 14:07:28 +05:30
Stypox 1bb166a9e8
Merge pull request #9553 from Redirion/exo182
Update ExoPlayer to 2.18.2
2023-01-02 18:21:53 +01:00
Stypox 8fa949537b
Merge pull request #8769 from Isira-Seneviratne/New_Utils_methods
Use new NPE UTF8 Utils methods
2023-01-02 17:59:26 +01:00
Stypox 7454b31788
Merge pull request #9562 from bravenewpipe/use-videostream-for-audio-only-background-playback
Support audio only background for services only supporting video streams
2023-01-02 17:51:10 +01:00
Stypox b6488fe342
Merge pull request #8841 from Isira-Seneviratne/Notification_mode_ListAdapter
Use ListAdapter in NotificationModeConfigAdapter.
2023-01-02 14:47:25 +01:00
Stypox b1d9080a0f
Simplify disposables handling in notification mode settings 2023-01-02 14:45:11 +01:00
pratyaksh1610 50269d0f5e updated caption file name and clean code 2023-01-02 16:23:45 +05:30
Robin f17155bb3f
Merge branch 'TeamNewPipe:dev' into exo182 2023-01-02 10:35:20 +01:00
Isira Seneviratne 7988fe0c5a Use new NewPipe Extractor Utils methods. 2023-01-02 07:03:18 +05:30
evermind f4a5b3bcbf set 'playback in background button' visible if there are videostreams 2023-01-01 21:55:03 +01:00
Stypox cd0e585586
Merge pull request #9568 from pratyaksh1610/branch-add-play-all-icon
Added icon for "Play All"
2023-01-01 18:03:42 +01:00
Stypox 464247784d
Merge pull request #9520 from Isira-Seneviratne/Update_RxJava
Update RxJava to 3.1.5.
2023-01-01 12:52:06 +01:00
Stypox 56800c24b9
Update rxandroid from 3.0.0 to 3.0.2 2023-01-01 12:46:56 +01:00
Stypox 6af2242d5d
Merge pull request #9521 from pratyaksh1610/branch-9518
Fixes #9518: Crash fix when click "Add to playlist" while the current list is still loading
2022-12-31 23:31:58 +01:00
Stypox d21fac658b
Remove playlist details toasts 2022-12-31 23:30:17 +01:00
Stypox 27f6c3b634
Merge pull request #9502 from Jared234/8585_download_in_queue
Added option to download items in the queue
2022-12-31 19:34:07 +01:00
Stypox b3bfec9505
Use correct fragment manager for download dialog
Tapping download on the long-press menu of queue items when the queue is shown inside the player would crash otherwise
2022-12-31 19:31:24 +01:00
Stypox 367ece8ffa
Merge pull request #9496 from Jared234/9437_continue_playing_while_seeking
Seeking no longer pauses the played video
2022-12-31 19:19:21 +01:00
Stypox 661cd4c182
Merge pull request #9159 from Isira-Seneviratne/Refactor_VideoPlayerUi
Refactor VideoPlayerUi.
2022-12-31 19:01:19 +01:00
Stypox be856f71c8
Merge pull request #8847 from Isira-Seneviratne/Refactor_VideoDetailFragment
Refactor VideoDetailFragment.
2022-12-31 18:41:06 +01:00
Stypox 97978033dd
Activate on click listeners only when not loading
For consistency with long click listeners, in VideoDetailFragment
2022-12-31 17:49:10 +01:00
Stypox 413a1b504a
Refactor constrolsTouchListener code 2022-12-31 17:47:57 +01:00
Stypox 8078620977
Merge pull request #9481 from TacoTheDank/bumpDesugaring
Update Desugaring to 1.1.8
2022-12-31 17:15:00 +01:00
Stypox 69e8e4d63e
Merge pull request #9306 from Stypox/target-api-33
Set compileSdk and targetSdk to 33 (Android 13)
2022-12-31 14:49:30 +01:00
Isira Seneviratne fb1360b72a Use ListAdapter in NotificationModeConfigAdapter. 2022-12-29 06:14:46 +05:30
Stypox 231e677b16
Merge pull request #8895 from Isira-Seneviratne/SparseArrayCompat
Use SparseArrayCompat.
2022-12-28 20:37:05 +01:00
Tobi fcac53cdc0
Merge pull request #9488 from Jared234/9353_night_theme_selection
Disabling night theme selection if auto theme is not used
2022-12-25 22:19:02 +01:00
Tobi b07f1a77aa
Merge pull request #9596 from TeamNewPipe/update-localization
Update translations
2022-12-25 21:40:47 +01:00
TobiGr c13b858f02 Add Nynorsk (nn) to the language chooser 2022-12-25 21:22:35 +01:00
Hosted Weblate 5d9bf8055e
Translated using Weblate (French)
Currently translated at 93.0% (67 of 72 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (French)

Currently translated at 91.6% (66 of 72 strings)

Translated using Weblate (Urdu)

Currently translated at 66.9% (434 of 648 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Telugu)

Currently translated at 6.9% (5 of 72 strings)

Translated using Weblate (Telugu)

Currently translated at 66.9% (434 of 648 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Tamil)

Currently translated at 54.0% (350 of 648 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (648 of 648 strings)

Co-authored-by: Ahmad Raza <ahmadrazaxm@gmail.com>
Co-authored-by: Dan <denqwerta@gmail.com>
Co-authored-by: Florian <flo.site@zaclys.net>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: K.B.Dharun Krishna <kbdharunkrishna@gmail.com>
Co-authored-by: Kiss Attila <gaxeco4855@pro5g.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Ray <ray.cfu@protonmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Terry Louwers <t.louwers@gmail.com>
Co-authored-by: Translator <kvb@tuta.io>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: subba raidu <raidu4u@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/te/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translation: NewPipe/Metadata
2022-12-25 21:22:19 +01:00
evermind dfc46c3b6c Support audio only background for services only supporting video streams
Some services may only have video streams and no separate audio streams available.
This commit will add audio background playback support for those services.
It uses the video source as audio source for background playback.
2022-12-17 21:17:42 +01:00
pratyaksh1610 d255d3e376 added icon for play all 2022-12-17 17:41:57 +05:30
Robin eea4f0f41c Update ExoPlayer to 2.18.2 2022-12-16 17:53:56 +01:00
Jared Fantaye 12796920a3 Removed the wasPlaying variable 2022-12-10 21:56:04 +01:00
Jared Fantaye dfd6534a1c Added "6.json" 2022-12-10 17:32:02 +01:00
Jared Fantaye fedc26e3cb Added migration to new database 2022-12-09 22:40:54 +01:00
Jared Fantaye 1ac62541a8 Formatting, renaming and small fixes 2022-12-09 12:01:59 +01:00
Tobi 5942add141
Merge pull request #9522 from TeamNewPipe/update-translations
Update translations
2022-12-09 11:35:18 +01:00
TobiGr 9eb72d5a86 Delete translation without default: progressive_load_interval_default 2022-12-09 10:42:45 +01:00
TobiGr 26579cc170 Delete translation without default: app_update_notification_content_title 2022-12-09 10:40:48 +01:00
TobiGr d70b768031 Delete translation without default: app_update_notification_content_text 2022-12-09 10:39:49 +01:00
TobiGr 0c47fc7017 Delete translation without default: app_update_notification_content_text 2022-12-09 10:39:22 +01:00
pratyaksh1610 c537776826 Fixes #9518
- Crash fix on clicking on add to playlist.
- Added toast when clicked on share button for better UI.
2022-12-09 14:09:40 +05:30
Isira Seneviratne 7c5b4510af Update RxJava to 3.1.5. 2022-12-09 07:56:14 +05:30
Jared Fantaye bf1ebf8733 Fixed some bugs and improved code quality 2022-12-08 23:31:20 +01:00
Hosted Weblate 8edfafcf09
Translated using Weblate (Spanish)
Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Esperanto)

Currently translated at 74.3% (482 of 648 strings)

Translated using Weblate (German)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (French)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (German)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (German)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (German)

Currently translated at 99.8% (647 of 648 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Greek)

Currently translated at 99.6% (646 of 648 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: BMN <weblate@yopmail.com>
Co-authored-by: C. Rüdinger <Mail-an-CR@web.de>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Eric <hamburger1024@duck.com>
Co-authored-by: Fjuro <ifjuro@proton.me>
Co-authored-by: Florian <flo.site@zaclys.net>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@protonmail.com>
Co-authored-by: Nidi <nizamismidov4@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Ricardo <contatorms7@tutamail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Skarvinius <saab_samuel@hotmail.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: argonfilm <gradicchuck@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translation: NewPipe/Metadata
2022-12-08 22:43:11 +01:00
Jared Fantaye 10a5741f36 Tried to implement the feature 2022-12-07 02:32:53 +01:00
Isira Seneviratne c7d392e77e Merge branch 'dev' into Refactor_VideoDetailFragment 2022-12-06 20:21:28 +05:30
Isira Seneviratne 161007fe92 Merge branch 'dev' into Refactor_VideoPlayerUi 2022-12-06 20:21:08 +05:30
Jared Fantaye 5fc85fa2e0 Implemented suggestions 2022-12-05 21:21:46 +01:00
Tobi 4a27d371e0
Merge pull request #9504 from dngray/pr-remove_privacytools
[PeerTube] Remove dead Privacy Tools instance
2022-12-05 13:32:38 +01:00
Daniel Gray a4c9e0a35e
Remove dead Privacy Tools instance (#9504) 2022-12-05 14:32:21 +10:30
Stypox a6f57a8665
Merge pull request #9173 from Theta-Dev/video-sub-count
Show subscriber count on video details page
2022-12-04 20:50:13 +01:00
Stypox 0df696739f
Make subscribers in video detail fragment dimmer 2022-12-04 20:45:10 +01:00
ThetaDev 86ee94eb04
show subscriber count on player page 2022-12-04 20:45:09 +01:00
Jared Fantaye 0923594e51 Added option to download items in the queue 2022-12-04 20:35:06 +01:00
Stypox 3bb51875bc
Merge pull request #9501 from Stypox/import-subscriptions-hint
Add hint to improve discoverability of subscription import
2022-12-04 20:19:19 +01:00
Stypox 40225443ed
Center text in empty views 2022-12-04 19:25:38 +01:00
Stypox 10977eaefa
Show hint about how to import subscriptions when there are none 2022-12-04 19:16:47 +01:00
Stypox 3103fd7302
Rename list_empty_subtitle string 2022-12-04 18:59:14 +01:00
Stypox 281ac13eed
Merge pull request #8883 from Douile/dev-enqueue-next-hide
Only show "Enqueue next" when in the middle of the queue
2022-12-04 18:43:45 +01:00
Douile e5f30a07bf
Only show "Enqueue next" when in the middle of the queue
Add a check that the queue position is not the last in the queue before
showing "Enqueue next".

Previously the "Enqueue next" action would always be shown if the queue
length was greater than one, this meant even if you were at the end of
the queue (when "Enqueue" would have the same effect as "Enqueue next")
the action would still be shown.
2022-12-04 18:20:50 +01:00
Stypox 9c4d5526f4
Merge pull request #8810 from Isira-Seneviratne/Math_floorDiv
Use Math.floorDiv().
2022-12-04 18:08:12 +01:00
Stypox 77737a5687
Merge pull request #9500 from pratyaksh1610/branch_9348
Rename `help` to `fast mode`
2022-12-04 17:45:23 +01:00
pratyaksh1610 869d46f15c rename help to fast mode 2022-12-04 19:11:38 +05:30
pratyaksh1610 1afb9cdba9 added Language suffix for subtitle downloads 2022-12-04 17:59:22 +05:30
Stypox 730664eefb
Merge pull request #8668 from Isira-Seneviratne/Show_no_update_notification
Show toast when no updates are available.
2022-12-04 12:03:21 +01:00
Isira Seneviratne 6b210e1542
Apply ktlint formatting. 2022-12-04 12:01:57 +01:00
Isira Seneviratne f1b15a95a4
Show toast when no updates are available.
Co-authored-by: Stypox <stypox@pm.me>
2022-12-04 12:01:56 +01:00
Hosted Weblate 1d53389ca9
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (French)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 9.1% (59 of 646 strings)

Translated using Weblate (Punjabi (Pakistan))

Currently translated at 19.6% (127 of 646 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 99.0% (640 of 646 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 5.5% (4 of 72 strings)

Translated using Weblate (Hindi)

Currently translated at 18.0% (13 of 72 strings)

Translated using Weblate (Hungarian)

Currently translated at 11.1% (8 of 72 strings)

Translated using Weblate (Portuguese)

Currently translated at 69.4% (50 of 72 strings)

Translated using Weblate (Filipino)

Currently translated at 5.5% (4 of 72 strings)

Translated using Weblate (Filipino)

Currently translated at 35.9% (232 of 646 strings)

Translated using Weblate (Catalan)

Currently translated at 95.9% (620 of 646 strings)

Translated using Weblate (Hindi)

Currently translated at 78.7% (509 of 646 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 69.4% (50 of 72 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Persian)

Currently translated at 61.1% (44 of 72 strings)

Translated using Weblate (Russian)

Currently translated at 41.6% (30 of 72 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 6.1% (40 of 646 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Odia)

Currently translated at 2.7% (2 of 72 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (French)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Odia)

Currently translated at 30.1% (195 of 646 strings)

Translated using Weblate (Russian)

Currently translated at 41.6% (30 of 72 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 92.2% (596 of 646 strings)

Translated using Weblate (Hindi)

Currently translated at 69.6% (450 of 646 strings)

Added translation using Weblate (Norwegian Nynorsk)

Translated using Weblate (Georgian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.0% (640 of 646 strings)

Translated using Weblate (Punjabi)

Currently translated at 6.9% (5 of 72 strings)

Translated using Weblate (German)

Currently translated at 62.5% (45 of 72 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Georgian)

Currently translated at 20.1% (130 of 646 strings)

Translated using Weblate (Georgian)

Currently translated at 98.6% (71 of 72 strings)

Added translation using Weblate (Georgian)

Co-authored-by: AudricV <avdivers84@gmail.com>
Co-authored-by: Boros Zsombor <zsombor2626@gmail.com>
Co-authored-by: Cyndaquissshhh <iversonbriones123@gmail.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: JY3 <GeeyunJY3@gmail.com>
Co-authored-by: L-M-H <lars.magnus@herland.priv.no>
Co-authored-by: M. Ll <mklr95@gmail.com>
Co-authored-by: M4SK <themightyloki@free.fr>
Co-authored-by: Nahla Hamdi <nahlahamdi87@gmail.com>
Co-authored-by: Net <nizamismidov4@gmail.com>
Co-authored-by: Nikoloz <nukushatugushi@gmail.com>
Co-authored-by: Pedro Henrique Vilela do Nascimento <pedro.hvn@usp.br>
Co-authored-by: Platon Terekhov <gibbonsville_cowal@simplelogin.com>
Co-authored-by: Ray <ray.cfu@protonmail.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: Trendyne <eiko@chiru.no>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: bgo-eiu <huyaqoob+toolforge@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: qqqq1 <qqqq1@hi2.in>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: tryvseu <tryvseu@tuta.io>
Co-authored-by: Артём Нефедов <artem10397g@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar_LY/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fil/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ka/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/or/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translation: NewPipe/Metadata
2022-12-04 00:15:40 +01:00
Jared Fantaye 8fc5fa979d Added menu with tappable list items 2022-12-03 09:52:04 +01:00
TacoTheDank 074a8ff46a Update Desugaring to 1.1.8 2022-12-02 13:52:30 -05:00
Jared Fantaye a2f2d562f6 Disabling night theme selection if auto theme is not used 2022-12-01 13:01:58 +01:00
Stypox bd6b3c53c5
Merge pull request #9480 from TacoTheDank/bumpSonar
Update Sonarqube to 3.5
2022-11-30 00:14:33 +01:00
TacoTheDank 8282b8a6c0 Update Sonarqube to 3.5 2022-11-29 11:20:31 -05:00
Stypox 72a250b610
Merge pull request #9479 from Stypox/create-scaled-bitmap
Use smoother bitmap downscaling for thumbnails
2022-11-29 16:09:31 +01:00
Isira Seneviratne b0516fbf1d
Use BitmapCompat.createScaledBitmap(). 2022-11-29 15:56:19 +01:00
Stypox 05903502c5
Merge pull request #8743 from shivambeohar/8615-gap-at-miniplayer-close-button-fix
Remove padding from the end of the mini-player
2022-11-29 12:31:55 +01:00
Stypox 2bf58abb89
Make miniplayer close button area larger 2022-11-29 12:07:30 +01:00
Stypox 9d01d88eed
Request permission to send notifications 2022-11-28 18:49:11 +01:00
Stypox f07886fc5e
Add notifications permission 2022-11-28 18:23:47 +01:00
Tobi 2984649106
Merge pull request #9471 from yashpalgoyal1304/set-width-image-minimizer
Set image-minimizer to specify image width ...
2022-11-28 14:32:09 +01:00
Stypox 60671c99ed
Merge pull request #9474 from bravenewpipe/avoid-wrong-nullable-notnull-annotation-imports
Forbid wrong `@Nullable` and `@NotNull` annotation imports
2022-11-28 14:30:52 +01:00
Stypox bce77aaec7
Block rxjava3 nullable/nonnull imports in checkstyle 2022-11-28 14:28:08 +01:00
evermind f2e3020f9d checkstyle: declare org.jetbrains and javax.annotation Nullable's and NotNull/Nonnull as illegal imports 2022-11-28 13:33:50 +01:00
yashpalgoyal1304 e9ef9451e5
Minimize not-so-long images too 2022-11-27 03:22:37 +05:30
yashpalgoyal1304 7c1d06e023
Resolve scoping issue to get values of probeResult 2022-11-27 02:06:01 +05:30
AudricV 6b89b44dcd
Merge pull request #8961 from dhruvpatidar359/Toast-Popup
Remove the redundant/overlapping toast "Copied to clipboard" for Android 13+
2022-11-26 21:33:12 +01:00
yashpalgoyal1304 225f69b75b
Fix value of width 2022-11-27 01:56:53 +05:30
yashpalgoyal1304 44bc6bf069
Set image-minimizer to specify image width ...
Fix https://github.com/TeamNewPipe/NewPipe/issues/9469
for portrait like orientations
2022-11-26 23:29:11 +05:30
Jared Fantaye e5af1c93ae Seeking no longer pauses the played video 2022-11-26 15:35:13 +01:00
Isira Seneviratne d6617007d4 Use SparseArrayCompat instead of SparseArray in StreamItemAdapter.
Make additional small improvements as well.
2022-11-22 18:31:58 +05:30
Isira Seneviratne 8db90ba449 Use SparseArrayCompat for thumbnails. 2022-11-22 17:51:54 +05:30
seojun0924 16b0df69b1
Update README.ko.md
Updated Korean README with latest version of README
2022-11-19 01:03:18 +09:00
Stypox 048b0972de Set compileSdk and targetSdk to 33 (Android 13)
android:exported in now required in the manifest on all activities/services/receivers/providers. It was set to true for those that need to interact with outside apps or the OS, while others have exported=false.
This also required updating LeakCanary to the latest version as the older version being used was not using android:exported in AndroidManifest.xml.
2022-11-18 08:33:13 +01:00
Isira Seneviratne a7989795e8 Merge branch 'dev' into Refactor_VideoPlayerUi 2022-11-14 08:59:03 +05:30
Isira Seneviratne a40f035810 Merge branch 'dev' into Refactor_VideoDetailFragment 2022-11-14 08:58:45 +05:30
Isira Seneviratne aad5e26f31
Merge pull request #8870 from Isira-Seneviratne/Locale_forLanguageTag
Use Locale.forLanguageTag().
2022-11-10 19:56:34 +05:30
Tobi 627c6e29a2
Merge pull request #8316 from han-sz/fix_video_mouse_hover_overlay
Fix persistent hover overlay when in desktop/DeX mode or using a mouse/non-touch input
2022-11-09 17:10:01 +01:00
TobiGr 95c32d6f4a Merge remote-tracking branch 'Weblate/dev' into dev 2022-11-09 16:48:12 +01:00
Coool (github.com/Coool) 747df59741
Translated using Weblate (Latvian)
Currently translated at 92.5% (598 of 646 strings)
2022-11-09 16:46:12 +01:00
Coool (github.com/Coool) a4e883c119
Translated using Weblate (Latvian)
Currently translated at 4.1% (3 of 72 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lv/
2022-11-09 16:46:12 +01:00
pjammo 289f9105d9
Translated using Weblate (Italian)
Currently translated at 100.0% (72 of 72 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
2022-11-09 16:46:12 +01:00
Fjuro 5804483c89
Translated using Weblate (Czech)
Currently translated at 100.0% (72 of 72 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
2022-11-09 16:46:09 +01:00
ShareASmile 16732905bf
Translated using Weblate (Punjabi)
Currently translated at 100.0% (646 of 646 strings)
2022-11-09 16:46:07 +01:00
TobiGr ef1e7e5b52 Merge branch 'master' into dev 2022-11-09 16:45:22 +01:00
AudricV abf1cc536d
Improve code of DeviceUtils.isDesktopMode
- Avoid NullPointerException crashes if there is no UiModeManager or desktop
system service mode
- Use final for every exception
- Suppress missing fields warnings
- Add missing NonNull annotation
2022-11-09 16:22:49 +01:00
cybersphinx c38f150562
Remove now obsolete API check. 2022-11-09 15:50:09 +01:00
cybersphinx d2b6bda7a2
Remove errant return. 2022-11-09 15:50:09 +01:00
cybersphinx 9e5c68c575
Add check for input devices with cursor. 2022-11-09 15:50:06 +01:00
Hanif Shersy 88eed6cc23
Add JSDoc comment and a performance note for `isDesktopMode` 2022-11-09 15:48:50 +01:00
Hanif Shersy a1773d166f
Fix JSDoc checkstyle warning 2022-11-09 15:44:36 +01:00
Hanif Shersy 5e2ef7ff0d
Address review comments 2022-11-09 15:44:36 +01:00
Hanif Shersy cfda073aa5
Fix DeX mode check 2022-11-09 15:44:36 +01:00
Hanif Shersy ff774a1870
Fix persistent hover overlay when mouse connected 2022-11-09 15:44:27 +01:00
Isira Seneviratne feb03f7e30 Use Math.floorDiv(). 2022-11-09 20:01:40 +05:30
Isira Seneviratne 95a65d5704
Merge pull request #9333 from Isira-Seneviratne/PendingIntent_mutability
Make PendingIntents immutable on Android 6.0 and later.
2022-11-09 08:58:16 +05:30
Isira Seneviratne 5c1af6d296 Group private Localization methods together. 2022-11-09 08:54:47 +05:30
Isira Seneviratne 6d812b86aa Use Locale.forLanguageTag(). 2022-11-09 08:51:12 +05:30
AudricV 7b7ab3f419
Remove Utility.copyToClipboard and use ShareUtils.copyToClipboard instead
This method is not needed anymore, as ShareUtils.copyToClipboard does
almost the same thing (no label is set on the ClipData used to copy text,
contrary to what Utility did, but using "text" as a ClipData label doesn't seem
useful).

It was used in MissionAdapter.handlePopupItem to copy the SHA1 or the MD5 of a
file.
2022-11-08 20:47:51 +01:00
dhruvpatidar359 ef35b36eba
Remove the redundant/overlapping toast "Copied to clipboard" for Android 13+
Signed-off-by: dhruv <dhruvpatidar35@gmail.com>

Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
Co-authored-by: AudricV <74829229+AudricV@users.noreply.github.com>
2022-11-08 20:47:50 +01:00
Hosted Weblate bb83d2b489
Translated using Weblate (Odia)
Currently translated at 6.1% (40 of 646 strings)

Translated using Weblate (Santali)

Currently translated at 10.3% (67 of 646 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 63.8% (46 of 72 strings)

Translated using Weblate (Arabic)

Currently translated at 50.0% (36 of 72 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Portuguese)

Currently translated at 69.4% (50 of 72 strings)

Translated using Weblate (Bulgarian)

Currently translated at 4.1% (3 of 72 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 16.6% (12 of 72 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 92.2% (596 of 646 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.6% (644 of 646 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 99.8% (645 of 646 strings)

Translated using Weblate (Korean)

Currently translated at 99.8% (645 of 646 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Punjabi (Pakistan))

Currently translated at 18.4% (119 of 646 strings)

Translated using Weblate (Slovak)

Currently translated at 9.7% (7 of 72 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 97.2% (70 of 72 strings)

Translated using Weblate (Polish)

Currently translated at 59.7% (43 of 72 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Turkish)

Currently translated at 99.8% (645 of 646 strings)

Translated using Weblate (German)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Hungarian)

Currently translated at 9.8% (7 of 71 strings)

Translated using Weblate (Punjabi)

Currently translated at 5.6% (4 of 71 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.6% (644 of 646 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Asturian)

Currently translated at 2.8% (2 of 71 strings)

Translated using Weblate (Punjabi)

Currently translated at 99.6% (644 of 646 strings)

Translated using Weblate (Vietnamese)

Currently translated at 98.9% (639 of 646 strings)

Translated using Weblate (Chinese (Traditional, Hong Kong))

Currently translated at 99.5% (643 of 646 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (646 of 646 strings)

Added translation using Weblate (English (Middle))

Added translation using Weblate (English (Old))

Added translation using Weblate (Sicilian)

Added translation using Weblate (Arabic (Najdi))

Added translation using Weblate (Kashmiri)

Added translation using Weblate (German (Low))

Added translation using Weblate (Aymará)

Added translation using Weblate (Kazakh)

Translated using Weblate (Bulgarian)

Currently translated at 2.8% (2 of 71 strings)

Translated using Weblate (Bengali)

Currently translated at 90.5% (585 of 646 strings)

Translated using Weblate (Punjabi)

Currently translated at 99.6% (644 of 646 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (646 of 646 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (646 of 646 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: Enol P <enolp@softastur.org>
Co-authored-by: Ergün Can Taş <erguntas1968@gmail.com>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Gediminas Murauskas <muziejusinfo@gmail.com>
Co-authored-by: Giovanni Donisi <giovannidonisi0701@gmail.com>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: L-M-H <lars.magnus@herland.priv.no>
Co-authored-by: Lyudmil Borisov <lyuskoborisov@abv.bg>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Nizami <nizamismidov4@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: Preston Waters <masatox3@yahoo.com>
Co-authored-by: Ray <ray.cfu@protonmail.com>
Co-authored-by: Rohan Deb Sarkar <rohandebsarkar+git@gmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Sebi <stoican_sebi@yahoo.com>
Co-authored-by: ShareASmile <aapshergill@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Thọ Bùi Nguyễn Hoàng <buitho061997@gmail.com>
Co-authored-by: Vri 🌈 <weblate@vrifox.cc>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: bgo-eiu <huyaqoob+toolforge@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: pjammo <adrianoghr@hotmail.it>
Co-authored-by: thami simo <simo.azad@gmail.com>
Co-authored-by: zaioti <zaioti@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ast/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bg/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2022-11-08 09:24:09 +01:00
Isira Seneviratne 3dc1adb69e Add helper methods for adding PendingIntent mutability. 2022-11-07 17:12:22 +05:30
Isira Seneviratne e4f97465a4 Use lambdas for VideoDetailFragment listeners. 2022-11-03 20:15:17 +05:30
Isira Seneviratne e333197ed5 Use OnClickListener and OnLongClickListener lambdas in the player UIs. 2022-11-03 05:09:35 +05:30
GGAutomaton 8ad7bf60d7 Delete saveImmediate warnings & add comments 2022-06-23 23:31:56 +08:00
GGAutomaton 898a936064 Update index modification logic & redo sorting in the merge algorithm 2022-06-23 23:19:59 +08:00
GGAutomaton 4e401bc059 Update playlists in parallel 2022-06-23 20:36:21 +08:00
GGAutomaton 9ecef6f011 Add abstract methods in PlaylistLocalItem & rename setIsModified 2022-06-23 19:20:16 +08:00
GGAutomaton ba394a7ab4 Update test and Javadoc 2022-05-11 18:08:14 +08:00
GGAutomaton d32490a4be Create sub-package and default interval for DebounceSaver & sort playlists in db 2022-05-11 16:47:34 +08:00
GGAutomaton 6526ff1612 Add tests 2022-04-17 20:20:20 +08:00
GGAutomaton bb5390d63a Reuse DebounceSaver 2022-04-17 14:53:02 +08:00
GGAutomaton bd1aae8d66 Fix sonar warning 2022-04-16 12:44:24 +08:00
GGAutomaton c24aed054f Fix sonar warning and typo 2022-04-16 12:00:02 +08:00
GGAutomaton 0aa08a5e40 Use new item holder 2022-04-15 23:19:24 +08:00
GGAutomaton 3c48825699 Debounced saver & bugfix & clean code 2022-04-15 20:44:54 +08:00
GGAutomaton bfb56b4144 UI design and behavior 2022-04-14 16:59:52 +08:00
GGAutomaton ba8370bcfd Save changes to the database and bugfix 2022-04-14 12:13:42 +08:00
GGAutomaton 813f55152a
Merge branch 'TeamNewPipe:dev' into feature-7870 2022-04-13 22:48:26 +08:00
GGAutomaton 270a541a7c Implement algorithm to merge playlists 2022-04-13 22:46:24 +08:00
GGAutomaton c34549a47d Update database migrations and getter/setter 2022-04-13 21:35:38 +08:00
GGAutomaton 96d6b309ec Migrate database 2022-04-13 19:41:07 +08:00
1289 changed files with 41146 additions and 11309 deletions

View File

@ -1,3 +1,5 @@
### Please do **not** open pull requests for *new features* now, as we are planning to rewrite large chunks of the code. Only bugfix PRs will be accepted. More details will be announced soon!
NewPipe contribution guidelines
===============================

View File

@ -1,11 +1,8 @@
name: Question
description: Ask about anything NewPipe-related
labels: [question, needs triage]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this issue! :hugs:
Thanks for taking the time to fill out this form! :hugs:
Note that you can also ask questions on our [IRC channel](https://web.libera.chat/#newpipe).
@ -14,7 +11,7 @@ body:
attributes:
label: "Checklist"
options:
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
- label: "I made sure that there are *no existing issues or discussions* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true
- label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my question isn't listed."
required: true

View File

@ -14,7 +14,7 @@ body:
attributes:
label: "Checklist"
options:
- label: "I am able to reproduce the bug with the [latest version](https://github.com/TeamNewPipe/NewPipe/releases/latest)."
- label: "I am able to reproduce the bug with the latest version given here: [CLICK THIS LINK](https://github.com/TeamNewPipe/NewPipe/releases/latest)."
required: true
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
required: true

View File

@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: ❓ Question
url: https://github.com/TeamNewPipe/NewPipe/discussions/new?category=questions
about: Ask about anything NewPipe-related
- name: 💬 IRC
url: https://web.libera.chat/#newpipe
about: Chat with us via IRC for quick Q/A

View File

@ -28,7 +28,7 @@
#### APK testing
<!-- Use a new, meaningfully named branch. The name is used as a suffix for the app ID to allow installing and testing multiple versions of NewPipe, e.g. "commentfix", if your PR implements a bugfix for comments. (No names like "patch-0" and "feature-1".) -->
<!-- Remove the following line if you directly link the APK created by the CI pipeline. Directly linking is preferred if you need to let users test.-->
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR.
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. You can find more info and a video demonstration [on this wiki page](https://github.com/TeamNewPipe/NewPipe/wiki/Download-APK-for-PR).
#### Due diligence
- [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).

17
.github/changed-lines-count-labeler.yml vendored Normal file
View File

@ -0,0 +1,17 @@
# Add 'size/small' label to any changes with less than 50 lines
size/small:
max: 49
# Add 'size/medium' label to any changes between 50 and 249 lines
size/medium:
min: 50
max: 249
# Add 'size/large' label to any changes between 250 and 749 lines
size/large:
min: 250
max: 749
# Add 'size/giant' label to any changes for more than 749 lines
size/giant:
min: 750

View File

@ -36,18 +36,20 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
- name: create and checkout branch
# push events already checked out the branch
if: github.event_name == 'pull_request'
run: git checkout -B ${{ github.head_ref }}
env:
BRANCH: ${{ github.head_ref }}
run: git checkout -B "$BRANCH"
- name: set up JDK 11
uses: actions/setup-java@v3
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 17
distribution: "temurin"
cache: 'gradle'
@ -55,30 +57,40 @@ jobs:
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint
- name: Upload APK
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: app
path: app/build/outputs/apk/debug/*.apk
test-android:
# macos has hardware acceleration. See android-emulator-runner action
runs-on: macos-latest
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
# api-level 19 is min sdk, but throws errors related to desugaring
api-level: [ 21, 29 ]
include:
- api-level: 21
target: default
arch: x86
- api-level: 33
target: google_apis # emulator API 33 only exists with Google APIs
arch: x86_64
permissions:
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: set up JDK 11
uses: actions/setup-java@v3
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 11
java-version: 17
distribution: "temurin"
cache: 'gradle'
@ -86,12 +98,12 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
# workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-build: 7425822
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
script: ./gradlew connectedCheck --stacktrace
- name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: failure()
with:
name: android-test-report-api${{ matrix.api-level }}
@ -104,19 +116,19 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 11
uses: actions/setup-java@v3
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 11 # Sonar requires JDK 11
java-version: 17
distribution: "temurin"
cache: 'gradle'
- name: Cache SonarCloud packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
@ -126,4 +138,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew build sonarqube --info
run: ./gradlew build sonar --info

View File

@ -17,6 +17,8 @@ module.exports = async ({github, context}) => {
initialBody = context.payload.comment.body;
} else if (context.eventName == 'issues') {
initialBody = context.payload.issue.body;
} else if (context.eventName == 'pull_request') {
initialBody = context.payload.pull_request.body;
} else {
console.log('Aborting: No body found');
return;
@ -30,10 +32,12 @@ module.exports = async ({github, context}) => {
}
// Regex for finding images (simple variant) ![ALT_TEXT](https://*.githubusercontent.com/<number>/<variousHexStringsAnd->.<fileExtension>)
const REGEX_IMAGE_LOOKUP = /\!\[(.*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
const REGEX_USER_CONTENT_IMAGE_LOOKUP = /\!\[(.*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
const REGEX_ASSETS_IMAGE_LOCKUP = /\!\[(.*)\]\((https:\/\/github\.com\/[-\w\d]+\/[-\w\d]+\/assets\/\d+\/[\-0-9a-f]{32,512})\)/gm;
// Check if we found something
let foundSimpleImages = REGEX_IMAGE_LOOKUP.test(initialBody);
let foundSimpleImages = REGEX_USER_CONTENT_IMAGE_LOOKUP.test(initialBody)
|| REGEX_ASSETS_IMAGE_LOCKUP.test(initialBody);
if (!foundSimpleImages) {
console.log('Found no simple images to process');
return;
@ -47,51 +51,8 @@ module.exports = async ({github, context}) => {
var wasMatchModified = false;
// Try to find and replace the images with minimized ones
let newBody = await replaceAsync(initialBody, REGEX_IMAGE_LOOKUP, async (match, g1, g2) => {
console.log(`Found match '${match}'`);
if (g1.endsWith(IGNORE_ALT_NAME_END)) {
console.log(`Ignoring match '${match}': IGNORE_ALT_NAME_END`);
return match;
}
let shouldModify = false;
try {
console.log(`Probing ${g2}`);
let probeResult = await probe(g2);
if (probeResult == null) {
throw 'No probeResult';
}
if (probeResult.hUnits != 'px') {
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
}
if (probeResult.height <= 0) {
throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
}
if (probeResult.wUnits != 'px') {
throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
}
if (probeResult.width <= 0) {
throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
}
console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && (probeResult.width / probeResult.height) < MIN_ASPECT_RATIO;
} catch(e) {
console.log('Probing failed:', e);
// Immediately abort
return match;
}
if (shouldModify) {
wasMatchModified = true;
console.log(`Modifying match '${match}'`);
return `<img alt="${g1}" src="${g2}" height=${IMG_MAX_HEIGHT_PX} />`;
}
console.log(`Match '${match}' is ok/will not be modified`);
return match;
});
let newBody = await replaceAsync(initialBody, REGEX_USER_CONTENT_IMAGE_LOOKUP, minimizeAsync);
newBody = await replaceAsync(newBody, REGEX_ASSETS_IMAGE_LOCKUP, minimizeAsync);
if (!wasMatchModified) {
console.log('Nothing was modified. Skipping update');
@ -115,9 +76,17 @@ module.exports = async ({github, context}) => {
repo: context.repo.repo,
body: newBody
});
} else if (context.eventName == 'pull_request') {
console.log('Updating pull request', context.payload.pull_request.number);
await github.rest.pulls.update({
pull_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: newBody
});
}
// Asnyc replace function from https://stackoverflow.com/a/48032528
// Async replace function from https://stackoverflow.com/a/48032528
async function replaceAsync(str, regex, asyncFn) {
const promises = [];
str.replace(regex, (match, ...args) => {
@ -127,4 +96,52 @@ module.exports = async ({github, context}) => {
const data = await Promise.all(promises);
return str.replace(regex, () => data.shift());
}
async function minimizeAsync(match, g1, g2) {
console.log(`Found match '${match}'`);
if (g1.endsWith(IGNORE_ALT_NAME_END)) {
console.log(`Ignoring match '${match}': IGNORE_ALT_NAME_END`);
return match;
}
let probeAspectRatio = 0;
let shouldModify = false;
try {
console.log(`Probing ${g2}`);
let probeResult = await probe(g2);
if (probeResult == null) {
throw 'No probeResult';
}
if (probeResult.hUnits != 'px') {
throw `Unexpected probeResult.hUnits (expected px but got ${probeResult.hUnits})`;
}
if (probeResult.height <= 0) {
throw `Unexpected probeResult.height (height is invalid: ${probeResult.height})`;
}
if (probeResult.wUnits != 'px') {
throw `Unexpected probeResult.wUnits (expected px but got ${probeResult.wUnits})`;
}
if (probeResult.width <= 0) {
throw `Unexpected probeResult.width (width is invalid: ${probeResult.width})`;
}
console.log(`Probing resulted in ${probeResult.width}x${probeResult.height}px`);
probeAspectRatio = probeResult.width / probeResult.height;
shouldModify = probeResult.height > IMG_MAX_HEIGHT_PX && probeAspectRatio < MIN_ASPECT_RATIO;
} catch(e) {
console.log('Probing failed:', e);
// Immediately abort
return match;
}
if (shouldModify) {
wasMatchModified = true;
console.log(`Modifying match '${match}'`);
return `<img alt="${g1}" src="${g2}" width=${Math.min(600, Math.floor(IMG_MAX_HEIGHT_PX * probeAspectRatio))} />`;
}
console.log(`Match '${match}' is ok/will not be modified`);
return match;
}
}

View File

@ -5,6 +5,8 @@ on:
types: [created, edited]
issues:
types: [opened, edited]
pull_request:
types: [opened, edited]
permissions:
issues: write
@ -15,9 +17,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 16
@ -25,7 +27,7 @@ jobs:
run: npm i probe-image-size@7.2.3 --ignore-scripts
- name: Minimize simple images
uses: actions/github-script@v6
uses: actions/github-script@v7
timeout-minutes: 3
with:
script: |

18
.github/workflows/pr-labeler.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: "PR size labeler"
on: [pull_request_target]
permissions:
contents: read
pull-requests: write
jobs:
changed-lines-count-labeler:
runs-on: ubuntu-latest
name: Automatically labelling pull requests based on the changed lines count
permissions:
pull-requests: write
steps:
- name: Set a label
uses: TeamNewPipe/changed-lines-count-labeler@main
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/changed-lines-count-labeler.yml

View File

@ -1,3 +1,6 @@
<h3 align="center">We are planning to <i>rewrite</i> large chunks of the codebase, to bring about <a href="https://github.com/TeamNewPipe/NewPipe/discussions/10118">a new, modern and stable NewPipe</a>!</h3>
<h4 align="center">Please do <b>not</b> open pull requests for <i>new features</i> now, only bugfix PRs will be accepted.</h4>
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
<h2 align="center"><b>NewPipe</b></h2>
<h4 align="center">A libre lightweight streaming front-end for Android.</h4>
@ -10,33 +13,34 @@
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
<a href="https://matrix.to/#/#newpipe:matrix.newpipe-ev.de" alt="Matrix channel: #newpipe"><img src="https://img.shields.io/badge/Matrix%20chat-%23newpipe-blue"></a>
</p>
<hr>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#supported-services">Supported Services</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#installation-and-updates">Installation and updates</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<p align="center"><a href="https://newpipe.net">Website</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">Press</a></p>
<hr>
*Read this in other languages: [English](README.md), [Español](doc/README.es.md), [हिन्दी](doc/README.hi.md), [한국어](doc/README.ko.md), [Soomaali](doc/README.so.md), [Português Brasil](doc/README.pt_BR.md), [Polski](doc/README.pl.md), [日本語](doc/README.ja.md), [Română](doc/README.ro.md), [Türkçe](doc/README.tr.md), [正體中文](doc/README.zh_TW.md).*
*Read this document in other languages: [Deutsch](doc/README.de.md), [English](README.md), [Español](doc/README.es.md), [Français](doc/README.fr.md), [हिन्दी](doc/README.hi.md), [Italiano](doc/README.it.md), [한국어](doc/README.ko.md), [Português Brasil](doc/README.pt_BR.md), [Polski](doc/README.pl.md), [ਪੰਜਾਬੀ ](doc/README.pa.md), [日本語](doc/README.ja.md), [Română](doc/README.ro.md), [Soomaali](doc/README.so.md), [Türkçe](doc/README.tr.md), [正體中文](doc/README.zh_TW.md), [অসমীয়া](doc/README.asm.md), [Српски](doc/README.sr.md)*
<b>WARNING: THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.</b>
<b>PUTTING NEWPIPE, OR ANY FORK OF IT, INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS.</b>
> [!warning]
> <b>THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.</b>
>
> <b>PUTTING NEWPIPE, OR ANY FORK OF IT, INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS.</b>
## Screenshots
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/00.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/00.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/01.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/02.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/03.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/04.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/05.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/06.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/07.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/08.png)
<br/><br/>
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/09.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/09.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/10.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/10.png)
### Supported Services
@ -100,10 +104,10 @@ You can install NewPipe using one of the following methods:
We recommend method 1 for most users. APKs installed using method 1 or 2 are compatible with each other (meaning that if you installed NewPipe using either method 1 or 2, you can also update NewPipe using the other), but not with those installed using method 3. This is due to the same signing key (ours) being used for 1 and 2, but a different signing key (F-Droid's) being used for 3. Building a debug APK using method 4 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app. When using method 5, each APK is signed with a different random key supplied by GitHub Actions, so you cannot even update it. You will have to backup and restore the app data each time you wish to use a new APK.
In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality breaks and F-Droid doesn't have the latest update yet), we recommend following this procedure:
1. Back up your data via Settings > Content > Export Database so you keep your history, subscriptions, and playlists
1. Back up your data via Settings > Backup and Restore > Export Database so you keep your history, subscriptions, and playlists
2. Uninstall NewPipe
3. Download the APK from the new source and install it
4. Import the data from step 1 via Settings > Content > Import Database
4. Import the data from step 1 via Settings > Backup and Restore > Import Database
<b>Note: when you're importing a database into the official app, always make sure that it is the one you exported _from_ the official app. If you import a database exported from an APK other than the official app, it may break things. Such an action is unsupported, and you should only do so when you're absolutely certain you know what you're doing.</b>
@ -123,16 +127,6 @@ If you like NewPipe, you're welcome to send a donation. We prefer Liberapay, as
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visit NewPipe at liberapay.com" width="100px"></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px"></a></td>
</tr>
<tr>
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin"></td>
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR code" width="100px"></td>
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
</tr>
<tr>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px"></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visit NewPipe at bountysource.com" width="100px"></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Check out how many bounties you can earn."></a></td>
</tr>
</table>
## Privacy Policy

View File

@ -1,23 +1,27 @@
import com.android.tools.profgen.ArtProfileKt
import com.android.tools.profgen.ArtProfileSerializer
import com.android.tools.profgen.DexFile
plugins {
id "com.android.application"
id "kotlin-android"
id "kotlin-kapt"
id "kotlin-parcelize"
id "checkstyle"
id "org.sonarqube" version "3.3"
id "org.sonarqube" version "4.0.0.2929"
}
android {
compileSdk 32
compileSdk 34
namespace 'org.schabi.newpipe'
defaultConfig {
applicationId "org.schabi.newpipe"
resValue "string", "app_name", "NewPipe"
minSdk 21
targetSdk 29
versionCode 991
versionName "0.24.1"
targetSdk 33
versionCode 997
versionName "0.27.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -46,9 +50,6 @@ android {
}
}
// Keep the release build type at the end of the list to override 'archivesBaseName' of
// debug build. This seems to be a Gradle bug, therefore
// TODO: update Gradle version
release {
if (System.properties.containsKey('packageSuffix')) {
applicationIdSuffix System.getProperty('packageSuffix')
@ -76,13 +77,13 @@ android {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
encoding 'utf-8'
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
jvmTarget = JavaVersion.VERSION_17
}
sourceSets {
@ -92,25 +93,33 @@ android {
buildFeatures {
viewBinding true
}
packagingOptions {
resources {
// remove two files which belong to jsoup
// no idea how they ended up in the META-INF dir...
excludes += ['META-INF/README.md', 'META-INF/CHANGES',
// 'COPYRIGHT' belongs to RxJava...
'META-INF/COPYRIGHT']
}
}
}
ext {
checkstyleVersion = '10.3.1'
checkstyleVersion = '10.12.1'
androidxLifecycleVersion = '2.5.1'
androidxRoomVersion = '2.4.3'
androidxWorkVersion = '2.7.1'
androidxLifecycleVersion = '2.6.2'
androidxRoomVersion = '2.6.1'
androidxWorkVersion = '2.8.1'
icepickVersion = '3.2.0'
exoPlayerVersion = '2.18.1'
googleAutoServiceVersion = '1.0.1'
exoPlayerVersion = '2.18.7'
googleAutoServiceVersion = '1.1.1'
groupieVersion = '2.10.1'
markwonVersion = '4.6.2'
leakCanaryVersion = '2.5'
leakCanaryVersion = '2.12'
stethoVersion = '1.6.0'
mockitoVersion = '4.0.0'
assertJVersion = '3.23.1'
}
configurations {
@ -125,7 +134,7 @@ checkstyle {
toolVersion = checkstyleVersion
}
task runCheckstyle(type: Checkstyle) {
tasks.register('runCheckstyle', Checkstyle) {
source 'src'
include '**/*.java'
exclude '**/gen/**'
@ -146,20 +155,22 @@ task runCheckstyle(type: Checkstyle) {
def outputDir = "${project.buildDir}/reports/ktlint/"
def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
task runKtlint(type: JavaExec) {
tasks.register('runKtlint', JavaExec) {
inputs.files(inputFiles)
outputs.dir(outputDir)
getMainClass().set("com.pinterest.ktlint.Main")
classpath = configurations.ktlint
args "src/**/*.kt"
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
task formatKtlint(type: JavaExec) {
tasks.register('formatKtlint', JavaExec) {
inputs.files(inputFiles)
outputs.dir(outputDir)
getMainClass().set("com.pinterest.ktlint.Main")
classpath = configurations.ktlint
args "-F", "src/**/*.kt"
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
afterEvaluate {
@ -169,7 +180,7 @@ afterEvaluate {
preDebugBuild.dependsOn runCheckstyle, runKtlint
}
sonarqube {
sonar {
properties {
property "sonar.projectKey", "TeamNewPipe_NewPipe"
property "sonar.organization", "teamnewpipe"
@ -179,7 +190,7 @@ sonarqube {
dependencies {
/** Desugaring **/
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4'
/** NewPipe libraries **/
// You can use a local version by uncommenting a few lines in settings.gradle
@ -187,7 +198,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:eb07d70a2ce03bee3cc74fc33b2e4173e1c21436'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.0'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
/** Checkstyle **/
@ -195,31 +206,31 @@ dependencies {
ktlint 'com.pinterest:ktlint:0.45.2'
/** Kotlin **/
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
/** AndroidX **/
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.fragment:fragment-ktx:1.6.2'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}"
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
implementation 'androidx.media:media:1.6.0'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
implementation 'com.google.android.material:material:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
/** Third-party libraries **/
// Instance state boilerplate elimination
@ -227,10 +238,10 @@ dependencies {
kapt "frankiesardo:icepick-processor:${icepickVersion}"
// HTML parser
implementation "org.jsoup:jsoup:1.15.3"
implementation "org.jsoup:jsoup:1.17.2"
// HTTP client
implementation "com.squareup.okhttp3:okhttp:4.10.0"
implementation "com.squareup.okhttp3:okhttp:4.12.0"
// Media player
implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}"
@ -259,38 +270,37 @@ dependencies {
implementation "io.noties.markwon:linkify:${markwonVersion}"
// Crash reporting
implementation "ch.acra:acra-core:5.9.3"
implementation "ch.acra:acra-core:5.11.3"
// Properly restarting
implementation 'com.jakewharton:process-phoenix:2.1.2'
// Reactive extensions for Java VM
implementation "io.reactivex.rxjava3:rxjava:3.0.13"
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
implementation "io.reactivex.rxjava3:rxjava:3.1.8"
implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
// RxJava binding APIs for Android UI widgets
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
// Date and time formatting
implementation "org.ocpsoft.prettytime:prettytime:5.0.6.Final"
implementation "org.ocpsoft.prettytime:prettytime:5.0.7.Final"
/** Debugging **/
// Memory leak detection
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
debugImplementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android-core:${leakCanaryVersion}"
// Debug bridge for Android
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
/** Testing **/
testImplementation 'junit:junit:4.13.2'
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
testImplementation 'org.mockito:mockito-core:5.6.0'
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test.ext:junit:1.1.5"
androidTestImplementation "androidx.test:runner:1.5.2"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
androidTestImplementation "org.assertj:assertj-core:${assertJVersion}"
androidTestImplementation "org.assertj:assertj-core:3.24.2"
}
static String getGitWorkingBranch() {
@ -308,3 +318,25 @@ static String getGitWorkingBranch() {
return ""
}
}
// fix reproducible builds
project.afterEvaluate {
tasks.compileReleaseArtProfile.doLast {
outputs.files.each { file ->
if (file.toString().endsWith(".profm")) {
println("Sorting ${file} ...")
def version = ArtProfileSerializer.valueOf("METADATA_0_0_2")
def profile = ArtProfileKt.ArtProfile(file)
def keys = new ArrayList(profile.profileData.keySet())
def sortedData = new LinkedHashMap()
Collections.sort keys, new DexFile.Companion()
keys.each { key -> sortedData[key] = profile.profileData[key] }
new FileOutputStream(file).with {
write(version.magicBytes$profgen)
write(version.versionBytes$profgen)
version.write$profgen(it, sortedData, "")
}
}
}
}
}

View File

@ -1,32 +1,18 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/the-scrabi/bin/Android/Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# https://developer.android.com/build/shrink-code
## Helps debug release versions
-dontobfuscate
## Rules for NewPipeExtractor
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
-keep class org.mozilla.javascript.** { *; }
-keep class org.mozilla.classfile.ClassFileWriter
-keep class com.google.android.exoplayer2.** { *; }
-dontwarn org.mozilla.javascript.tools.**
# Rules for icepick. Copy paste from https://github.com/frankiesardo/icepick
## Rules for ExoPlayer
-keep class com.google.android.exoplayer2.** { *; }
## Rules for Icepick. Copy pasted from https://github.com/frankiesardo/icepick
-dontwarn icepick.**
-keep class icepick.** { *; }
-keep class **$$Icepick { *; }
@ -35,11 +21,11 @@
}
-keepnames class * { @icepick.State *;}
## Rules for OkHttp. Copy paste from https://github.com/square/okhttp
## Rules for OkHttp. Copy pasted from https://github.com/square/okhttp
-dontwarn okhttp3.**
-dontwarn okio.**
##
## See https://github.com/TeamNewPipe/NewPipe/pull/1441
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
!static !transient <fields>;
@ -47,5 +33,5 @@
private void readObject(java.io.ObjectInputStream);
}
# for some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
## For some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
-keep class org.schabi.newpipe.settings.notifications.** { *; }

View File

@ -0,0 +1,19 @@
{
"data": [
{
"name": "BBC",
"additional": "12K subscribers•233 videos",
"description": "The BBC is the worlds leading public service broadcaster. Were impartial and independent, and every day we create distinctive, world-class programmes and content which inform, educate and entertain millions of people in the UK and around the world. SUBSCRIBE to our YouTube channel to get the best of BBC entertainment and comedy programmes, stories from science and nature documentaries, and much more! https://bit.ly/2IXqEIn Get ALL your fresh TV, and sofa-hugging box sets on iPlayer https://bbc.in/2J18jYJ"
},
{
"name": "Linus Tech Tips",
"additional": "1M subscribers•233 videos",
"description": "Looking for a Tech YouTuber?\n\nLinus Tech Tips is a passionate team of \"professionally curious\" experts in consumer technology and video production which aims to inform and educate people of all ages through our entertaining videos. We create product reviews, step-by-step computer build guides, and a variety of other tech-focused content.\n\nSchedule:\nNew videos every Saturday to Thursday @ 10:00am Pacific\nLive WAN Show podcasts every Friday @ ~5:00pm Pacific"
},
{
"name": "Marques Brownlee",
"additional": "13 subscribers•12K videos",
"description": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics | Tech Head | Internet Personality!\n\nbusiness@MKBHD.com\n\nNYC"
}
]
}

View File

@ -0,0 +1,737 @@
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "4084aa342aef315dc7b558770a7755a9",
"entities": [
{
"tableName": "subscriptions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatarUrl",
"columnName": "avatar_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subscriberCount",
"columnName": "subscriber_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "notificationMode",
"columnName": "notification_mode",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_subscriptions_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "search_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "creationDate",
"columnName": "creation_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "search",
"columnName": "search",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_search_history_search",
"unique": false,
"columnNames": [
"search"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
}
],
"foreignKeys": []
},
{
"tableName": "streams",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "streamType",
"columnName": "stream_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploader_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "viewCount",
"columnName": "view_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "textualUploadDate",
"columnName": "textual_upload_date",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploadDate",
"columnName": "upload_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isUploadDateApproximation",
"columnName": "is_upload_date_approximation",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_streams_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "stream_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accessDate",
"columnName": "access_date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "repeatCount",
"columnName": "repeat_count",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id",
"access_date"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_stream_history_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "stream_state",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "progressMillis",
"columnName": "progress_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `thumbnail_url` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isThumbnailPermanent",
"columnName": "is_thumbnail_permanent",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
}
],
"foreignKeys": []
},
{
"tableName": "playlist_stream_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "playlistUid",
"columnName": "playlist_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "index",
"columnName": "join_index",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"playlist_id",
"join_index"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_playlist_stream_join_playlist_id_join_index",
"unique": true,
"columnNames": [
"playlist_id",
"join_index"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
},
{
"name": "index_playlist_stream_join_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "playlists",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"playlist_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "remote_playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "streamCount",
"columnName": "stream_count",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_remote_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
},
{
"name": "index_remote_playlists_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "streamId",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id",
"subscription_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_feed_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_group",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sortOrder",
"columnName": "sort_order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_feed_group_sort_order",
"unique": false,
"columnNames": [
"sort_order"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed_group_subscription_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "feedGroupId",
"columnName": "group_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"group_id",
"subscription_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_feed_group_subscription_join_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "feed_group",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"group_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_last_updated",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastUpdated",
"columnName": "last_updated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"subscription_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4084aa342aef315dc7b558770a7755a9')"
]
}
}

View File

@ -0,0 +1,737 @@
{
"formatVersion": 1,
"database": {
"version": 7,
"identityHash": "012fc8e7ad3333f1597347f34e76a513",
"entities": [
{
"tableName": "subscriptions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatarUrl",
"columnName": "avatar_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subscriberCount",
"columnName": "subscriber_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "notificationMode",
"columnName": "notification_mode",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_subscriptions_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "search_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "creationDate",
"columnName": "creation_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "search",
"columnName": "search",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_search_history_search",
"unique": false,
"columnNames": [
"search"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
}
],
"foreignKeys": []
},
{
"tableName": "streams",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "streamType",
"columnName": "stream_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploader_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "viewCount",
"columnName": "view_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "textualUploadDate",
"columnName": "textual_upload_date",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploadDate",
"columnName": "upload_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isUploadDateApproximation",
"columnName": "is_upload_date_approximation",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_streams_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "stream_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accessDate",
"columnName": "access_date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "repeatCount",
"columnName": "repeat_count",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id",
"access_date"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_stream_history_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "stream_state",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "progressMillis",
"columnName": "progress_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, `thumbnail_stream_id` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isThumbnailPermanent",
"columnName": "is_thumbnail_permanent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "thumbnailStreamId",
"columnName": "thumbnail_stream_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
}
],
"foreignKeys": []
},
{
"tableName": "playlist_stream_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "playlistUid",
"columnName": "playlist_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "index",
"columnName": "join_index",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"playlist_id",
"join_index"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_playlist_stream_join_playlist_id_join_index",
"unique": true,
"columnNames": [
"playlist_id",
"join_index"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
},
{
"name": "index_playlist_stream_join_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "playlists",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"playlist_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "remote_playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "streamCount",
"columnName": "stream_count",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_remote_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
},
{
"name": "index_remote_playlists_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "streamId",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"stream_id",
"subscription_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_feed_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_group",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sortOrder",
"columnName": "sort_order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_feed_group_sort_order",
"unique": false,
"columnNames": [
"sort_order"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed_group_subscription_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "feedGroupId",
"columnName": "group_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"group_id",
"subscription_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_feed_group_subscription_join_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "feed_group",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"group_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_last_updated",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastUpdated",
"columnName": "last_updated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"subscription_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '012fc8e7ad3333f1597347f34e76a513')"
]
}
}

View File

@ -0,0 +1,737 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "012fc8e7ad3333f1597347f34e76a513",
"entities": [
{
"tableName": "subscriptions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatarUrl",
"columnName": "avatar_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subscriberCount",
"columnName": "subscriber_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "notificationMode",
"columnName": "notification_mode",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_subscriptions_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "search_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "creationDate",
"columnName": "creation_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "search",
"columnName": "search",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_search_history_search",
"unique": false,
"columnNames": [
"search"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
}
],
"foreignKeys": []
},
{
"tableName": "streams",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "streamType",
"columnName": "stream_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploader_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "viewCount",
"columnName": "view_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "textualUploadDate",
"columnName": "textual_upload_date",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploadDate",
"columnName": "upload_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isUploadDateApproximation",
"columnName": "is_upload_date_approximation",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_streams_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "stream_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accessDate",
"columnName": "access_date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "repeatCount",
"columnName": "repeat_count",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"stream_id",
"access_date"
]
},
"indices": [
{
"name": "index_stream_history_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "stream_state",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "progressMillis",
"columnName": "progress_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"stream_id"
]
},
"indices": [],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, `thumbnail_stream_id` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isThumbnailPermanent",
"columnName": "is_thumbnail_permanent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "thumbnailStreamId",
"columnName": "thumbnail_stream_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlists_name` ON `${TABLE_NAME}` (`name`)"
}
],
"foreignKeys": []
},
{
"tableName": "playlist_stream_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "playlistUid",
"columnName": "playlist_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "index",
"columnName": "join_index",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"playlist_id",
"join_index"
]
},
"indices": [
{
"name": "index_playlist_stream_join_playlist_id_join_index",
"unique": true,
"columnNames": [
"playlist_id",
"join_index"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
},
{
"name": "index_playlist_stream_join_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "playlists",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"playlist_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "remote_playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "streamCount",
"columnName": "stream_count",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_remote_playlists_name",
"unique": false,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_remote_playlists_name` ON `${TABLE_NAME}` (`name`)"
},
{
"name": "index_remote_playlists_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "streamId",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"stream_id",
"subscription_id"
]
},
"indices": [
{
"name": "index_feed_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_group",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sortOrder",
"columnName": "sort_order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_feed_group_sort_order",
"unique": false,
"columnNames": [
"sort_order"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed_group_subscription_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "feedGroupId",
"columnName": "group_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"group_id",
"subscription_id"
]
},
"indices": [
{
"name": "index_feed_group_subscription_join_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "feed_group",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"group_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_last_updated",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastUpdated",
"columnName": "last_updated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"subscription_id"
]
},
"indices": [],
"foreignKeys": [
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '012fc8e7ad3333f1597347f34e76a513')"
]
}
}

View File

@ -0,0 +1,730 @@
{
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "7591e8039faa74d8c0517dc867af9d3e",
"entities": [
{
"tableName": "subscriptions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT, `name` TEXT, `avatar_url` TEXT, `subscriber_count` INTEGER, `description` TEXT, `notification_mode` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "avatarUrl",
"columnName": "avatar_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subscriberCount",
"columnName": "subscriber_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "notificationMode",
"columnName": "notification_mode",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_subscriptions_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_subscriptions_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "search_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`creation_date` INTEGER, `service_id` INTEGER NOT NULL, `search` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "creationDate",
"columnName": "creation_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "search",
"columnName": "search",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_search_history_search",
"unique": false,
"columnNames": [
"search"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_search_history_search` ON `${TABLE_NAME}` (`search`)"
}
],
"foreignKeys": []
},
{
"tableName": "streams",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `stream_type` TEXT NOT NULL, `duration` INTEGER NOT NULL, `uploader` TEXT NOT NULL, `uploader_url` TEXT, `thumbnail_url` TEXT, `view_count` INTEGER, `textual_upload_date` TEXT, `upload_date` INTEGER, `is_upload_date_approximation` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "streamType",
"columnName": "stream_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploader_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "viewCount",
"columnName": "view_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "textualUploadDate",
"columnName": "textual_upload_date",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploadDate",
"columnName": "upload_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isUploadDateApproximation",
"columnName": "is_upload_date_approximation",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_streams_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_streams_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "stream_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `access_date` INTEGER NOT NULL, `repeat_count` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `access_date`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accessDate",
"columnName": "access_date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "repeatCount",
"columnName": "repeat_count",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"stream_id",
"access_date"
]
},
"indices": [
{
"name": "index_stream_history_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_stream_history_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "stream_state",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `progress_time` INTEGER NOT NULL, PRIMARY KEY(`stream_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "progressMillis",
"columnName": "progress_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"stream_id"
]
},
"indices": [],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, `thumbnail_stream_id` INTEGER NOT NULL, `display_index` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isThumbnailPermanent",
"columnName": "is_thumbnail_permanent",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "thumbnailStreamId",
"columnName": "thumbnail_stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "displayIndex",
"columnName": "display_index",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "playlist_stream_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlist_id` INTEGER NOT NULL, `stream_id` INTEGER NOT NULL, `join_index` INTEGER NOT NULL, PRIMARY KEY(`playlist_id`, `join_index`), FOREIGN KEY(`playlist_id`) REFERENCES `playlists`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "playlistUid",
"columnName": "playlist_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamUid",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "index",
"columnName": "join_index",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"playlist_id",
"join_index"
]
},
"indices": [
{
"name": "index_playlist_stream_join_playlist_id_join_index",
"unique": true,
"columnNames": [
"playlist_id",
"join_index"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_playlist_stream_join_playlist_id_join_index` ON `${TABLE_NAME}` (`playlist_id`, `join_index`)"
},
{
"name": "index_playlist_stream_join_stream_id",
"unique": false,
"columnNames": [
"stream_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_stream_join_stream_id` ON `${TABLE_NAME}` (`stream_id`)"
}
],
"foreignKeys": [
{
"table": "playlists",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"playlist_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "remote_playlists",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, `thumbnail_url` TEXT, `uploader` TEXT, `display_index` INTEGER NOT NULL, `stream_count` INTEGER)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serviceId",
"columnName": "service_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnail_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "displayIndex",
"columnName": "display_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamCount",
"columnName": "stream_count",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_remote_playlists_service_id_url",
"unique": true,
"columnNames": [
"service_id",
"url"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_playlists_service_id_url` ON `${TABLE_NAME}` (`service_id`, `url`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stream_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`stream_id`, `subscription_id`), FOREIGN KEY(`stream_id`) REFERENCES `streams`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "streamId",
"columnName": "stream_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"stream_id",
"subscription_id"
]
},
"indices": [
{
"name": "index_feed_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "streams",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"stream_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_group",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `icon_id` INTEGER NOT NULL, `sort_order` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sortOrder",
"columnName": "sort_order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"uid"
]
},
"indices": [
{
"name": "index_feed_group_sort_order",
"unique": false,
"columnNames": [
"sort_order"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
}
],
"foreignKeys": []
},
{
"tableName": "feed_group_subscription_join",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group_id` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, PRIMARY KEY(`group_id`, `subscription_id`), FOREIGN KEY(`group_id`) REFERENCES `feed_group`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "feedGroupId",
"columnName": "group_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"group_id",
"subscription_id"
]
},
"indices": [
{
"name": "index_feed_group_subscription_join_subscription_id",
"unique": false,
"columnNames": [
"subscription_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_feed_group_subscription_join_subscription_id` ON `${TABLE_NAME}` (`subscription_id`)"
}
],
"foreignKeys": [
{
"table": "feed_group",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"group_id"
],
"referencedColumns": [
"uid"
]
},
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
},
{
"tableName": "feed_last_updated",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`subscription_id` INTEGER NOT NULL, `last_updated` INTEGER, PRIMARY KEY(`subscription_id`), FOREIGN KEY(`subscription_id`) REFERENCES `subscriptions`(`uid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "subscriptionId",
"columnName": "subscription_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastUpdated",
"columnName": "last_updated",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"subscription_id"
]
},
"indices": [],
"foreignKeys": [
{
"table": "subscriptions",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"subscription_id"
],
"referencedColumns": [
"uid"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7591e8039faa74d8c0517dc867af9d3e')"
]
}
}

View File

@ -4,15 +4,18 @@ import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import androidx.room.Room
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.schabi.newpipe.database.playlist.model.PlaylistEntity
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.extractor.stream.StreamType
@RunWith(AndroidJUnit4::class)
@ -21,19 +24,23 @@ class DatabaseMigrationTest {
private const val DEFAULT_SERVICE_ID = 0
private const val DEFAULT_URL = "https://www.youtube.com/watch?v=cDphUib5iG4"
private const val DEFAULT_TITLE = "Test Title"
private const val DEFAULT_NAME = "Test Name"
private val DEFAULT_TYPE = StreamType.VIDEO_STREAM
private const val DEFAULT_DURATION = 480L
private const val DEFAULT_UPLOADER_NAME = "Uploader Test"
private const val DEFAULT_THUMBNAIL = "https://example.com/example.jpg"
private const val DEFAULT_SECOND_SERVICE_ID = 0
private const val DEFAULT_SECOND_SERVICE_ID = 1
private const val DEFAULT_SECOND_URL = "https://www.youtube.com/watch?v=ncQU6iBn5Fc"
private const val DEFAULT_THIRD_SERVICE_ID = 2
private const val DEFAULT_THIRD_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
@get:Rule
val testHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory()
AppDatabase::class.java
)
@Test
@ -42,7 +49,8 @@ class DatabaseMigrationTest {
databaseInV2.run {
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
"streams",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SERVICE_ID)
put("url", DEFAULT_URL)
@ -54,14 +62,16 @@ class DatabaseMigrationTest {
}
)
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
"streams",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("url", DEFAULT_SECOND_URL)
}
)
insert(
"streams", SQLiteDatabase.CONFLICT_FAIL,
"streams",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SERVICE_ID)
}
@ -70,18 +80,52 @@ class DatabaseMigrationTest {
}
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_3,
true, Migrations.MIGRATION_2_3
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_3,
true,
Migrations.MIGRATION_2_3
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_4,
true, Migrations.MIGRATION_3_4
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_4,
true,
Migrations.MIGRATION_3_4
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_5,
true, Migrations.MIGRATION_4_5
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_5,
true,
Migrations.MIGRATION_4_5
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_6,
true,
Migrations.MIGRATION_5_6
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_7,
true,
Migrations.MIGRATION_6_7
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_8,
true,
Migrations.MIGRATION_7_8
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_9,
true,
Migrations.MIGRATION_8_9
)
val migratedDatabaseV3 = getMigratedDatabase()
@ -118,10 +162,162 @@ class DatabaseMigrationTest {
assertNull(secondStreamFromMigratedDatabase.isUploadDateApproximation)
}
@Test
fun migrateDatabaseFrom7to8() {
val databaseInV7 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_7)
val defaultSearch1 = " abc "
val defaultSearch2 = " abc"
val serviceId = DEFAULT_SERVICE_ID // YouTube
// Use id different to YouTube because two searches with the same query
// but different service are considered not equal.
val otherServiceId = ServiceList.SoundCloud.serviceId
databaseInV7.run {
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", serviceId)
put("search", defaultSearch1)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", serviceId)
put("search", defaultSearch2)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", otherServiceId)
put("search", defaultSearch1)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", otherServiceId)
put("search", defaultSearch2)
}
)
close()
}
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_8,
true, Migrations.MIGRATION_7_8
)
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_9,
true, Migrations.MIGRATION_8_9
)
val migratedDatabaseV8 = getMigratedDatabase()
val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
assertEquals(2, listFromDB.size)
assertEquals("abc", listFromDB[0].search)
assertEquals("abc", listFromDB[1].search)
assertNotEquals(listFromDB[0].serviceId, listFromDB[1].serviceId)
}
@Test
fun migrateDatabaseFrom8to9() {
val databaseInV8 = testHelper.createDatabase(AppDatabase.DATABASE_NAME, Migrations.DB_VER_8)
val localUid1: Long
val localUid2: Long
val remoteUid1: Long
val remoteUid2: Long
databaseInV8.run {
localUid1 = insert(
"playlists", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("name", DEFAULT_NAME + "1")
put("is_thumbnail_permanent", false)
put("thumbnail_stream_id", -1)
}
)
localUid2 = insert(
"playlists", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("name", DEFAULT_NAME + "2")
put("is_thumbnail_permanent", false)
put("thumbnail_stream_id", -1)
}
)
delete(
"playlists", "uid = ?",
Array(1) { localUid1 }
)
remoteUid1 = insert(
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SERVICE_ID)
put("url", DEFAULT_URL)
}
)
remoteUid2 = insert(
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("url", DEFAULT_SECOND_URL)
}
)
delete(
"remote_playlists", "uid = ?",
Array(1) { remoteUid2 }
)
close()
}
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_9,
true,
Migrations.MIGRATION_8_9
)
val migratedDatabaseV9 = getMigratedDatabase()
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
assertEquals(1, localListFromDB.size)
assertEquals(localUid2, localListFromDB[0].uid)
assertEquals(-1, localListFromDB[0].displayIndex)
assertEquals(1, remoteListFromDB.size)
assertEquals(remoteUid1, remoteListFromDB[0].uid)
assertEquals(-1, remoteListFromDB[0].displayIndex)
val localUid3 = migratedDatabaseV9.playlistDAO().insert(
PlaylistEntity(DEFAULT_NAME + "3", false, -1, -1)
)
val remoteUid3 = migratedDatabaseV9.playlistRemoteDAO().insert(
PlaylistRemoteEntity(
DEFAULT_THIRD_SERVICE_ID, DEFAULT_NAME, DEFAULT_THIRD_URL,
DEFAULT_THUMBNAIL, DEFAULT_UPLOADER_NAME, -1, 10
)
)
localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
assertEquals(2, localListFromDB.size)
assertEquals(localUid3, localListFromDB[1].uid)
assertEquals(-1, localListFromDB[1].displayIndex)
assertEquals(2, remoteListFromDB.size)
assertEquals(remoteUid3, remoteListFromDB[1].uid)
assertEquals(-1, remoteListFromDB[1].displayIndex)
}
private fun getMigratedDatabase(): AppDatabase {
val database: AppDatabase = Room.databaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, AppDatabase.DATABASE_NAME
AppDatabase::class.java,
AppDatabase.DATABASE_NAME
)
.build()
testHelper.closeWhenFinished(database)

View File

@ -0,0 +1,130 @@
package org.schabi.newpipe.database
import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import io.reactivex.rxjava3.core.Single
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.schabi.newpipe.database.feed.dao.FeedDAO
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.stream.StreamWithState
import org.schabi.newpipe.database.stream.dao.StreamDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.subscription.SubscriptionDAO
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.extractor.channel.ChannelInfo
import org.schabi.newpipe.extractor.stream.StreamType
import java.io.IOException
import java.time.OffsetDateTime
import kotlin.streams.toList
class FeedDAOTest {
private lateinit var db: AppDatabase
private lateinit var feedDAO: FeedDAO
private lateinit var streamDAO: StreamDAO
private lateinit var subscriptionDAO: SubscriptionDAO
private val serviceId = ServiceList.YouTube.serviceId
private val stream1 = StreamEntity(1, serviceId, "https://youtube.com/watch?v=1", "stream 1", StreamType.VIDEO_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-01", OffsetDateTime.parse("2023-01-01T00:00:00Z"))
private val stream2 = StreamEntity(2, serviceId, "https://youtube.com/watch?v=2", "stream 2", StreamType.VIDEO_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-02", OffsetDateTime.parse("2023-01-02T00:00:00Z"))
private val stream3 = StreamEntity(3, serviceId, "https://youtube.com/watch?v=3", "stream 3", StreamType.LIVE_STREAM, 1000, "channel-1", "https://youtube.com/channel/1", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-01-03", OffsetDateTime.parse("2023-01-03T00:00:00Z"))
private val stream4 = StreamEntity(4, serviceId, "https://youtube.com/watch?v=4", "stream 4", StreamType.VIDEO_STREAM, 1000, "channel-2", "https://youtube.com/channel/2", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-10", OffsetDateTime.parse("2023-08-10T00:00:00Z"))
private val stream5 = StreamEntity(5, serviceId, "https://youtube.com/watch?v=5", "stream 5", StreamType.VIDEO_STREAM, 1000, "channel-2", "https://youtube.com/channel/2", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-20", OffsetDateTime.parse("2023-08-20T00:00:00Z"))
private val stream6 = StreamEntity(6, serviceId, "https://youtube.com/watch?v=6", "stream 6", StreamType.VIDEO_STREAM, 1000, "channel-3", "https://youtube.com/channel/3", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-09-01", OffsetDateTime.parse("2023-09-01T00:00:00Z"))
private val stream7 = StreamEntity(7, serviceId, "https://youtube.com/watch?v=7", "stream 7", StreamType.VIDEO_STREAM, 1000, "channel-4", "https://youtube.com/channel/4", "https://i.ytimg.com/vi/1/hqdefault.jpg", 100, "2023-08-10", OffsetDateTime.parse("2023-08-10T00:00:00Z"))
private val allStreams = listOf(
stream1, stream2, stream3, stream4, stream5, stream6, stream7
)
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(
context, AppDatabase::class.java
).build()
feedDAO = db.feedDAO()
streamDAO = db.streamDAO()
subscriptionDAO = db.subscriptionDAO()
}
@After
@Throws(IOException::class)
fun closeDb() {
db.close()
}
@Test
fun testUnlinkStreamsOlderThan_KeepOne() {
setupUnlinkDelete("2023-08-15T00:00:00Z")
val streams = feedDAO.getStreams(
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
)
.blockingGet()
val allowedStreams = listOf(stream3, stream5, stream6, stream7)
assertEqual(streams, allowedStreams)
}
@Test
fun testUnlinkStreamsOlderThan_KeepMultiple() {
setupUnlinkDelete("2023-08-01T00:00:00Z")
val streams = feedDAO.getStreams(
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
)
.blockingGet()
val allowedStreams = listOf(stream3, stream4, stream5, stream6, stream7)
assertEqual(streams, allowedStreams)
}
private fun assertEqual(streams: List<StreamWithState>?, allowedStreams: List<StreamEntity>) {
assertNotNull(streams)
assertEquals(
allowedStreams,
streams!!
.map { it.stream }
.sortedBy { it.uid }
.toList()
)
}
private fun setupUnlinkDelete(time: String) {
clearAndFillTables()
Single.fromCallable {
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
}.blockingSubscribe()
Single.fromCallable {
streamDAO.deleteOrphans()
}.blockingSubscribe()
}
private fun clearAndFillTables() {
db.clearAllTables()
streamDAO.insertAll(allStreams)
subscriptionDAO.insertAll(
listOf(
SubscriptionEntity.from(ChannelInfo(serviceId, "1", "https://youtube.com/channel/1", "https://youtube.com/channel/1", "channel-1")),
SubscriptionEntity.from(ChannelInfo(serviceId, "2", "https://youtube.com/channel/2", "https://youtube.com/channel/2", "channel-2")),
SubscriptionEntity.from(ChannelInfo(serviceId, "3", "https://youtube.com/channel/3", "https://youtube.com/channel/3", "channel-3")),
SubscriptionEntity.from(ChannelInfo(serviceId, "4", "https://youtube.com/channel/4", "https://youtube.com/channel/4", "channel-4")),
)
)
feedDAO.insertAll(
listOf(
FeedEntity(1, 1),
FeedEntity(2, 1),
FeedEntity(3, 1),
FeedEntity(4, 2),
FeedEntity(5, 2),
FeedEntity(6, 3),
FeedEntity(7, 4),
)
)
}
}

View File

@ -0,0 +1,82 @@
package org.schabi.newpipe.local.subscription;
import static org.junit.Assert.assertEquals;
import androidx.test.core.app.ApplicationProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.schabi.newpipe.database.AppDatabase;
import org.schabi.newpipe.database.feed.model.FeedGroupEntity;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.testUtil.TestDatabase;
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule;
import java.io.IOException;
import java.util.List;
public class SubscriptionManagerTest {
private AppDatabase database;
private SubscriptionManager manager;
@Rule
public TrampolineSchedulerRule trampolineScheduler = new TrampolineSchedulerRule();
private SubscriptionEntity getAssertOneSubscriptionEntity() {
final List<SubscriptionEntity> entities = manager
.getSubscriptions(FeedGroupEntity.GROUP_ALL_ID, "", false)
.blockingFirst();
assertEquals(1, entities.size());
return entities.get(0);
}
@Before
public void setup() {
database = TestDatabase.Companion.createReplacingNewPipeDatabase();
manager = new SubscriptionManager(ApplicationProvider.getApplicationContext());
}
@After
public void cleanUp() {
database.close();
}
@Test
public void testInsert() throws ExtractionException, IOException {
final ChannelInfo info = ChannelInfo.getInfo("https://www.youtube.com/c/3blue1brown");
final SubscriptionEntity subscription = SubscriptionEntity.from(info);
manager.insertSubscription(subscription);
final SubscriptionEntity readSubscription = getAssertOneSubscriptionEntity();
// the uid has changed, since the uid is chosen upon inserting, but the rest should match
assertEquals(subscription.getServiceId(), readSubscription.getServiceId());
assertEquals(subscription.getUrl(), readSubscription.getUrl());
assertEquals(subscription.getName(), readSubscription.getName());
assertEquals(subscription.getAvatarUrl(), readSubscription.getAvatarUrl());
assertEquals(subscription.getSubscriberCount(), readSubscription.getSubscriberCount());
assertEquals(subscription.getDescription(), readSubscription.getDescription());
}
@Test
public void testUpdateNotificationMode() throws ExtractionException, IOException {
final ChannelInfo info = ChannelInfo.getInfo("https://www.youtube.com/c/veritasium");
final SubscriptionEntity subscription = SubscriptionEntity.from(info);
subscription.setNotificationMode(0);
manager.insertSubscription(subscription);
manager.updateNotificationMode(subscription.getServiceId(), subscription.getUrl(), 1)
.blockingAwait();
final SubscriptionEntity anotherSubscription = getAssertOneSubscriptionEntity();
assertEquals(0, subscription.getNotificationMode());
assertEquals(subscription.getUrl(), anotherSubscription.getUrl());
assertEquals(1, anotherSubscription.getNotificationMode());
}
}

View File

@ -1,26 +1,32 @@
package org.schabi.newpipe.util
import android.content.Context
import android.util.SparseArray
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.Spinner
import androidx.collection.SparseArrayCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.MediaFormat
import org.schabi.newpipe.extractor.downloader.Response
import org.schabi.newpipe.extractor.stream.AudioStream
import org.schabi.newpipe.extractor.stream.Stream
import org.schabi.newpipe.extractor.stream.SubtitlesStream
import org.schabi.newpipe.extractor.stream.VideoStream
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper
@MediumTest
@RunWith(AndroidJUnit4::class)
@ -39,9 +45,7 @@ class StreamItemAdapterTest {
@Test
fun videoStreams_noSecondaryStream() {
val adapter = StreamItemAdapter<VideoStream, AudioStream>(
context,
getVideoStreams(true, true, true, true),
null
getVideoStreams(true, true, true, true)
)
spinner.adapter = adapter
@ -54,7 +58,6 @@ class StreamItemAdapterTest {
@Test
fun videoStreams_hasSecondaryStream() {
val adapter = StreamItemAdapter(
context,
getVideoStreams(false, true, false, true),
getAudioStreams(false, true, false, true)
)
@ -69,7 +72,6 @@ class StreamItemAdapterTest {
@Test
fun videoStreams_Mixed() {
val adapter = StreamItemAdapter(
context,
getVideoStreams(true, true, true, true, true, false, true, true),
getAudioStreams(false, true, false, false, false, true, true, true)
)
@ -88,8 +90,7 @@ class StreamItemAdapterTest {
@Test
fun subtitleStreams_noIcon() {
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
context,
StreamItemAdapter.StreamSizeWrapper(
StreamItemAdapter.StreamInfoWrapper(
(0 until 5).map {
SubtitlesStream.Builder()
.setContent("https://example.com", true)
@ -99,8 +100,7 @@ class StreamItemAdapterTest {
.build()
},
context
),
null
)
)
spinner.adapter = adapter
for (i in 0 until spinner.count) {
@ -111,8 +111,7 @@ class StreamItemAdapterTest {
@Test
fun audioStreams_noIcon() {
val adapter = StreamItemAdapter<AudioStream, Stream>(
context,
StreamItemAdapter.StreamSizeWrapper(
StreamItemAdapter.StreamInfoWrapper(
(0 until 5).map {
AudioStream.Builder()
.setId(Stream.ID_UNKNOWN)
@ -122,8 +121,7 @@ class StreamItemAdapterTest {
.build()
},
context
),
null
)
)
spinner.adapter = adapter
for (i in 0 until spinner.count) {
@ -131,12 +129,109 @@ class StreamItemAdapterTest {
}
}
@Test
fun retrieveMediaFormatFromFileTypeHeaders() {
val streams = getIncompleteAudioStreams(5)
val wrapper = StreamInfoWrapper(streams, context)
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
StreamInfoWrapper.retrieveMediaFormatFromFileTypeHeaders(stream, wrapper, response)
}
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0)
helper.assertInvalidResponse(getResponse(mapOf(Pair("file-type", "mp0"))), 1)
helper.assertValidResponse(getResponse(mapOf(Pair("x-amz-meta-file-type", "aiff"))), 2, MediaFormat.AIFF)
helper.assertValidResponse(getResponse(mapOf(Pair("file-type", "mp3"))), 3, MediaFormat.MP3)
}
@Test
fun retrieveMediaFormatFromContentDispositionHeader() {
val streams = getIncompleteAudioStreams(11)
val wrapper = StreamInfoWrapper(streams, context)
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
StreamInfoWrapper.retrieveMediaFormatFromContentDispositionHeader(stream, wrapper, response)
}
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0)
helper.assertInvalidResponse(
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.png\""))), 1
)
helper.assertInvalidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"data.csv\""))), 2
)
helper.assertInvalidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; filename=\"data.csv\""))), 3
)
helper.assertInvalidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"fieldName\"; filename*=\"filename.jpg\""))), 4
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.ogg\""))),
5, MediaFormat.OGG
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "some-form-data; filename=\"audio.flac\""))),
6, MediaFormat.FLAC
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.aiff\"; filename=\"audio.aiff\""))),
7, MediaFormat.AIFF
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"alien?\"; filename*=UTF-8''%CE%B1%CE%BB%CE%B9%CF%B5%CE%BD.m4a"))),
8, MediaFormat.M4A
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=UTF-8''alien.opus"))),
9, MediaFormat.OPUS
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=\"UTF-8''alien.opus\""))),
10, MediaFormat.OPUS
)
}
@Test
fun retrieveMediaFormatFromContentTypeHeader() {
val streams = getIncompleteAudioStreams(12)
val wrapper = StreamInfoWrapper(streams, context)
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
StreamInfoWrapper.retrieveMediaFormatFromContentTypeHeader(stream, wrapper, response)
}
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "984501"))), 0)
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/xyz"))), 1)
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "mp3"))), 2)
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "mp3"))), 3)
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/mpeg"))), 4)
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/aif"))), 5)
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "whatever"))), 6)
helper.assertInvalidResponse(getResponse(mapOf()), 7)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/flac"))), 8, MediaFormat.FLAC
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/wav"))), 9, MediaFormat.WAV
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/opus"))), 10, MediaFormat.OPUS
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/aiff"))), 11, MediaFormat.AIFF
)
}
/**
* @return a list of video streams, in which their video only property mirrors the provided
* [videoOnly] vararg.
*/
private fun getVideoStreams(vararg videoOnly: Boolean) =
StreamItemAdapter.StreamSizeWrapper(
StreamItemAdapter.StreamInfoWrapper(
videoOnly.map {
VideoStream.Builder()
.setId(Stream.ID_UNKNOWN)
@ -169,6 +264,19 @@ class StreamItemAdapterTest {
}
)
private fun getIncompleteAudioStreams(size: Int): List<AudioStream> {
val list = ArrayList<AudioStream>(size)
for (i in 1..size) {
list.add(
AudioStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com/$i", true)
.build()
)
}
return list
}
/**
* Checks whether the item at [position] in the [spinner] has the correct icon visibility when
* it is shown in normal mode (selected) and in dropdown mode (user is choosing one of a list).
@ -200,15 +308,60 @@ class StreamItemAdapterTest {
* Helper function that builds a secondary stream list.
*/
private fun <T : Stream> getSecondaryStreamsFromList(streams: List<T?>) =
SparseArray<SecondaryStreamHelper<T>?>(streams.size).apply {
SparseArrayCompat<SecondaryStreamHelper<T>?>(streams.size).apply {
streams.forEachIndexed { index, stream ->
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
SecondaryStreamHelper(
StreamItemAdapter.StreamSizeWrapper(streams, context),
StreamItemAdapter.StreamInfoWrapper(streams, context),
it
)
}
put(index, secondaryStreamHelper)
}
}
private fun getResponse(headers: Map<String, String>): Response {
val listHeaders = HashMap<String, List<String>>()
headers.forEach { entry ->
listHeaders[entry.key] = listOf(entry.value)
}
return Response(200, null, listHeaders, "", "")
}
/**
* Helper class for assertion related to extractions of [MediaFormat]s.
*/
class AssertionHelper<T : Stream>(
private val streams: List<T>,
private val wrapper: StreamInfoWrapper<T>,
private val retrieveMediaFormat: (stream: T, response: Response) -> Boolean
) {
/**
* Assert that an invalid response does not result in wrongly extracted [MediaFormat].
*/
fun assertInvalidResponse(
response: Response,
index: Int
) {
assertFalse(
"invalid header returns valid value", retrieveMediaFormat(streams[index], response)
)
assertNull("Media format extracted although stated otherwise", wrapper.getFormat(index))
}
/**
* Assert that a valid response results in correctly extracted and handled [MediaFormat].
*/
fun assertValidResponse(
response: Response,
index: Int,
format: MediaFormat
) {
assertTrue(
"header was not recognized", retrieveMediaFormat(streams[index], response)
)
assertEquals("Wrong media format extracted", format, wrapper.getFormat(index))
}
}
}

View File

@ -3,7 +3,6 @@ package org.schabi.newpipe
import androidx.preference.PreferenceManager
import com.facebook.stetho.Stetho
import com.facebook.stetho.okhttp3.StethoInterceptor
import leakcanary.AppWatcher
import leakcanary.LeakCanary
import okhttp3.OkHttpClient
import org.schabi.newpipe.extractor.downloader.Downloader
@ -13,8 +12,6 @@ class DebugApp : App() {
super.onCreate()
initStetho()
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
LeakCanary.config = LeakCanary.config.copy(
dumpHeap = PreferenceManager
.getDefaultSharedPreferences(this).getBoolean(

View File

@ -9,6 +9,15 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- We need to be able to open links in the browser on API 30+ -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
</queries>
<uses-feature
android:name="android.hardware.touchscreen"
@ -24,11 +33,12 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:theme="@style/OpeningTheme"
android:resizeableActivity="true"
android:theme="@style/OpeningTheme"
tools:ignore="AllowBackup">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
@ -39,7 +49,9 @@
</intent-filter>
</activity>
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
@ -47,7 +59,7 @@
<service
android:name=".player.PlayerService"
android:exported="false"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
@ -56,15 +68,18 @@
<activity
android:name=".player.PlayQueueActivity"
android:exported="false"
android:label="@string/title_activity_play_queue"
android:launchMode="singleTask" />
<activity
android:name=".settings.SettingsActivity"
android:exported="false"
android:label="@string/settings" />
<activity
android:name=".about.AboutActivity"
android:exported="false"
android:label="@string/title_activity_about" />
<service android:name=".local.subscription.services.SubscriptionsImportService" />
@ -73,6 +88,7 @@
<activity
android:name=".PanicResponderActivity"
android:exported="true"
android:launchMode="singleInstance"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
@ -85,13 +101,18 @@
<activity
android:name=".ExitActivity"
android:exported="false"
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".error.ErrorActivity" />
<activity
android:name=".error.ErrorActivity"
android:exported="false" />
<!-- giga get related -->
<activity
android:name=".download.DownloadActivity"
android:exported="false"
android:label="@string/app_name"
android:launchMode="singleTask" />
@ -99,6 +120,7 @@
<activity
android:name=".util.FilePickerActivityHelper"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/FilePickerThemeDark">
<intent-filter>
@ -109,6 +131,7 @@
<activity
android:name=".error.ReCaptchaActivity"
android:exported="false"
android:label="@string/recaptcha" />
<provider
@ -124,6 +147,7 @@
<activity
android:name=".RouterActivity"
android:excludeFromRecents="true"
android:exported="true"
android:label="@string/preferred_open_action_share_menu_title"
android:taskAffinity=""
android:theme="@style/RouterActivityThemeDark">
@ -149,6 +173,7 @@
<data android:pathPrefix="/watch" />
<data android:pathPrefix="/attribution_link" />
<data android:pathPrefix="/shorts/" />
<data android:pathPrefix="/live/" />
<!-- channel prefix -->
<data android:pathPrefix="/channel/" />
<data android:pathPrefix="/user/" />
@ -332,16 +357,16 @@
<data android:host="eduvid.org" />
<data android:host="framatube.org" />
<data android:host="media.assassinate-you.net" />
<data android:host="media.fsfe.org" />
<data android:host="peertube.co.uk" />
<data android:host="peertube.cpy.re" />
<data android:host="peertube.mastodon.host" />
<data android:host="peertube.fr" />
<data android:host="tilvids.com" />
<data android:host="tube.privacytools.io" />
<data android:host="video.ploud.fr" />
<data android:host="video.lqdn.fr" />
<data android:host="peertube.mastodon.host" />
<data android:host="peertube.stream" />
<data android:host="skeptikon.fr" />
<data android:host="media.fsfe.org" />
<data android:host="tilvids.com" />
<data android:host="video.lqdn.fr" />
<data android:host="video.ploud.fr" />
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
<data android:pathPrefix="/w/" /> <!-- short video URLs -->
@ -354,30 +379,30 @@
<!-- Bandcamp filter for tracks, albums and playlists -->
<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="*.bandcamp.com"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*.bandcamp.com" />
</intent-filter>
<!-- Bandcamp filter for radio -->
<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:sspPattern="bandcamp.com/?show=*"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:sspPattern="bandcamp.com/?show=*" />
</intent-filter>
</activity>
@ -386,11 +411,17 @@
android:exported="false" />
<!-- opting out of sending metrics to Google in Android System WebView -->
<meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />
<meta-data
android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
<!-- see https://github.com/TeamNewPipe/NewPipe/issues/3947 -->
<!-- Version < 3.0. DeX Mode and Screen Mirroring support -->
<meta-data android:name="com.samsung.android.keepalive.density" android:value="true"/>
<meta-data
android:name="com.samsung.android.keepalive.density"
android:value="true" />
<!-- Version >= 3.0. DeX Dual Mode support -->
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true"/>
<meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="true" />
</application>
</manifest>

View File

@ -25,6 +25,7 @@ import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.os.BundleCompat;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager.widget.PagerAdapter;
@ -284,7 +285,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
Bundle state = null;
if (!mSavedState.isEmpty()) {
state = new Bundle();
state.putParcelableArray("states", mSavedState.toArray(new Fragment.SavedState[0]));
state.putParcelableArrayList("states", mSavedState);
}
for (int i = 0; i < mFragments.size(); i++) {
final Fragment f = mFragments.get(i);
@ -311,13 +312,12 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
if (state != null) {
final Bundle bundle = (Bundle) state;
bundle.setClassLoader(loader);
final Parcelable[] fss = bundle.getParcelableArray("states");
final var states = BundleCompat.getParcelableArrayList(bundle, "states",
Fragment.SavedState.class);
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (final Parcelable parcelable : fss) {
mSavedState.add((Fragment.SavedState) parcelable);
}
if (states != null) {
mSavedState.addAll(states);
}
final Iterable<String> keys = bundle.keySet();
for (final String key : keys) {

View File

@ -20,9 +20,11 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.image.PreferredImageQuality;
import java.io.IOException;
import java.io.InterruptedIOException;
@ -58,6 +60,8 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins;
public class App extends Application {
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
private static final String TAG = App.class.toString();
private boolean isFirstRun = false;
private static App app;
@NonNull
@ -83,7 +87,13 @@ public class App extends Application {
return;
}
// Initialize settings first because others inits can use its values
// check if the last used preference version is set
// to determine whether this is the first app run
final int lastUsedPrefVersion = PreferenceManager.getDefaultSharedPreferences(this)
.getInt(getString(R.string.last_used_preferences_version), -1);
isFirstRun = lastUsedPrefVersion == -1;
// Initialize settings first because other initializations can use its values
NewPipeSettings.initSettings(this);
NewPipe.init(getDownloader(),
@ -99,8 +109,9 @@ public class App extends Application {
// Initialize image loader
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
PicassoHelper.init(this);
PicassoHelper.setShouldLoadImages(
prefs.getBoolean(getString(R.string.download_thumbnail_key), true));
ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this,
prefs.getString(getString(R.string.image_quality_key),
getString(R.string.image_quality_default))));
PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
@ -252,4 +263,7 @@ public class App extends Application {
return false;
}
public boolean isFirstRun() {
return isFirstRun;
}
}

View File

@ -12,7 +12,6 @@ import androidx.fragment.app.FragmentManager;
import icepick.Icepick;
import icepick.State;
import leakcanary.AppWatcher;
public abstract class BaseFragment extends Fragment {
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
@ -77,20 +76,33 @@ public abstract class BaseFragment extends Fragment {
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
}
@Override
public void onDestroy() {
super.onDestroy();
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
/**
* This method is called in {@link #onViewCreated(View, Bundle)} to initialize the views.
*
* <p>
* {@link #initListeners()} is called after this method to initialize the corresponding
* listeners.
* </p>
* @param rootView The inflated view for this fragment
* (provided by {@link #onViewCreated(View, Bundle)})
* @param savedInstanceState The saved state of this fragment
* (provided by {@link #onViewCreated(View, Bundle)})
*/
protected void initViews(final View rootView, final Bundle savedInstanceState) {
}
/**
* Initialize the listeners for this fragment.
*
* <p>
* This method is called after {@link #initViews(View, Bundle)}
* in {@link #onViewCreated(View, Bundle)}.
* </p>
*/
protected void initListeners() {
}
@ -108,9 +120,20 @@ public abstract class BaseFragment extends Fragment {
}
}
/**
* Finds the root fragment by looping through all of the parent fragments. The root fragment
* is supposed to be {@link org.schabi.newpipe.fragments.MainFragment}, and is the fragment that
* handles keeping the backstack of opened fragments in NewPipe, and also the player bottom
* sheet. This function therefore returns the fragment manager of said fragment.
*
* @return the fragment manager of the root fragment, i.e.
* {@link org.schabi.newpipe.fragments.MainFragment}
*/
protected FragmentManager getFM() {
return getParentFragment() == null
? getFragmentManager()
: getParentFragment().getFragmentManager();
Fragment current = this;
while (current.getParentFragment() != null) {
current = current.getParentFragment();
}
return current.getFragmentManager();
}
}

View File

@ -44,6 +44,7 @@ import android.widget.FrameLayout;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
@ -51,6 +52,7 @@ import androidx.core.app.ActivityCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentContainerView;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
@ -64,17 +66,20 @@ import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.comments.CommentRepliesFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.settings.UpdateSettingsFragment;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.KioskTranslator;
@ -82,6 +87,7 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PeertubeHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ReleaseVersionUtil;
import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
@ -157,9 +163,17 @@ public class MainActivity extends AppCompatActivity {
}
openMiniPlayerUponPlayerStarted();
// Schedule worker for checking for new streams and creating corresponding notifications
// if this is enabled by the user.
NotificationWorker.initialize(this);
if (PermissionHelper.checkPostNotificationsPermission(this,
PermissionHelper.POST_NOTIFICATIONS_REQUEST_CODE)) {
// Schedule worker for checking for new streams and creating corresponding notifications
// if this is enabled by the user.
NotificationWorker.initialize(this);
}
if (!UpdateSettingsFragment.wasUserAskedForConsent(this)
&& !App.getApp().isFirstRun()
&& ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
UpdateSettingsFragment.askForConsentToUpdateChecks(this);
}
}
@Override
@ -169,10 +183,11 @@ public class MainActivity extends AppCompatActivity {
final App app = App.getApp();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) {
if (prefs.getBoolean(app.getString(R.string.update_app_key), false)
&& prefs.getBoolean(app.getString(R.string.update_check_consent_key), false)) {
// Start the worker which is checking all conditions
// and eventually searching for a new version.
NewVersionWorker.enqueueNewVersionCheckingWork(app);
NewVersionWorker.enqueueNewVersionCheckingWork(app, false);
}
}
@ -216,14 +231,14 @@ public class MainActivity extends AppCompatActivity {
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
int kioskId = 0;
int kioskMenuItemId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
.add(R.id.menu_tabs_group, kioskMenuItemId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks));
kioskId++;
kioskMenuItemId++;
}
drawerLayoutBinding.navigation.getMenu()
@ -232,7 +247,7 @@ public class MainActivity extends AppCompatActivity {
.setIcon(R.drawable.ic_tv);
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
.setIcon(R.drawable.ic_rss_feed);
.setIcon(R.drawable.ic_subscriptions);
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
.setIcon(R.drawable.ic_bookmark);
@ -303,20 +318,16 @@ public class MainActivity extends AppCompatActivity {
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
break;
default:
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
String serviceName = "";
int kioskId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
if (kioskId == item.getItemId()) {
serviceName = ks;
final StreamingService currentService = ServiceHelper.getSelectedService(this);
int kioskMenuItemId = 0;
for (final String kioskId : currentService.getKioskList().getAvailableKiosks()) {
if (kioskMenuItemId == item.getItemId()) {
NavigationHelper.openKioskFragment(getSupportFragmentManager(),
currentService.getServiceId(), kioskId);
break;
}
kioskId++;
kioskMenuItemId++;
}
NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId,
serviceName);
break;
}
}
@ -547,14 +558,21 @@ public class MainActivity extends AppCompatActivity {
// interacts with a fragment inside fragment_holder so all back presses should be
// handled by it
if (bottomSheetHiddenOrCollapsed()) {
final Fragment fragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_holder);
final FragmentManager fm = getSupportFragmentManager();
final Fragment fragment = fm.findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragment instanceof BackPressable) {
if (((BackPressable) fragment).onBackPressed()) {
return;
}
} else if (fragment instanceof CommentRepliesFragment) {
// expand DetailsFragment if CommentRepliesFragment was opened
// to show the top level comments again
// Expand DetailsFragment if CommentRepliesFragment was opened
// and no other CommentRepliesFragments are on top of the back stack
// to show the top level comments again.
openDetailFragmentFromCommentReplies(fm, false);
}
} else {
@ -599,6 +617,9 @@ public class MainActivity extends AppCompatActivity {
((VideoDetailFragment) fragment).openDownloadDialog();
}
break;
case PermissionHelper.POST_NOTIFICATIONS_REQUEST_CODE:
NotificationWorker.initialize(this);
break;
}
}
@ -627,10 +648,17 @@ public class MainActivity extends AppCompatActivity {
* </pre>
*/
private void onHomeButtonPressed() {
// If search fragment wasn't found in the backstack...
if (!NavigationHelper.tryGotoSearchFragment(getSupportFragmentManager())) {
// ...go to the main fragment
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
final FragmentManager fm = getSupportFragmentManager();
final Fragment fragment = fm.findFragmentById(R.id.fragment_holder);
if (fragment instanceof CommentRepliesFragment) {
// Expand DetailsFragment if CommentRepliesFragment was opened
// and no other CommentRepliesFragments are on top of the back stack
// to show the top level comments again.
openDetailFragmentFromCommentReplies(fm, true);
} else if (!NavigationHelper.tryGotoSearchFragment(fm)) {
// If search fragment wasn't found in the backstack go to the main fragment
NavigationHelper.gotoMainFragment(fm);
}
}
@ -826,6 +854,68 @@ public class MainActivity extends AppCompatActivity {
}
}
private void openDetailFragmentFromCommentReplies(
@NonNull final FragmentManager fm,
final boolean popBackStack
) {
// obtain the name of the fragment under the replies fragment that's going to be popped
@Nullable final String fragmentUnderEntryName;
if (fm.getBackStackEntryCount() < 2) {
fragmentUnderEntryName = null;
} else {
fragmentUnderEntryName = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 2)
.getName();
}
// the root comment is the comment for which the user opened the replies page
@Nullable final CommentRepliesFragment repliesFragment =
(CommentRepliesFragment) fm.findFragmentByTag(CommentRepliesFragment.TAG);
@Nullable final CommentsInfoItem rootComment =
repliesFragment == null ? null : repliesFragment.getCommentsInfoItem();
// sometimes this function pops the backstack, other times it's handled by the system
if (popBackStack) {
fm.popBackStackImmediate();
}
// only expand the bottom sheet back if there are no more nested comment replies fragments
// stacked under the one that is currently being popped
if (CommentRepliesFragment.TAG.equals(fragmentUnderEntryName)) {
return;
}
final BottomSheetBehavior<FragmentContainerView> behavior = BottomSheetBehavior
.from(mainBinding.fragmentPlayerHolder);
// do not return to the comment if the details fragment was closed
if (behavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
return;
}
// scroll to the root comment once the bottom sheet expansion animation is finished
behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull final View bottomSheet,
final int newState) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
final Fragment detailFragment = fm.findFragmentById(
R.id.fragment_player_holder);
if (detailFragment instanceof VideoDetailFragment && rootComment != null) {
// should always be the case
((VideoDetailFragment) detailFragment).scrollToComment(rootComment);
}
behavior.removeBottomSheetCallback(this);
}
}
@Override
public void onSlide(@NonNull final View bottomSheet, final float slideOffset) {
// not needed, listener is removed once the sheet is expanded
}
});
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
private boolean bottomSheetHiddenOrCollapsed() {
final BottomSheetBehavior<FrameLayout> bottomSheetBehavior =
BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder);

View File

@ -5,6 +5,10 @@ import static org.schabi.newpipe.database.Migrations.MIGRATION_1_2;
import static org.schabi.newpipe.database.Migrations.MIGRATION_2_3;
import static org.schabi.newpipe.database.Migrations.MIGRATION_3_4;
import static org.schabi.newpipe.database.Migrations.MIGRATION_4_5;
import static org.schabi.newpipe.database.Migrations.MIGRATION_5_6;
import static org.schabi.newpipe.database.Migrations.MIGRATION_6_7;
import static org.schabi.newpipe.database.Migrations.MIGRATION_7_8;
import static org.schabi.newpipe.database.Migrations.MIGRATION_8_9;
import android.content.Context;
import android.database.Cursor;
@ -24,7 +28,8 @@ public final class NewPipeDatabase {
private static AppDatabase getDatabase(final Context context) {
return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5,
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
.build();
}

View File

@ -1,26 +1,26 @@
package org.schabi.newpipe
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.preference.PreferenceManager
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkRequest
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.grack.nanojson.JsonParser
import com.grack.nanojson.JsonParserException
import org.schabi.newpipe.extractor.downloader.Response
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry
import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired
import org.schabi.newpipe.util.ReleaseVersionUtil.isReleaseApk
import org.schabi.newpipe.util.ReleaseVersionUtil
import java.io.IOException
class NewVersionWorker(
@ -42,42 +42,58 @@ class NewVersionWorker(
versionCode: Int
) {
if (BuildConfig.VERSION_CODE >= versionCode) {
if (inputData.getBoolean(IS_MANUAL, false)) {
// Show toast stating that the app is up-to-date if the update check was manual.
ContextCompat.getMainExecutor(applicationContext).execute {
Toast.makeText(
applicationContext, R.string.app_update_unavailable_toast,
Toast.LENGTH_SHORT
).show()
}
}
return
}
val app = App.getApp()
// A pending intent to open the apk location url in the browser.
val intent = Intent(Intent.ACTION_VIEW, apkLocationUrl?.toUri())
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = PendingIntent.getActivity(app, 0, intent, 0)
val channelId = app.getString(R.string.app_update_notification_channel_id)
val notificationBuilder = NotificationCompat.Builder(app, channelId)
val pendingIntent = PendingIntentCompat.getActivity(
applicationContext, 0, intent, 0, false
)
val channelId = applicationContext.getString(R.string.app_update_notification_channel_id)
val notificationBuilder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
.setContentText(
app.getString(R.string.app_update_notification_content_text) +
" " + versionName
.setContentIntent(pendingIntent)
.setContentTitle(
applicationContext.getString(R.string.app_update_available_notification_title)
)
val notificationManager = NotificationManagerCompat.from(app)
.setContentText(
applicationContext.getString(
R.string.app_update_available_notification_text, versionName
)
)
val notificationManager = NotificationManagerCompat.from(applicationContext)
notificationManager.notify(2000, notificationBuilder.build())
}
@Throws(IOException::class, ReCaptchaException::class)
private fun checkNewVersion() {
// Check if the current apk is a github one or not.
if (!isReleaseApk()) {
if (!ReleaseVersionUtil.isReleaseApk) {
return
}
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
// Check if the last request has happened a certain time ago
// to reduce the number of API requests.
val expiry = prefs.getLong(applicationContext.getString(R.string.update_expiry_key), 0)
if (!isLastUpdateCheckExpired(expiry)) {
return
if (!inputData.getBoolean(IS_MANUAL, false)) {
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
// Check if the last request has happened a certain time ago
// to reduce the number of API requests.
val expiry = prefs.getLong(applicationContext.getString(R.string.update_expiry_key), 0)
if (!ReleaseVersionUtil.isLastUpdateCheckExpired(expiry)) {
return
}
}
// Make a network request to get latest NewPipe data.
@ -90,7 +106,7 @@ class NewVersionWorker(
try {
// Store a timestamp which needs to be exceeded,
// before a new request to the API is made.
val newExpiry = coerceUpdateCheckExpiry(response.getHeader("expires"))
val newExpiry = ReleaseVersionUtil.coerceUpdateCheckExpiry(response.getHeader("expires"))
prefs.edit {
putLong(applicationContext.getString(R.string.update_expiry_key), newExpiry)
}
@ -102,13 +118,13 @@ class NewVersionWorker(
// Parse the json from the response.
try {
val githubStableObject = JsonParser.`object`()
val newpipeVersionInfo = JsonParser.`object`()
.from(response.responseBody()).getObject("flavors")
.getObject("github").getObject("stable")
.getObject("newpipe")
val versionName = githubStableObject.getString("version")
val versionCode = githubStableObject.getInt("version_code")
val apkLocationUrl = githubStableObject.getString("apk")
val versionName = newpipeVersionInfo.getString("version")
val versionCode = newpipeVersionInfo.getInt("version_code")
val apkLocationUrl = newpipeVersionInfo.getString("apk")
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode)
} catch (e: JsonParserException) {
// Most likely something is wrong in data received from NEWPIPE_API_URL.
@ -120,43 +136,42 @@ class NewVersionWorker(
}
override fun doWork(): Result {
try {
return try {
checkNewVersion()
Result.success()
} catch (e: IOException) {
Log.w(TAG, "Could not fetch NewPipe API: probably network problem", e)
return Result.failure()
Result.failure()
} catch (e: ReCaptchaException) {
Log.e(TAG, "ReCaptchaException should never happen here.", e)
return Result.failure()
Result.failure()
}
return Result.success()
}
companion object {
private val DEBUG = MainActivity.DEBUG
private val TAG = NewVersionWorker::class.java.simpleName
private const val NEWPIPE_API_URL = "https://newpipe.net/api/data.json"
private const val IS_MANUAL = "isManual"
/**
* Start a new worker which
* checks if all conditions for performing a version check are met,
* fetches the API endpoint [.NEWPIPE_API_URL] containing info
* about the latest NewPipe version
* and displays a notification about ana available update.
* Start a new worker which checks if all conditions for performing a version check are met,
* fetches the API endpoint [.NEWPIPE_API_URL] containing info about the latest NewPipe
* version and displays a notification about an available update if one is available.
* <br></br>
* Following conditions need to be met, before data is request from the server:
* Following conditions need to be met, before data is requested from the server:
*
* * The app is signed with the correct signing key (by TeamNewPipe / schabi).
* If the signing key differs from the one used upstream, the update cannot be installed.
* * The user enabled searching for and notifying about updates in the settings.
* * The app did not recently check for updates.
* We do not want to make unnecessary connections and DOS our servers.
*
*/
@JvmStatic
fun enqueueNewVersionCheckingWork(context: Context) {
val workRequest: WorkRequest =
OneTimeWorkRequest.Builder(NewVersionWorker::class.java).build()
fun enqueueNewVersionCheckingWork(context: Context, isManual: Boolean) {
val workRequest = OneTimeWorkRequestBuilder<NewVersionWorker>()
.setInputData(workDataOf(IS_MANUAL to isManual))
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
}

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe;
import static org.schabi.newpipe.util.SparseItemUtil.fetchStreamInfoAndSaveToDatabase;
import static org.schabi.newpipe.util.external_communication.ShareUtils.shareText;
import android.content.Context;
@ -10,6 +11,7 @@ import android.widget.PopupMenu;
import androidx.fragment.app.FragmentManager;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@ -73,7 +75,15 @@ public final class QueueItemMenuUtil {
return true;
case R.id.menu_item_share:
shareText(context, item.getTitle(), item.getUrl(),
item.getThumbnailUrl());
item.getThumbnails());
return true;
case R.id.menu_item_download:
fetchStreamInfoAndSaveToDatabase(context, item.getServiceId(), item.getUrl(),
info -> {
final DownloadDialog downloadDialog = new DownloadDialog(context,
info);
downloadDialog.show(fragmentManager, "downloadDialog");
});
return true;
}
return false;

View File

@ -10,12 +10,14 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.RadioButton;
import android.widget.RadioGroup;
@ -31,13 +33,19 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
import androidx.core.math.MathUtils;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.download.LoadingDialog;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.ReCaptchaActivity;
@ -57,6 +65,7 @@ import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.ktx.ExceptionUtils;
@ -64,10 +73,11 @@ import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@ -80,9 +90,13 @@ import org.schabi.newpipe.util.urlfinder.UrlFinder;
import org.schabi.newpipe.views.FocusOverlayView;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import icepick.Icepick;
import icepick.State;
@ -91,7 +105,6 @@ import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
@ -111,12 +124,57 @@ public class RouterActivity extends AppCompatActivity {
private boolean selectionIsDownload = false;
private boolean selectionIsAddToPlaylist = false;
private AlertDialog alertDialogChoice = null;
private FragmentManager.FragmentLifecycleCallbacks dismissListener = null;
@Override
protected void onCreate(final Bundle savedInstanceState) {
ThemeHelper.setDayNightMode(this);
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
Localization.assureCorrectAppLanguage(this);
// Pass-through touch events to background activities
// so that our transparent window won't lock UI in the mean time
// network request is underway before showing PlaylistDialog or DownloadDialog
// (ref: https://stackoverflow.com/a/10606141)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
// Android never fails to impress us with a list of new restrictions per API.
// Starting with S (Android 12) one of the prerequisite conditions has to be met
// before the FLAG_NOT_TOUCHABLE flag is allowed to kick in:
// @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE
// For our present purpose it seems we can just set LayoutParams.alpha to 0
// on the strength of "4. Fully transparent windows" without affecting the scrim of dialogs
final WindowManager.LayoutParams params = getWindow().getAttributes();
params.alpha = 0f;
getWindow().setAttributes(params);
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
// FragmentManager will take care to recreate (Playlist|Download)Dialog when screen rotates
// We used to .setOnDismissListener(dialog -> finish()); when creating these DialogFragments
// but those callbacks won't survive a config change
// Try an alternate approach to hook into FragmentManager instead, to that effect
// (ref: https://stackoverflow.com/a/44028453)
final FragmentManager fm = getSupportFragmentManager();
if (dismissListener == null) {
dismissListener = new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentDestroyed(@NonNull final FragmentManager fm,
@NonNull final Fragment f) {
super.onFragmentDestroyed(fm, f);
if (f instanceof DialogFragment && fm.getFragments().isEmpty()) {
// No more DialogFragments, we're done
finish();
}
}
};
}
fm.registerFragmentLifecycleCallbacks(dismissListener, false);
if (TextUtils.isEmpty(currentUrl)) {
currentUrl = getUrl(getIntent());
@ -125,11 +183,6 @@ public class RouterActivity extends AppCompatActivity {
finish();
}
}
ThemeHelper.setDayNightMode(this);
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
Localization.assureCorrectAppLanguage(this);
}
@Override
@ -151,16 +204,34 @@ public class RouterActivity extends AppCompatActivity {
protected void onStart() {
super.onStart();
handleUrl(currentUrl);
// Don't overlap the DialogFragment after rotating the screen
// If there's no DialogFragment, we're either starting afresh
// or we didn't make it to PlaylistDialog or DownloadDialog before the orientation change
if (getSupportFragmentManager().getFragments().isEmpty()) {
// Start over from scratch
handleUrl(currentUrl);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (dismissListener != null) {
getSupportFragmentManager().unregisterFragmentLifecycleCallbacks(dismissListener);
}
disposables.clear();
}
@Override
public void finish() {
// allow the activity to recreate in case orientation changes
if (!isChangingConfigurations()) {
super.finish();
}
}
private void handleUrl(final String url) {
disposables.add(Observable
.fromCallable(() -> {
@ -240,7 +311,7 @@ public class RouterActivity extends AppCompatActivity {
}
}
private void showUnsupportedUrlDialog(final String url) {
protected void showUnsupportedUrlDialog(final String url) {
final Context context = getThemeWrapperContext();
new AlertDialog.Builder(context)
.setTitle(R.string.unsupported_url)
@ -527,7 +598,7 @@ public class RouterActivity extends AppCompatActivity {
return returnedItems;
}
private Context getThemeWrapperContext() {
protected Context getThemeWrapperContext() {
return new ContextThemeWrapper(this, ThemeHelper.isLightThemeSelected(this)
? R.style.LightTheme : R.style.DarkTheme);
}
@ -563,8 +634,7 @@ public class RouterActivity extends AppCompatActivity {
}
if (selectedChoiceKey.equals(getString(R.string.popup_player_key))
&& !PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
&& !PermissionHelper.isPopupEnabledElseAsk(this)) {
finish();
return;
}
@ -634,54 +704,184 @@ public class RouterActivity extends AppCompatActivity {
return playerType == null || playerType == PlayerType.MAIN;
}
private void openAddToPlaylistDialog() {
// Getting the stream info usually takes a moment
// Notifying the user here to ensure that no confusion arises
Toast.makeText(
getApplicationContext(),
getString(R.string.processing_may_take_a_moment),
Toast.LENGTH_SHORT)
.show();
public static class PersistentFragment extends Fragment {
private WeakReference<AppCompatActivity> weakContext;
private final CompositeDisposable disposables = new CompositeDisposable();
private int running = 0;
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
info -> PlaylistDialog.createCorrespondingDialog(
getThemeWrapperContext(),
List.of(new StreamEntity(info)),
playlistDialog -> {
playlistDialog.setOnDismissListener(dialog -> finish());
private synchronized void inFlight(final boolean started) {
if (started) {
running++;
} else {
running--;
if (running <= 0) {
getActivityContext().ifPresent(context -> context.getSupportFragmentManager()
.beginTransaction().remove(this).commit());
}
}
}
playlistDialog.show(
this.getSupportFragmentManager(),
"addToPlaylistDialog"
);
}
),
throwable -> handleError(this, new ErrorInfo(
throwable,
UserAction.REQUESTED_STREAM,
"Tried to add " + currentUrl + " to a playlist",
currentService.getServiceId())
)
)
);
@Override
public void onAttach(@NonNull final Context activityContext) {
super.onAttach(activityContext);
weakContext = new WeakReference<>((AppCompatActivity) activityContext);
}
@Override
public void onDetach() {
super.onDetach();
weakContext = null;
}
@SuppressWarnings("deprecation")
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onDestroy() {
super.onDestroy();
disposables.clear();
}
/**
* @return the activity context, if there is one and the activity is not finishing
*/
private Optional<AppCompatActivity> getActivityContext() {
return Optional.ofNullable(weakContext)
.map(Reference::get)
.filter(context -> !context.isFinishing());
}
// guard against IllegalStateException in calling DialogFragment.show() whilst in background
// (which could happen, say, when the user pressed the home button while waiting for
// the network request to return) when it internally calls FragmentTransaction.commit()
// after the FragmentManager has saved its states (isStateSaved() == true)
// (ref: https://stackoverflow.com/a/39813506)
private void runOnVisible(final Consumer<AppCompatActivity> runnable) {
getActivityContext().ifPresentOrElse(context -> {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
context.runOnUiThread(() -> {
runnable.accept(context);
inFlight(false);
});
} else {
getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onResume(@NonNull final LifecycleOwner owner) {
getLifecycle().removeObserver(this);
getActivityContext().ifPresentOrElse(context ->
context.runOnUiThread(() -> {
runnable.accept(context);
inFlight(false);
}),
() -> inFlight(false)
);
}
});
// this trick doesn't seem to work on Android 10+ (API 29)
// which places restrictions on starting activities from the background
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
&& !context.isChangingConfigurations()) {
// try to bring the activity back to front if minimised
final Intent i = new Intent(context, RouterActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(i);
}
}
}, () ->
// this branch is executed if there is no activity context
inFlight(false)
);
}
<T> Single<T> pleaseWait(final Single<T> single) {
// 'abuse' ambWith() here to cancel the toast for us when the wait is over
return single.ambWith(Single.create(emitter -> getActivityContext().ifPresent(context ->
context.runOnUiThread(() -> {
// Getting the stream info usually takes a moment
// Notifying the user here to ensure that no confusion arises
final Toast toast = Toast.makeText(context,
getString(R.string.processing_may_take_a_moment),
Toast.LENGTH_LONG);
toast.show();
emitter.setCancellable(toast::cancel);
}))));
}
@SuppressLint("CheckResult")
private void openDownloadDialog(final int currentServiceId, final String currentUrl) {
inFlight(true);
final LoadingDialog loadingDialog = new LoadingDialog(R.string.loading_metadata_title);
loadingDialog.show(getParentFragmentManager(), "loadingDialog");
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this::pleaseWait)
.subscribe(result ->
runOnVisible(ctx -> {
loadingDialog.dismiss();
final FragmentManager fm = ctx.getSupportFragmentManager();
final DownloadDialog downloadDialog = new DownloadDialog(ctx, result);
// dismiss listener to be handled by FragmentManager
downloadDialog.show(fm, "downloadDialog");
}
), throwable -> runOnVisible(ctx -> {
loadingDialog.dismiss();
((RouterActivity) ctx).showUnsupportedUrlDialog(currentUrl);
})));
}
private void openAddToPlaylistDialog(final int currentServiceId, final String currentUrl) {
inFlight(true);
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this::pleaseWait)
.subscribe(
info -> getActivityContext().ifPresent(context ->
PlaylistDialog.createCorrespondingDialog(context,
List.of(new StreamEntity(info)),
playlistDialog -> runOnVisible(ctx -> {
// dismiss listener to be handled by FragmentManager
final FragmentManager fm =
ctx.getSupportFragmentManager();
playlistDialog.show(fm, "addToPlaylistDialog");
})
)),
throwable -> runOnVisible(ctx -> handleError(ctx, new ErrorInfo(
throwable,
UserAction.REQUESTED_STREAM,
"Tried to add " + currentUrl + " to a playlist",
((RouterActivity) ctx).currentService.getServiceId())
))
)
);
}
}
@SuppressLint("CheckResult")
private void openDownloadDialog() {
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
final DownloadDialog downloadDialog = new DownloadDialog(this, result);
downloadDialog.setOnDismissListener(dialog -> finish());
private void openAddToPlaylistDialog() {
getPersistFragment().openAddToPlaylistDialog(currentServiceId, currentUrl);
}
final FragmentManager fm = getSupportFragmentManager();
downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions();
}, throwable -> showUnsupportedUrlDialog(currentUrl)));
private void openDownloadDialog() {
getPersistFragment().openDownloadDialog(currentServiceId, currentUrl);
}
private PersistentFragment getPersistFragment() {
final FragmentManager fm = getSupportFragmentManager();
PersistentFragment persistFragment =
(PersistentFragment) fm.findFragmentByTag("PERSIST_FRAGMENT");
if (persistFragment == null) {
persistFragment = new PersistentFragment();
fm.beginTransaction()
.add(persistFragment, "PERSIST_FRAGMENT")
.commitNow();
}
return persistFragment;
}
@Override
@ -824,7 +1024,16 @@ public class RouterActivity extends AppCompatActivity {
}
playQueue = new SinglePlayQueue((StreamInfo) info);
} else if (info instanceof ChannelInfo) {
playQueue = new ChannelPlayQueue((ChannelInfo) info);
final Optional<ListLinkHandler> playableTab = ((ChannelInfo) info).getTabs()
.stream()
.filter(ChannelTabHelper::isStreamsTab)
.findFirst();
if (playableTab.isPresent()) {
playQueue = new ChannelTabPlayQueue(info.getServiceId(), playableTab.get());
} else {
return; // there is no playable tab
}
} else if (info instanceof PlaylistInfo) {
playQueue = new PlaylistPlayQueue((PlaylistInfo) info);
} else {

View File

@ -6,6 +6,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
@ -57,13 +58,9 @@ class AboutActivity : AppCompatActivity() {
* A placeholder fragment containing a simple view.
*/
class AboutFragment : Fragment() {
private fun Button.openLink(url: Int) {
private fun Button.openLink(@StringRes url: Int) {
setOnClickListener {
ShareUtils.openUrlInBrowser(
context,
requireContext().getString(url),
false
)
ShareUtils.openUrlInApp(context, requireContext().getString(url))
}
}
@ -119,7 +116,7 @@ class AboutActivity : AppCompatActivity() {
/**
* List of all software components.
*/
private val SOFTWARE_COMPONENTS = arrayOf(
private val SOFTWARE_COMPONENTS = arrayListOf(
SoftwareComponent(
"ACRA", "2013", "Kevin Gaudin",
"https://github.com/ACRA/acra", StandardLicenses.APACHE2

View File

@ -1,30 +1,40 @@
package org.schabi.newpipe.about
import android.os.Bundle
import android.util.Base64
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.FragmentLicensesBinding
import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding
import org.schabi.newpipe.ktx.parcelableArrayList
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.external_communication.ShareUtils
/**
* Fragment containing the software licenses.
*/
class LicenseFragment : Fragment() {
private lateinit var softwareComponents: Array<SoftwareComponent>
private var activeLicense: License? = null
private lateinit var softwareComponents: List<SoftwareComponent>
private var activeSoftwareComponent: SoftwareComponent? = null
private val compositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
softwareComponents = arguments?.getParcelableArray(ARG_COMPONENTS) as Array<SoftwareComponent>
activeLicense = savedInstanceState?.getSerializable(LICENSE_KEY) as? License
// Sort components by name
softwareComponents.sortBy { it.name }
softwareComponents = arguments?.parcelableArrayList<SoftwareComponent>(ARG_COMPONENTS)!!
.sortedBy { it.name } // Sort components by name
activeSoftwareComponent = savedInstanceState?.getSerializable(SOFTWARE_COMPONENT_KEY) as? SoftwareComponent
}
override fun onDestroy() {
@ -39,9 +49,8 @@ class LicenseFragment : Fragment() {
): View {
val binding = FragmentLicensesBinding.inflate(inflater, container, false)
binding.licensesAppReadLicense.setOnClickListener {
activeLicense = StandardLicenses.GPL3
compositeDisposable.add(
showLicense(activity, StandardLicenses.GPL3)
showLicense(NEWPIPE_SOFTWARE_COMPONENT)
)
}
for (component in softwareComponents) {
@ -57,27 +66,72 @@ class LicenseFragment : Fragment() {
val root: View = componentBinding.root
root.tag = component
root.setOnClickListener {
activeLicense = component.license
compositeDisposable.add(
showLicense(activity, component)
showLicense(component)
)
}
binding.licensesSoftwareComponents.addView(root)
registerForContextMenu(root)
}
activeLicense?.let { compositeDisposable.add(showLicense(activity, it)) }
activeSoftwareComponent?.let { compositeDisposable.add(showLicense(it)) }
return binding.root
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
activeLicense?.let { savedInstanceState.putSerializable(LICENSE_KEY, it) }
activeSoftwareComponent?.let { savedInstanceState.putSerializable(SOFTWARE_COMPONENT_KEY, it) }
}
private fun showLicense(
softwareComponent: SoftwareComponent
): Disposable {
return if (context == null) {
Disposable.empty()
} else {
val context = requireContext()
activeSoftwareComponent = softwareComponent
Observable.fromCallable { getFormattedLicense(context, softwareComponent.license) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { formattedLicense ->
val webViewData = Base64.encodeToString(
formattedLicense.toByteArray(), Base64.NO_PADDING
)
val webView = WebView(context)
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64")
Localization.assureCorrectAppLanguage(context)
val builder = AlertDialog.Builder(requireContext())
.setTitle(softwareComponent.name)
.setView(webView)
.setOnCancelListener { activeSoftwareComponent = null }
.setOnDismissListener { activeSoftwareComponent = null }
.setPositiveButton(R.string.done) { dialog, _ -> dialog.dismiss() }
if (softwareComponent != NEWPIPE_SOFTWARE_COMPONENT) {
builder.setNeutralButton(R.string.open_website_license) { _, _ ->
ShareUtils.openUrlInApp(requireContext(), softwareComponent.link)
}
}
builder.show()
}
}
}
companion object {
private const val ARG_COMPONENTS = "components"
private const val LICENSE_KEY = "ACTIVE_LICENSE"
fun newInstance(softwareComponents: Array<SoftwareComponent>): LicenseFragment {
private const val SOFTWARE_COMPONENT_KEY = "ACTIVE_SOFTWARE_COMPONENT"
private val NEWPIPE_SOFTWARE_COMPONENT = SoftwareComponent(
"NewPipe",
"2014-2023",
"Team NewPipe",
"https://newpipe.net/",
StandardLicenses.GPL3,
BuildConfig.VERSION_NAME
)
fun newInstance(softwareComponents: ArrayList<SoftwareComponent>): LicenseFragment {
val fragment = LicenseFragment()
fragment.arguments = bundleOf(ARG_COMPONENTS to softwareComponents)
return fragment

View File

@ -1,17 +1,8 @@
package org.schabi.newpipe.about
import android.content.Context
import android.util.Base64
import android.webkit.WebView
import androidx.appcompat.app.AlertDialog
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.R
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ThemeHelper
import org.schabi.newpipe.util.external_communication.ShareUtils
import java.io.IOException
/**
@ -20,7 +11,7 @@ import java.io.IOException
* @return String which contains a HTML formatted license page
* styled according to the context's theme
*/
private fun getFormattedLicense(context: Context, license: License): String {
fun getFormattedLicense(context: Context, license: License): String {
try {
return context.assets.open(license.filename).bufferedReader().use { it.readText() }
// split the HTML file and insert the stylesheet into the HEAD of the file
@ -34,7 +25,7 @@ private fun getFormattedLicense(context: Context, license: License): String {
* @param context the Android context
* @return String which is a CSS stylesheet according to the context's theme
*/
private fun getLicenseStylesheet(context: Context): String {
fun getLicenseStylesheet(context: Context): String {
val isLightTheme = ThemeHelper.isLightThemeSelected(context)
val licenseBackgroundColor = getHexRGBColor(
context, if (isLightTheme) R.color.light_license_background_color else R.color.dark_license_background_color
@ -56,48 +47,6 @@ private fun getLicenseStylesheet(context: Context): String {
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
private fun getHexRGBColor(context: Context, color: Int): String {
fun getHexRGBColor(context: Context, color: Int): String {
return context.getString(color).substring(3)
}
fun showLicense(context: Context?, component: SoftwareComponent): Disposable {
return showLicense(context, component.license) {
setPositiveButton(R.string.dismiss) { dialog, _ ->
dialog.dismiss()
}
setNeutralButton(R.string.open_website_license) { _, _ ->
ShareUtils.openUrlInBrowser(context!!, component.link)
}
}
}
fun showLicense(context: Context?, license: License) = showLicense(context, license) {
setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() }
}
private fun showLicense(
context: Context?,
license: License,
block: AlertDialog.Builder.() -> AlertDialog.Builder
): Disposable {
return if (context == null) {
Disposable.empty()
} else {
Observable.fromCallable { getFormattedLicense(context, license) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { formattedLicense ->
val webViewData =
Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING)
val webView = WebView(context)
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64")
Localization.assureCorrectAppLanguage(context)
AlertDialog.Builder(context)
.setTitle(license.name)
.setView(webView)
.block()
.show()
}
}
}

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.about
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.io.Serializable
@Parcelize
class SoftwareComponent
@ -13,4 +14,4 @@ constructor(
val link: String,
val license: License,
val version: String? = null
) : Parcelable
) : Parcelable, Serializable

View File

@ -1,6 +1,6 @@
package org.schabi.newpipe.database;
import static org.schabi.newpipe.database.Migrations.DB_VER_5;
import static org.schabi.newpipe.database.Migrations.DB_VER_9;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@ -38,7 +38,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
FeedLastUpdatedEntity.class
},
version = DB_VER_5
version = DB_VER_9
)
public abstract class AppDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "newpipe.db";

View File

@ -7,7 +7,7 @@ import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset
object Converters {
class Converters {
/**
* Convert a long value to a [OffsetDateTime].
*
@ -47,6 +47,6 @@ object Converters {
@TypeConverter
fun feedGroupIconOf(id: Int): FeedGroupIcon {
return FeedGroupIcon.values().first { it.id == id }
return FeedGroupIcon.entries.first { it.id == id }
}
}

View File

@ -23,6 +23,10 @@ public final class Migrations {
public static final int DB_VER_3 = 3;
public static final int DB_VER_4 = 4;
public static final int DB_VER_5 = 5;
public static final int DB_VER_6 = 6;
public static final int DB_VER_7 = 7;
public static final int DB_VER_8 = 8;
public static final int DB_VER_9 = 9;
private static final String TAG = Migrations.class.getName();
public static final boolean DEBUG = MainActivity.DEBUG;
@ -184,7 +188,117 @@ public final class Migrations {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `subscriptions` ADD COLUMN `notification_mode` "
+ "INTEGER NOT NULL DEFAULT 0");
+ "INTEGER NOT NULL DEFAULT 0");
}
};
public static final Migration MIGRATION_5_6 = new Migration(DB_VER_5, DB_VER_6) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `playlists` ADD COLUMN `is_thumbnail_permanent` "
+ "INTEGER NOT NULL DEFAULT 0");
}
};
public static final Migration MIGRATION_6_7 = new Migration(DB_VER_6, DB_VER_7) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
// Create a new column thumbnail_stream_id
database.execSQL("ALTER TABLE `playlists` ADD COLUMN `thumbnail_stream_id` "
+ "INTEGER NOT NULL DEFAULT -1");
// Migrate the thumbnail_url to the thumbnail_stream_id
database.execSQL("UPDATE playlists SET thumbnail_stream_id = ("
+ " SELECT CASE WHEN COUNT(*) != 0 then stream_uid ELSE -1 END"
+ " FROM ("
+ " SELECT p.uid AS playlist_uid, s.uid AS stream_uid"
+ " FROM playlists p"
+ " LEFT JOIN playlist_stream_join ps ON p.uid = ps.playlist_id"
+ " LEFT JOIN streams s ON s.uid = ps.stream_id"
+ " WHERE s.thumbnail_url = p.thumbnail_url) AS temporary_table"
+ " WHERE playlist_uid = playlists.uid)");
// Remove the thumbnail_url field in the playlist table
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists_new`"
+ "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "name TEXT, "
+ "is_thumbnail_permanent INTEGER NOT NULL, "
+ "thumbnail_stream_id INTEGER NOT NULL)");
database.execSQL("INSERT INTO playlists_new"
+ " SELECT uid, name, is_thumbnail_permanent, thumbnail_stream_id "
+ " FROM playlists");
database.execSQL("DROP TABLE playlists");
database.execSQL("ALTER TABLE playlists_new RENAME TO playlists");
database.execSQL("CREATE INDEX IF NOT EXISTS "
+ "`index_playlists_name` ON `playlists` (`name`)");
}
};
public static final Migration MIGRATION_7_8 = new Migration(DB_VER_7, DB_VER_8) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("DELETE FROM search_history WHERE id NOT IN (SELECT id FROM (SELECT "
+ "MIN(id) as id FROM search_history GROUP BY trim(search), service_id ) tmp)");
database.execSQL("UPDATE search_history SET search = trim(search)");
}
};
public static final Migration MIGRATION_8_9 = new Migration(DB_VER_8, DB_VER_9) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
try {
database.beginTransaction();
// Update playlists.
// Create a temp table to initialize display_index.
database.execSQL("CREATE TABLE `playlists_tmp` "
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "`name` TEXT, `is_thumbnail_permanent` INTEGER NOT NULL, "
+ "`thumbnail_stream_id` INTEGER NOT NULL, "
+ "`display_index` INTEGER NOT NULL)");
database.execSQL("INSERT INTO `playlists_tmp` "
+ "(`uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id`, "
+ "`display_index`) "
+ "SELECT `uid`, `name`, `is_thumbnail_permanent`, `thumbnail_stream_id`, "
+ "-1 "
+ "FROM `playlists`");
// Replace the old table, note that this also removes the index on the name which
// we don't need anymore.
database.execSQL("DROP TABLE `playlists`");
database.execSQL("ALTER TABLE `playlists_tmp` RENAME TO `playlists`");
// Update remote_playlists.
// Create a temp table to initialize display_index.
database.execSQL("CREATE TABLE `remote_playlists_tmp` "
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
+ "`thumbnail_url` TEXT, `uploader` TEXT, "
+ "`display_index` INTEGER NOT NULL,"
+ "`stream_count` INTEGER)");
database.execSQL("INSERT INTO `remote_playlists_tmp` (`uid`, `service_id`, "
+ "`name`, `url`, `thumbnail_url`, `uploader`, `display_index`, "
+ "`stream_count`)"
+ "SELECT `uid`, `service_id`, `name`, `url`, `thumbnail_url`, `uploader`, "
+ "-1, `stream_count` FROM `remote_playlists`");
// Replace the old table, note that this also removes the index on the name which
// we don't need anymore.
database.execSQL("DROP TABLE `remote_playlists`");
database.execSQL("ALTER TABLE `remote_playlists_tmp` RENAME TO `remote_playlists`");
// Create index on the new table.
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
+ "ON `remote_playlists` (`service_id`, `url`)");
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}
};

View File

@ -32,6 +32,7 @@ abstract class FeedDAO {
* @return the feed streams filtered according to the conditions provided in the parameters
* @see StreamStateEntity.isFinished()
* @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS
* @see StreamStateEntity.PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS
*/
@Query(
"""
@ -66,6 +67,15 @@ abstract class FeedDAO {
OR s.stream_type = 'LIVE_STREAM'
OR s.stream_type = 'AUDIO_LIVE_STREAM'
)
AND (
:includePartiallyPlayed
OR sh.stream_id IS NULL
OR sst.stream_id IS NULL
OR (sst.progress_time <= ${StreamStateEntity.PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS}
AND sst.progress_time <= s.duration * 1000 / 4)
OR (sst.progress_time >= s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS}
AND sst.progress_time >= s.duration * 1000 * 3 / 4)
)
AND (
:uploadDateBefore IS NULL
OR s.upload_date IS NULL
@ -79,21 +89,34 @@ abstract class FeedDAO {
abstract fun getStreams(
groupId: Long,
includePlayed: Boolean,
includePartiallyPlayed: Boolean,
uploadDateBefore: OffsetDateTime?
): Maybe<List<StreamWithState>>
/**
* Remove links to streams that are older than the given date
* **but keep at least one stream per uploader**.
*
* One stream per uploader is kept because it is needed as reference
* when fetching new streams to check if they are new or not.
* @param offsetDateTime the newest date to keep, older streams are removed
*/
@Query(
"""
DELETE FROM feed WHERE
feed.stream_id IN (
SELECT s.uid FROM streams s
INNER JOIN feed f
ON s.uid = f.stream_id
WHERE s.upload_date < :offsetDateTime
)
DELETE FROM feed
WHERE feed.stream_id IN (SELECT uid from (
SELECT s.uid,
(SELECT MAX(upload_date)
FROM streams s1
INNER JOIN feed f1
ON s1.uid = f1.stream_id
WHERE f1.subscription_id = f.subscription_id) max_upload_date
FROM streams s
INNER JOIN feed f
ON s.uid = f.stream_id
WHERE s.upload_date < :offsetDateTime
AND s.upload_date <> max_upload_date))
"""
)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)

View File

@ -3,7 +3,6 @@ package org.schabi.newpipe.database.feed.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.ForeignKey.CASCADE
import androidx.room.Index
import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity.Companion.FEED_GROUP_SUBSCRIPTION_TABLE
import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity.Companion.GROUP_ID
@ -19,14 +18,14 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
entity = FeedGroupEntity::class,
parentColumns = [FeedGroupEntity.ID],
childColumns = [GROUP_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
),
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = CASCADE, onUpdate = CASCADE, deferred = true
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
)
]
)

View File

@ -0,0 +1,29 @@
package org.schabi.newpipe.database.playlist;
import androidx.room.ColumnInfo;
/**
* This class adds a field to {@link PlaylistMetadataEntry} that contains an integer representing
* how many times a specific stream is already contained inside a local playlist. Used to be able
* to grey out playlists which already contain the current stream in the playlist append dialog.
* @see org.schabi.newpipe.local.playlist.LocalPlaylistManager#getPlaylistDuplicates(String)
*/
public class PlaylistDuplicatesEntry extends PlaylistMetadataEntry {
public static final String PLAYLIST_TIMES_STREAM_IS_CONTAINED = "timesStreamIsContained";
@ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED)
public final long timesStreamIsContained;
@SuppressWarnings("checkstyle:ParameterNumber")
public PlaylistDuplicatesEntry(final long uid,
final String name,
final String thumbnailUrl,
final boolean isThumbnailPermanent,
final long thumbnailStreamId,
final long displayIndex,
final long streamCount,
final long timesStreamIsContained) {
super(uid, name, thumbnailUrl, isThumbnailPermanent, thumbnailStreamId, displayIndex,
streamCount);
this.timesStreamIsContained = timesStreamIsContained;
}
}

View File

@ -1,22 +1,13 @@
package org.schabi.newpipe.database.playlist;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public interface PlaylistLocalItem extends LocalItem {
String getOrderingName();
static List<PlaylistLocalItem> merge(
final List<PlaylistMetadataEntry> localPlaylists,
final List<PlaylistRemoteEntity> remotePlaylists) {
return Stream.concat(localPlaylists.stream(), remotePlaylists.stream())
.sorted(Comparator.comparing(PlaylistLocalItem::getOrderingName,
Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)))
.collect(Collectors.toList());
}
long getDisplayIndex();
long getUid();
void setDisplayIndex(long displayIndex);
}

View File

@ -2,27 +2,40 @@ package org.schabi.newpipe.database.playlist;
import androidx.room.ColumnInfo;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_DISPLAY_INDEX;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_PERMANENT;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
public class PlaylistMetadataEntry implements PlaylistLocalItem {
public static final String PLAYLIST_STREAM_COUNT = "streamCount";
@ColumnInfo(name = PLAYLIST_ID)
public final long uid;
private final long uid;
@ColumnInfo(name = PLAYLIST_NAME)
public final String name;
@ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT)
private final boolean isThumbnailPermanent;
@ColumnInfo(name = PLAYLIST_THUMBNAIL_STREAM_ID)
private final long thumbnailStreamId;
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
public final String thumbnailUrl;
@ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
private long displayIndex;
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
public final long streamCount;
public PlaylistMetadataEntry(final long uid, final String name, final String thumbnailUrl,
final long streamCount) {
final boolean isThumbnailPermanent, final long thumbnailStreamId,
final long displayIndex, final long streamCount) {
this.uid = uid;
this.name = name;
this.thumbnailUrl = thumbnailUrl;
this.isThumbnailPermanent = isThumbnailPermanent;
this.thumbnailStreamId = thumbnailStreamId;
this.displayIndex = displayIndex;
this.streamCount = streamCount;
}
@ -35,4 +48,27 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
public String getOrderingName() {
return name;
}
public boolean isThumbnailPermanent() {
return isThumbnailPermanent;
}
public long getThumbnailStreamId() {
return thumbnailStreamId;
}
@Override
public long getDisplayIndex() {
return displayIndex;
}
@Override
public long getUid() {
return uid;
}
@Override
public void setDisplayIndex(final long displayIndex) {
this.displayIndex = displayIndex;
}
}

View File

@ -7,6 +7,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
data class PlaylistStreamEntry(
@Embedded
@ -28,7 +29,7 @@ data class PlaylistStreamEntry(
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
return item
}

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.database.playlist.dao;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Transaction;
import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
@ -36,4 +37,17 @@ public interface PlaylistDAO extends BasicDAO<PlaylistEntity> {
@Query("SELECT COUNT(*) FROM " + PLAYLIST_TABLE)
Flowable<Long> getCount();
@Transaction
default long upsertPlaylist(final PlaylistEntity playlist) {
final long playlistId = playlist.getUid();
if (playlistId == -1) {
// This situation is probably impossible.
return insert(playlist);
} else {
update(playlist);
return playlistId;
}
}
}

View File

@ -11,6 +11,7 @@ import java.util.List;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_DISPLAY_INDEX;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_SERVICE_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_TABLE;
@ -31,10 +32,18 @@ public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
+ " WHERE " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
Flowable<List<PlaylistRemoteEntity>> listByService(int serviceId);
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+ REMOTE_PLAYLIST_ID + " = :playlistId")
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId);
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+ REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long serviceId, String url);
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
+ " ORDER BY " + REMOTE_PLAYLIST_DISPLAY_INDEX)
Flowable<List<PlaylistRemoteEntity>> getPlaylists();
@Query("SELECT " + REMOTE_PLAYLIST_ID + " FROM " + REMOTE_PLAYLIST_TABLE
+ " WHERE " + REMOTE_PLAYLIST_URL + " = :url "
+ "AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")

View File

@ -6,18 +6,25 @@ import androidx.room.RewriteQueriesToDropUnusedColumns;
import androidx.room.Transaction;
import org.schabi.newpipe.database.BasicDAO;
import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
import java.util.List;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry.PLAYLIST_TIMES_STREAM_IS_CONTAINED;
import static org.schabi.newpipe.database.playlist.PlaylistMetadataEntry.PLAYLIST_STREAM_COUNT;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_DISPLAY_INDEX;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.DEFAULT_THUMBNAIL;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_PERMANENT;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
@ -25,6 +32,8 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JO
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_THUMBNAIL_URL;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@ -53,6 +62,16 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
Flowable<Integer> getMaximumIndexOf(long playlistId);
@Query("SELECT CASE WHEN COUNT(*) != 0 then " + STREAM_ID
+ " ELSE " + PlaylistEntity.DEFAULT_THUMBNAIL_ID + " END"
+ " FROM " + STREAM_TABLE
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId "
+ " LIMIT 1"
)
Flowable<Long> getAutomaticThumbnailStreamId(long playlistId);
@RewriteQueriesToDropUnusedColumns
@Transaction
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN "
@ -74,13 +93,67 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
@Transaction
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", " + PLAYLIST_THUMBNAIL_URL + ", "
@Query("SELECT " + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", "
+ PLAYLIST_THUMBNAIL_PERMANENT + ", " + PLAYLIST_THUMBNAIL_STREAM_ID + ", "
+ PLAYLIST_DISPLAY_INDEX + ", "
+ " CASE WHEN " + PLAYLIST_THUMBNAIL_STREAM_ID + " = "
+ PlaylistEntity.DEFAULT_THUMBNAIL_ID + " THEN " + "'" + DEFAULT_THUMBNAIL + "'"
+ " ELSE (SELECT " + STREAM_THUMBNAIL_URL
+ " FROM " + STREAM_TABLE
+ " WHERE " + STREAM_TABLE + "." + STREAM_ID + " = " + PLAYLIST_THUMBNAIL_STREAM_ID
+ " ) END AS " + PLAYLIST_THUMBNAIL_URL + ", "
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT
+ " FROM " + PLAYLIST_TABLE
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
+ " ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
+ " GROUP BY " + PLAYLIST_ID
+ " ORDER BY " + PLAYLIST_DISPLAY_INDEX)
Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
@RewriteQueriesToDropUnusedColumns
@Transaction
@Query("SELECT *, MIN(" + JOIN_INDEX + ")"
+ " FROM " + STREAM_TABLE + " INNER JOIN"
+ " (SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_MILLIS
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS
+ " GROUP BY " + STREAM_ID
+ " ORDER BY MIN(" + JOIN_INDEX + ") ASC")
Flowable<List<PlaylistStreamEntry>> getStreamsWithoutDuplicates(long playlistId);
@Transaction
@Query("SELECT " + PLAYLIST_TABLE + "." + PLAYLIST_ID + ", " + PLAYLIST_NAME + ", "
+ PLAYLIST_THUMBNAIL_PERMANENT + ", " + PLAYLIST_THUMBNAIL_STREAM_ID + ", "
+ PLAYLIST_DISPLAY_INDEX + ", "
+ " CASE WHEN " + PLAYLIST_THUMBNAIL_STREAM_ID + " = "
+ PlaylistEntity.DEFAULT_THUMBNAIL_ID + " THEN " + "'" + DEFAULT_THUMBNAIL + "'"
+ " ELSE (SELECT " + STREAM_THUMBNAIL_URL
+ " FROM " + STREAM_TABLE
+ " WHERE " + STREAM_TABLE + "." + STREAM_ID + " = " + PLAYLIST_THUMBNAIL_STREAM_ID
+ " ) END AS " + PLAYLIST_THUMBNAIL_URL + ", "
+ "COALESCE(COUNT(" + JOIN_PLAYLIST_ID + "), 0) AS " + PLAYLIST_STREAM_COUNT + ", "
+ "COALESCE(SUM(" + STREAM_URL + " = :streamUrl), 0) AS "
+ PLAYLIST_TIMES_STREAM_IS_CONTAINED
+ " FROM " + PLAYLIST_TABLE
+ " LEFT JOIN " + PLAYLIST_STREAM_JOIN_TABLE
+ " ON " + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
+ " ON " + PLAYLIST_TABLE + "." + PLAYLIST_ID + " = " + JOIN_PLAYLIST_ID
+ " LEFT JOIN " + STREAM_TABLE
+ " ON " + STREAM_TABLE + "." + STREAM_ID + " = " + JOIN_STREAM_ID
+ " AND :streamUrl = :streamUrl"
+ " GROUP BY " + JOIN_PLAYLIST_ID
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
+ " ORDER BY " + PLAYLIST_DISPLAY_INDEX)
Flowable<List<PlaylistDuplicatesEntry>> getPlaylistDuplicatesMetadata(String streamUrl);
}

View File

@ -2,19 +2,28 @@ package org.schabi.newpipe.database.playlist.model;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
@Entity(tableName = PLAYLIST_TABLE,
indices = {@Index(value = {PLAYLIST_NAME})})
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
@Entity(tableName = PLAYLIST_TABLE)
public class PlaylistEntity {
public static final String DEFAULT_THUMBNAIL = "drawable://"
+ R.drawable.placeholder_thumbnail_playlist;
public static final long DEFAULT_THUMBNAIL_ID = -1;
public static final String PLAYLIST_TABLE = "playlists";
public static final String PLAYLIST_ID = "uid";
public static final String PLAYLIST_NAME = "name";
public static final String PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
public static final String PLAYLIST_DISPLAY_INDEX = "display_index";
public static final String PLAYLIST_THUMBNAIL_PERMANENT = "is_thumbnail_permanent";
public static final String PLAYLIST_THUMBNAIL_STREAM_ID = "thumbnail_stream_id";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = PLAYLIST_ID)
@ -23,12 +32,30 @@ public class PlaylistEntity {
@ColumnInfo(name = PLAYLIST_NAME)
private String name;
@ColumnInfo(name = PLAYLIST_THUMBNAIL_URL)
private String thumbnailUrl;
@ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT)
private boolean isThumbnailPermanent;
public PlaylistEntity(final String name, final String thumbnailUrl) {
@ColumnInfo(name = PLAYLIST_THUMBNAIL_STREAM_ID)
private long thumbnailStreamId;
@ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
private long displayIndex;
public PlaylistEntity(final String name, final boolean isThumbnailPermanent,
final long thumbnailStreamId, final long displayIndex) {
this.name = name;
this.thumbnailUrl = thumbnailUrl;
this.isThumbnailPermanent = isThumbnailPermanent;
this.thumbnailStreamId = thumbnailStreamId;
this.displayIndex = displayIndex;
}
@Ignore
public PlaylistEntity(final PlaylistMetadataEntry item) {
this.uid = item.getUid();
this.name = item.name;
this.isThumbnailPermanent = item.isThumbnailPermanent();
this.thumbnailStreamId = item.getThumbnailStreamId();
this.displayIndex = item.getDisplayIndex();
}
public long getUid() {
@ -47,11 +74,27 @@ public class PlaylistEntity {
this.name = name;
}
public String getThumbnailUrl() {
return thumbnailUrl;
public long getThumbnailStreamId() {
return thumbnailStreamId;
}
public void setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
public void setThumbnailStreamId(final long thumbnailStreamId) {
this.thumbnailStreamId = thumbnailStreamId;
}
public boolean getIsThumbnailPermanent() {
return isThumbnailPermanent;
}
public void setIsThumbnailPermanent(final boolean isThumbnailSet) {
this.isThumbnailPermanent = isThumbnailSet;
}
public long getDisplayIndex() {
return displayIndex;
}
public void setDisplayIndex(final long displayIndex) {
this.displayIndex = displayIndex;
}
}

View File

@ -11,6 +11,7 @@ import androidx.room.PrimaryKey;
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.image.ImageStrategy;
import static org.schabi.newpipe.database.LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_NAME;
@ -20,7 +21,6 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.RE
@Entity(tableName = REMOTE_PLAYLIST_TABLE,
indices = {
@Index(value = {REMOTE_PLAYLIST_NAME}),
@Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true)
})
public class PlaylistRemoteEntity implements PlaylistLocalItem {
@ -31,6 +31,7 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
public static final String REMOTE_PLAYLIST_URL = "url";
public static final String REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url";
public static final String REMOTE_PLAYLIST_UPLOADER_NAME = "uploader";
public static final String REMOTE_PLAYLIST_DISPLAY_INDEX = "display_index";
public static final String REMOTE_PLAYLIST_STREAM_COUNT = "stream_count";
@PrimaryKey(autoGenerate = true)
@ -52,6 +53,9 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
@ColumnInfo(name = REMOTE_PLAYLIST_UPLOADER_NAME)
private String uploader;
@ColumnInfo(name = REMOTE_PLAYLIST_DISPLAY_INDEX)
private long displayIndex = -1; // Make sure the new item is on the top
@ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
private Long streamCount;
@ -66,11 +70,25 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
this.streamCount = streamCount;
}
@Ignore
public PlaylistRemoteEntity(final int serviceId, final String name, final String url,
final String thumbnailUrl, final String uploader,
final long displayIndex, final Long streamCount) {
this.serviceId = serviceId;
this.name = name;
this.url = url;
this.thumbnailUrl = thumbnailUrl;
this.uploader = uploader;
this.displayIndex = displayIndex;
this.streamCount = streamCount;
}
@Ignore
public PlaylistRemoteEntity(final PlaylistInfo info) {
this(info.getServiceId(), info.getName(), info.getUrl(),
info.getThumbnailUrl() == null
? info.getUploaderAvatarUrl() : info.getThumbnailUrl(),
// use uploader avatar when no thumbnail is available
ImageStrategy.imageListToDbUrl(info.getThumbnails().isEmpty()
? info.getUploaderAvatars() : info.getThumbnails()),
info.getUploaderName(), info.getStreamCount());
}
@ -84,10 +102,14 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
&& getStreamCount() == info.getStreamCount()
&& TextUtils.equals(getName(), info.getName())
&& TextUtils.equals(getUrl(), info.getUrl())
&& TextUtils.equals(getThumbnailUrl(), info.getThumbnailUrl())
// we want to update the local playlist data even when either the remote thumbnail
// URL changes, or the preferred image quality setting is changed by the user
&& TextUtils.equals(getThumbnailUrl(),
ImageStrategy.imageListToDbUrl(info.getThumbnails()))
&& TextUtils.equals(getUploader(), info.getUploaderName());
}
@Override
public long getUid() {
return uid;
}
@ -136,6 +158,16 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
this.uploader = uploader;
}
@Override
public long getDisplayIndex() {
return displayIndex;
}
@Override
public void setDisplayIndex(final long displayIndex) {
this.displayIndex = displayIndex;
}
public Long getStreamCount() {
return streamCount;
}

View File

@ -7,6 +7,7 @@ import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
import java.time.OffsetDateTime
class StreamStatisticsEntry(
@ -30,7 +31,7 @@ class StreamStatisticsEntry(
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnailUrl = streamEntity.thumbnailUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
return item
}

View File

@ -13,6 +13,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.player.playqueue.PlayQueueItem
import org.schabi.newpipe.util.image.ImageStrategy
import java.io.Serializable
import java.time.OffsetDateTime
@ -67,7 +68,8 @@ data class StreamEntity(
constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
uploaderUrl = item.uploaderUrl, thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
uploaderUrl = item.uploaderUrl,
thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails), viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation
)
@ -76,7 +78,8 @@ data class StreamEntity(
constructor(info: StreamInfo) : this(
serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
uploaderUrl = info.uploaderUrl, thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
uploaderUrl = info.uploaderUrl,
thumbnailUrl = ImageStrategy.imageListToDbUrl(info.thumbnails), viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation
)
@ -85,7 +88,8 @@ data class StreamEntity(
constructor(item: PlayQueueItem) : this(
serviceId = item.serviceId, url = item.url, title = item.title,
streamType = item.streamType, duration = item.duration, uploader = item.uploader,
uploaderUrl = item.uploaderUrl, thumbnailUrl = item.thumbnailUrl
uploaderUrl = item.uploaderUrl,
thumbnailUrl = ImageStrategy.imageListToDbUrl(item.thumbnails)
)
fun toStreamInfoItem(): StreamInfoItem {
@ -93,7 +97,7 @@ data class StreamEntity(
item.duration = duration
item.uploaderName = uploader
item.uploaderUrl = uploaderUrl
item.thumbnailUrl = thumbnailUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(thumbnailUrl)
if (viewCount != null) item.viewCount = viewCount as Long
item.textualUploadDate = textualUploadDate

View File

@ -30,7 +30,7 @@ public class StreamStateEntity {
/**
* Playback state will not be saved, if playback time is less than this threshold (5000ms = 5s).
*/
private static final long PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS = 5000;
public static final long PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS = 5000;
/**
* Stream will be considered finished if the playback time left exceeds this threshold

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.database.subscription;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
@ -10,6 +11,7 @@ import androidx.room.PrimaryKey;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.image.ImageStrategy;
import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_SERVICE_ID;
import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_TABLE;
@ -57,8 +59,8 @@ public class SubscriptionEntity {
final SubscriptionEntity result = new SubscriptionEntity();
result.setServiceId(info.getServiceId());
result.setUrl(info.getUrl());
result.setData(info.getName(), info.getAvatarUrl(), info.getDescription(),
info.getSubscriberCount());
result.setData(info.getName(), ImageStrategy.imageListToDbUrl(info.getAvatars()),
info.getDescription(), info.getSubscriberCount());
return result;
}
@ -94,11 +96,12 @@ public class SubscriptionEntity {
this.name = name;
}
@Nullable
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(final String avatarUrl) {
public void setAvatarUrl(@Nullable final String avatarUrl) {
this.avatarUrl = avatarUrl;
}
@ -138,7 +141,7 @@ public class SubscriptionEntity {
@Ignore
public ChannelInfoItem toChannelInfoItem() {
final ChannelInfoItem item = new ChannelInfoItem(getServiceId(), getUrl(), getName());
item.setThumbnailUrl(getAvatarUrl());
item.setThumbnails(ImageStrategy.dbUrlToImageList(getAvatarUrl()));
item.setSubscriberCount(getSubscriberCount());
item.setDescription(getDescription());
return item;

View File

@ -7,8 +7,6 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
@ -16,8 +14,8 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -36,6 +34,7 @@ import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.appcompat.widget.Toolbar;
import androidx.collection.SparseArrayCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
@ -67,14 +66,18 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.SecondaryStreamHelper;
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper;
import org.schabi.newpipe.util.AudioTrackAdapter;
import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import icepick.Icepick;
import icepick.State;
@ -94,11 +97,13 @@ public class DownloadDialog extends DialogFragment
@State
StreamInfo currentInfo;
@State
StreamSizeWrapper<AudioStream> wrappedAudioStreams;
StreamInfoWrapper<VideoStream> wrappedVideoStreams;
@State
StreamSizeWrapper<VideoStream> wrappedVideoStreams;
StreamInfoWrapper<SubtitlesStream> wrappedSubtitleStreams;
@State
StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams;
AudioTracksWrapper wrappedAudioTracks;
@State
int selectedAudioTrackIndex;
@State
int selectedVideoIndex; // set in the constructor
@State
@ -106,16 +111,14 @@ public class DownloadDialog extends DialogFragment
@State
int selectedSubtitleIndex = 0; // default to the first item
@Nullable
private OnDismissListener onDismissListener = null;
private StoredDirectoryHelper mainStorageAudio = null;
private StoredDirectoryHelper mainStorageVideo = null;
private DownloadManager downloadManager = null;
private ActionMenuItemView okButton = null;
private Context context;
private Context context = null;
private boolean askForSavePath;
private AudioTrackAdapter audioTrackAdapter;
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
@ -140,7 +143,6 @@ public class DownloadDialog extends DialogFragment
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadPickVideoFolderResult);
/*//////////////////////////////////////////////////////////////////////////
// Instance creation
//////////////////////////////////////////////////////////////////////////*/
@ -162,31 +164,32 @@ public class DownloadDialog extends DialogFragment
public DownloadDialog(@NonNull final Context context, @NonNull final StreamInfo info) {
this.currentInfo = info;
final List<AudioStream> audioStreams =
getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP);
final List<List<AudioStream>> groupedAudioStreams =
ListHelper.getGroupedAudioStreams(context, audioStreams);
this.wrappedAudioTracks = new AudioTracksWrapper(groupedAudioStreams, context);
this.selectedAudioTrackIndex =
ListHelper.getDefaultAudioTrackGroup(context, groupedAudioStreams);
// TODO: Adapt this code when the downloader support other types of stream deliveries
final List<VideoStream> videoStreams = ListHelper.getSortedStreamVideosList(
context,
getStreamsOfSpecifiedDelivery(info.getVideoStreams(), PROGRESSIVE_HTTP),
getStreamsOfSpecifiedDelivery(info.getVideoOnlyStreams(), PROGRESSIVE_HTTP),
false,
false
// If there are multiple languages available, prefer streams without audio
// to allow language selection
wrappedAudioTracks.size() > 1
);
this.wrappedVideoStreams = new StreamSizeWrapper<>(videoStreams, context);
this.wrappedAudioStreams = new StreamSizeWrapper<>(
getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP), context);
this.wrappedSubtitleStreams = new StreamSizeWrapper<>(
this.wrappedVideoStreams = new StreamInfoWrapper<>(videoStreams, context);
this.wrappedSubtitleStreams = new StreamInfoWrapper<>(
getStreamsOfSpecifiedDelivery(info.getSubtitles(), PROGRESSIVE_HTTP), context);
this.selectedVideoIndex = ListHelper.getDefaultResolutionIndex(context, videoStreams);
}
/**
* @param onDismissListener the listener to call in {@link #onDismiss(DialogInterface)}
*/
public void setOnDismissListener(@Nullable final OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener;
}
/*//////////////////////////////////////////////////////////////////////////
// Android lifecycle
@ -206,40 +209,16 @@ public class DownloadDialog extends DialogFragment
return;
}
// context will remain null if dismiss() was called above, allowing to check whether the
// dialog is being dismissed in onViewCreated()
context = getContext();
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
Icepick.restoreInstanceState(this, savedInstanceState);
final SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams =
new SparseArray<>(4);
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) {
continue;
}
final AudioStream audioStream = SecondaryStreamHelper
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams,
audioStream));
} else if (DEBUG) {
final MediaFormat mediaFormat = videoStreams.get(i).getFormat();
if (mediaFormat != null) {
Log.w(TAG, "No audio stream candidates for video format "
+ mediaFormat.name());
} else {
Log.w(TAG, "No audio stream candidates for unknown video format");
}
}
}
this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams,
secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
this.audioTrackAdapter = new AudioTrackAdapter(wrappedAudioTracks);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(wrappedSubtitleStreams);
updateSecondaryStreams();
final Intent intent = new Intent(context, DownloadManagerService.class);
context.startService(intent);
@ -266,6 +245,39 @@ public class DownloadDialog extends DialogFragment
}, Context.BIND_AUTO_CREATE);
}
/**
* Update the displayed video streams based on the selected audio track.
*/
private void updateSecondaryStreams() {
final StreamInfoWrapper<AudioStream> audioStreams = getWrappedAudioStreams();
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
wrappedVideoStreams.resetInfo();
for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) {
continue;
}
final AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(
context, audioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(audioStreams, audioStream));
} else if (DEBUG) {
final MediaFormat mediaFormat = videoStreams.get(i).getFormat();
if (mediaFormat != null) {
Log.w(TAG, "No audio stream candidates for video format "
+ mediaFormat.name());
} else {
Log.w(TAG, "No audio stream candidates for unknown video format");
}
}
}
this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(audioStreams);
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
@ -283,16 +295,20 @@ public class DownloadDialog extends DialogFragment
@Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
dialogBinding = DownloadDialogBinding.bind(view);
if (context == null) {
return; // the dialog is being dismissed, see the call to dismiss() in onCreate()
}
dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(),
currentInfo.getName()));
selectedAudioIndex = ListHelper
.getDefaultAudioFormat(getContext(), wrappedAudioStreams.getStreamsList());
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(),
getWrappedAudioStreams().getStreamsList());
selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());
dialogBinding.qualitySpinner.setOnItemSelectedListener(this);
dialogBinding.audioStreamSpinner.setOnItemSelectedListener(this);
dialogBinding.audioTrackSpinner.setOnItemSelectedListener(this);
dialogBinding.videoAudioGroup.setOnCheckedChangeListener(this);
initToolbar(dialogBinding.toolbarLayout.toolbar);
@ -341,14 +357,6 @@ public class DownloadDialog extends DialogFragment
});
}
@Override
public void onDismiss(@NonNull final DialogInterface dialog) {
super.onDismiss(dialog);
if (onDismissListener != null) {
onDismissListener.onDismiss(dialog);
}
}
@Override
public void onDestroy() {
super.onDestroy();
@ -374,7 +382,7 @@ public class DownloadDialog extends DialogFragment
private void fetchStreamsSize() {
disposables.clear();
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(wrappedVideoStreams)
.subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
== R.id.video_button) {
@ -384,7 +392,7 @@ public class DownloadDialog extends DialogFragment
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading video stream size",
currentInfo.getServiceId()))));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(getWrappedAudioStreams())
.subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
== R.id.audio_button) {
@ -394,7 +402,7 @@ public class DownloadDialog extends DialogFragment
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading audio stream size",
currentInfo.getServiceId()))));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams)
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(wrappedSubtitleStreams)
.subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
== R.id.subtitle_button) {
@ -406,14 +414,28 @@ public class DownloadDialog extends DialogFragment
currentInfo.getServiceId()))));
}
private void setupAudioTrackSpinner() {
if (getContext() == null) {
return;
}
dialogBinding.audioTrackSpinner.setAdapter(audioTrackAdapter);
dialogBinding.audioTrackSpinner.setSelection(selectedAudioTrackIndex);
}
private void setupAudioSpinner() {
if (getContext() == null) {
return;
}
dialogBinding.qualitySpinner.setAdapter(audioStreamsAdapter);
dialogBinding.qualitySpinner.setSelection(selectedAudioIndex);
dialogBinding.qualitySpinner.setVisibility(View.GONE);
setRadioButtonsState(true);
dialogBinding.audioStreamSpinner.setAdapter(audioStreamsAdapter);
dialogBinding.audioStreamSpinner.setSelection(selectedAudioIndex);
dialogBinding.audioStreamSpinner.setVisibility(View.VISIBLE);
dialogBinding.audioTrackSpinner.setVisibility(
wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
dialogBinding.audioTrackPresentInVideoText.setVisibility(View.GONE);
}
private void setupVideoSpinner() {
@ -423,7 +445,19 @@ public class DownloadDialog extends DialogFragment
dialogBinding.qualitySpinner.setAdapter(videoStreamsAdapter);
dialogBinding.qualitySpinner.setSelection(selectedVideoIndex);
dialogBinding.qualitySpinner.setVisibility(View.VISIBLE);
setRadioButtonsState(true);
dialogBinding.audioStreamSpinner.setVisibility(View.GONE);
onVideoStreamSelected();
}
private void onVideoStreamSelected() {
final boolean isVideoOnly = videoStreamsAdapter.getItem(selectedVideoIndex).isVideoOnly();
dialogBinding.audioTrackSpinner.setVisibility(
isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
dialogBinding.audioTrackPresentInVideoText.setVisibility(
!isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
}
private void setupSubtitleSpinner() {
@ -433,7 +467,11 @@ public class DownloadDialog extends DialogFragment
dialogBinding.qualitySpinner.setAdapter(subtitleStreamsAdapter);
dialogBinding.qualitySpinner.setSelection(selectedSubtitleIndex);
dialogBinding.qualitySpinner.setVisibility(View.VISIBLE);
setRadioButtonsState(true);
dialogBinding.audioStreamSpinner.setVisibility(View.GONE);
dialogBinding.audioTrackSpinner.setVisibility(View.GONE);
dialogBinding.audioTrackPresentInVideoText.setVisibility(View.GONE);
}
@ -512,7 +550,6 @@ public class DownloadDialog extends DialogFragment
}
}
/*//////////////////////////////////////////////////////////////////////////
// Listeners
//////////////////////////////////////////////////////////////////////////*/
@ -551,16 +588,62 @@ public class DownloadDialog extends DialogFragment
+ "parent = [" + parent + "], view = [" + view + "], "
+ "position = [" + position + "], id = [" + id + "]");
}
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
switch (parent.getId()) {
case R.id.quality_spinner:
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.video_button:
selectedVideoIndex = position;
onVideoStreamSelected();
break;
case R.id.subtitle_button:
selectedSubtitleIndex = position;
break;
}
onItemSelectedSetFileName();
break;
case R.id.audio_track_spinner:
final boolean trackChanged = selectedAudioTrackIndex != position;
selectedAudioTrackIndex = position;
if (trackChanged) {
updateSecondaryStreams();
fetchStreamsSize();
}
break;
case R.id.audio_stream_spinner:
selectedAudioIndex = position;
break;
case R.id.video_button:
selectedVideoIndex = position;
break;
case R.id.subtitle_button:
selectedSubtitleIndex = position;
break;
}
}
private void onItemSelectedSetFileName() {
final String fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
final String prevFileName = Optional.ofNullable(dialogBinding.fileName.getText())
.map(Object::toString)
.orElse("");
if (prevFileName.isEmpty()
|| prevFileName.equals(fileName)
|| prevFileName.startsWith(getString(R.string.caption_file_name, fileName, ""))) {
// only update the file name field if it was not edited by the user
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
case R.id.video_button:
if (!prevFileName.equals(fileName)) {
// since the user might have switched between audio and video, the correct
// text might already be in place, so avoid resetting the cursor position
dialogBinding.fileName.setText(fileName);
}
break;
case R.id.subtitle_button:
final String setSubtitleLanguageCode = subtitleStreamsAdapter
.getItem(selectedSubtitleIndex).getLanguageTag();
// this will reset the cursor position, which is bad UX, but it can't be avoided
dialogBinding.fileName.setText(getString(
R.string.caption_file_name, fileName, setSubtitleLanguageCode));
break;
}
}
}
@ -575,6 +658,7 @@ public class DownloadDialog extends DialogFragment
protected void setupDownloadOptions() {
setRadioButtonsState(false);
setupAudioTrackSpinner();
final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
@ -625,6 +709,13 @@ public class DownloadDialog extends DialogFragment
dialogBinding.subtitleButton.setEnabled(enabled);
}
private StreamInfoWrapper<AudioStream> getWrappedAudioStreams() {
if (selectedAudioTrackIndex < 0 || selectedAudioTrackIndex > wrappedAudioTracks.size()) {
return StreamInfoWrapper.empty();
}
return wrappedAudioTracks.getTracksList().get(selectedAudioTrackIndex);
}
private int getSubtitleIndexBy(@NonNull final List<SubtitlesStream> streams) {
final Localization preferredLocalization = NewPipe.getPreferredLocalization();
@ -660,12 +751,11 @@ public class DownloadDialog extends DialogFragment
}
private void showFailedDialog(@StringRes final int msg) {
assureCorrectAppLanguage(getContext());
assureCorrectAppLanguage(requireContext());
new AlertDialog.Builder(context)
.setTitle(R.string.general_error)
.setMessage(msg)
.setNegativeButton(getString(R.string.ok), null)
.create()
.show();
}
@ -678,6 +768,7 @@ public class DownloadDialog extends DialogFragment
final StoredDirectoryHelper mainStorage;
final MediaFormat format;
final String selectedMediaType;
final long size;
// first, build the filename and get the output folder (if possible)
// later, run a very very very large file checking logic
@ -689,35 +780,38 @@ public class DownloadDialog extends DialogFragment
selectedMediaType = getString(R.string.last_download_type_audio_key);
mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
size = getWrappedAudioStreams().getSizeInBytes(selectedAudioIndex);
if (format == MediaFormat.WEBMA_OPUS) {
mimeTmp = "audio/ogg";
filenameTmp += "opus";
} else if (format != null) {
mimeTmp = format.mimeType;
filenameTmp += format.suffix;
filenameTmp += format.getSuffix();
}
break;
case R.id.video_button:
selectedMediaType = getString(R.string.last_download_type_video_key);
mainStorage = mainStorageVideo;
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
size = wrappedVideoStreams.getSizeInBytes(selectedVideoIndex);
if (format != null) {
mimeTmp = format.mimeType;
filenameTmp += format.suffix;
filenameTmp += format.getSuffix();
}
break;
case R.id.subtitle_button:
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
size = wrappedSubtitleStreams.getSizeInBytes(selectedSubtitleIndex);
if (format != null) {
mimeTmp = format.mimeType;
}
if (format == MediaFormat.TTML) {
filenameTmp += MediaFormat.SRT.suffix;
filenameTmp += MediaFormat.SRT.getSuffix();
} else if (format != null) {
filenameTmp += format.suffix;
filenameTmp += format.getSuffix();
}
break;
default:
@ -765,6 +859,21 @@ public class DownloadDialog extends DialogFragment
return;
}
// Check for free storage space
final long freeSpace = mainStorage.getFreeStorageSpace();
if (freeSpace <= size) {
Toast.makeText(context, getString(R.
string.error_insufficient_storage), Toast.LENGTH_LONG).show();
// move the user to storage setting tab
final Intent storageSettingsIntent = new Intent(Settings.
ACTION_INTERNAL_STORAGE_SETTINGS);
if (storageSettingsIntent.resolveActivity(context.getPackageManager())
!= null) {
startActivity(storageSettingsIntent);
}
return;
}
// check for existing file with the same name
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
mimeTmp);
@ -878,7 +987,7 @@ public class DownloadDialog extends DialogFragment
break;
}
askDialog.create().show();
askDialog.show();
return;
}
@ -922,7 +1031,7 @@ public class DownloadDialog extends DialogFragment
}
});
askDialog.create().show();
askDialog.show();
}
private void continueSelectedDownload(@NonNull final StoredFileHelper storage) {
@ -947,7 +1056,7 @@ public class DownloadDialog extends DialogFragment
final char kind;
int threads = dialogBinding.threads.getProgress() + 1;
final String[] urls;
final MissionRecoveryInfo[] recoveryInfo;
final List<MissionRecoveryInfo> recoveryInfo;
String psName = null;
String[] psArgs = null;
long nearLength = 0;
@ -981,7 +1090,6 @@ public class DownloadDialog extends DialogFragment
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
}
psArgs = null;
final long videoSize = wrappedVideoStreams.getSizeInBytes(
(VideoStream) selectedStream);
@ -1013,9 +1121,7 @@ public class DownloadDialog extends DialogFragment
urls = new String[] {
selectedStream.getContent()
};
recoveryInfo = new MissionRecoveryInfo[] {
new MissionRecoveryInfo(selectedStream)
};
recoveryInfo = List.of(new MissionRecoveryInfo(selectedStream));
} else {
if (secondaryStream.getDeliveryMethod() != PROGRESSIVE_HTTP) {
throw new IllegalArgumentException("Unsupported stream delivery format"
@ -1025,12 +1131,14 @@ public class DownloadDialog extends DialogFragment
urls = new String[] {
selectedStream.getContent(), secondaryStream.getContent()
};
recoveryInfo = new MissionRecoveryInfo[] {new MissionRecoveryInfo(selectedStream),
new MissionRecoveryInfo(secondaryStream)};
recoveryInfo = List.of(
new MissionRecoveryInfo(selectedStream),
new MissionRecoveryInfo(secondaryStream)
);
}
DownloadManagerService.startMission(context, urls, storage, kind, threads,
currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo);
currentInfo.getUrl(), psName, psArgs, nearLength, new ArrayList<>(recoveryInfo));
Toast.makeText(context, getString(R.string.download_has_started),
Toast.LENGTH_SHORT).show();

View File

@ -0,0 +1,87 @@
package org.schabi.newpipe.download;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.DownloadLoadingDialogBinding;
/**
* This class contains a dialog which shows a loading indicator and has a customizable title.
*/
public class LoadingDialog extends DialogFragment {
private static final String TAG = "LoadingDialog";
private static final boolean DEBUG = MainActivity.DEBUG;
private DownloadLoadingDialogBinding dialogLoadingBinding;
private final @StringRes int title;
/**
* Create a new LoadingDialog.
*
* <p>
* The dialog contains a loading indicator and has a customizable title.
* <br/>
* Use {@code show()} to display the dialog to the user.
* </p>
*
* @param title an informative title shown in the dialog's toolbar
*/
public LoadingDialog(final @StringRes int title) {
this.title = title;
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) {
Log.d(TAG, "onCreate() called with: "
+ "savedInstanceState = [" + savedInstanceState + "]");
}
this.setCancelable(false);
}
@Override
public View onCreateView(
@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (DEBUG) {
Log.d(TAG, "onCreateView() called with: "
+ "inflater = [" + inflater + "], container = [" + container + "], "
+ "savedInstanceState = [" + savedInstanceState + "]");
}
return inflater.inflate(R.layout.download_loading_dialog, container);
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
dialogLoadingBinding = DownloadLoadingDialogBinding.bind(view);
initToolbar(dialogLoadingBinding.toolbarLayout.toolbar);
}
private void initToolbar(final Toolbar toolbar) {
if (DEBUG) {
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
}
toolbar.setTitle(requireContext().getString(title));
toolbar.setNavigationOnClickListener(v -> dismiss());
}
@Override
public void onDestroyView() {
dialogLoadingBinding = null;
super.onDestroyView();
}
}

View File

@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.IntentCompat;
import com.grack.nanojson.JsonWriter;
@ -105,7 +106,7 @@ public class ErrorActivity extends AppCompatActivity {
actionBar.setDisplayShowTitleEnabled(true);
}
errorInfo = intent.getParcelableExtra(ERROR_INFO);
errorInfo = IntentCompat.getParcelableExtra(intent, ERROR_INFO, ErrorInfo.class);
// important add guru meditation
addGuruMeditation();
@ -160,7 +161,7 @@ public class ErrorActivity extends AppCompatActivity {
.setMessage(R.string.start_accept_privacy_policy)
.setCancelable(false)
.setNeutralButton(R.string.read_privacy_policy, (dialog, which) ->
ShareUtils.openUrlInBrowser(context,
ShareUtils.openUrlInApp(context,
context.getString(R.string.privacy_policy_url)))
.setPositiveButton(R.string.accept, (dialog, which) -> {
if (action.equals("EMAIL")) { // send on email
@ -171,14 +172,12 @@ public class ErrorActivity extends AppCompatActivity {
+ getString(R.string.app_name) + " "
+ BuildConfig.VERSION_NAME)
.putExtra(Intent.EXTRA_TEXT, buildJson());
ShareUtils.openIntentInApp(context, i, true);
ShareUtils.openIntentInApp(context, i);
} else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub
ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL, false);
ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL);
}
})
.setNegativeButton(R.string.decline, (dialog, which) -> {
// do nothing
})
.setNegativeButton(R.string.decline, null)
.show();
}

View File

@ -11,7 +11,6 @@ import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
import org.schabi.newpipe.extractor.exceptions.ExtractionException
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException
import org.schabi.newpipe.ktx.isNetworkRelated
import org.schabi.newpipe.util.ServiceHelper
@ -96,7 +95,6 @@ class ErrorInfo(
throwable is ContentNotAvailableException -> R.string.content_not_available
throwable != null && throwable.isNetworkRelated -> R.string.network_error
throwable is ContentNotSupportedException -> R.string.content_not_supported
throwable is DeobfuscateException -> R.string.youtube_signature_deobfuscation_error
throwable is ExtractionException -> R.string.parsing_error
throwable is ExoPlaybackException -> {
when (throwable.type) {

View File

@ -6,7 +6,6 @@ import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.annotation.Nullable
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
@ -144,7 +143,7 @@ class ErrorPanelHelper(
*/
private fun showAndSetErrorButtonAction(
@StringRes resid: Int,
@Nullable listener: View.OnClickListener
listener: View.OnClickListener
) {
errorActionButton.isVisible = true
errorActionButton.setText(resid)
@ -156,7 +155,7 @@ class ErrorPanelHelper(
) {
errorOpenInBrowserButton.isVisible = true
errorOpenInBrowserButton.setOnClickListener {
ShareUtils.openUrlInBrowser(context, errorInfo.request, true)
ShareUtils.openUrlInBrowser(context, errorInfo.request)
}
}

View File

@ -5,11 +5,11 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.view.View
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.PendingIntentCompat
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
import org.schabi.newpipe.R
@ -54,7 +54,7 @@ class ErrorUtil {
*/
@JvmStatic
fun showSnackbar(context: Context, errorInfo: ErrorInfo) {
val rootView = if (context is Activity) context.findViewById<View>(R.id.content) else null
val rootView = (context as? Activity)?.findViewById<View>(android.R.id.content)
showSnackbar(context, rootView, errorInfo)
}
@ -71,7 +71,7 @@ class ErrorUtil {
fun showSnackbar(fragment: Fragment, errorInfo: ErrorInfo) {
var rootView = fragment.view
if (rootView == null && fragment.activity != null) {
rootView = fragment.requireActivity().findViewById(R.id.content)
rootView = fragment.requireActivity().findViewById(android.R.id.content)
}
showSnackbar(fragment.requireContext(), rootView, errorInfo)
}
@ -104,11 +104,6 @@ class ErrorUtil {
*/
@JvmStatic
fun createNotification(context: Context, errorInfo: ErrorInfo) {
var pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
pendingIntentFlags = pendingIntentFlags or PendingIntent.FLAG_IMMUTABLE
}
val notificationBuilder: NotificationCompat.Builder =
NotificationCompat.Builder(
context,
@ -119,11 +114,12 @@ class ErrorUtil {
.setContentText(context.getString(errorInfo.messageStringId))
.setAutoCancel(true)
.setContentIntent(
PendingIntent.getActivity(
PendingIntentCompat.getActivity(
context,
0,
getErrorActivityIntent(context, errorInfo),
pendingIntentFlags
PendingIntent.FLAG_UPDATE_CURRENT,
false
)
)

View File

@ -20,14 +20,14 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NavUtils;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
import org.schabi.newpipe.extractor.utils.Utils;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
/*
* Created by beneth <bmauduit@beneth.fr> on 06.12.16.
@ -188,7 +188,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
try {
String abuseCookie = url.substring(abuseStart + 13, abuseEnd);
abuseCookie = URLDecoder.decode(abuseCookie, "UTF-8");
abuseCookie = Utils.decodeUrlUtf8(abuseCookie);
handleCookies(abuseCookie);
} catch (UnsupportedEncodingException | StringIndexOutOfBoundsException e) {
if (MainActivity.DEBUG) {

View File

@ -6,6 +6,7 @@ package org.schabi.newpipe.error;
public enum UserAction {
USER_REPORT("user report"),
UI_ERROR("ui error"),
DATABASE_IMPORT_EXPORT("database import or export"),
SUBSCRIPTION_CHANGE("subscription change"),
SUBSCRIPTION_UPDATE("subscription update"),
SUBSCRIPTION_GET("get subscription"),
@ -19,6 +20,7 @@ public enum UserAction {
REQUESTED_PLAYLIST("requested playlist"),
REQUESTED_KIOSK("requested kiosk"),
REQUESTED_COMMENTS("requested comments"),
REQUESTED_COMMENT_REPLIES("requested comment replies"),
REQUESTED_FEED("requested feed"),
REQUESTED_BOOKMARK("bookmark"),
DELETE_FROM_HISTORY("delete from history"),

View File

@ -1,12 +1,16 @@
package org.schabi.newpipe.fragments;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.BaseFragment;
@ -20,15 +24,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
public abstract class BaseStateFragment<I> extends BaseFragment implements ViewContract<I> {
@State
protected AtomicBoolean wasLoading = new AtomicBoolean();
protected AtomicBoolean isLoading = new AtomicBoolean();
@Nullable
private View emptyStateView;
protected View emptyStateView;
@Nullable
protected TextView emptyStateMessageView;
@Nullable
private ProgressBar loadingProgressBar;
@ -65,6 +69,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
emptyStateView = rootView.findViewById(R.id.empty_state_view);
emptyStateMessageView = rootView.findViewById(R.id.empty_state_message);
loadingProgressBar = rootView.findViewById(R.id.loading_progress_bar);
errorPanelHelper = new ErrorPanelHelper(this, rootView, this::onRetryButtonClicked);
}
@ -75,6 +80,8 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
if (errorPanelHelper != null) {
errorPanelHelper.dispose();
}
emptyStateView = null;
emptyStateMessageView = null;
}
protected void onRetryButtonClicked() {
@ -189,6 +196,12 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
errorPanelHelper.showTextError(errorString);
}
protected void setEmptyStateMessage(@StringRes final int text) {
if (emptyStateMessageView != null) {
emptyStateMessageView.setText(text);
}
}
public final void hideErrorPanel() {
errorPanelHelper.hide();
lastPanelError = null;

View File

@ -1,6 +1,16 @@
package org.schabi.newpipe.fragments;
import static android.widget.RelativeLayout.ABOVE;
import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
import static android.widget.RelativeLayout.ALIGN_PARENT_TOP;
import static android.widget.RelativeLayout.BELOW;
import static com.google.android.material.tabs.TabLayout.INDICATOR_GRAVITY_BOTTOM;
import static com.google.android.material.tabs.TabLayout.INDICATOR_GRAVITY_TOP;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@ -9,7 +19,9 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
@ -17,6 +29,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
@ -25,10 +38,13 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentMainBinding;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
import org.schabi.newpipe.settings.tabs.Tab;
import org.schabi.newpipe.settings.tabs.TabsManager;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.ScrollableTabLayout;
import java.util.ArrayList;
import java.util.List;
@ -42,8 +58,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
private boolean hasTabsChanged = false;
private boolean previousYoutubeRestrictedModeEnabled;
private SharedPreferences prefs;
private boolean youtubeRestrictedModeEnabled;
private String youtubeRestrictedModeEnabledKey;
private boolean mainTabsPositionBottom;
private String mainTabsPositionKey;
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
@ -66,10 +85,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
});
prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
previousYoutubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
youtubeRestrictedModeEnabled = prefs.getBoolean(youtubeRestrictedModeEnabledKey, false);
mainTabsPositionKey = getString(R.string.main_tabs_position_key);
mainTabsPositionBottom = prefs.getBoolean(mainTabsPositionKey, false);
}
@Override
@ -87,25 +107,27 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
binding.mainTabLayout.setupWithViewPager(binding.pager);
binding.mainTabLayout.addOnTabSelectedListener(this);
binding.mainTabLayout.setTabRippleColor(binding.mainTabLayout.getTabRippleColor()
.withAlpha(32));
setupTabs();
updateTabLayoutPosition();
}
@Override
public void onResume() {
super.onResume();
final boolean youtubeRestrictedModeEnabled =
PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(youtubeRestrictedModeEnabledKey, false);
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
setupTabs();
} else if (hasTabsChanged) {
final boolean newYoutubeRestrictedModeEnabled =
prefs.getBoolean(youtubeRestrictedModeEnabledKey, false);
if (youtubeRestrictedModeEnabled != newYoutubeRestrictedModeEnabled || hasTabsChanged) {
youtubeRestrictedModeEnabled = newYoutubeRestrictedModeEnabled;
setupTabs();
}
final boolean newMainTabsPosition = prefs.getBoolean(mainTabsPositionKey, false);
if (mainTabsPositionBottom != newMainTabsPosition) {
mainTabsPositionBottom = newMainTabsPosition;
updateTabLayoutPosition();
}
}
@Override
@ -118,6 +140,12 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@ -166,7 +194,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
}
binding.pager.setAdapter(null);
binding.pager.setOffscreenPageLimit(tabsList.size());
binding.pager.setAdapter(pagerAdapter);
updateTabsIconAndDescription();
@ -190,6 +217,44 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
setTitle(tabsList.get(tabPosition).getTabName(requireContext()));
}
public void commitPlaylistTabs() {
pagerAdapter.getLocalPlaylistFragments()
.stream()
.forEach(LocalPlaylistFragment::saveImmediate);
}
private void updateTabLayoutPosition() {
final ScrollableTabLayout tabLayout = binding.mainTabLayout;
final ViewPager viewPager = binding.pager;
final boolean bottom = mainTabsPositionBottom;
// change layout params to make the tab layout appear either at the top or at the bottom
final var tabParams = (RelativeLayout.LayoutParams) tabLayout.getLayoutParams();
final var pagerParams = (RelativeLayout.LayoutParams) viewPager.getLayoutParams();
tabParams.removeRule(bottom ? ALIGN_PARENT_TOP : ALIGN_PARENT_BOTTOM);
tabParams.addRule(bottom ? ALIGN_PARENT_BOTTOM : ALIGN_PARENT_TOP);
pagerParams.removeRule(bottom ? BELOW : ABOVE);
pagerParams.addRule(bottom ? ABOVE : BELOW, R.id.main_tab_layout);
tabLayout.setSelectedTabIndicatorGravity(
bottom ? INDICATOR_GRAVITY_TOP : INDICATOR_GRAVITY_BOTTOM);
tabLayout.setLayoutParams(tabParams);
viewPager.setLayoutParams(pagerParams);
// change the background and icon color of the tab layout:
// service-colored at the top, app-background-colored at the bottom
tabLayout.setBackgroundColor(ThemeHelper.resolveColorFromAttr(requireContext(),
bottom ? android.R.attr.windowBackground : R.attr.colorPrimary));
@ColorInt final int iconColor = bottom
? ThemeHelper.resolveColorFromAttr(requireContext(), android.R.attr.colorAccent)
: Color.WHITE;
tabLayout.setTabRippleColor(ColorStateList.valueOf(iconColor).withAlpha(32));
tabLayout.setTabIconTint(ColorStateList.valueOf(iconColor));
tabLayout.setSelectedTabIndicatorColor(iconColor);
}
@Override
public void onTabSelected(final TabLayout.Tab selectedTab) {
if (DEBUG) {
@ -209,10 +274,18 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
updateTitleForTab(tab.getPosition());
}
private static final class SelectedTabsPagerAdapter
public static final class SelectedTabsPagerAdapter
extends FragmentStatePagerAdapterMenuWorkaround {
private final Context context;
private final List<Tab> internalTabsList;
/**
* Keep reference to LocalPlaylistFragments, because their data can be modified by the user
* during runtime and changes are not committed immediately. However, in some cases,
* the changes need to be committed immediately by calling
* {@link LocalPlaylistFragment#saveImmediate()}.
* The fragments are removed when {@link LocalPlaylistFragment#onDestroy()} is called.
*/
private final List<LocalPlaylistFragment> localPlaylistFragments = new ArrayList<>();
private SelectedTabsPagerAdapter(final Context context,
final FragmentManager fragmentManager,
@ -239,9 +312,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
((BaseFragment) fragment).useAsFrontPage(true);
}
if (fragment instanceof LocalPlaylistFragment) {
localPlaylistFragments.add((LocalPlaylistFragment) fragment);
}
return fragment;
}
public List<LocalPlaylistFragment> getLocalPlaylistFragments() {
return localPlaylistFragments;
}
@Override
public int getItemPosition(@NonNull final Object object) {
// Causes adapter to reload all Fragments when

View File

@ -0,0 +1,281 @@
package org.schabi.newpipe.fragments.detail;
import static android.text.TextUtils.isEmpty;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.TooltipCompat;
import androidx.core.text.HtmlCompat;
import com.google.android.material.chip.Chip;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentDescriptionBinding;
import org.schabi.newpipe.databinding.ItemMetadataBinding;
import org.schabi.newpipe.databinding.ItemMetadataTagsBinding;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.text.TextLinkifier;
import java.util.List;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public abstract class BaseDescriptionFragment extends BaseFragment {
private final CompositeDisposable descriptionDisposables = new CompositeDisposable();
protected FragmentDescriptionBinding binding;
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
binding = FragmentDescriptionBinding.inflate(inflater, container, false);
setupDescription();
setupMetadata(inflater, binding.detailMetadataLayout);
addTagsMetadataItem(inflater, binding.detailMetadataLayout);
return binding.getRoot();
}
@Override
public void onDestroy() {
descriptionDisposables.clear();
super.onDestroy();
}
/**
* Get the description to display.
* @return description object, if available
*/
@Nullable
protected abstract Description getDescription();
/**
* Get the streaming service. Used for generating description links.
* @return streaming service
*/
@NonNull
protected abstract StreamingService getService();
/**
* Get the streaming service ID. Used for tag links.
* @return service ID
*/
protected abstract int getServiceId();
/**
* Get the URL of the described video or audio, used to generate description links.
* @return stream URL
*/
@Nullable
protected abstract String getStreamUrl();
/**
* Get the list of tags to display below the description.
* @return tag list
*/
@NonNull
public abstract List<String> getTags();
/**
* Add additional metadata to display.
* @param inflater LayoutInflater
* @param layout detailMetadataLayout
*/
protected abstract void setupMetadata(LayoutInflater inflater, LinearLayout layout);
private void setupDescription() {
final Description description = getDescription();
if (description == null || isEmpty(description.getContent())
|| description == Description.EMPTY_DESCRIPTION) {
binding.detailDescriptionView.setVisibility(View.GONE);
binding.detailSelectDescriptionButton.setVisibility(View.GONE);
return;
}
// start with disabled state. This also loads description content (!)
disableDescriptionSelection();
binding.detailSelectDescriptionButton.setOnClickListener(v -> {
if (binding.detailDescriptionNoteView.getVisibility() == View.VISIBLE) {
disableDescriptionSelection();
} else {
// enable selection only when button is clicked to prevent flickering
enableDescriptionSelection();
}
});
}
private void enableDescriptionSelection() {
binding.detailDescriptionNoteView.setVisibility(View.VISIBLE);
binding.detailDescriptionView.setTextIsSelectable(true);
final String buttonLabel = getString(R.string.description_select_disable);
binding.detailSelectDescriptionButton.setContentDescription(buttonLabel);
TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel);
binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_close);
}
private void disableDescriptionSelection() {
// show description content again, otherwise some links are not clickable
final Description description = getDescription();
if (description != null) {
TextLinkifier.fromDescription(binding.detailDescriptionView,
description, HtmlCompat.FROM_HTML_MODE_LEGACY,
getService(), getStreamUrl(),
descriptionDisposables, SET_LINK_MOVEMENT_METHOD);
}
binding.detailDescriptionNoteView.setVisibility(View.GONE);
binding.detailDescriptionView.setTextIsSelectable(false);
final String buttonLabel = getString(R.string.description_select_enable);
binding.detailSelectDescriptionButton.setContentDescription(buttonLabel);
TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel);
binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_select_all);
}
protected void addMetadataItem(final LayoutInflater inflater,
final LinearLayout layout,
final boolean linkifyContent,
@StringRes final int type,
@NonNull final String content) {
if (isBlank(content)) {
return;
}
final ItemMetadataBinding itemBinding =
ItemMetadataBinding.inflate(inflater, layout, false);
itemBinding.metadataTypeView.setText(type);
itemBinding.metadataTypeView.setOnLongClickListener(v -> {
ShareUtils.copyToClipboard(requireContext(), content);
return true;
});
if (linkifyContent) {
TextLinkifier.fromPlainText(itemBinding.metadataContentView, content, null, null,
descriptionDisposables, SET_LINK_MOVEMENT_METHOD);
} else {
itemBinding.metadataContentView.setText(content);
}
itemBinding.metadataContentView.setClickable(true);
layout.addView(itemBinding.getRoot());
}
private String imageSizeToText(final int heightOrWidth) {
if (heightOrWidth < 0) {
return getString(R.string.question_mark);
} else {
return String.valueOf(heightOrWidth);
}
}
protected void addImagesMetadataItem(final LayoutInflater inflater,
final LinearLayout layout,
@StringRes final int type,
final List<Image> images) {
final String preferredImageUrl = ImageStrategy.choosePreferredImage(images);
if (preferredImageUrl == null) {
return; // null will be returned in case there is no image
}
final ItemMetadataBinding itemBinding =
ItemMetadataBinding.inflate(inflater, layout, false);
itemBinding.metadataTypeView.setText(type);
final SpannableStringBuilder urls = new SpannableStringBuilder();
for (final Image image : images) {
if (urls.length() != 0) {
urls.append(", ");
}
final int entryBegin = urls.length();
if (image.getHeight() != Image.HEIGHT_UNKNOWN
|| image.getWidth() != Image.WIDTH_UNKNOWN
// if even the resolution level is unknown, ?x? will be shown
|| image.getEstimatedResolutionLevel() == Image.ResolutionLevel.UNKNOWN) {
urls.append(imageSizeToText(image.getHeight()));
urls.append('x');
urls.append(imageSizeToText(image.getWidth()));
} else {
switch (image.getEstimatedResolutionLevel()) {
case LOW -> urls.append(getString(R.string.image_quality_low));
case MEDIUM -> urls.append(getString(R.string.image_quality_medium));
case HIGH -> urls.append(getString(R.string.image_quality_high));
default -> {
// unreachable, Image.ResolutionLevel.UNKNOWN is already filtered out
}
}
}
urls.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull final View widget) {
ShareUtils.openUrlInBrowser(requireContext(), image.getUrl());
}
}, entryBegin, urls.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (preferredImageUrl.equals(image.getUrl())) {
urls.setSpan(new StyleSpan(Typeface.BOLD), entryBegin, urls.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
itemBinding.metadataContentView.setText(urls);
itemBinding.metadataContentView.setMovementMethod(LinkMovementMethod.getInstance());
layout.addView(itemBinding.getRoot());
}
private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {
final List<String> tags = getTags();
if (!tags.isEmpty()) {
final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false);
tags.stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> {
final Chip chip = (Chip) inflater.inflate(R.layout.chip,
itemBinding.metadataTagsChips, false);
chip.setText(tag);
chip.setOnClickListener(this::onTagClick);
chip.setOnLongClickListener(this::onTagLongClick);
itemBinding.metadataTagsChips.addView(chip);
});
layout.addView(itemBinding.getRoot());
}
}
private void onTagClick(final View chip) {
if (getParentFragment() != null) {
NavigationHelper.openSearchFragment(getParentFragment().getParentFragmentManager(),
getServiceId(), ((Chip) chip).getText().toString());
}
}
private boolean onTagLongClick(final View chip) {
ShareUtils.copyToClipboard(requireContext(), ((Chip) chip).getText().toString());
return true;
}
}

View File

@ -1,232 +1,112 @@
package org.schabi.newpipe.fragments.detail;
import static android.text.TextUtils.isEmpty;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.util.Localization.getAppLocale;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.TooltipCompat;
import androidx.core.text.HtmlCompat;
import com.google.android.material.chip.Chip;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentDescriptionBinding;
import org.schabi.newpipe.databinding.ItemMetadataBinding;
import org.schabi.newpipe.databinding.ItemMetadataTagsBinding;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.external_communication.TextLinkifier;
import java.util.List;
import icepick.State;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class DescriptionFragment extends BaseFragment {
public class DescriptionFragment extends BaseDescriptionFragment {
@State
StreamInfo streamInfo = null;
final CompositeDisposable descriptionDisposables = new CompositeDisposable();
FragmentDescriptionBinding binding;
public DescriptionFragment() {
}
StreamInfo streamInfo;
public DescriptionFragment(final StreamInfo streamInfo) {
this.streamInfo = streamInfo;
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
binding = FragmentDescriptionBinding.inflate(inflater, container, false);
if (streamInfo != null) {
setupUploadDate();
setupDescription();
setupMetadata(inflater, binding.detailMetadataLayout);
}
return binding.getRoot();
}
@Override
public void onDestroy() {
descriptionDisposables.clear();
super.onDestroy();
public DescriptionFragment() {
// keep empty constructor for IcePick when resuming fragment from memory
}
private void setupUploadDate() {
if (streamInfo.getUploadDate() != null) {
@Nullable
@Override
protected Description getDescription() {
return streamInfo.getDescription();
}
@NonNull
@Override
protected StreamingService getService() {
return streamInfo.getService();
}
@Override
protected int getServiceId() {
return streamInfo.getServiceId();
}
@NonNull
@Override
protected String getStreamUrl() {
return streamInfo.getUrl();
}
@NonNull
@Override
public List<String> getTags() {
return streamInfo.getTags();
}
@Override
protected void setupMetadata(final LayoutInflater inflater,
final LinearLayout layout) {
if (streamInfo != null && streamInfo.getUploadDate() != null) {
binding.detailUploadDateView.setText(Localization
.localizeUploadDate(activity, streamInfo.getUploadDate().offsetDateTime()));
} else {
binding.detailUploadDateView.setVisibility(View.GONE);
}
}
private void setupDescription() {
final Description description = streamInfo.getDescription();
if (description == null || isEmpty(description.getContent())
|| description == Description.EMPTY_DESCRIPTION) {
binding.detailDescriptionView.setVisibility(View.GONE);
binding.detailSelectDescriptionButton.setVisibility(View.GONE);
if (streamInfo == null) {
return;
}
// start with disabled state. This also loads description content (!)
disableDescriptionSelection();
addMetadataItem(inflater, layout, false, R.string.metadata_category,
streamInfo.getCategory());
binding.detailSelectDescriptionButton.setOnClickListener(v -> {
if (binding.detailDescriptionNoteView.getVisibility() == View.VISIBLE) {
disableDescriptionSelection();
} else {
// enable selection only when button is clicked to prevent flickering
enableDescriptionSelection();
}
});
}
private void enableDescriptionSelection() {
binding.detailDescriptionNoteView.setVisibility(View.VISIBLE);
binding.detailDescriptionView.setTextIsSelectable(true);
final String buttonLabel = getString(R.string.description_select_disable);
binding.detailSelectDescriptionButton.setContentDescription(buttonLabel);
TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel);
binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_close);
}
private void disableDescriptionSelection() {
// show description content again, otherwise some links are not clickable
loadDescriptionContent();
binding.detailDescriptionNoteView.setVisibility(View.GONE);
binding.detailDescriptionView.setTextIsSelectable(false);
final String buttonLabel = getString(R.string.description_select_enable);
binding.detailSelectDescriptionButton.setContentDescription(buttonLabel);
TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel);
binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_select_all);
}
private void loadDescriptionContent() {
final Description description = streamInfo.getDescription();
switch (description.getType()) {
case Description.HTML:
TextLinkifier.createLinksFromHtmlBlock(binding.detailDescriptionView,
description.getContent(), HtmlCompat.FROM_HTML_MODE_LEGACY, streamInfo,
descriptionDisposables);
break;
case Description.MARKDOWN:
TextLinkifier.createLinksFromMarkdownText(binding.detailDescriptionView,
description.getContent(), streamInfo, descriptionDisposables);
break;
case Description.PLAIN_TEXT: default:
TextLinkifier.createLinksFromPlainText(binding.detailDescriptionView,
description.getContent(), streamInfo, descriptionDisposables);
break;
}
}
private void setupMetadata(final LayoutInflater inflater,
final LinearLayout layout) {
addMetadataItem(inflater, layout, false,
R.string.metadata_category, streamInfo.getCategory());
addMetadataItem(inflater, layout, false,
R.string.metadata_licence, streamInfo.getLicence());
addMetadataItem(inflater, layout, false, R.string.metadata_licence,
streamInfo.getLicence());
addPrivacyMetadataItem(inflater, layout);
if (streamInfo.getAgeLimit() != NO_AGE_LIMIT) {
addMetadataItem(inflater, layout, false,
R.string.metadata_age_limit, String.valueOf(streamInfo.getAgeLimit()));
addMetadataItem(inflater, layout, false, R.string.metadata_age_limit,
String.valueOf(streamInfo.getAgeLimit()));
}
if (streamInfo.getLanguageInfo() != null) {
addMetadataItem(inflater, layout, false,
R.string.metadata_language, streamInfo.getLanguageInfo().getDisplayLanguage());
addMetadataItem(inflater, layout, false, R.string.metadata_language,
streamInfo.getLanguageInfo().getDisplayLanguage(getAppLocale(getContext())));
}
addMetadataItem(inflater, layout, true,
R.string.metadata_support, streamInfo.getSupportInfo());
addMetadataItem(inflater, layout, true,
R.string.metadata_host, streamInfo.getHost());
addMetadataItem(inflater, layout, true,
R.string.metadata_thumbnail_url, streamInfo.getThumbnailUrl());
addMetadataItem(inflater, layout, true, R.string.metadata_support,
streamInfo.getSupportInfo());
addMetadataItem(inflater, layout, true, R.string.metadata_host,
streamInfo.getHost());
addTagsMetadataItem(inflater, layout);
}
private void addMetadataItem(final LayoutInflater inflater,
final LinearLayout layout,
final boolean linkifyContent,
@StringRes final int type,
@Nullable final String content) {
if (isBlank(content)) {
return;
}
final ItemMetadataBinding itemBinding =
ItemMetadataBinding.inflate(inflater, layout, false);
itemBinding.metadataTypeView.setText(type);
itemBinding.metadataTypeView.setOnLongClickListener(v -> {
ShareUtils.copyToClipboard(requireContext(), content);
return true;
});
if (linkifyContent) {
TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content, null,
descriptionDisposables);
} else {
itemBinding.metadataContentView.setText(content);
}
layout.addView(itemBinding.getRoot());
}
private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {
if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) {
final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false);
streamInfo.getTags().stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> {
final Chip chip = (Chip) inflater.inflate(R.layout.chip,
itemBinding.metadataTagsChips, false);
chip.setText(tag);
chip.setOnClickListener(this::onTagClick);
chip.setOnLongClickListener(this::onTagLongClick);
itemBinding.metadataTagsChips.addView(chip);
});
layout.addView(itemBinding.getRoot());
}
}
private void onTagClick(final View chip) {
if (getParentFragment() != null) {
NavigationHelper.openSearchFragment(getParentFragment().getParentFragmentManager(),
streamInfo.getServiceId(), ((Chip) chip).getText().toString());
}
}
private boolean onTagLongClick(final View chip) {
ShareUtils.copyToClipboard(requireContext(), ((Chip) chip).getText().toString());
return true;
addImagesMetadataItem(inflater, layout, R.string.metadata_thumbnails,
streamInfo.getThumbnails());
addImagesMetadataItem(inflater, layout, R.string.metadata_uploader_avatars,
streamInfo.getUploaderAvatars());
addImagesMetadataItem(inflater, layout, R.string.metadata_subchannel_avatars,
streamInfo.getSubChannelAvatars());
}
private void addPrivacyMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {
@ -245,14 +125,15 @@ public class DescriptionFragment extends BaseFragment {
case INTERNAL:
contentRes = R.string.metadata_privacy_internal;
break;
case OTHER: default:
case OTHER:
default:
contentRes = 0;
break;
}
if (contentRes != 0) {
addMetadataItem(inflater, layout, false,
R.string.metadata_privacy, getString(contentRes));
addMetadataItem(inflater, layout, false, R.string.metadata_privacy,
getString(contentRes));
}
}
}

View File

@ -26,6 +26,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@ -91,11 +92,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = isGridLayout();
itemsList.setLayoutManager(useGrid
? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setUseGridVariant(useGrid);
infoListAdapter.notifyDataSetChanged();
refreshItemViewMode();
}
updateFlags = 0;
}
@ -215,22 +212,29 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
/ (double) width);
final int spanCount = Math.floorDiv(resources.getDisplayMetrics().widthPixels, width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
return lm;
}
/**
* Updates the item view mode based on user preference.
*/
private void refreshItemViewMode() {
final ItemViewMode itemViewMode = getItemViewMode();
itemsList.setLayoutManager((itemViewMode == ItemViewMode.GRID)
? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setItemViewMode(itemViewMode);
infoListAdapter.notifyDataSetChanged();
}
@Override
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
final boolean useGrid = isGridLayout();
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
infoListAdapter.setUseGridVariant(useGrid);
refreshItemViewMode();
final Supplier<View> listHeaderSupplier = getListHeaderSupplier();
if (listHeaderSupplier != null) {
@ -470,12 +474,16 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (key.equals(getString(R.string.list_view_mode_key))) {
if (getString(R.string.list_view_mode_key).equals(key)) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}
protected boolean isGridLayout() {
return ThemeHelper.shouldUseGridLayout(activity);
/**
* Returns preferred item view mode.
* @return ItemViewMode
*/
protected ItemViewMode getItemViewMode() {
return ThemeHelper.getItemViewMode(requireContext());
}
}

View File

@ -1,5 +1,7 @@
package org.schabi.newpipe.fragments.list;
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
@ -7,13 +9,13 @@ import android.view.View;
import androidx.annotation.NonNull;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.views.NewPipeRecyclerView;
@ -229,13 +231,11 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
if (!result.getRelatedItems().isEmpty()) {
infoListAdapter.addInfoItemList(result.getRelatedItems());
showListFooter(hasMoreItems());
} else if (hasMoreItems()) {
loadMoreItems();
} else {
infoListAdapter.clearStreamItemList();
// showEmptyState should be called only if there is no item as
// well as no header in infoListAdapter
if (!(result instanceof ChannelInfo && infoListAdapter.getItemCount() == 1)) {
showEmptyState();
}
showEmptyState();
}
}
@ -252,6 +252,20 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
}
}
@Override
public void showEmptyState() {
// show "no streams" for SoundCloud; otherwise "no videos"
// showing "no live streams" is handled in KioskFragment
if (emptyStateView != null) {
if (currentInfo.getService() == SoundCloud) {
setEmptyStateMessage(R.string.no_streams);
} else {
setEmptyStateMessage(R.string.no_videos);
}
}
super.showEmptyState();
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/

View File

@ -0,0 +1,94 @@
package org.schabi.newpipe.fragments.list.channel;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.UNKNOWN_SUBSCRIBER_COUNT;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.fragments.detail.BaseDescriptionFragment;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Localization;
import java.util.List;
import icepick.State;
public class ChannelAboutFragment extends BaseDescriptionFragment {
@State
protected ChannelInfo channelInfo;
ChannelAboutFragment(@NonNull final ChannelInfo channelInfo) {
this.channelInfo = channelInfo;
}
public ChannelAboutFragment() {
// keep empty constructor for IcePick when resuming fragment from memory
}
@Override
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
binding.constraintLayout.setPadding(0, DeviceUtils.dpToPx(8, requireContext()), 0, 0);
}
@Nullable
@Override
protected Description getDescription() {
return new Description(channelInfo.getDescription(), Description.PLAIN_TEXT);
}
@NonNull
@Override
protected StreamingService getService() {
return channelInfo.getService();
}
@Override
protected int getServiceId() {
return channelInfo.getServiceId();
}
@Nullable
@Override
protected String getStreamUrl() {
return null;
}
@NonNull
@Override
public List<String> getTags() {
return channelInfo.getTags();
}
@Override
protected void setupMetadata(final LayoutInflater inflater,
final LinearLayout layout) {
// There is no upload date available for channels, so hide the relevant UI element
binding.detailUploadDateView.setVisibility(View.GONE);
if (channelInfo == null) {
return;
}
if (channelInfo.getSubscriberCount() != UNKNOWN_SUBSCRIBER_COUNT) {
addMetadataItem(inflater, layout, false, R.string.metadata_subscribers,
Localization.localizeNumber(
requireContext(),
channelInfo.getSubscriberCount()));
}
addImagesMetadataItem(inflater, layout, R.string.metadata_avatars,
channelInfo.getAvatars());
addImagesMetadataItem(inflater, layout, R.string.metadata_banners,
channelInfo.getBanners());
}
}

View File

@ -5,6 +5,7 @@ import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
@ -16,51 +17,50 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.preference.PreferenceManager;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import com.jakewharton.rxbinding4.view.RxView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.NotificationMode;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
import org.schabi.newpipe.databinding.FragmentChannelBinding;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.detail.TabAdapter;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.local.feed.notifications.NotificationHelper;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Action;
@ -68,29 +68,37 @@ import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, ChannelInfo>
implements View.OnClickListener {
public class ChannelFragment extends BaseStateFragment<ChannelInfo>
implements StateSaver.WriteRead {
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG";
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
protected String name;
@State
protected String url;
private ChannelInfo currentInfo;
private Disposable currentWorker;
private final CompositeDisposable disposables = new CompositeDisposable();
private Disposable subscribeButtonMonitor;
private SubscriptionManager subscriptionManager;
private int lastTab;
private boolean channelContentNotSupported = false;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private SubscriptionManager subscriptionManager;
private FragmentChannelBinding channelBinding;
private ChannelHeaderBinding headerBinding;
private PlaylistControlBinding playlistControlBinding;
private FragmentChannelBinding binding;
private TabAdapter tabAdapter;
private MenuItem menuRssButton;
private MenuItem menuNotifyButton;
private SubscriptionEntity channelSubscription;
public static ChannelFragment getInstance(final int serviceId, final String url,
final String name) {
@ -99,22 +107,23 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
return instance;
}
public ChannelFragment() {
super(UserAction.REQUESTED_CHANNEL);
private void setInitialData(final int sid, final String u, final String title) {
this.serviceId = sid;
this.url = u;
this.name = !TextUtils.isEmpty(title) ? title : "";
}
@Override
public void onResume() {
super.onResume();
if (activity != null && useAsFrontPage) {
setTitle(currentInfo != null ? currentInfo.getName() : name);
}
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
@ -125,49 +134,58 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_channel, container, false);
binding = FragmentChannelBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
channelBinding = FragmentChannelBinding.bind(rootView);
showContentNotSupportedIfNeeded();
}
@Override // called from onViewCreated in BaseFragment.onViewCreated
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
@Override
public void onDestroy() {
super.onDestroy();
disposables.clear();
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
tabAdapter = new TabAdapter(getChildFragmentManager());
binding.viewPager.setAdapter(tabAdapter);
binding.tabLayout.setupWithViewPager(binding.viewPager);
setTitle(name);
binding.channelTitleView.setText(name);
if (!ImageStrategy.shouldLoadImages()) {
// do not waste space for the banner if it is not going to be loaded
binding.channelBannerImage.setImageDrawable(null);
}
channelBinding = null;
headerBinding = null;
playlistControlBinding = null;
}
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Supplier<View> getListHeaderSupplier() {
headerBinding = ChannelHeaderBinding
.inflate(activity.getLayoutInflater(), itemsList, false);
playlistControlBinding = headerBinding.playlistControl;
return headerBinding::getRoot;
}
@Override
protected void initListeners() {
super.initListeners();
headerBinding.subChannelTitleView.setOnClickListener(this);
headerBinding.subChannelAvatarView.setOnClickListener(this);
final View.OnClickListener openSubChannel = v -> {
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
try {
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
} else if (DEBUG) {
Log.i(TAG, "Can't open parent channel because we got no channel URL");
}
};
binding.subChannelAvatarView.setOnClickListener(openSubChannel);
binding.subChannelTitleView.setOnClickListener(openSubChannel);
}
@Override
public void onDestroy() {
super.onDestroy();
if (currentWorker != null) {
currentWorker.dispose();
}
disposables.clear();
binding = null;
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@ -176,36 +194,36 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
public void onCreateOptionsMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
final ActionBar supportActionBar = activity.getSupportActionBar();
if (useAsFrontPage && supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(false);
} else {
inflater.inflate(R.menu.menu_channel, menu);
inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
menuRssButton = menu.findItem(R.id.menu_item_rss);
menuNotifyButton = menu.findItem(R.id.menu_item_notify);
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
public void onPrepareOptionsMenu(@NonNull final Menu menu) {
super.onPrepareOptionsMenu(menu);
menuRssButton = menu.findItem(R.id.menu_item_rss);
menuNotifyButton = menu.findItem(R.id.menu_item_notify);
updateNotifyButton(channelSubscription);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
case R.id.menu_item_notify:
final boolean value = !item.isChecked();
item.setEnabled(false);
setNotify(value);
break;
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
case R.id.menu_item_rss:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(
requireContext(), currentInfo.getFeedUrl(), false);
ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl());
}
break;
case R.id.menu_item_openInBrowser:
@ -216,7 +234,7 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name, currentInfo.getOriginalUrl(),
currentInfo.getAvatarUrl());
currentInfo.getAvatars());
}
break;
default:
@ -225,13 +243,14 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Channel Subscription
//////////////////////////////////////////////////////////////////////////*/
private void monitorSubscription(final ChannelInfo info) {
final Consumer<Throwable> onError = (Throwable throwable) -> {
animate(headerBinding.channelSubscribeButton, false, 100);
animate(binding.channelSubscribeButton, false, 100);
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_GET,
"Get subscription status", currentInfo));
};
@ -264,10 +283,9 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
}, onError));
}
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription,
final ChannelInfo info) {
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
return (@NonNull Object o) -> {
subscriptionManager.insertSubscription(subscription, info);
subscriptionManager.insertSubscription(subscription);
return o;
};
}
@ -299,8 +317,7 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
.subscribe(onComplete, onError));
}
private Disposable monitorSubscribeButton(final Button subscribeButton,
final Function<Object, Object> action) {
private Disposable monitorSubscribeButton(final Function<Object, Object> action) {
final Consumer<Object> onNext = (@NonNull Object o) -> {
if (DEBUG) {
Log.d(TAG, "Changed subscription status to this channel!");
@ -312,7 +329,7 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
"Changing subscription for " + currentInfo.getUrl(), currentInfo));
/* Emit clicks from main thread unto io thread */
return RxView.clicks(subscribeButton)
return RxView.clicks(binding.channelSubscribeButton)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.debounce(BUTTON_DEBOUNCE_INTERVAL, TimeUnit.MILLISECONDS) // Ignore rapid clicks
@ -338,20 +355,20 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
channel.setServiceId(info.getServiceId());
channel.setUrl(info.getUrl());
channel.setData(info.getName(),
info.getAvatarUrl(),
ImageStrategy.imageListToDbUrl(info.getAvatars()),
info.getDescription(),
info.getSubscriberCount());
channelSubscription = null;
updateNotifyButton(null);
subscribeButtonMonitor = monitorSubscribeButton(
headerBinding.channelSubscribeButton, mapOnSubscribe(channel, info));
subscribeButtonMonitor = monitorSubscribeButton(mapOnSubscribe(channel));
} else {
if (DEBUG) {
Log.d(TAG, "Found subscription to this channel!");
}
final SubscriptionEntity subscription = subscriptionEntities.get(0);
updateNotifyButton(subscription);
subscribeButtonMonitor = monitorSubscribeButton(
headerBinding.channelSubscribeButton, mapOnUnsubscribe(subscription));
channelSubscription = subscriptionEntities.get(0);
updateNotifyButton(channelSubscription);
subscribeButtonMonitor =
monitorSubscribeButton(mapOnUnsubscribe(channelSubscription));
}
};
}
@ -362,34 +379,33 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
+ "isSubscribed = [" + isSubscribed + "]");
}
final boolean isButtonVisible = headerBinding.channelSubscribeButton.getVisibility()
final boolean isButtonVisible = binding.channelSubscribeButton.getVisibility()
== View.VISIBLE;
final int backgroundDuration = isButtonVisible ? 300 : 0;
final int textDuration = isButtonVisible ? 200 : 0;
final int subscribeBackground = ThemeHelper
.resolveColorFromAttr(activity, R.attr.colorPrimary);
final int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
final int subscribedBackground = ContextCompat
.getColor(activity, R.color.subscribed_background_color);
final int subscribedText = ContextCompat.getColor(activity, R.color.subscribed_text_color);
final int subscribeBackground = ColorUtils.blendARGB(ThemeHelper
.resolveColorFromAttr(activity, R.attr.colorPrimary), subscribedBackground, 0.35f);
final int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
if (!isSubscribed) {
headerBinding.channelSubscribeButton.setText(R.string.subscribe_button_title);
animateBackgroundColor(headerBinding.channelSubscribeButton, backgroundDuration,
subscribedBackground, subscribeBackground);
animateTextColor(headerBinding.channelSubscribeButton, textDuration, subscribedText,
subscribeText);
} else {
headerBinding.channelSubscribeButton.setText(R.string.subscribed_button_title);
animateBackgroundColor(headerBinding.channelSubscribeButton, backgroundDuration,
if (isSubscribed) {
binding.channelSubscribeButton.setText(R.string.subscribed_button_title);
animateBackgroundColor(binding.channelSubscribeButton, backgroundDuration,
subscribeBackground, subscribedBackground);
animateTextColor(headerBinding.channelSubscribeButton, textDuration, subscribeText,
animateTextColor(binding.channelSubscribeButton, textDuration, subscribeText,
subscribedText);
} else {
binding.channelSubscribeButton.setText(R.string.subscribe_button_title);
animateBackgroundColor(binding.channelSubscribeButton, backgroundDuration,
subscribedBackground, subscribeBackground);
animateTextColor(binding.channelSubscribeButton, textDuration, subscribedText,
subscribeText);
}
animate(headerBinding.channelSubscribeButton, true, 100,
AnimationType.LIGHT_SCALE_AND_ALPHA);
animate(binding.channelSubscribeButton, true, 100, AnimationType.LIGHT_SCALE_AND_ALPHA);
}
private void updateNotifyButton(@Nullable final SubscriptionEntity subscription) {
@ -425,108 +441,179 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
* Show a snackbar with the option to enable notifications on new streams for this channel.
*/
private void showNotifySnackbar() {
Snackbar.make(itemsList, R.string.you_successfully_subscribed, Snackbar.LENGTH_LONG)
Snackbar.make(binding.getRoot(), R.string.you_successfully_subscribed, Snackbar.LENGTH_LONG)
.setAction(R.string.get_notified, v -> setNotify(true))
.setActionTextColor(Color.YELLOW)
.show();
}
/*//////////////////////////////////////////////////////////////////////////
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<ListExtractor.InfoItemsPage<StreamInfoItem>> loadMoreItemsLogic() {
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
}
@Override
protected Single<ChannelInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
}
/*//////////////////////////////////////////////////////////////////////////
// OnClick
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onClick(final View v) {
if (isLoading.get() || currentInfo == null) {
return;
}
private void updateTabs() {
tabAdapter.clearAllItems();
switch (v.getId()) {
case R.id.sub_channel_avatar_view:
case R.id.sub_channel_title_view:
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
try {
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
currentInfo.getParentChannelUrl(),
currentInfo.getParentChannelName());
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
}
} else if (DEBUG) {
Log.i(TAG, "Can't open parent channel because we got no channel URL");
if (currentInfo != null && !channelContentNotSupported) {
final Context context = requireContext();
final SharedPreferences preferences = PreferenceManager
.getDefaultSharedPreferences(context);
for (final ListLinkHandler linkHandler : currentInfo.getTabs()) {
final String tab = linkHandler.getContentFilters().get(0);
if (ChannelTabHelper.showChannelTab(context, preferences, tab)) {
final ChannelTabFragment channelTabFragment =
ChannelTabFragment.getInstance(serviceId, linkHandler, name);
channelTabFragment.useAsFrontPage(useAsFrontPage);
tabAdapter.addFragment(channelTabFragment,
context.getString(ChannelTabHelper.getTranslationKey(tab)));
}
break;
}
if (ChannelTabHelper.showChannelTab(
context, preferences, R.string.show_channel_tabs_about)) {
tabAdapter.addFragment(
new ChannelAboutFragment(currentInfo),
context.getString(R.string.channel_tab_about));
}
}
tabAdapter.notifyDataSetUpdate();
for (int i = 0; i < tabAdapter.getCount(); i++) {
binding.tabLayout.getTabAt(i).setText(tabAdapter.getItemTitle(i));
}
// Restore previously selected tab
final TabLayout.Tab ltab = binding.tabLayout.getTabAt(lastTab);
if (ltab != null) {
binding.tabLayout.selectTab(ltab);
}
}
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/
@Override
public String generateSuffix() {
return null;
}
@Override
public void writeTo(final Queue<Object> objectsToSave) {
objectsToSave.add(currentInfo);
objectsToSave.add(binding == null ? 0 : binding.tabLayout.getSelectedTabPosition());
}
@Override
public void readFrom(@NonNull final Queue<Object> savedObjects) {
currentInfo = (ChannelInfo) savedObjects.poll();
lastTab = (Integer) savedObjects.poll();
}
@Override
public void onSaveInstanceState(final @NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (binding != null) {
outState.putInt("LastTab", binding.tabLayout.getSelectedTabPosition());
}
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
lastTab = savedInstanceState.getInt("LastTab", 0);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
protected void doInitialLoadLogic() {
if (currentInfo == null) {
startLoading(false);
} else {
handleResult(currentInfo);
}
}
@Override
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
currentInfo = null;
updateTabs();
if (currentWorker != null) {
currentWorker.dispose();
}
runWorker(forceLoad);
}
private void runWorker(final boolean forceLoad) {
currentWorker = ExtractorHelper.getChannelInfo(serviceId, url, forceLoad)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
isLoading.set(false);
handleResult(result);
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_CHANNEL,
url == null ? "No URL" : url, serviceId)));
}
@Override
public void showLoading() {
super.showLoading();
PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG);
animate(headerBinding.channelSubscribeButton, false, 100);
animate(binding.channelSubscribeButton, false, 100);
}
@Override
public void handleResult(@NonNull final ChannelInfo result) {
super.handleResult(result);
currentInfo = result;
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
headerBinding.getRoot().setVisibility(View.VISIBLE);
PicassoHelper.loadBanner(result.getBannerUrl()).tag(PICASSO_CHANNEL_TAG)
.into(headerBinding.channelBannerImage);
PicassoHelper.loadAvatar(result.getAvatarUrl()).tag(PICASSO_CHANNEL_TAG)
.into(headerBinding.channelAvatarView);
PicassoHelper.loadAvatar(result.getParentChannelAvatarUrl()).tag(PICASSO_CHANNEL_TAG)
.into(headerBinding.subChannelAvatarView);
if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) {
PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG)
.into(binding.channelBannerImage);
} else {
// do not waste space for the banner, if the user disabled images or there is not one
binding.channelBannerImage.setImageDrawable(null);
}
headerBinding.channelSubscriberView.setVisibility(View.VISIBLE);
PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG)
.into(binding.channelAvatarView);
PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG)
.into(binding.subChannelAvatarView);
binding.channelTitleView.setText(result.getName());
binding.channelSubscriberView.setVisibility(View.VISIBLE);
if (result.getSubscriberCount() >= 0) {
headerBinding.channelSubscriberView.setText(Localization
binding.channelSubscriberView.setText(Localization
.shortSubscriberCount(activity, result.getSubscriberCount()));
} else {
headerBinding.channelSubscriberView.setText(R.string.subscribers_count_not_available);
binding.channelSubscriberView.setText(R.string.subscribers_count_not_available);
}
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
headerBinding.subChannelTitleView.setText(String.format(
binding.subChannelTitleView.setText(String.format(
getString(R.string.channel_created_by),
currentInfo.getParentChannelName())
);
headerBinding.subChannelTitleView.setVisibility(View.VISIBLE);
headerBinding.subChannelAvatarView.setVisibility(View.VISIBLE);
} else {
headerBinding.subChannelTitleView.setVisibility(View.GONE);
binding.subChannelTitleView.setVisibility(View.VISIBLE);
binding.subChannelAvatarView.setVisibility(View.VISIBLE);
}
if (menuRssButton != null) {
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
}
// PlaylistControls should be visible only if there is some item in
// infoListAdapter other than header
if (infoListAdapter.getItemCount() != 1) {
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
} else {
playlistControlBinding.getRoot().setVisibility(View.GONE);
}
channelContentNotSupported = false;
for (final Throwable throwable : result.getErrors()) {
if (throwable instanceof ContentNotSupportedException) {
@ -540,62 +627,21 @@ public class ChannelFragment extends BaseListInfoFragment<StreamInfoItem, Channe
if (subscribeButtonMonitor != null) {
subscribeButtonMonitor.dispose();
}
updateTabs();
updateSubscription(result);
monitorSubscription(result);
playlistControlBinding.playlistCtrlPlayAllButton
.setOnClickListener(view -> NavigationHelper
.playOnMainPlayer(activity, getPlayQueue()));
playlistControlBinding.playlistCtrlPlayPopupButton
.setOnClickListener(view -> NavigationHelper
.playOnPopupPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayBgButton
.setOnClickListener(view -> NavigationHelper
.playOnBackgroundPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP);
return true;
});
playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO);
return true;
});
}
private void showContentNotSupportedIfNeeded() {
// channelBinding might not be initialized when handleResult() is called
// (e.g. after rotating the screen, #6696)
if (!channelContentNotSupported || channelBinding == null) {
if (!channelContentNotSupported || binding == null) {
return;
}
channelBinding.errorContentNotSupported.setVisibility(View.VISIBLE);
channelBinding.channelKaomoji.setText("(︶︹︺)");
channelBinding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);
channelBinding.channelNoVideos.setVisibility(View.GONE);
}
private PlayQueue getPlayQueue() {
final List<StreamInfoItem> streamItems = infoListAdapter.getItemsList().stream()
.filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast)
.collect(Collectors.toList());
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
currentInfo.getNextPage(), streamItems, 0);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(final String title) {
super.setTitle(title);
if (!useAsFrontPage) {
headerBinding.channelTitleView.setText(title);
}
binding.errorContentNotSupported.setVisibility(View.VISIBLE);
binding.channelKaomoji.setText("(︶︹︺)");
binding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);
}
}

View File

@ -0,0 +1,168 @@
package org.schabi.newpipe.fragments.list.channel;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabInfo;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder;
import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.PlayButtonHelper;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import icepick.State;
import io.reactivex.rxjava3.core.Single;
public class ChannelTabFragment extends BaseListInfoFragment<InfoItem, ChannelTabInfo>
implements PlaylistControlViewHolder {
// states must be protected and not private for IcePick being able to access them
@State
protected ListLinkHandler tabHandler;
@State
protected String channelName;
private PlaylistControlBinding playlistControlBinding;
@NonNull
public static ChannelTabFragment getInstance(final int serviceId,
final ListLinkHandler tabHandler,
final String channelName) {
final ChannelTabFragment instance = new ChannelTabFragment();
instance.serviceId = serviceId;
instance.tabHandler = tabHandler;
instance.channelName = channelName;
return instance;
}
public ChannelTabFragment() {
super(UserAction.REQUESTED_CHANNEL);
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(false);
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_channel_tab, container, false);
}
@Override
public void onDestroyView() {
super.onDestroyView();
playlistControlBinding = null;
}
@Override
protected Supplier<View> getListHeaderSupplier() {
if (ChannelTabHelper.isStreamsTab(tabHandler)) {
playlistControlBinding = PlaylistControlBinding
.inflate(activity.getLayoutInflater(), itemsList, false);
return playlistControlBinding::getRoot;
}
return null;
}
@Override
protected Single<ChannelTabInfo> loadResult(final boolean forceLoad) {
return ExtractorHelper.getChannelTab(serviceId, tabHandler, forceLoad);
}
@Override
protected Single<ListExtractor.InfoItemsPage<InfoItem>> loadMoreItemsLogic() {
return ExtractorHelper.getMoreChannelTabItems(serviceId, tabHandler, currentNextPage);
}
@Override
public void setTitle(final String title) {
// The channel name is displayed as title in the toolbar.
// The title is always a description of the content of the tab fragment.
// It should be unique for each channel because multiple channel tabs
// can be added to the main page. Therefore, the channel name is used.
// Using the title variable would cause the title to be the same for all channel tabs.
super.setTitle(channelName);
}
@Override
public void handleResult(@NonNull final ChannelTabInfo result) {
super.handleResult(result);
// FIXME this is a really hacky workaround, to avoid storing useless data in the fragment
// state. The problem is, `ReadyChannelTabListLinkHandler` might contain raw JSON data that
// uses a lot of memory (e.g. ~800KB for YouTube). While 800KB doesn't seem much, if
// you combine just a couple of channel tab fragments you easily go over the 1MB
// save&restore transaction limit, and get `TransactionTooLargeException`s. A proper
// solution would require rethinking about `ReadyChannelTabListLinkHandler`s.
if (tabHandler instanceof ReadyChannelTabListLinkHandler) {
try {
// once `handleResult` is called, the parsed data was already saved to cache, so
// we can discard any raw data in ReadyChannelTabListLinkHandler and create a
// link handler with identical properties, but without any raw data
final ListLinkHandlerFactory channelTabLHFactory = result.getService()
.getChannelTabLHFactory();
if (channelTabLHFactory != null) {
// some services do not not have a ChannelTabLHFactory
tabHandler = channelTabLHFactory.fromQuery(tabHandler.getId(),
tabHandler.getContentFilters(), tabHandler.getSortFilter());
}
} catch (final ParsingException e) {
// silently ignore the error, as the app can continue to function normally
Log.w(TAG, "Could not recreate channel tab handler", e);
}
}
if (playlistControlBinding != null) {
// PlaylistControls should be visible only if there is some item in
// infoListAdapter other than header
if (infoListAdapter.getItemCount() > 1) {
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
} else {
playlistControlBinding.getRoot().setVisibility(View.GONE);
}
PlayButtonHelper.initPlaylistControlClickListener(
activity, playlistControlBinding, this);
}
}
public PlayQueue getPlayQueue() {
final List<StreamInfoItem> streamItems = infoListAdapter.getItemsList().stream()
.filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast)
.collect(Collectors.toList());
return new ChannelTabPlayQueue(currentInfo.getServiceId(), tabHandler,
currentInfo.getNextPage(), streamItems, 0);
}
}

View File

@ -0,0 +1,170 @@
package org.schabi.newpipe.fragments.list.comments;
import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.text.HtmlCompat;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.CommentRepliesHeaderBinding;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.text.TextLinkifier;
import java.util.Queue;
import java.util.function.Supplier;
import icepick.State;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public final class CommentRepliesFragment
extends BaseListInfoFragment<CommentsInfoItem, CommentRepliesInfo> {
public static final String TAG = CommentRepliesFragment.class.getSimpleName();
@State
CommentsInfoItem commentsInfoItem; // the comment to show replies of
private final CompositeDisposable disposables = new CompositeDisposable();
/*//////////////////////////////////////////////////////////////////////////
// Constructors and lifecycle
//////////////////////////////////////////////////////////////////////////*/
// only called by the Android framework, after which readFrom is called and restores all data
public CommentRepliesFragment() {
super(UserAction.REQUESTED_COMMENT_REPLIES);
}
public CommentRepliesFragment(@NonNull final CommentsInfoItem commentsInfoItem) {
this();
this.commentsInfoItem = commentsInfoItem;
// setting "" as title since the title will be properly set right after
setInitialData(commentsInfoItem.getServiceId(), commentsInfoItem.getUrl(), "");
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_comments, container, false);
}
@Override
public void onDestroyView() {
disposables.clear();
super.onDestroyView();
}
@Override
protected Supplier<View> getListHeaderSupplier() {
return () -> {
final CommentRepliesHeaderBinding binding = CommentRepliesHeaderBinding
.inflate(activity.getLayoutInflater(), itemsList, false);
final CommentsInfoItem item = commentsInfoItem;
// load the author avatar
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar);
binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
? View.VISIBLE : View.GONE);
// setup author name and comment date
binding.authorName.setText(item.getUploaderName());
binding.uploadDate.setText(Localization.relativeTimeOrTextual(
getContext(), item.getUploadDate(), item.getTextualUploadDate()));
binding.authorTouchArea.setOnClickListener(
v -> NavigationHelper.openCommentAuthorIfPresent(requireActivity(), item));
// setup like count, hearted and pinned
binding.thumbsUpCount.setText(
Localization.likeCount(requireContext(), item.getLikeCount()));
// for heartImage goneMarginEnd was used, but there is no way to tell ConstraintLayout
// not to use a different margin only when both the next two views are gone
((ConstraintLayout.LayoutParams) binding.thumbsUpCount.getLayoutParams())
.setMarginEnd(DeviceUtils.dpToPx(
(item.isHeartedByUploader() || item.isPinned() ? 8 : 16),
requireContext()));
binding.heartImage.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
binding.pinnedImage.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
// setup comment content
TextLinkifier.fromDescription(binding.commentContent, item.getCommentText(),
HtmlCompat.FROM_HTML_MODE_LEGACY, getServiceById(item.getServiceId()),
item.getUrl(), disposables, null);
return binding.getRoot();
};
}
/*//////////////////////////////////////////////////////////////////////////
// State saving
//////////////////////////////////////////////////////////////////////////*/
@Override
public void writeTo(final Queue<Object> objectsToSave) {
super.writeTo(objectsToSave);
objectsToSave.add(commentsInfoItem);
}
@Override
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
super.readFrom(savedObjects);
commentsInfoItem = (CommentsInfoItem) savedObjects.poll();
}
/*//////////////////////////////////////////////////////////////////////////
// Data loading
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<CommentRepliesInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> new CommentRepliesInfo(commentsInfoItem,
// the reply count string will be shown as the activity title
Localization.replyCount(requireContext(), commentsInfoItem.getReplyCount())));
}
@Override
protected Single<ListExtractor.InfoItemsPage<CommentsInfoItem>> loadMoreItemsLogic() {
// commentsInfoItem.getUrl() should contain the url of the original
// ListInfo<CommentsInfoItem>, which should be the stream url
return ExtractorHelper.getMoreCommentItems(
serviceId, commentsInfoItem.getUrl(), currentNextPage);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
protected ItemViewMode getItemViewMode() {
return ItemViewMode.LIST;
}
/**
* @return the comment to which the replies are shown
*/
public CommentsInfoItem getCommentsInfoItem() {
return commentsInfoItem;
}
}

View File

@ -0,0 +1,22 @@
package org.schabi.newpipe.fragments.list.comments;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import java.util.Collections;
public final class CommentRepliesInfo extends ListInfo<CommentsInfoItem> {
/**
* This class is used to wrap the comment replies page into a ListInfo object.
*
* @param comment the comment from which to get replies
* @param name will be shown as the fragment title
*/
public CommentRepliesInfo(final CommentsInfoItem comment, final String name) {
super(comment.getServiceId(),
new ListLinkHandler("", "", "", Collections.emptyList(), null), name);
setNextPage(comment.getReplies());
setRelatedItems(Collections.emptyList()); // since it must be non-null
}
}

View File

@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@ -106,7 +107,17 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfoItem, Com
@NonNull final MenuInflater inflater) { }
@Override
protected boolean isGridLayout() {
return false;
protected ItemViewMode getItemViewMode() {
return ItemViewMode.LIST;
}
public boolean scrollToComment(final CommentsInfoItem comment) {
final int position = infoListAdapter.getItemsList().indexOf(comment);
if (position < 0) {
return false;
}
itemsList.scrollToPosition(position);
return true;
}
}

View File

@ -16,11 +16,13 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCLiveStreamKiosk;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.util.ExtractorHelper;
@ -161,4 +163,14 @@ public class KioskFragment extends BaseListInfoFragment<StreamInfoItem, KioskInf
name = kioskTranslatedName;
setTitle(kioskTranslatedName);
}
@Override
public void showEmptyState() {
// show "no live streams" for live stream kiosk
super.showEmptyState();
if (MediaCCCLiveStreamKiosk.KIOSK_ID.equals(currentInfo.getId())
&& ServiceList.MediaCCC.getServiceId() == currentInfo.getServiceId()) {
setEmptyStateMessage(R.string.no_live_streams);
}
}
}

View File

@ -0,0 +1,11 @@
package org.schabi.newpipe.fragments.list.playlist;
import org.schabi.newpipe.player.playqueue.PlayQueue;
/**
* Interface for {@code R.layout.playlist_control} view holders
* to give access to the play queue.
*/
public interface PlaylistControlViewHolder {
PlayQueue getPlayQueue();
}

View File

@ -1,7 +1,9 @@
package org.schabi.newpipe.fragments.list.playlist;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
import android.content.Context;
import android.os.Bundle;
@ -37,20 +39,22 @@ import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.PlayButtonHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.text.TextEllipsizer;
import java.util.ArrayList;
import java.util.List;
@ -64,7 +68,8 @@ import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, PlaylistInfo> {
public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, PlaylistInfo>
implements PlaylistControlViewHolder {
private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG";
@ -84,6 +89,9 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
private MenuItem playlistBookmarkButton;
private long streamCount;
private long playlistOverallDurationSeconds;
public static PlaylistFragment getInstance(final int serviceId, final String url,
final String name) {
final PlaylistFragment instance = new PlaylistFragment();
@ -132,6 +140,8 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
// Is mini variant still relevant?
// Only the remote playlist screen uses it now
infoListAdapter.setUseMiniVariant(true);
}
@ -230,24 +240,24 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
ShareUtils.openUrlInBrowser(requireContext(), url);
break;
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name, url,
currentInfo.getThumbnailUrl());
}
ShareUtils.shareText(requireContext(), name, url,
currentInfo == null ? List.of() : currentInfo.getThumbnails());
break;
case R.id.menu_item_bookmark:
onBookmarkClicked();
break;
case R.id.menu_item_append_playlist:
disposables.add(PlaylistDialog.createCorrespondingDialog(
getContext(),
getPlayQueue()
.getStreams()
.stream()
.map(StreamEntity::new)
.collect(Collectors.toList()),
dialog -> dialog.show(getFM(), TAG)
));
if (currentInfo != null) {
disposables.add(PlaylistDialog.createCorrespondingDialog(
getContext(),
getPlayQueue()
.getStreams()
.stream()
.map(StreamEntity::new)
.collect(Collectors.toList()),
dialog -> dialog.show(getFM(), TAG)
));
}
break;
default:
return super.onOptionsItemSelected(item);
@ -270,6 +280,12 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
animate(headerBinding.uploaderLayout, false, 200);
}
@Override
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
super.handleNextItems(result);
setStreamCountAndOverallDuration(result.getItems(), !result.hasNextPage());
}
@Override
public void handleResult(@NonNull final PlaylistInfo result) {
super.handleResult(result);
@ -296,7 +312,6 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
final String avatarUrl = result.getUploaderAvatarUrl();
if (result.getServiceId() == ServiceList.YouTube.getServiceId()
&& (YoutubeParsingHelper.isYoutubeMixId(result.getId())
|| YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) {
@ -312,12 +327,35 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
R.drawable.ic_radio)
);
} else {
PicassoHelper.loadAvatar(avatarUrl).tag(PICASSO_PLAYLIST_TAG)
PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG)
.into(headerBinding.uploaderAvatarView);
}
headerBinding.playlistStreamCount.setText(Localization
.localizeStreamCount(getContext(), result.getStreamCount()));
streamCount = result.getStreamCount();
setStreamCountAndOverallDuration(result.getRelatedItems(), !result.hasNextPage());
final Description description = result.getDescription();
if (description != null && description != Description.EMPTY_DESCRIPTION
&& !isBlank(description.getContent())) {
final TextEllipsizer ellipsizer = new TextEllipsizer(
headerBinding.playlistDescription, 5, getServiceById(result.getServiceId()));
ellipsizer.setStateChangeListener(isEllipsized ->
headerBinding.playlistDescriptionReadMore.setText(
Boolean.TRUE.equals(isEllipsized) ? R.string.show_more : R.string.show_less
));
ellipsizer.setOnContentChanged(canBeEllipsized -> {
headerBinding.playlistDescriptionReadMore.setVisibility(
Boolean.TRUE.equals(canBeEllipsized) ? View.VISIBLE : View.GONE);
if (Boolean.TRUE.equals(canBeEllipsized)) {
ellipsizer.ellipsize();
}
});
ellipsizer.setContent(description);
headerBinding.playlistDescriptionReadMore.setOnClickListener(v -> ellipsizer.toggle());
} else {
headerBinding.playlistDescription.setVisibility(View.GONE);
headerBinding.playlistDescriptionReadMore.setVisibility(View.GONE);
}
if (!result.getErrors().isEmpty()) {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
@ -330,25 +368,10 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistBookmarkSubscriber());
playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP);
return true;
});
playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO);
return true;
});
PlayButtonHelper.initPlaylistControlClickListener(activity, playlistControlBinding, this);
}
private PlayQueue getPlayQueue() {
public PlayQueue getPlayQueue() {
return getPlayQueue(0);
}
@ -472,4 +495,20 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
playlistBookmarkButton.setIcon(drawable);
playlistBookmarkButton.setTitle(titleRes);
}
private void setStreamCountAndOverallDuration(final List<StreamInfoItem> list,
final boolean isDurationComplete) {
if (activity != null && headerBinding != null) {
playlistOverallDurationSeconds += list.stream()
.mapToLong(x -> x.getDuration())
.sum();
headerBinding.playlistStreamCount.setText(
Localization.concatenateStrings(
Localization.localizeStreamCount(activity, streamCount),
Localization.getDurationString(playlistOverallDurationSeconds,
isDurationComplete, true))
);
}
}
}

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.fragments.list.search;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
import static java.util.Arrays.asList;
@ -33,6 +34,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat;
import androidx.collection.SparseArrayCompat;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
@ -70,9 +72,7 @@ import org.schabi.newpipe.util.ServiceHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -141,7 +141,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State
boolean wasSearchFocused = false;
@Nullable private Map<Integer, String> menuItemToFilterName = null;
private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>();
private StreamingService service;
private Page nextPage;
private boolean showLocalSuggestions = true;
@ -168,6 +168,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
/*////////////////////////////////////////////////////////////////////////*/
/**
* TextWatcher to remove rich-text formatting on the search EditText when pasting content
* from the clipboard.
*/
private TextWatcher textWatcher;
public static SearchFragment getInstance(final int serviceId, final String searchString) {
@ -386,7 +390,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void onSaveInstanceState(@NonNull final Bundle bundle) {
searchString = searchEditText != null
? searchEditText.getText().toString()
? getSearchEditString().trim()
: searchString;
super.onSaveInstanceState(bundle);
}
@ -397,11 +401,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void reloadContent() {
if (!TextUtils.isEmpty(searchString)
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
if (!TextUtils.isEmpty(searchString) || (searchEditText != null
&& !isSearchEditBlank())) {
search(!TextUtils.isEmpty(searchString)
? searchString
: searchEditText.getText().toString(), this.contentFilter, "");
: getSearchEditString(), this.contentFilter, "");
} else {
if (searchEditText != null) {
searchEditText.setText("");
@ -426,8 +430,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
supportActionBar.setDisplayHomeAsUpEnabled(true);
}
menuItemToFilterName = new HashMap<>();
int itemId = 0;
boolean isFirstItem = true;
final Context c = getContext();
@ -468,11 +470,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (menuItemToFilterName != null) {
final List<String> cf = new ArrayList<>(1);
cf.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, cf);
}
final var filter = Collections.singletonList(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, filter);
return true;
}
@ -500,7 +499,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
searchEditText.setText(searchString);
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
if (TextUtils.isEmpty(searchString)
|| isSearchEditBlank()) {
searchToolbarContainer.setTranslationX(100);
searchToolbarContainer.setAlpha(0.0f);
searchToolbarContainer.setVisibility(View.VISIBLE);
@ -524,7 +524,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (TextUtils.isEmpty(searchEditText.getText())) {
if (isSearchEditBlank()) {
NavigationHelper.gotoMainFragment(getFM());
return;
}
@ -589,11 +589,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) {
// Do nothing, old text is already clean
}
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) {
// Changes are handled in afterTextChanged; CharSequence cannot be changed here.
}
@Override
@ -603,7 +605,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
s.removeSpan(span);
}
final String newText = searchEditText.getText().toString();
final String newText = getSearchEditString().trim();
suggestionPublisher.onNext(newText);
}
};
@ -619,7 +621,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} else if (event != null
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
search(searchEditText.getText().toString(), new String[0], "");
searchEditText.setText(getSearchEditString().trim());
search(getSearchEditString(), new String[0], "");
return true;
}
return false;
@ -694,7 +697,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> suggestionPublisher
.onNext(searchEditText.getText().toString()),
.onNext(getSearchEditString()),
throwable -> showSnackBarError(new ErrorInfo(throwable,
UserAction.DELETE_FROM_HISTORY,
"Deleting item failed")));
@ -723,9 +726,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
.getRelatedSearches(query, similarQueryLimit, 25)
.toObservable()
.map(searchHistoryEntries ->
searchHistoryEntries.stream()
.map(entry -> new SuggestionItem(true, entry))
.collect(Collectors.toList()));
searchHistoryEntries.stream()
.map(entry -> new SuggestionItem(true, entry))
.collect(Collectors.toList()));
}
private Observable<List<SuggestionItem>> getRemoteSuggestionsObservable(final String query) {
@ -792,12 +795,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} else if (listNotification.isOnError()
&& listNotification.getError() != null
&& !ExceptionUtils.isInterruptedCaused(
listNotification.getError())) {
listNotification.getError())) {
showSnackBarError(new ErrorInfo(listNotification.getError(),
UserAction.GET_SUGGESTIONS, searchString, serviceId));
}
}, throwable -> showSnackBarError(new ErrorInfo(
throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId)));
throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId)));
}
@Override
@ -805,7 +808,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// no-op
}
private void search(final String theSearchString,
/**
* Perform a search.
* @param theSearchString the trimmed search string
* @param theContentFilter the content filter to use. FIXME: unused param
* @param theSortFilter FIXME: unused param
*/
private void search(@NonNull final String theSearchString,
final String[] theContentFilter,
final String theSortFilter) {
if (DEBUG) {
@ -815,25 +824,26 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return;
}
// Check if theSearchString is a URL which can be opened by NewPipe directly
// and open it if possible.
try {
final StreamingService streamingService = NewPipe.getServiceByUrl(theSearchString);
if (streamingService != null) {
showLoading();
disposables.add(Observable
.fromCallable(() -> NavigationHelper.getIntentByLink(activity,
streamingService, theSearchString))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
getFM().popBackStackImmediate();
activity.startActivity(intent);
}, throwable -> showTextError(getString(R.string.unsupported_url))));
return;
}
showLoading();
disposables.add(Observable
.fromCallable(() -> NavigationHelper.getIntentByLink(activity,
streamingService, theSearchString))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
getFM().popBackStackImmediate();
activity.startActivity(intent);
}, throwable -> showTextError(getString(R.string.unsupported_url))));
return;
} catch (final Exception ignored) {
// Exception occurred, it's not a url
}
// prepare search
lastSearchedString = this.searchString;
this.searchString = theSearchString;
infoListAdapter.clearStreamItemList();
@ -842,13 +852,17 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
searchBinding.searchMetaInfoSeparator, disposables);
hideKeyboardSearch();
// store search query if search history is enabled
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ignored -> { },
ignored -> {
},
throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED,
theSearchString, serviceId))
));
// load search results
suggestionPublisher.onNext(theSearchString);
startLoading(false);
}
@ -938,6 +952,14 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
sortFilter = theSortFilter;
}
private String getSearchEditString() {
return searchEditText.getText().toString();
}
private boolean isSearchEditBlank() {
return isBlank(getSearchEditString());
}
/*//////////////////////////////////////////////////////////////////////////
// Suggestion Results
//////////////////////////////////////////////////////////////////////////*/
@ -979,6 +1001,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
searchSuggestion = result.getSearchSuggestion();
if (searchSuggestion != null) {
searchSuggestion = searchSuggestion.trim();
}
isCorrectedSearch = result.isCorrectedSearch();
// List<MetaInfo> cannot be bundled without creating some containers
@ -1080,7 +1105,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> suggestionPublisher
.onNext(searchEditText.getText().toString()),
.onNext(getSearchEditString()),
throwable -> showSnackBarError(new ErrorInfo(throwable,
UserAction.DELETE_FROM_HISTORY, "Deleting item failed")));
disposables.add(onDelete);

View File

@ -19,19 +19,19 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.util.RelatedItemInfo;
import java.io.Serializable;
import java.util.function.Supplier;
import io.reactivex.rxjava3.core.Single;
public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, RelatedItemInfo>
public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, RelatedItemsInfo>
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String INFO_KEY = "related_info_key";
private RelatedItemInfo relatedItemInfo;
private RelatedItemsInfo relatedItemsInfo;
/*//////////////////////////////////////////////////////////////////////////
// Views
@ -68,7 +68,7 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
@Override
protected Supplier<View> getListHeaderSupplier() {
if (relatedItemInfo == null || relatedItemInfo.getRelatedItems() == null) {
if (relatedItemsInfo == null || relatedItemsInfo.getRelatedItems() == null) {
return null;
}
@ -96,8 +96,8 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<RelatedItemInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedItemInfo);
protected Single<RelatedItemsInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedItemsInfo);
}
@Override
@ -109,7 +109,7 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
}
@Override
public void handleResult(@NonNull final RelatedItemInfo result) {
public void handleResult(@NonNull final RelatedItemsInfo result) {
super.handleResult(result);
if (headerBinding != null) {
@ -136,38 +136,41 @@ public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, Related
private void setInitialData(final StreamInfo info) {
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
if (this.relatedItemInfo == null) {
this.relatedItemInfo = RelatedItemInfo.getInfo(info);
if (this.relatedItemsInfo == null) {
this.relatedItemsInfo = new RelatedItemsInfo(info);
}
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedItemInfo);
outState.putSerializable(INFO_KEY, relatedItemsInfo);
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState);
final Serializable serializable = savedState.getSerializable(INFO_KEY);
if (serializable instanceof RelatedItemInfo) {
this.relatedItemInfo = (RelatedItemInfo) serializable;
if (serializable instanceof RelatedItemsInfo) {
this.relatedItemsInfo = (RelatedItemsInfo) serializable;
}
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String s) {
if (headerBinding != null) {
headerBinding.autoplaySwitch.setChecked(
sharedPreferences.getBoolean(
getString(R.string.auto_queue_key), false));
final String key) {
if (headerBinding != null && getString(R.string.auto_queue_key).equals(key)) {
headerBinding.autoplaySwitch.setChecked(sharedPreferences.getBoolean(key, false));
}
}
@Override
protected boolean isGridLayout() {
return false;
protected ItemViewMode getItemViewMode() {
ItemViewMode mode = super.getItemViewMode();
// Only list mode is supported. Either List or card will be used.
if (mode != ItemViewMode.LIST && mode != ItemViewMode.CARD) {
mode = ItemViewMode.LIST;
}
return mode;
}
}

View File

@ -0,0 +1,22 @@
package org.schabi.newpipe.fragments.list.videos;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import java.util.ArrayList;
import java.util.Collections;
public final class RelatedItemsInfo extends ListInfo<InfoItem> {
/**
* This class is used to wrap the related items of a StreamInfo into a ListInfo object.
*
* @param info the stream info from which to get related items
*/
public RelatedItemsInfo(final StreamInfo info) {
super(info.getServiceId(), new ListLinkHandler(info.getOriginalUrl(), info.getUrl(),
info.getId(), Collections.emptyList(), null), info.getName());
setRelatedItems(new ArrayList<>(info.getRelatedItems()));
}
}

View File

@ -13,8 +13,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentInfoItemHolder;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
@ -87,8 +86,7 @@ public class InfoItemBuilder {
return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent)
: new PlaylistInfoItemHolder(this, parent);
case COMMENT:
return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent)
: new CommentsInfoItemHolder(this, parent);
return new CommentInfoItemHolder(this, parent);
default:
throw new RuntimeException("InfoType not expected = " + infoType.name());
}

View File

@ -17,15 +17,17 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.holder.ChannelCardInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.CommentInfoItemHolder;
import org.schabi.newpipe.info_list.holder.InfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistCardInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder;
import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamCardInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
@ -67,14 +69,16 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private static final int MINI_STREAM_HOLDER_TYPE = 0x100;
private static final int STREAM_HOLDER_TYPE = 0x101;
private static final int GRID_STREAM_HOLDER_TYPE = 0x102;
private static final int CARD_STREAM_HOLDER_TYPE = 0x103;
private static final int MINI_CHANNEL_HOLDER_TYPE = 0x200;
private static final int CHANNEL_HOLDER_TYPE = 0x201;
private static final int GRID_CHANNEL_HOLDER_TYPE = 0x202;
private static final int CARD_CHANNEL_HOLDER_TYPE = 0x203;
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
private static final int MINI_COMMENT_HOLDER_TYPE = 0x400;
private static final int COMMENT_HOLDER_TYPE = 0x401;
private static final int CARD_PLAYLIST_HOLDER_TYPE = 0x303;
private static final int COMMENT_HOLDER_TYPE = 0x400;
private final LayoutInflater layoutInflater;
private final InfoItemBuilder infoItemBuilder;
@ -82,9 +86,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private final HistoryRecordManager recordManager;
private boolean useMiniVariant = false;
private boolean useGridVariant = false;
private boolean showFooter = false;
private ItemViewMode itemMode = ItemViewMode.LIST;
private Supplier<View> headerSupplier = null;
public InfoListAdapter(final Context context) {
@ -114,8 +119,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
this.useMiniVariant = useMiniVariant;
}
public void setUseGridVariant(final boolean useGridVariant) {
this.useGridVariant = useGridVariant;
public void setItemViewMode(final ItemViewMode itemViewMode) {
this.itemMode = itemViewMode;
}
public void addInfoItemList(@Nullable final List<? extends InfoItem> data) {
@ -234,16 +239,37 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
final InfoItem item = infoItemList.get(position);
switch (item.getInfoType()) {
case STREAM:
return useGridVariant ? GRID_STREAM_HOLDER_TYPE : useMiniVariant
? MINI_STREAM_HOLDER_TYPE : STREAM_HOLDER_TYPE;
if (itemMode == ItemViewMode.CARD) {
return CARD_STREAM_HOLDER_TYPE;
} else if (itemMode == ItemViewMode.GRID) {
return GRID_STREAM_HOLDER_TYPE;
} else if (useMiniVariant) {
return MINI_STREAM_HOLDER_TYPE;
} else {
return STREAM_HOLDER_TYPE;
}
case CHANNEL:
return useGridVariant ? GRID_CHANNEL_HOLDER_TYPE : useMiniVariant
? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE;
if (itemMode == ItemViewMode.CARD) {
return CARD_CHANNEL_HOLDER_TYPE;
} else if (itemMode == ItemViewMode.GRID) {
return GRID_CHANNEL_HOLDER_TYPE;
} else if (useMiniVariant) {
return MINI_CHANNEL_HOLDER_TYPE;
} else {
return CHANNEL_HOLDER_TYPE;
}
case PLAYLIST:
return useGridVariant ? GRID_PLAYLIST_HOLDER_TYPE : useMiniVariant
? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE;
if (itemMode == ItemViewMode.CARD) {
return CARD_PLAYLIST_HOLDER_TYPE;
} else if (itemMode == ItemViewMode.GRID) {
return GRID_PLAYLIST_HOLDER_TYPE;
} else if (useMiniVariant) {
return MINI_PLAYLIST_HOLDER_TYPE;
} else {
return PLAYLIST_HOLDER_TYPE;
}
case COMMENT:
return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE;
return COMMENT_HOLDER_TYPE;
default:
return -1;
}
@ -274,10 +300,14 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return new StreamInfoItemHolder(infoItemBuilder, parent);
case GRID_STREAM_HOLDER_TYPE:
return new StreamGridInfoItemHolder(infoItemBuilder, parent);
case CARD_STREAM_HOLDER_TYPE:
return new StreamCardInfoItemHolder(infoItemBuilder, parent);
case MINI_CHANNEL_HOLDER_TYPE:
return new ChannelMiniInfoItemHolder(infoItemBuilder, parent);
case CHANNEL_HOLDER_TYPE:
return new ChannelInfoItemHolder(infoItemBuilder, parent);
case CARD_CHANNEL_HOLDER_TYPE:
return new ChannelCardInfoItemHolder(infoItemBuilder, parent);
case GRID_CHANNEL_HOLDER_TYPE:
return new ChannelGridInfoItemHolder(infoItemBuilder, parent);
case MINI_PLAYLIST_HOLDER_TYPE:
@ -286,10 +316,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return new PlaylistInfoItemHolder(infoItemBuilder, parent);
case GRID_PLAYLIST_HOLDER_TYPE:
return new PlaylistGridInfoItemHolder(infoItemBuilder, parent);
case MINI_COMMENT_HOLDER_TYPE:
return new CommentsMiniInfoItemHolder(infoItemBuilder, parent);
case CARD_PLAYLIST_HOLDER_TYPE:
return new PlaylistCardInfoItemHolder(infoItemBuilder, parent);
case COMMENT_HOLDER_TYPE:
return new CommentsInfoItemHolder(infoItemBuilder, parent);
return new CommentInfoItemHolder(infoItemBuilder, parent);
default:
return new FallbackViewHolder(new View(parent.getContext()));
}

View File

@ -0,0 +1,23 @@
package org.schabi.newpipe.info_list;
/**
* Item view mode for streams & playlist listing screens.
*/
public enum ItemViewMode {
/**
* Default mode.
*/
AUTO,
/**
* Full width list item with thumb on the left and two line title & uploader in right.
*/
LIST,
/**
* Grid mode places two cards per row.
*/
GRID,
/**
* A full width card in phone - portrait.
*/
CARD
}

View File

@ -8,7 +8,7 @@ import com.xwray.groupie.Item
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.stream.StreamSegment
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.PicassoHelper
import org.schabi.newpipe.util.image.PicassoHelper
class StreamSegmentItem(
private val item: StreamSegment,

View File

@ -252,10 +252,11 @@ public final class InfoItemDialog {
* @return the current {@link Builder} instance
*/
public Builder addEnqueueEntriesIfNeeded() {
if (PlayerHolder.getInstance().isPlayQueueReady()) {
final PlayerHolder holder = PlayerHolder.getInstance();
if (holder.isPlayQueueReady()) {
addEntry(StreamDialogDefaultEntry.ENQUEUE);
if (PlayerHolder.getInstance().getQueueSize() > 1) {
if (holder.getQueuePosition() < holder.getQueueSize() - 1) {
addEntry(StreamDialogDefaultEntry.ENQUEUE_NEXT);
}
}

View File

@ -99,18 +99,12 @@ public enum StreamDialogDefaultEntry {
)
),
PLAY_WITH_KODI(R.string.play_with_kodi_title, (fragment, item) -> {
final Uri videoUrl = Uri.parse(item.getUrl());
try {
NavigationHelper.playWithKore(fragment.requireContext(), videoUrl);
} catch (final Exception e) {
KoreUtils.showInstallKoreDialog(fragment.requireActivity());
}
}),
PLAY_WITH_KODI(R.string.play_with_kodi_title, (fragment, item) ->
KoreUtils.playWithKore(fragment.requireContext(), Uri.parse(item.getUrl()))),
SHARE(R.string.share, (fragment, item) ->
ShareUtils.shareText(fragment.requireContext(), item.getName(), item.getUrl(),
item.getThumbnailUrl())),
item.getThumbnails())),
/**
* Opens a {@link DownloadDialog} after fetching some stream info.

View File

@ -0,0 +1,22 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder;
public class ChannelCardInfoItemHolder extends ChannelMiniInfoItemHolder {
public ChannelCardInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_channel_card_item, parent);
}
@Override
protected int getDescriptionMaxLineCount(@Nullable final String content) {
// Based on `list_channel_card_item` left side content (thumbnail 100dp
// + additional details), Right side description can grow up to 8 lines.
return 8;
}
}

View File

@ -1,14 +1,9 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.Localization;
/*
* Created by Christian Schabesberger on 12.02.17.
@ -31,40 +26,7 @@ import org.schabi.newpipe.util.Localization;
*/
public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder {
private final TextView itemChannelDescriptionView;
public ChannelInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_channel_item, parent);
itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView);
}
@Override
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof ChannelInfoItem)) {
return;
}
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
itemChannelDescriptionView.setText(item.getDescription());
}
@Override
protected String getDetailLine(final ChannelInfoItem item) {
String details = super.getDetailLine(item);
if (item.getStreamCount() >= 0) {
final String formattedVideoAmount = Localization.localizeStreamCount(
itemBuilder.getContext(), item.getStreamCount());
if (!details.isEmpty()) {
details += "" + formattedVideoAmount;
} else {
details = formattedVideoAmount;
}
}
return details;
}
}

View File

@ -1,21 +1,26 @@
package org.schabi.newpipe.info_list.holder;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.utils.Utils;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.Localization;
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView;
public final TextView itemTitleView;
private final ImageView itemThumbnailView;
private final TextView itemTitleView;
private final TextView itemAdditionalDetailView;
private final TextView itemChannelDescriptionView;
ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
@ -24,6 +29,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemTitleView = itemView.findViewById(R.id.itemTitleView);
itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails);
itemChannelDescriptionView = itemView.findViewById(R.id.itemChannelDescriptionView);
}
public ChannelMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
@ -40,9 +46,17 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
itemTitleView.setText(item.getName());
itemAdditionalDetailView.setText(getDetailLine(item));
itemTitleView.setSelected(true);
PicassoHelper.loadAvatar(item.getThumbnailUrl()).into(itemThumbnailView);
final String detailLine = getDetailLine(item);
if (detailLine == null) {
itemAdditionalDetailView.setVisibility(View.GONE);
} else {
itemAdditionalDetailView.setVisibility(View.VISIBLE);
itemAdditionalDetailView.setText(getDetailLine(item));
}
PicassoHelper.loadAvatar(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnChannelSelectedListener() != null) {
@ -56,14 +70,48 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
}
return true;
});
if (itemChannelDescriptionView != null) {
// itemChannelDescriptionView will be null in the mini variant
if (Utils.isBlank(item.getDescription())) {
itemChannelDescriptionView.setVisibility(View.GONE);
} else {
itemChannelDescriptionView.setVisibility(View.VISIBLE);
itemChannelDescriptionView.setText(item.getDescription());
// setMaxLines utilize the line space for description if the additional details
// (sub / video count) are not present.
// Case1: 2 lines of description + 1 line additional details
// Case2: 3 lines of description (additionalDetails is GONE)
itemChannelDescriptionView.setMaxLines(getDescriptionMaxLineCount(detailLine));
}
}
}
protected String getDetailLine(final ChannelInfoItem item) {
String details = "";
if (item.getSubscriberCount() >= 0) {
details += Localization.shortSubscriberCount(itemBuilder.getContext(),
/**
* Returns max number of allowed lines for the description field.
* @param content additional detail content (video / sub count)
* @return max line count
*/
protected int getDescriptionMaxLineCount(@Nullable final String content) {
return content == null ? 3 : 2;
}
@Nullable
private String getDetailLine(final ChannelInfoItem item) {
if (item.getStreamCount() >= 0 && item.getSubscriberCount() >= 0) {
return Localization.concatenateStrings(
Localization.shortSubscriberCount(itemBuilder.getContext(),
item.getSubscriberCount()),
Localization.localizeStreamCount(itemBuilder.getContext(),
item.getStreamCount()));
} else if (item.getStreamCount() >= 0) {
return Localization.localizeStreamCount(itemBuilder.getContext(),
item.getStreamCount());
} else if (item.getSubscriberCount() >= 0) {
return Localization.shortSubscriberCount(itemBuilder.getContext(),
item.getSubscriberCount());
} else {
return null;
}
return details;
}
}

View File

@ -0,0 +1,188 @@
package org.schabi.newpipe.info_list.holder;
import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
import org.schabi.newpipe.util.text.TextEllipsizer;
public class CommentInfoItemHolder extends InfoItemHolder {
private static final int COMMENT_DEFAULT_LINES = 2;
private final int commentHorizontalPadding;
private final int commentVerticalPadding;
private final RelativeLayout itemRoot;
private final ImageView itemThumbnailView;
private final TextView itemContentView;
private final ImageView itemThumbsUpView;
private final TextView itemLikesCountView;
private final TextView itemTitleView;
private final ImageView itemHeartView;
private final ImageView itemPinnedView;
private final Button repliesButton;
@NonNull
private final TextEllipsizer textEllipsizer;
public CommentInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_comment_item, parent);
itemRoot = itemView.findViewById(R.id.itemRoot);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
itemThumbsUpView = itemView.findViewById(R.id.detail_thumbs_up_img_view);
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
itemTitleView = itemView.findViewById(R.id.itemTitleView);
itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
repliesButton = itemView.findViewById(R.id.replies_button);
commentHorizontalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_horizontal_padding);
commentVerticalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_vertical_padding);
textEllipsizer = new TextEllipsizer(itemContentView, COMMENT_DEFAULT_LINES, null);
textEllipsizer.setStateChangeListener(isEllipsized -> {
if (Boolean.TRUE.equals(isEllipsized)) {
denyLinkFocus();
} else {
determineMovementMethod();
}
});
}
@Override
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof CommentsInfoItem)) {
return;
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
// load the author avatar
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
if (ImageStrategy.shouldLoadImages()) {
itemThumbnailView.setVisibility(View.VISIBLE);
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
commentVerticalPadding, commentVerticalPadding);
} else {
itemThumbnailView.setVisibility(View.GONE);
itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
commentHorizontalPadding, commentVerticalPadding);
}
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
// setup the top row, with pinned icon, author name and comment date
itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
itemTitleView.setText(Localization.concatenateStrings(item.getUploaderName(),
Localization.relativeTimeOrTextual(itemBuilder.getContext(), item.getUploadDate(),
item.getTextualUploadDate())));
// setup bottom row, with likes, heart and replies button
itemLikesCountView.setText(
Localization.likeCount(itemBuilder.getContext(), item.getLikeCount()));
itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
final boolean hasReplies = item.getReplies() != null;
repliesButton.setOnClickListener(hasReplies ? v -> openCommentReplies(item) : null);
repliesButton.setVisibility(hasReplies ? View.VISIBLE : View.GONE);
repliesButton.setText(hasReplies
? Localization.replyCount(itemBuilder.getContext(), item.getReplyCount()) : "");
((RelativeLayout.LayoutParams) itemThumbsUpView.getLayoutParams()).topMargin =
hasReplies ? 0 : DeviceUtils.dpToPx(6, itemBuilder.getContext());
// setup comment content and click listeners to expand/ellipsize it
textEllipsizer.setStreamingService(getServiceById(item.getServiceId()));
textEllipsizer.setStreamUrl(item.getUrl());
textEllipsizer.setContent(item.getCommentText());
textEllipsizer.ellipsize();
//noinspection ClickableViewAccessibility
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
itemView.setOnClickListener(view -> {
textEllipsizer.toggle();
if (itemBuilder.getOnCommentsSelectedListener() != null) {
itemBuilder.getOnCommentsSelectedListener().selected(item);
}
});
itemView.setOnLongClickListener(view -> {
if (DeviceUtils.isTv(itemBuilder.getContext())) {
openCommentAuthor(item);
} else {
final CharSequence text = itemContentView.getText();
if (text != null) {
ShareUtils.copyToClipboard(itemBuilder.getContext(), text.toString());
}
}
return true;
});
}
private void openCommentAuthor(@NonNull final CommentsInfoItem item) {
NavigationHelper.openCommentAuthorIfPresent((FragmentActivity) itemBuilder.getContext(),
item);
}
private void openCommentReplies(@NonNull final CommentsInfoItem item) {
NavigationHelper.openCommentRepliesFragment((FragmentActivity) itemBuilder.getContext(),
item);
}
private void allowLinkFocus() {
itemContentView.setMovementMethod(LinkMovementMethod.getInstance());
}
private void denyLinkFocus() {
itemContentView.setMovementMethod(null);
}
private boolean shouldFocusLinks() {
if (itemView.isInTouchMode()) {
return false;
}
final URLSpan[] urls = itemContentView.getUrls();
return urls != null && urls.length != 0;
}
private void determineMovementMethod() {
if (shouldFocusLinks()) {
allowLinkFocus();
} else {
denyLinkFocus();
}
}
}

View File

@ -1,63 +0,0 @@
package org.schabi.newpipe.info_list.holder;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
/*
* Created by Christian Schabesberger on 12.02.17.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ChannelInfoItemHolder .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 CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
public final TextView itemTitleView;
private final ImageView itemHeartView;
private final ImageView itemPinnedView;
public CommentsInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_comments_item, parent);
itemTitleView = itemView.findViewById(R.id.itemTitleView);
itemHeartView = itemView.findViewById(R.id.detail_heart_image_view);
itemPinnedView = itemView.findViewById(R.id.detail_pinned_view);
}
@Override
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
super.updateFromItem(infoItem, historyRecordManager);
if (!(infoItem instanceof CommentsInfoItem)) {
return;
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
itemTitleView.setText(item.getUploaderName());
itemHeartView.setVisibility(item.isHeartedByUploader() ? View.VISIBLE : View.GONE);
itemPinnedView.setVisibility(item.isPinned() ? View.VISIBLE : View.GONE);
}
}

View File

@ -1,243 +0,0 @@
package org.schabi.newpipe.info_list.holder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.text.util.LinkifyCompat;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.external_communication.TimestampExtractor;
import java.util.Objects;
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private static final String TAG = "CommentsMiniIIHolder";
private static final int COMMENT_DEFAULT_LINES = 2;
private static final int COMMENT_EXPANDED_LINES = 1000;
private final int commentHorizontalPadding;
private final int commentVerticalPadding;
private final RelativeLayout itemRoot;
private final ImageView itemThumbnailView;
private final TextView itemContentView;
private final TextView itemLikesCountView;
private final TextView itemPublishedTime;
private String commentText;
private String streamUrl;
CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
itemRoot = itemView.findViewById(R.id.itemRoot);
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
commentHorizontalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_horizontal_padding);
commentVerticalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_vertical_padding);
}
public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
}
@Override
public void updateFromItem(final InfoItem infoItem,
final HistoryRecordManager historyRecordManager) {
if (!(infoItem instanceof CommentsInfoItem)) {
return;
}
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
PicassoHelper.loadAvatar(item.getUploaderAvatarUrl()).into(itemThumbnailView);
if (PicassoHelper.getShouldLoadImages()) {
itemThumbnailView.setVisibility(View.VISIBLE);
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
commentVerticalPadding, commentVerticalPadding);
} else {
itemThumbnailView.setVisibility(View.GONE);
itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
commentHorizontalPadding, commentVerticalPadding);
}
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
streamUrl = item.getUrl();
itemContentView.setLines(COMMENT_DEFAULT_LINES);
commentText = item.getCommentText();
itemContentView.setText(commentText, TextView.BufferType.SPANNABLE);
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
if (itemContentView.getLineCount() == 0) {
itemContentView.post(this::ellipsize);
} else {
ellipsize();
}
if (item.getLikeCount() >= 0) {
itemLikesCountView.setText(
Localization.shortCount(
itemBuilder.getContext(),
item.getLikeCount()));
} else {
itemLikesCountView.setText("-");
}
if (item.getUploadDate() != null) {
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate()
.offsetDateTime()));
} else {
itemPublishedTime.setText(item.getTextualUploadDate());
}
itemView.setOnClickListener(view -> {
toggleEllipsize();
if (itemBuilder.getOnCommentsSelectedListener() != null) {
itemBuilder.getOnCommentsSelectedListener().selected(item);
}
});
itemView.setOnLongClickListener(view -> {
if (DeviceUtils.isTv(itemBuilder.getContext())) {
openCommentAuthor(item);
} else {
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
}
return true;
});
}
private void openCommentAuthor(final CommentsInfoItem item) {
if (TextUtils.isEmpty(item.getUploaderUrl())) {
return;
}
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
try {
NavigationHelper.openChannelFragment(
activity.getSupportFragmentManager(),
item.getServiceId(),
item.getUploaderUrl(),
item.getUploaderName());
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
}
}
private void allowLinkFocus() {
itemContentView.setMovementMethod(LinkMovementMethod.getInstance());
}
private void denyLinkFocus() {
itemContentView.setMovementMethod(null);
}
private boolean shouldFocusLinks() {
if (itemView.isInTouchMode()) {
return false;
}
final URLSpan[] urls = itemContentView.getUrls();
return urls != null && urls.length != 0;
}
private void determineLinkFocus() {
if (shouldFocusLinks()) {
allowLinkFocus();
} else {
denyLinkFocus();
}
}
private void ellipsize() {
boolean hasEllipsis = false;
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
final int endOfLastLine = itemContentView
.getLayout()
.getLineEnd(COMMENT_DEFAULT_LINES - 1);
int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2);
if (end == -1) {
end = Math.max(endOfLastLine - 2, 0);
}
final String newVal = itemContentView.getText().subSequence(0, end) + "";
itemContentView.setText(newVal);
hasEllipsis = true;
}
linkify();
if (hasEllipsis) {
denyLinkFocus();
} else {
determineLinkFocus();
}
}
private void toggleEllipsize() {
if (itemContentView.getText().toString().equals(commentText)) {
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
ellipsize();
}
} else {
expand();
}
}
private void expand() {
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
itemContentView.setText(commentText);
linkify();
determineLinkFocus();
}
private void linkify() {
LinkifyCompat.addLinks(itemContentView, Linkify.WEB_URLS);
LinkifyCompat.addLinks(itemContentView, TimestampExtractor.TIMESTAMPS_PATTERN, null, null,
(match, url) -> {
try {
final var timestampMatch = TimestampExtractor
.getTimestampFromMatcher(match, commentText);
if (timestampMatch == null) {
return url;
}
return streamUrl + url.replace(Objects.requireNonNull(match.group(0)),
"#timestamp=" + timestampMatch.seconds());
} catch (final Exception ex) {
Log.e(TAG, "Unable to process url='" + url + "' as timestampLink", ex);
return url;
}
});
}
}

View File

@ -0,0 +1,17 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder;
/**
* Playlist card layout.
*/
public class PlaylistCardInfoItemHolder extends PlaylistMiniInfoItemHolder {
public PlaylistCardInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_playlist_card_item, parent);
}
}

View File

@ -9,7 +9,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.Localization;
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
@ -46,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
itemUploaderView.setText(item.getUploaderName());
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnPlaylistSelectedListener() != null) {

View File

@ -0,0 +1,16 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder;
/**
* Card layout for stream.
*/
public class StreamCardInfoItemHolder extends StreamInfoItemHolder {
public StreamCardInfoItemHolder(final InfoItemBuilder infoItemBuilder, final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_stream_card_item, parent);
}
}

View File

@ -12,10 +12,6 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.Localization;
import androidx.preference.PreferenceManager;
import static org.schabi.newpipe.MainActivity.DEBUG;
/*
* Created by Christian Schabesberger on 01.08.16.
* <p>
@ -81,7 +77,9 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
}
}
final String uploadDate = getFormattedRelativeUploadDate(infoItem);
final String uploadDate = Localization.relativeTimeOrTextual(itemBuilder.getContext(),
infoItem.getUploadDate(),
infoItem.getTextualUploadDate());
if (!TextUtils.isEmpty(uploadDate)) {
if (viewsAndDate.isEmpty()) {
return uploadDate;
@ -92,20 +90,4 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
return viewsAndDate;
}
private String getFormattedRelativeUploadDate(final StreamInfoItem infoItem) {
if (infoItem.getUploadDate() != null) {
String formattedRelativeTime = Localization
.relativeTime(infoItem.getUploadDate().offsetDateTime());
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(itemBuilder.getContext())
.getBoolean(itemBuilder.getContext()
.getString(R.string.show_original_time_ago_key), false)) {
formattedRelativeTime += " (" + infoItem.getTextualUploadDate() + ")";
}
return formattedRelativeTime;
} else {
return infoItem.getTextualUploadDate();
}
}
}

View File

@ -14,8 +14,9 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.DependentPreferenceHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.views.AnimatedProgressBar;
@ -60,8 +61,12 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
final StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem)
.blockingGet()[0];
StreamStateEntity state2 = null;
if (DependentPreferenceHelper
.getPositionsInListsEnabled(itemProgressView.getContext())) {
state2 = historyRecordManager.loadStreamState(infoItem)
.blockingGet()[0];
}
if (state2 != null) {
itemProgressView.setVisibility(View.VISIBLE);
itemProgressView.setMax((int) item.getDuration());
@ -82,7 +87,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
// Default thumbnail is shown on error, while loading and if the url is empty
PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView);
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnStreamSelectedListener() != null) {
@ -111,9 +116,12 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
final HistoryRecordManager historyRecordManager) {
final StreamInfoItem item = (StreamInfoItem) infoItem;
final StreamStateEntity state = historyRecordManager
.loadStreamState(infoItem)
.blockingGet()[0];
StreamStateEntity state = null;
if (DependentPreferenceHelper.getPositionsInListsEnabled(itemProgressView.getContext())) {
state = historyRecordManager
.loadStreamState(infoItem)
.blockingGet()[0];
}
if (state != null && item.getDuration() > 0
&& !StreamTypeUtil.isLiveStream(item.getStreamType())) {
itemProgressView.setMax((int) item.getDuration());

View File

@ -0,0 +1,9 @@
package org.schabi.newpipe.ktx
import android.os.Bundle
import android.os.Parcelable
import androidx.core.os.BundleCompat
inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String?): ArrayList<T>? {
return BundleCompat.getParcelableArrayList(this, key, T::class.java)
}

View File

@ -22,10 +22,11 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.PignateFooterBinding;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.list.ListViewContract;
import org.schabi.newpipe.info_list.ItemViewMode;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
import static org.schabi.newpipe.util.ThemeHelper.getItemViewMode;
/**
* This fragment is design to be used with persistent data such as
@ -77,16 +78,23 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
super.onResume();
if (updateFlags != 0) {
if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) {
final boolean useGrid = shouldUseGridLayout(requireContext());
itemsList.setLayoutManager(
useGrid ? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setUseGridVariant(useGrid);
itemListAdapter.notifyDataSetChanged();
refreshItemViewMode();
}
updateFlags = 0;
}
}
/**
* Updates the item view mode based on user preference.
*/
private void refreshItemViewMode() {
final ItemViewMode itemViewMode = getItemViewMode(requireContext());
itemsList.setLayoutManager((itemViewMode == ItemViewMode.GRID)
? getGridLayoutManager() : getListLayoutManager());
itemListAdapter.setItemViewMode(itemViewMode);
itemListAdapter.notifyDataSetChanged();
}
/*//////////////////////////////////////////////////////////////////////////
// Lifecycle - View
//////////////////////////////////////////////////////////////////////////*/
@ -104,8 +112,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
final Resources resources = activity.getResources();
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
width += (24 * resources.getDisplayMetrics().density);
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
/ (double) width);
final int spanCount = Math.floorDiv(resources.getDisplayMetrics().widthPixels, width);
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
return lm;
@ -121,11 +128,9 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
itemListAdapter = new LocalItemListAdapter(activity);
final boolean useGrid = shouldUseGridLayout(requireContext());
itemsList = rootView.findViewById(R.id.items_list);
itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager());
refreshItemViewMode();
itemListAdapter.setUseGridVariant(useGrid);
headerRootBinding = getListHeader();
if (headerRootBinding != null) {
itemListAdapter.setHeader(headerRootBinding.getRoot());
@ -256,7 +261,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (key.equals(getString(R.string.list_view_mode_key))) {
if (getString(R.string.list_view_mode_key).equals(key)) {
updateFlags |= LIST_MODE_UPDATE_FLAG;
}
}

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