Compare commits

..
1412 Commits
Author SHA1 Message Date
Isira Seneviratne 52886c235a [Refactor] Convert playback buttons to segmented button (#13669)
Add system overlay permission check
2026-08-01 16:20:44 +00:00
TobiGr 80d86b2b51 Delete translations of removed string resources 2026-08-01 18:09:01 +02:00
TobiGr 6f7276759b Update NewPipe Extractor to v0.26.4 2026-08-01 17:57:53 +02:00
tobigr 8065b6cc3f Move string resource to comments section 2026-07-30 16:15:28 +02:00
Abhinav kumar 271edd826e Feature: Add "(edited)" indicator to modified comments (#13604) 2026-07-30 07:10:05 -07:00
Thompson3142 3a3eac0cf2 Improve look of search menu "titles" (#13605) 2026-07-30 05:26:26 -07:00
Isira Seneviratne fe084598ba Merge pull request #11947 from Isira-Seneviratne/History-Compose
Migrate watch history to Jetpack Compose
2026-07-10 05:49:58 +05:30
Isira Seneviratne 9ddfb81bf4 Fix compile errors 2026-06-21 11:54:29 +05:30
Isira Seneviratne 01c565e4f7 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
#	app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
#	app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
#	app/src/main/res/values-ca/strings.xml
#	app/src/main/res/values-eu/strings.xml
#	app/src/main/res/values-in/strings.xml
#	app/src/main/res/values-lv/strings.xml
2026-06-18 07:49:10 +05:30
Isira Seneviratne 079f5f1d70 Merge pull request #13598 from Isira-Seneviratne/Merge-dev-to-refactor
Merge dev to refactor
2026-06-13 21:43:35 +05:30
Isira Seneviratne 0334074931 Fix compile error 2026-06-13 21:17:36 +05:30
Isira Seneviratne c24cc85743 Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	.gitignore
#	app/build.gradle.kts
#	app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
#	app/src/main/res/values-in/strings.xml
#	app/src/main/res/values-nl/strings.xml
#	build.gradle.kts
#	gradle/libs.versions.toml
2026-06-13 21:09:35 +05:30
Tobi e2a659dc7f Merge pull request #13589 from weblate/weblate-newpipe-strings
Translations update from Hosted Weblate
2026-06-13 11:33:22 +02:00
+22 9e3e8a83b9 Translated using Weblate (German)
Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Somali)

Currently translated at 72.8% (565 of 776 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (German)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (French)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Italian)

Currently translated at 62.7% (59 of 94 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (German)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (French)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (93 of 93 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (93 of 93 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (German)

Currently translated at 100.0% (776 of 776 strings)

Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translated using Weblate (Arabic)

Currently translated at 99.8% (775 of 776 strings)

Translated using Weblate (Arabic)

Currently translated at 97.8% (91 of 93 strings)

Translated using Weblate (Slovak)

Currently translated at 77.4% (72 of 93 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Spanish)

Currently translated at 74.1% (69 of 93 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Polish)

Currently translated at 64.5% (60 of 93 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 47.3% (44 of 93 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 47.3% (44 of 93 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 47.3% (44 of 93 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 66.6% (62 of 93 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 66.6% (62 of 93 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.0% (769 of 776 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Swedish)

Currently translated at 96.7% (90 of 93 strings)

Translated using Weblate (Spanish)

Currently translated at 74.1% (69 of 93 strings)

Translated using Weblate (Spanish)

Currently translated at 74.1% (69 of 93 strings)

Translated using Weblate (Spanish)

Currently translated at 74.1% (69 of 93 strings)

Translated using Weblate (Spanish)

Currently translated at 74.1% (69 of 93 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (93 of 93 strings)

Translated using Weblate (Latvian)

Currently translated at 22.5% (21 of 93 strings)

Translated using Weblate (German)

Currently translated at 100.0% (93 of 93 strings)

Translated using Weblate (German)

Currently translated at 100.0% (93 of 93 strings)

Translated using Weblate (French)

Currently translated at 100.0% (93 of 93 strings)

Translated using Weblate (Catalan)

Currently translated at 96.5% (749 of 776 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (93 of 93 strings)

Translated using Weblate (Latvian)

Currently translated at 97.6% (758 of 776 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 66.6% (62 of 93 strings)

Translated using Weblate (Spanish)

Currently translated at 72.0% (67 of 93 strings)

Co-authored-by: Alejandro Rodriguez <artecnico@yahoo.com>
Co-authored-by: Andrés Paredes <andresparedeszaa@gmail.com>
Co-authored-by: CJ Montero <cristlad@proton.me>
Co-authored-by: Cabdi Waaxid Siciid <cabdiwaaxidsiciid100@gmail.com>
Co-authored-by: Dora Chiu <dorachiu7@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hasan <osama201029331611@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hosted Weblate user 54392 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Hugoren Martinako <aumpfbahn@gmail.com>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Michael Moroni <michaelmoroni@disroot.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Mona Lisa <monalisa@users.noreply.hosted.weblate.org>
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 <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: VfBFan <vfbfan@users.noreply.hosted.weblate.org>
Co-authored-by: heydootdoot <new3dsmailly@gmail.com>
Co-authored-by: iwjck <iawojcik83@protonmail.com>
Co-authored-by: july-atrophy-emit <july-atrophy-emit@duck.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: ojppe <ojppe@users.noreply.hosted.weblate.org>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/fr/
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/lv/
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_BR/
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/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/newpipe/strings/
Translation: NewPipe/Metadata
Translation: NewPipe/Strings
2026-06-13 11:24:55 +02:00
Tobi bb02927fa3 Merge pull request #13588 from weblate/weblate-newpipe-compose
Translations update from Hosted Weblate
2026-06-13 11:24:42 +02:00
Isira Seneviratne 84142c1468 Merge pull request #13596 from Isira-Seneviratne/Merge-dev-to-refactor
Merge dev to refactor
2026-06-13 12:43:21 +05:30
+12 ddceb10fba Translated using Weblate (Somali)
Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Turkish)

Currently translated at 3.7% (1 of 27 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Galician)

Currently translated at 66.6% (18 of 27 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Icelandic)

Currently translated at 3.7% (1 of 27 strings)

Translated using Weblate (Dutch)

Currently translated at 81.4% (22 of 27 strings)

Translated using Weblate (Estonian)

Currently translated at 29.6% (8 of 27 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Persian)

Currently translated at 96.2% (26 of 27 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Dutch)

Currently translated at 48.1% (13 of 27 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (French)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (27 of 27 strings)

Added translation using Weblate (Lombard)

Added translation using Weblate (ryu (generated) (ryu))

Added translation using Weblate (Toki Pona)

Added translation using Weblate (Undetermined)

Added translation using Weblate (Dutch (Middle))

Added translation using Weblate (Punjabi (Pakistan))

Added translation using Weblate (Arabic (Libya))

Added translation using Weblate (Berber)

Added translation using Weblate (Norwegian Bokmål)

Added translation using Weblate (Tamazight (Central Atlas))

Added translation using Weblate (Acehnese)

Added translation using Weblate (Kurdish (Kurmanji))

Added translation using Weblate (Bambara)

Added translation using Weblate (Bavarian)

Added translation using Weblate (Bengali (Bangladesh))

Added translation using Weblate (Kabyle)

Added translation using Weblate (Uzbek (Latin script))

Added translation using Weblate (Latin)

Added translation using Weblate (Chinese (Traditional Han script))

Added translation using Weblate (Santali)

Added translation using Weblate (Burmese)

Added translation using Weblate (Assamese)

Added translation using Weblate (Sardinian)

Added translation using Weblate (N’Ko)

Added translation using Weblate (Tagalog)

Added translation using Weblate (Dutch (Belgium))

Added translation using Weblate (Kurdish (Sorani))

Added translation using Weblate (Portuguese (Portugal))

Added translation using Weblate (Urdu)

Added translation using Weblate (Slovenian)

Added translation using Weblate (Kurdish)

Added translation using Weblate (Sinhala)

Added translation using Weblate (Slovak)

Added translation using Weblate (Kannada)

Added translation using Weblate (Korean)

Added translation using Weblate (Albanian)

Added translation using Weblate (Serbian)

Added translation using Weblate (Kazakh)

Added translation using Weblate (Georgian)

Added translation using Weblate (Finnish)

Added translation using Weblate (Swedish)

Added translation using Weblate (French)

Added translation using Weblate (Somali)

Added translation using Weblate (Spanish)

Added translation using Weblate (Neapolitan)

Added translation using Weblate (Nepali)

Added translation using Weblate (Norwegian Nynorsk)

Added translation using Weblate (Dutch)

Added translation using Weblate (Bengali (India))

Added translation using Weblate (Indonesian)

Added translation using Weblate (Azerbaijani)

Added translation using Weblate (Interlingua)

Added translation using Weblate (Arabic)

Added translation using Weblate (Swahili)

Added translation using Weblate (Afrikaans)

Added translation using Weblate (Marathi)

Added translation using Weblate (Malay)

Added translation using Weblate (Macedonian)

Added translation using Weblate (Mongolian)

Added translation using Weblate (Malayalam)

Added translation using Weblate (Hebrew)

Added translation using Weblate (Hindi)

Added translation using Weblate (Hungarian)

Added translation using Weblate (Croatian)

Added translation using Weblate (Armenian)

Added translation using Weblate (English (United Kingdom))

Added translation using Weblate (Icelandic)

Added translation using Weblate (Vietnamese)

Added translation using Weblate (Punjabi)

Added translation using Weblate (Portuguese)

Added translation using Weblate (Czech)

Added translation using Weblate (Welsh)

Added translation using Weblate (Catalan)

Added translation using Weblate (Odia)

Added translation using Weblate (Occitan)

Added translation using Weblate (Japanese)

Added translation using Weblate (Bosnian)

Added translation using Weblate (Breton)

Added translation using Weblate (Javanese)

Added translation using Weblate (Asturian)

Added translation using Weblate (Ukrainian)

Added translation using Weblate (Bulgarian)

Added translation using Weblate (Belarusian)

Added translation using Weblate (Bengali)

Added translation using Weblate (Romanian)

Added translation using Weblate (Russian)

Added translation using Weblate (Estonian)

Added translation using Weblate (Basque)

Added translation using Weblate (Esperanto)

Added translation using Weblate (Greek)

Added translation using Weblate (Danish)

Added translation using Weblate (Portuguese (Brazil))

Added translation using Weblate (Chinese (Traditional Han script, Hong Kong))

Added translation using Weblate (Tamil)

Added translation using Weblate (Filipino)

Added translation using Weblate (Telugu)

Added translation using Weblate (Tigrinya)

Added translation using Weblate (Thai)

Added translation using Weblate (Lithuanian)

Added translation using Weblate (Latvian)

Added translation using Weblate (Turkish)

Added translation using Weblate (Tatar)

Added translation using Weblate (Galician)

Added translation using Weblate (Gujarati)

Translated using Weblate (Italian)

Currently translated at 100.0% (27 of 27 strings)

Added translation using Weblate (Italian)

Translated using Weblate (German)

Currently translated at 100.0% (27 of 27 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (27 of 27 strings)

Added translation using Weblate (German)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 96.2% (26 of 27 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 96.2% (26 of 27 strings)

Translated using Weblate (Polish)

Currently translated at 96.2% (26 of 27 strings)

Translated using Weblate (Polish)

Currently translated at 96.2% (26 of 27 strings)

Translated using Weblate (Persian)

Currently translated at 88.8% (24 of 27 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (2 of 2 strings)

Added translation using Weblate (Persian)

Translated using Weblate (Polish)

Currently translated at 100.0% (2 of 2 strings)

Added translation using Weblate (Polish)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (2 of 2 strings)

Added translation using Weblate (Chinese (Simplified Han script))

Co-authored-by: Aayush Gupta <aayushgupta219@gmail.com>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Cabdi Waaxid Siciid <cabdiwaaxidsiciid100@gmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hosted Weblate user 54392 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Michael Moroni <michaelmoroni@disroot.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: july-atrophy-emit <july-atrophy-emit@duck.com>
Co-authored-by: ojppe <ojppe@users.noreply.hosted.weblate.org>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/az/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/bg/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/et/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/fa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/gl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/is/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/nl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/so/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/newpipe/compose/zh_Hant/
Translation: NewPipe/compose
2026-06-13 07:10:51 +00:00
Tobi 64b09e5491 Merge pull request #13594 from Thompson3142/#13450-fix_seekbar_crash
Fix crash when seeking to the end of special videos (again)
2026-06-13 09:10:20 +02:00
Isira Seneviratne 2bbd439e9a Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
#	build.gradle.kts
#	gradle/libs.versions.toml
2026-06-13 06:52:40 +05:30
Tobi 8dc1315d5f Merge pull request #13590 from TeamNewPipe/misc
Misc changes for building
2026-06-12 07:35:13 +02:00
Aayush Gupta 192dc99a83 gradle: Also append git branch name to continuous builds
Signed-off-by: Aayush Gupta <contact@aayush.io>
2026-06-12 11:54:40 +08:00
Aayush Gupta 87e06a436c gradle: Set continuous as the default build type
We cannot enable optimizations by default in debug builds, so simply set our new variant
as the default

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-12 11:54:40 +08:00
Aayush Gupta cf49cc4c0f ci: Build and test new variant with optimizations enabled
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-12 11:54:40 +08:00
Thompson3142 18d6ba3503 Fix crash when seeking to the end of special videos (again) 2026-06-12 00:43:12 +02:00
tobigr d927f44c46 Merge branch 'master' into dev 2026-06-10 08:17:50 +02:00
Tobi 84d7db5c13 Merge pull request #13591 from AudricV/yt_support_properly_sabr_workaround
[YouTube] Support properly new client which works around SABR enforcement
2026-06-10 00:01:33 +02:00
AudricV 058810f452 [YouTube] Support new client in YoutubeHttpDataSource 2026-06-09 23:36:41 +02:00
tobigr 1058370db0 Release NewPipe 0.28.8 (1013) 2026-06-09 23:16:38 +02:00
tobigr 4f7fd37926 Add changelog for NewPipe 0.28.8 (1013) 2026-06-09 23:16:38 +02:00
tobigr 30c86abe8a Update NewPipe Extractor to 0.26.3 2026-06-09 23:16:38 +02:00
Aayush Gupta c7e4681932 gradle: Configure project's name to resolve type-safe accessor warnings
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-09 13:42:20 +08:00
Aayush Gupta 8d484317ce app: Register a build variant with optimizations enabled
This would help to test and debug progaurd changes easily

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-09 11:37:16 +08:00
Aayush Gupta f102205f00 Merge pull request #13486 from TeamNewPipe/cmp
AboutActivity rewrite
2026-06-08 17:44:03 +08:00
Aayush Gupta 2f1407980e shared: Remove non-required escape characters
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-08 16:36:23 +08:00
Aayush Gupta f1e145f690 shared: Remove app_name from string resources
We cannot mark strings as non-translatable in KMP. Remove it and make it
a constant in config instead so that the users don't translate it.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-08 16:34:35 +08:00
Aayush Gupta b3d3f5445c shared: Store license information in seaprate variable
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-08 16:29:16 +08:00
Aayush Gupta fdb28cdc4a shared: Inject navigation using Koin as well
Largely inspired from https://insert-koin.io/docs/reference/koin-compose/navigation3/
However I adapted certain things as needed.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-08 15:54:16 +08:00
Aayush Gupta 6208f9a80c shared: Address review feedback
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-06 21:23:01 +08:00
Aayush Gupta 8c295a34fb shared: composable: Ensure library's developers and licenses are available
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-04 18:47:33 +08:00
Aayush Gupta ae42026e82 shared: Fix AboutScreen preview
Actual pages require Koin to handle callbacks which we don't need during preview.
Extract page setup into a parameter and override it to page content during preview.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 18:00:18 +08:00
Aayush Gupta 0de011447d shared: Optimize imports using ktlint
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 17:16:39 +08:00
Aayush Gupta dbe64a88dc shared: Fix UI tests
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 17:15:32 +08:00
Aayush Gupta 5f55717a9f shared: composable: Increase LinkListItem's padding
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 17:15:32 +08:00
Aayush Gupta ac180ced84 app: Drop about activity implementation
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 17:15:32 +08:00
Aayush Gupta 8392cd56bd app: Switch to compose implementation of AboutActivity
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 17:15:29 +08:00
Aayush Gupta 43dca2b45b shared: Switch to reading licenses directly
It seems the licenses in the generated BOM by aboutlibraries plugin isn't consistent
and changes without any reason

Licenses were downloaded using reuse tool

reuse --root shared/src/commonMain/composeResources/files/ download Apache-2.0 BSD-2-Clause EPL-1.0 GPL-3.0-only GPL-3.0-or-later MIT MPL-2.0 MIT-0

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 15:58:25 +08:00
Aayush Gupta 0d06dd9b1a shared: Add composables for displaying library and link information
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-06-02 14:40:00 +08:00
Aayush Gupta 860a9004fe Import and generate BOM using about libraries plugin
The plugin traverses child libraries automatically, so adding it to the parent modules
allows us to include dependencies of the whole app and not just the shared part.

Configure app and desktopApp to generate the BOM in respective directories
of shared module. As iosApp only contains swift, add it's configuration in shared.

Also override extractor and evernote's messed up license

Generated using: ./gradlew exportLibraryDefinitions

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-26 19:33:37 +08:00
Aayush Gupta df6786c176 Merge pull request #13531 from TeamNewPipe/misc
Update dependencies
2026-05-24 15:25:10 +08:00
Aayush Gupta a4abadd02a gradle: Format build scripts with ktlint
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-24 14:56:04 +08:00
Aayush Gupta 50925a1d6c libs: Bump dependencies to latest releases
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-24 13:58:45 +08:00
94005cfe9c Translated using Weblate (Dutch)
Currently translated at 55.4% (51 of 92 strings)

Translated using Weblate (Latvian)

Currently translated at 97.8% (759 of 776 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Latvian)

Currently translated at 97.8% (759 of 776 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 66.3% (61 of 92 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 65.2% (60 of 92 strings)

Translated using Weblate (Slovak)

Currently translated at 77.1% (71 of 92 strings)

Translated using Weblate (Croatian)

Currently translated at 99.8% (775 of 776 strings)

Translated using Weblate (Serbian)

Currently translated at 99.2% (770 of 776 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nero <guihsalmeida0210@hotmail.com>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
Co-authored-by: Yauhen <bugomol@users.noreply.hosted.weblate.org>
Co-authored-by: ojppe <ojppe@users.noreply.hosted.weblate.org>
Co-authored-by: Саша Петровић <salepetronije@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translation: NewPipe/Metadata
2026-05-23 20:15:02 +00:00
tobigr d3d6b0283f Merge branch 'master' into dev 2026-05-23 20:18:19 +02:00
tobigr 45af64a518 Release NewPipe 0.28.7 (1012) 2026-05-23 19:34:09 +02:00
tobigr 28654471d5 Add changelog for NewPipe 0.28.7 (1012) 2026-05-23 19:34:09 +02:00
tobigr 82e3e71849 Update NewPipe Extractor to v0.26.2 2026-05-23 19:29:38 +02:00
Ida Delphine d4698f9d8e Merge pull request #13525 from theimpulson/cmp
shared: Add top app bar composable to reflect different active services
2026-05-23 09:22:51 +01:00
Aayush Gupta 01e3742521 shared: Add missing settings implementation
This seems to have gotten missed during moving settings to different modules

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-23 16:14:52 +08:00
Aayush Gupta 0f2bbf11ff shared: Add top app bar composable to reflect different active services
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-23 16:14:48 +08:00
Tobi aa48a76486 Merge pull request #13524 from TeamNewPipe/proguard
proguard-rules: Keep resource classes for prettytime
2026-05-23 00:37:33 +02:00
Aayush Gupta abf97404a0 proguard-rules: Keep resource classes for prettytime
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-22 20:21:55 +08:00
Aayush Gupta dfc3f4b9f3 Merge pull request #13507 from theimpulson/cmp
Introduce compose multiplatform
2026-05-21 16:06:44 +08:00
Aayush Gupta d1bc8c23cf Better share version information between modules
Move important version properties to buildSrc directory to access between modules
as needed.

Also add a simple task to generate a simple BuildConfig class to access version name.
This is better than adding dependency on a third-party library/plugin.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-20 18:27:08 +08:00
Aayush Gupta 53f36154aa Switch to new syntax for declaring SDK versions
Current ones are planned to be deprecated in AGP 10.x

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-19 12:27:42 +08:00
Aayush Gupta 89d55ede72 Initial setup for navigation display with nav3
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-19 12:27:29 +08:00
Aayush Gupta 909bd347a7 Setup multiplatform settings with KMP and theme
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-19 12:24:35 +08:00
Aayush Gupta cc74ac8ce8 Initial support for compose multiplatform
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-19 12:22:31 +08:00
TobiGr ef85e567fb Update NewPIpe Extractor to the latest version 2026-05-16 09:20:30 +02:00
+16 238124c7be Translated using Weblate (Russian)
Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Croatian)

Currently translated at 99.7% (774 of 776 strings)

Translated using Weblate (Spanish)

Currently translated at 72.8% (67 of 92 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Latvian)

Currently translated at 97.1% (754 of 776 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (French)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (French)

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Persian)

Currently translated at 98.8% (767 of 776 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (German)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (German)

Currently translated at 100.0% (92 of 92 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (776 of 776 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 63.7% (58 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 83.5% (76 of 91 strings)

Translated using Weblate (Spanish)

Currently translated at 72.5% (66 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (773 of 773 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439jbyl80igqtf25uxnr0x1bg@users.noreply.hosted.weblate.org>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nicolás Pérez <nicoperez241@proton.me>
Co-authored-by: Olly <pifahif961@cadinr.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: UDP <udp@users.noreply.hosted.weblate.org>
Co-authored-by: VfBFan <vfbfan@users.noreply.hosted.weblate.org>
Co-authored-by: delvani <del.cidrak@users.noreply.hosted.weblate.org>
Co-authored-by: justcontributor <kty5663@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/en_GB/
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/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translation: NewPipe/Metadata
2026-05-16 06:41:13 +00:00
Tobi 6f3f4219dc Merge pull request #13504 from laiosg/repeatmode-ui-fix
Fix repeat mode UI out-of-sync when switching videos
2026-05-16 08:41:01 +02:00
TobiGr 9dd0f1984f Replace if-else with switch 2026-05-16 08:35:29 +02:00
Tobi b6a2167516 Merge pull request #13505 from theimpulson/depUpdate
Update dependencies and plugins to latest releases
2026-05-16 08:22:05 +02:00
Aayush Gupta c2ead904fe Update libs to latest stable releases
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-16 12:36:21 +08:00
Aayush Gupta 6419b6bac7 Update Gradle and wrapper to 9.5.1
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-16 12:34:57 +08:00
Aayush Gupta 6cd63dc0c5 Upgrade AGP to 9.2.0
Building release builds is still broken on encrypted linux file systems but that's seems
to be not a priority for Google to fix. Upgrade so that developers can avoid suffering from
bugs such as preview rendering failure.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-05-16 12:25:24 +08:00
George Laios 18d54b6225 Fix repeat mode UI out-of-sync when switching videos 2026-05-16 05:46:36 +03:00
Tobi cd171dab54 Merge pull request #13454 from TeamNewPipe/depUpdate
Update dependencies and build with JDK 21
2026-05-03 09:09:39 +02:00
tobigr 07b4fa7b35 Merge branch 'master' into dev 2026-05-01 23:59:24 +02:00
Stypox 6c65949f5b Merge pull request #13462 from Stypox/release-0.28.6 2026-05-01 16:07:45 +02:00
Stypox d4941c4424 Release v0.28.6 (1011) 2026-05-01 14:34:35 +02:00
Stypox 9373a6c40d Add changelog for v0.28.6 (1011) 2026-05-01 14:33:51 +02:00
Stypox 6e3b858f42 Merge pull request #13458 from Stypox/minsdk-23-popup 2026-05-01 14:29:44 +02:00
Stypox 6d52522523 Add popup for NewPipe dropping support for Android 5 2026-05-01 12:05:04 +02:00
Stypox 9360d2f61c Cleanup KeepAndroidOpen dialog code 2026-04-29 14:49:31 +02:00
Tobi 56982455f6 Merge pull request #13455 from TeamNewPipe/weblate
Translation updates via weblate
2026-04-26 11:20:02 +02:00
Aayush Gupta ad7f8ba9c3 gradle: Build with JDK 21
Checkstyle was already requiring JDK 21 and now about libraries need it too

Use the kotlin extension method as it will also configure it for java code

Ref: https://kotlinlang.org/docs/gradle-configure-project.html#gradle-java-toolchains-support

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-26 12:14:57 +08:00
Aayush Gupta ff597f8e95 Update dependencies to latest stable releases
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-26 12:14:57 +08:00
+7 8805c9e8c9 Translated using Weblate (Swedish)
Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Swedish)

Currently translated at 95.6% (87 of 91 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 63.7% (58 of 91 strings)

Translated using Weblate (Polish)

Currently translated at 63.7% (58 of 91 strings)

Translated using Weblate (Swedish)

Currently translated at 95.6% (87 of 91 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 63.7% (58 of 91 strings)

Translated using Weblate (Bengali (India))

Currently translated at 2.1% (2 of 91 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 63.7% (58 of 91 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 27.4% (25 of 91 strings)

Translated using Weblate (Turkish)

Currently translated at 35.1% (32 of 91 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (German)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Persian)

Currently translated at 95.6% (739 of 773 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (German)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.0% (758 of 773 strings)

Translated using Weblate (French)

Currently translated at 100.0% (91 of 91 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439jbyl80igqtf25uxnr0x1bg@users.noreply.hosted.weblate.org>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Doniyor Nasriddinov <nasridoninl@gmail.com>
Co-authored-by: Edward <edwardchirita@mailbox.org>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Gabriel Wirtuwiusz <konstantyd@o2.pl>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: IEEE-754 <253034919+IEEE-754@users.noreply.github.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Mona Lisa <nickwick@users.noreply.hosted.weblate.org>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org>
Co-authored-by: UDP <udp@users.noreply.hosted.weblate.org>
Co-authored-by: VfBFan <vfbfan@users.noreply.hosted.weblate.org>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Co-authored-by: পার্থ ধর <partha.dhar2008@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn_IN/
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/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
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/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uz_Latn/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translation: NewPipe/Metadata
2026-04-25 11:21:56 +02:00
Y2kz 6740af2c71 Fix video playback when exiting popup to main screen (#13437)
Fix for the issue ( #6400 ) wrong video plays when exiting popup mode to fullscreen.
2026-04-25 02:21:38 -07:00
Aayush Gupta a44e65e709 Merge pull request #13448 from TeamNewPipe/comment-crash
fix app crash on loading comment section with no internet
2026-04-24 17:48:39 +08:00
Ida Delphine 188ba0a462 refactor if statements to when block 2026-04-24 07:44:34 +01:00
Aayush Gupta 7b98b4058a Merge pull request #13447 from TeamNewPipe/about-activity
remove AboutActivity
2026-04-24 10:23:25 +08:00
Ida Delphine 78f9fbadb7 fix app crash on loading comment with no internet 2026-04-24 00:22:03 +01:00
Ida Delphine 2ebb0dc114 remove AboutActivity 2026-04-23 16:28:08 +01:00
Ida Delphine 9bd9a5c6e9 Convert ErrorActivity to compose (#13434)
* add error screen compasable

* add error activity

* create separate privacy policy dialog

* move base activity

* refactor error activity and tiny tweaks

* add new compose activity and navdisplay

* refactor app to utilize new compose activity

* clean up arguments in the ErrorReportScreen

* get rid of redundant colors

* tackle review issues

* rename getErrorActivityIntent
2026-04-23 07:16:06 +00:00
Aayush Gupta ee25b44170 Merge pull request #13445 from TeamNewPipe/mergeDev
Merge dev into refactor
2026-04-23 14:56:52 +08:00
Aayush Gupta 43f40c485d gradle: Build with JDK 21
Checkstyle was already requiring JDK 21 and now about libraries need it too

Use the kotlin extension method as it will also configure it for java code

Ref: https://kotlinlang.org/docs/gradle-configure-project.html#gradle-java-toolchains-support

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-23 14:31:45 +08:00
Aayush Gupta c992b85b25 libs: Update compose-related libraries to latest release
The about libraries gradle plugin does merge of duplicate by defaults.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-23 12:09:16 +08:00
Aayush Gupta 49354a3c04 fixup! Relocate toml lint task to buildSrc and extend against default task
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-21 10:42:21 +08:00
Aayush Gupta bfefd43700 Merge branch 'dev' into refactor 2026-04-21 10:42:09 +08:00
Aayush Gupta c1692322bb Merge pull request #13444 from TobiGr/extractor
Update extractor and remove prguard rules for jsoup
2026-04-21 08:43:40 +08:00
tobigr ec81b6bb62 Remove proguard rules for optional jsoup classes
The underlying problem is fixed with jsoup 1.12.2. See https://github.com/TeamNewPipe/NewPipeExtractor/pull/1480.

Revert 6c5d58bed3 /  #13038.
2026-04-20 15:39:14 +02:00
tobigr bdebc926a8 Update extractor 2026-04-20 15:35:08 +02:00
Tobi 936d5d27ff Merge pull request #13390 from priomsrb/fix-playback-after-idle
Fix playback not working after player enters idle state
2026-04-20 06:10:06 +02:00
Shafqat Bhuiyan a719b898a1 Fix playback not working after player enters idle state
On some phones (e.g. Oppo and Oneplus) the video player enters the STATE_IDLE 10 minutes after being paused. This causes the play button to stop working.

This happens because once a player has become idle, we need to call prepare() before playback can happen again.

But after I added prepare(), it would just skip to the end of the video. So now I'm executing the same code that happens when ERROR_CODE_UNSPECIFIED is done. This causes playback to resume normally.
2026-04-20 05:48:06 +02:00
Aayush Gupta 05333265ec Merge pull request #13441 from TeamNewPipe/apiMChanges
Remove non-required API M version checks
2026-04-19 21:01:54 +08:00
Aayush Gupta c4a6bdd7d4 Remove non-required API M version checks
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-19 19:44:23 +08:00
Stypox 5836f3a492 Merge pull request #13425 from TeamNewPipe/depUpdate 2026-04-19 12:51:46 +02:00
Aayush Gupta aa094bc782 libs: Bump all libs stuck due to minSdk to latest release
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-16 16:38:19 +08:00
Aayush Gupta 7a3d1d9b5f libs: Bump minSdk to API 23
androidx framework has bumped minSdk requirement to API 23. Most libs dependening
upon the framework as a result require us to bump API level or keep using outdated versions.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-16 16:38:19 +08:00
Aayush Gupta 2682f233a0 libs: Update kotlinx.serialization to latest release
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-16 16:13:59 +08:00
Aayush Gupta 4effcad436 libs: Update checkstyle to latest release and fix violations
Javadoc comment at column 46 has parse error. Details: no viable alternative at input '<http:/' while parsing HTML_ELEMENT [UnusedImports]

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-04-16 16:13:25 +08:00
Tobi dd917ca240 Merge pull request #13418 from Ecomont/fix/offline-comments-crash
Fix crash when loading offline comments in CommentRepliesSource
2026-04-14 23:21:00 +02:00
Tobi ae03b4f2af Merge pull request #13420 from TeamNewPipe/mergeDev
Merge dev into refactor
2026-04-14 23:17:57 +02:00
Aayush Gupta 53d8d4d4da Merge branch 'dev' into refactor 2026-04-14 22:23:49 +08:00
Tobi 3e297fd892 Merge pull request #13391 from TobiGr/fix/downloaderPermission
Fix SecurityException when opening downloaded files in other app
2026-04-14 11:35:15 +02:00
h 064458cccd Fix crash when loading offline comments in CommentRepliesSource 2026-04-14 06:35:35 +02:00
tobigr 1ce1dbe73c Bump NewPipe version to 0.28.5 (1010) 2026-04-10 16:06:21 +02:00
24abbb8111 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Polish)

Currently translated at 63.7% (58 of 91 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2026-04-10 16:05:58 +02:00
tobigr 148b6f7998 Update NewPipe Extractor to v0.26.1 2026-04-10 14:03:57 +02:00
tobigr 0ce141f79d Add changelog for NewPipe 0.28.5 (1010) 2026-04-10 11:34:24 +02:00
+2 df6ec4462c Translated using Weblate (Polish)
Currently translated at 63.3% (57 of 90 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 64.4% (58 of 90 strings)

Translated using Weblate (Swedish)

Currently translated at 99.8% (772 of 773 strings)

Translated using Weblate (Burmese)

Currently translated at 1.1% (1 of 90 strings)

Translated using Weblate (Latvian)

Currently translated at 97.6% (755 of 773 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Latvian)

Currently translated at 97.6% (755 of 773 strings)

Translated using Weblate (Latvian)

Currently translated at 18.8% (17 of 90 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 64.4% (58 of 90 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Russian)

Currently translated at 98.8% (764 of 773 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Latvian)

Currently translated at 97.5% (754 of 773 strings)

Deleted translation using Weblate (Aymara (Southern))

Deleted translation using Weblate (Mainfränkisch)

Deleted translation using Weblate (Azerbaijani (Southern))

Deleted translation using Weblate (Azerbaijani (Southern))

Deleted translation using Weblate (Kashmiri)

Deleted translation using Weblate (Sicilian)

Deleted translation using Weblate (Aymara)

Deleted translation using Weblate (Luri (Bakhtiari))

Deleted translation using Weblate (Yiddish)

Deleted translation using Weblate (Romany)

Deleted translation using Weblate (Corsican)

Deleted translation using Weblate (Gaelic)

Deleted translation using Weblate (Arabic (Tunisian))

Deleted translation using Weblate (French (Louisiana))

Deleted translation using Weblate (German (Low))

Deleted translation using Weblate (English (Old))

Deleted translation using Weblate (English (Middle))

Deleted translation using Weblate (Arabic (Najdi))

Co-authored-by: Arif Budiman <arifpedia@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mona Lisa <nickwick@users.noreply.hosted.weblate.org>
Co-authored-by: Morkovka21Vek <morkovka21vek@gmail.com>
Co-authored-by: Nicolás Pérez <nicoperez241@proton.me>
Co-authored-by: Omikorin <git@omikor.in>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Tun Tun AUNG <tuntunaung@codingclassroom.org>
Co-authored-by: UDP <udp@users.noreply.hosted.weblate.org>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/my/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translation: NewPipe/Metadata
2026-04-10 11:25:19 +02:00
Tobi 2f14473e2a Merge pull request #13403 from litetex/update-extractor
Update extractor to latest commit
2026-04-08 17:28:00 +02:00
litetex 3aa74f1e77 Update extractor to latest commit
To get the changes from https://github.com/TeamNewPipe/NewPipeExtractor/pull/1464
2026-04-08 16:14:52 +02:00
Ida Delphine 44bf345ba9 Merge pull request #13333 from evermind-zz/rework-video-quality-representation-and-stream-selection_refactor
Rework video quality representation and stream selection refactor
Added unit tests for ListHelper
2026-04-03 16:22:27 +01:00
Ida Delphine 0e2e94dcb9 add unit tests for ListHelper 2026-04-03 14:34:47 +01:00
Ida Delphine 67b6f0a10b refactor ListHelper Kotlin methods 2026-04-03 09:58:17 +01:00
evermind 274cb7c170 ListHelper: rework video quality representation and VideoStream selection
Replace the previous string-based resolution/fps/etc. handling for VideoStreams
by parsing the 'getResolution()'-string to a data class to improve maintainability.

Video resolutions (e.g. "720p60") were previously compared and matched using
regex-based string manipulation all over the place. This was fragile,
required repeated parsing and made it difficult to extend the matching logic.

It can handle all the strings like before: "720p", "720p60"

Additionally this patch can also handle strings like: "720p60@123k", or "720p@2m"

Main changes:
- Introduce internal data class VideoQuality (resolution, fps, bitrate, formatRank).
- Introduce internal VideoStreamWithQuality to associate streams with parsed quality.
- Parse quality only once.
- Replace resolution string sorting with a comparator based on
  resolution -> fps -> formatRank -> bitrate.
- Rework stream selection to use a priority-based matching algorithm
  (format, resolution, fps, bitrate).
- Prefer candidates with bitrate closest to the requested one when multiple
  streams match the same priority class.
2026-04-02 10:29:40 +01:00
evermind 62e66326a5 convert ListHelper to kotlin 2026-04-02 10:29:40 +01:00
TobiGr db8edd3b44 Fix SecurityException when opening downloaded files in other app
The flag was not passed to the intent that wraps the actual view intent for the app chooser.
2026-04-01 20:55:16 +02:00
Ida Delphine 25c12ef81e Merge pull request #12803 from HatakeKakashri/debug_screen_migration
Complete the debug screen migration to Compose
2026-03-30 09:27:48 +01:00
Ida Delphine d33ba8ac75 refactor settings screen, debug, viewmodels, etc 2026-03-30 03:06:31 +01:00
Ida Delphine fbfa1fd2b1 refactor navigation and settings screens 2026-03-30 01:17:14 +01:00
Ida Delphine 881792ebda add navigation keys 2026-03-29 23:57:01 +01:00
Tobi 50e8189363 Merge pull request #13385 from arjun1194/fix/use-application-context-for-enqueue
Fix NullPointerException in enqueue actions by using Application Context
2026-03-29 20:57:53 +02:00
Aayush Gupta b7c339c81c Merge pull request #13382 from TeamNewPipe/weblate
Delete untranslated locales
2026-03-30 01:13:49 +08:00
Hatake Kakashri de344100db Complete the debug screen migration to Compose with Nav3
- Completed the UI and logic for pending items of Debug screen using Jetpack Compose.
- Implemented a new navigation system for settings using the navigation3
  lib as it is now stable.
- Reuses the `ScaffoldWithToolbar` composable and removed the previous
  `Toolbar` composable to avoid redundancy in code.
- Refactored the `SettingsViewModel` to use a `BooleanPreference` helper
  class to reuse and reducing boilerplate for state management.
- Created a shared `TextBase` composable to remove duplicated UI logic
  between `SwitchPreference` and `TextPreference`.
- Move the build-variant-dependent logic for LeakCanary and reused it in
  Compose and Fragment, this logic is used for ensuring the leak canary
  fields are only enabled in debug variants.
- Fixed a layout bug in `SwitchPreference` where long summary text could
  misalign the switch component and also adjusted the paddings for consistency.
2026-03-28 20:08:07 +00:00
arjun 07a2ab29de Fix NullPointerException in enqueue actions by using Application Context
Use getApplicationContext() instead of getContext() in ENQUEUE,
ENQUEUE_NEXT, START_HERE_ON_BACKGROUND, and START_HERE_ON_POPUP
entries to prevent NullPointerException when the fragment's activity
context becomes null during configuration changes (e.g. device rotation).

The fragment context can become null when the activity is destroyed
during rotation, but the async callback from fetchItemInfoIfSparse
still holds a reference to the now-detached fragment. Using
Application context ensures a stable, non-null context throughout
the async operation lifecycle.
2026-03-28 14:25:48 +05:30
Ida Delphine 5fafccba33 Merge pull request #12862 from guymarshall/change-2k-to-2.5k-to-fix-common-mistake
UPDATE: Replace 2k with 1440p
2026-03-27 11:30:22 +00:00
Ida Delphine 5e9c24a7b3 fix duplicate PreferenceSearchItem and extractor hash issue 2026-03-27 11:08:35 +00:00
Guy Marshall c85655f5bb UPDATE: Replace 2.5k and 2.5K with 1440p to match desktop website 2026-03-27 09:59:39 +00:00
Guy Marshall dfe12222ff UPDATE: Replace 2k and 2K with 2.5k and 2.5K to fix common mistake 2026-03-27 09:58:42 +00:00
tobigr da76ddbf19 Remove French (Lousiana) frc from app locales 2026-03-27 08:35:49 +01:00
TobiGr 75ba70ab41 Deleted translation using Weblate (Aymara (Southern)) 2026-03-27 08:30:49 +01:00
TobiGr 6d50fe79b8 Deleted translation using Weblate (Mainfränkisch) 2026-03-27 08:23:30 +01:00
TobiGr f8c9ab8b11 Deleted translation using Weblate (Azerbaijani (Southern)) 2026-03-27 08:22:23 +01:00
TobiGr 949f0f3448 Deleted translation using Weblate (Azerbaijani (Southern)) 2026-03-27 08:22:22 +01:00
TobiGr af08ddcf04 Deleted translation using Weblate (Kashmiri) 2026-03-27 08:19:25 +01:00
TobiGr 09c96159d1 Deleted translation using Weblate (Sicilian) 2026-03-27 08:19:05 +01:00
TobiGr 2dda39205d Deleted translation using Weblate (Aymara) 2026-03-27 08:18:48 +01:00
TobiGr 3112eefb99 Deleted translation using Weblate (Luri (Bakhtiari)) 2026-03-27 08:18:29 +01:00
TobiGr cf0d7016ec Deleted translation using Weblate (Yiddish) 2026-03-27 08:18:05 +01:00
TobiGr b0f2d509e6 Deleted translation using Weblate (Romany) 2026-03-27 08:17:48 +01:00
TobiGr db61af15e8 Deleted translation using Weblate (Corsican) 2026-03-27 08:17:29 +01:00
TobiGr 80a47be218 Deleted translation using Weblate (Gaelic) 2026-03-27 08:17:11 +01:00
TobiGr 179a713561 Deleted translation using Weblate (Arabic (Tunisian)) 2026-03-27 08:16:35 +01:00
TobiGr fa6412a9aa Deleted translation using Weblate (French (Louisiana)) 2026-03-27 08:15:57 +01:00
TobiGr 3f8d26d33e Deleted translation using Weblate (German (Low)) 2026-03-27 08:15:23 +01:00
TobiGr 2fec3a3c58 Deleted translation using Weblate (English (Old)) 2026-03-27 08:15:07 +01:00
TobiGr 08326c64cb Deleted translation using Weblate (English (Middle)) 2026-03-27 08:14:50 +01:00
TobiGr 831425a742 Deleted translation using Weblate (Arabic (Najdi)) 2026-03-27 08:14:12 +01:00
+24 fbf3b7a905 Translated using Weblate (Galician)
Currently translated at 94.9% (734 of 773 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Vietnamese)

Currently translated at 98.3% (760 of 773 strings)

Translated using Weblate (Dutch)

Currently translated at 99.0% (766 of 773 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (German)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Turkish)

Currently translated at 99.0% (766 of 773 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (French)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Hindi)

Currently translated at 98.9% (765 of 773 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (773 of 773 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Somali)

Currently translated at 73.3% (561 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 97.5% (746 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 97.6% (747 of 765 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Turkish)

Currently translated at 34.4% (31 of 90 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Romanian)

Currently translated at 12.2% (11 of 90 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.3% (760 of 765 strings)

Translated using Weblate (Vietnamese)

Currently translated at 73.3% (66 of 90 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (765 of 765 strings)

Added translation using Weblate (Corsican)

Translated using Weblate (Indonesian)

Currently translated at 86.6% (78 of 90 strings)

Translated using Weblate (German)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (French)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (763 of 765 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Italian)

Currently translated at 64.4% (58 of 90 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Kabyle)

Currently translated at 27.9% (214 of 765 strings)

Translated using Weblate (German)

Currently translated at 99.8% (764 of 765 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Cabdi Waaxid Siciid <cabdiwaaxidsiciid100@gmail.com>
Co-authored-by: David Bol <CapybarGooner2@proton.me>
Co-authored-by: Dot Max <ahmadfadil.original@outlook.com>
Co-authored-by: EESF-2 <eesf-2@users.noreply.hosted.weblate.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hosted Weblate user 142171 <traaduttore@users.noreply.hosted.weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Quý <quycodergithub@gmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Stéphanie Olier <olier.stephanie130@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: Vasilis K. <skyhirules@gmail.com>
Co-authored-by: VfBFan <vfbfan@users.noreply.hosted.weblate.org>
Co-authored-by: WB <wb@users.noreply.hosted.weblate.org>
Co-authored-by: codetabsite <info.codetabteknofest@gmail.com>
Co-authored-by: justcontributor <kty5663@gmail.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/ro/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translation: NewPipe/Metadata
2026-03-27 00:09:53 +00:00
Tobi 515bb6e94d Merge pull request #13363 from theimpulson/depUpdate
Update dependencies and Gradle to latest stable release
2026-03-18 10:16:47 +01:00
Aayush Gupta 349000857a Update dependencies and Gradle to latest stable release
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-03-18 15:52:23 +08:00
Aayush Gupta f1c608b396 Merge pull request #13352 from theimpulson/path
Port path related changes from refactor
2026-03-18 15:45:45 +08:00
Tobi d0a0b4a541 Merge pull request #13357 from WSU-CptS-481-Spring-2026/bug/refactor/13248-fix-no-comments-scroll
[Refactor] bugfix: make comment section take up full vertical space
2026-03-17 17:04:29 +01:00
Isira Seneviratne 5f1a270ca4 Fix database import 2026-03-16 14:48:23 +08:00
Isira Seneviratne aba2a385fd Inline variable 2026-03-16 14:47:24 +08:00
Isira Seneviratne 71a3bf2855 Use InputStream#transferTo() 2026-03-16 14:47:17 +08:00
Isira Seneviratne ebb937934a Fix DB import/export issue 2026-03-16 14:47:08 +08:00
Isira Seneviratne 223b240299 Refactor zip import/export using Path 2026-03-16 14:46:09 +08:00
Aayush Gupta 668af4fc3e Merge pull request #13347 from theimpulson/subscriptions
Port subscriptions related changes from refactor
2026-03-16 09:23:16 +08:00
evermind bfcc31ec89 BackupRestoreSettingsFragment: add UI options to import/export subscriptions
* create SubscriptionsImportExportHelper to share common code used in
  SubscriptionFragment and BackupRestoreSettingsFragment
* Add UI options for import/export in BackupRestoreSettingsFragment
2026-03-15 20:54:58 +08:00
Aayush Gupta 6fa97e17f5 subscription: Port subscription import-export to workers from refactor
Please see https://github.com/TeamNewPipe/NewPipe/pull/11759/ for the original change

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-03-15 20:51:29 +08:00
Aayush Gupta 00208fa8bd Merge pull request #13250 from dustdfg/refactor_tablet_related_items
VideoDetailFragment: hide relatedItemsLayout in tablet mode after fullscreen
2026-03-15 19:22:53 +08:00
Aayush Gupta 0d65733e53 Merge pull request #13207 from TobiGr/small-fixes
Small improvements to Image handling
2026-03-15 19:21:06 +08:00
Aayush Gupta 9cc6f9fd68 Merge pull request #13305 from dustdfg/remove_freedroid_license
Remove freedroidwarn license
2026-03-15 19:18:32 +08:00
Evan Glasscock e0a69e8840 Make comment section take up full vertical space 2026-03-08 18:05:24 -07:00
tobigr 79767f95f7 Merge branch 'master' into dev 2026-03-08 21:19:30 +01:00
Tobi 21b37b5fa4 Merge pull request #13296 from TeamNewPipe/release-0.28.4
Release 0.28.4
2026-03-08 11:07:54 -07:00
Tobi 675d15686d Merge pull request #13324 from TeamNewPipe/backport-13310-to-release-0.28.4
[Backport release-0.28.4] Add link to FAQ entry to "Sign in to confirm not a bot" exception message
2026-03-08 11:01:41 -07:00
tobigr 54021a96ea Complete sentence.
(cherry picked from commit 47624a575a)
2026-03-08 18:01:10 +00:00
AbsurdlyLongUsername 7c88f06196 Change to FAQ entry instead
(cherry picked from commit 3b3348e7a1)
2026-03-08 18:01:10 +00:00
AbsurdlyLongUsername 93f9a52e3e Change URL to FAQ
(cherry picked from commit 521f60af85)
2026-03-08 18:01:10 +00:00
AbsurdlyLongUsername d9fd1bf003 Maybe this fix ktlint errors?
(cherry picked from commit b8ec9bf412)
2026-03-08 18:01:10 +00:00
AbsurdlyLongUsername dc2e4aaea5 Add Sign in confirm not a bot issue URL to the exception error message
Make error panel and error activity exception message URLs clickable via extension method

Change ErrorMessage.getString to getText and return CharSequence, and use getText with formatArgs to preserve styling (i.e. URLs)

(cherry picked from commit e173bf4252)
2026-03-08 18:01:10 +00:00
d5f941ff3d Translated using Weblate (Vietnamese)
Currently translated at 70.0% (63 of 90 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (765 of 765 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nicolás Pérez <nicoperez241@proton.me>
Co-authored-by: Quý <quycodergithub@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translation: NewPipe/Metadata
2026-03-08 18:00:50 +00:00
Tobi 05f09c94d1 Merge pull request #13310 from absurdlylongusername/add-issue-url-sign-in-not-bot-exception
Add link to FAQ entry to "Sign in to confirm not a bot" exception message
2026-03-08 11:00:37 -07:00
816f5f9aba Translated using Weblate (Vietnamese)
Currently translated at 70.0% (63 of 90 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (765 of 765 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nicolás Pérez <nicoperez241@proton.me>
Co-authored-by: Quý <quycodergithub@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translation: NewPipe/Metadata
2026-03-08 18:57:50 +01:00
tobigr 47624a575a Complete sentence. 2026-03-08 18:54:56 +01:00
AbsurdlyLongUsername 3b3348e7a1 Change to FAQ entry instead 2026-03-07 23:38:51 +00:00
AbsurdlyLongUsername 521f60af85 Change URL to FAQ 2026-03-07 19:13:02 +00:00
+9 d497f1d88e Translated using Weblate (Latvian)
Currently translated at 97.9% (749 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 97.9% (749 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 18.8% (17 of 90 strings)

Translated using Weblate (Latvian)

Currently translated at 96.9% (742 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 97.3% (745 of 765 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 17.7% (16 of 90 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Armenian)

Currently translated at 31.6% (242 of 765 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Slovak)

Currently translated at 76.6% (69 of 90 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Japanese)

Currently translated at 13.3% (12 of 90 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (French)

Currently translated at 98.8% (89 of 90 strings)

Translated using Weblate (Japanese)

Currently translated at 95.5% (731 of 765 strings)

Translated using Weblate (Polish)

Currently translated at 56.6% (51 of 90 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Tamil)

Currently translated at 48.8% (44 of 90 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (German)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (German)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439jbyl80igqtf25uxnr0x1bg@users.noreply.hosted.weblate.org>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Davit Mayilyan <davit.mayilyan@protonmail.ch>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: VfBFan <vfbfan@users.noreply.hosted.weblate.org>
Co-authored-by: justcontributor <kty5663@gmail.com>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ta/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2026-03-07 17:44:37 +01:00
+9 dda219a9e9 Translated using Weblate (Latvian)
Currently translated at 97.9% (749 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 97.9% (749 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 18.8% (17 of 90 strings)

Translated using Weblate (Latvian)

Currently translated at 96.9% (742 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 97.3% (745 of 765 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Latvian)

Currently translated at 17.7% (16 of 90 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Armenian)

Currently translated at 31.6% (242 of 765 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Slovak)

Currently translated at 76.6% (69 of 90 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Japanese)

Currently translated at 13.3% (12 of 90 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (French)

Currently translated at 98.8% (89 of 90 strings)

Translated using Weblate (Japanese)

Currently translated at 95.5% (731 of 765 strings)

Translated using Weblate (Polish)

Currently translated at 56.6% (51 of 90 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Tamil)

Currently translated at 48.8% (44 of 90 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (German)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (German)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439jbyl80igqtf25uxnr0x1bg@users.noreply.hosted.weblate.org>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Davit Mayilyan <davit.mayilyan@protonmail.ch>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: VfBFan <vfbfan@users.noreply.hosted.weblate.org>
Co-authored-by: justcontributor <kty5663@gmail.com>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ta/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2026-03-06 20:30:04 +01:00
AbsurdlyLongUsername b8ec9bf412 Maybe this fix ktlint errors? 2026-03-06 12:55:09 +00:00
AbsurdlyLongUsername e173bf4252 Add Sign in confirm not a bot issue URL to the exception error message
Make error panel and error activity exception message URLs clickable via extension method

Change ErrorMessage.getString to getText and return CharSequence, and use getText with formatArgs to preserve styling (i.e. URLs)
2026-03-06 12:35:19 +00:00
Yevhen Babiichuk (DustDFG) 9f45aa571c Remove freedroidwarn license 2026-03-04 22:45:07 +02:00
Stypox e6e5fc70f2 Merge pull request #13299 from TeamNewPipe/backport-13298-to-release-0.28.4 2026-03-02 09:27:38 +01:00
Yevhen Babiichuk (DustDFG) 8fa6f9670d Apply suggestion
(cherry picked from commit 0020a02a28)
2026-03-02 08:27:17 +00:00
Stypox 0cdf40cd5f Merge pull request #13298 from dustdfg/shareutils_kao 2026-03-02 09:26:52 +01:00
Yevhen Babiichuk (DustDFG) 0020a02a28 Apply suggestion 2026-03-02 08:42:07 +02:00
tobigr 1fbf9fc025 Bump version to 0.28.4 (1009) 2026-02-28 10:06:03 +01:00
tobigr 8b500c7b83 Add changelog for NewPipe 0.28.4 (1009) 2026-02-28 10:06:03 +01:00
tobigr 98a883d377 Update NewPipe Extractor to 0.26.0 2026-02-28 10:06:03 +01:00
6dddcf3805 Translated using Weblate (French)
Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Greek)

Currently translated at 99.8% (764 of 765 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (765 of 765 strings)

Translated using Weblate (Swedish)

Currently translated at 96.6% (86 of 89 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Mona Lisa <nickwick@users.noreply.hosted.weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Vasilis K. <skyhirules@gmail.com>
Co-authored-by: delvani <del.cidrak@users.noreply.hosted.weblate.org>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translation: NewPipe/Metadata
2026-02-28 10:03:02 +01:00
Tobi bf1633265c Merge pull request #13293 from TeamNewPipe/getQuantity-inconsistency
Fix inconsistency in getQuantity and add docs
2026-02-26 11:45:41 -08:00
Stypox e22b046326 Fix inconsistency in getQuantity and add docs
`getQuantity()` was being called in a couple of places with `zeroCaseStringId=0`, but that wasn't documented anywhere, and if `count==0` then `getString(zeroCaseStringId /* == 0 */)` would be returned which doesn't make sense
2026-02-26 19:08:33 +01:00
Stypox 56fb31d0fd Merge pull request #13292 from dustdfg/kao_release_only 2026-02-26 11:01:56 +01:00
Yevhen Babiichuk (DustDFG) 042f9460b0 Don't show Keep Android Open popup on debug builds 2026-02-26 11:39:10 +02:00
Tobi 9f1e2c6fd0 Merge pull request #13282 from dustdfg/keep_android_open
Add warning banner about ongoing google certification for android apps
2026-02-25 17:08:49 -08:00
tobigr 66237abb3c KeepAndroidOpen: Choose website language from list of supported languages 2026-02-26 01:27:36 +01:00
Tobi dd65db56a9 Merge pull request #13290 from dustdfg/correct_download_fragment
Correctly retrieve menu item inside download dialog
2026-02-25 15:42:05 -08:00
Yevhen Babiichuk (DustDFG) 195a76bb08 Correctly retrieve menu item inside download dialog 2026-02-26 01:09:07 +02:00
Yevhen Babiichuk (DustDFG) 06e4548c14 Add warning banner about ongoing google certification for android apps 2026-02-25 18:35:41 +02:00
Tobi 4c82e8bafd Merge pull request #13278 from theimpulson/merge
Merge dev into refactor
2026-02-22 11:43:04 -08:00
Aayush Gupta 064e53221e Merge branch 'dev' into refactor 2026-02-22 19:45:09 +08:00
9a292e33f9 Translated using Weblate (Albanian)
Currently translated at 2.2% (2 of 89 strings)

Translated using Weblate (Georgian)

Currently translated at 55.0% (49 of 89 strings)

Translated using Weblate (Latvian)

Currently translated at 97.9% (748 of 764 strings)

Translated using Weblate (Georgian)

Currently translated at 96.5% (738 of 764 strings)

Co-authored-by: Flavjo Avdiu <flavjoavdiu10@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nikoloz <nukushatugushi@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ka/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sq/
Translation: NewPipe/Metadata
2026-02-22 11:32:29 +01:00
Tobi d1ad1e843f Merge pull request #13276 from TeamNewPipe/depupdate
Update dependencies and Gradle to latest stable releases
2026-02-21 23:59:11 -08:00
Aayush Gupta 8902ce8678 Update dependencies and Gradle to latest stable releases
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-22 10:46:58 +08:00
Aayush Gupta 9f193a3b00 Merge pull request #13272 from theimpulson/coilport
Migrate from Picasso to Coil
2026-02-22 10:35:43 +08:00
Tobi 232d023c02 Merge pull request #13030 from iampopovich/fix/6815-double-tap-to-resume
[fix] Hide controls when resuming playback via double tap
2026-02-21 11:08:51 -08:00
Alex Popov 423f95a65d Refactor double tap logic to use isPlaying() method for better readability 2026-02-21 20:03:25 +01:00
Alex Popov caebf8461a Hide controls when resuming playback via double tap
Remove redundant comment about hiding controls on double tap
2026-02-21 19:54:59 +01:00
Tobi 52a9b60180 Merge pull request #13148 from dustdfg/extractor_enum_set
Accomodate extractor changes (EnumSet for service media capabilities)
2026-02-21 09:10:55 -08:00
Tobi 4bad882668 Merge pull request #13037 from dustdfg/tablet_related_items
VideoDetailFragment: hide relatedItemsLayout in tablet mode after fullscreen
2026-02-21 09:03:11 -08:00
tobigr 63e19890bf Update extractor to the latest version 2026-02-21 17:00:05 +01:00
Yevhen Babiichuk (DustDFG) 08b7da4b2b Accomodate extractor changes (EnumSet for service media capabilities) 2026-02-21 16:55:31 +01:00
Aayush Gupta 402a0aaaa3 Delete unused preference title and summary
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-21 16:48:57 +08:00
Aayush Gupta 57364109f4 Migrate from Picasso to Coil
Based on changes from refactor

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-21 16:48:57 +08:00
Clippy 4481dd7fe6 Merge pull request #13135 from dustdfg/player_loop_refactor
Misc player loop/stream refactors
2026-02-21 06:25:58 +00:00
Aayush Gupta 273b09a3e8 App: Migrate from Java to Kotlin
Based on changes from refactor branch for code parity

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-21 00:06:12 +08:00
Tobi 35b70c5e9e Merge pull request #13194 from TeamNewPipe/r8fixes
Enable lint checks and resource shrinking
2026-02-20 00:47:16 -08:00
Aayush Gupta 653b33bdb9 FocusOverlayView: Avoid accessing restricted API
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 5eb5f7533d ic_smart_display: Fix invalid vector path on older devices
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 15829c882b lint: Supress more translation related errors
They should be fixed by translators or weblate in general.

 ../../src/main/res/values-lt/strings.xml:87: For locale "lt" (Lithuanian) the following quantity should also be defined: many (e.g. "1.1 obuolio")
 ../../src/main/res/values-is/strings.xml:318: The quantity 'one' matches more than one specific number in this locale (1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …), but the message did not include a formatting argument (such as %d). This is usually an internationalization error. See full issue explanation for more.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 10b943f37e DownloadDialog: Avoid using restricted API for menuitem
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 3d43e52afb activity_player_queue_control: Switch to app:tint instead of android:tint
../../src/main/res/layout/activity_player_queue_control.xml:208: Must use app:tint instead of android:tint

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta f64e40e958 DownloadRunnableFallback: Fix error with log tag being too long
Abbreviate Download -> DL

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 2182ff12b7 Use correct constant for hiding keyboard
../../src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt:509: Must be one or more of: InputMethodManager.HIDE_IMPLICIT_ONLY, InputMethodManager.HIDE_NOT_ALWAYS

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta b3f4cb8114 Use requireContext() instead of asserting non-null context
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta deb6b4230d FinishedMIssionStore: Throw exception if column is missing
../../src/main/java/us/shandian/giga/get/sqlite/FinishedMissionStore.java:105: Value must be ≥ 0 but getColumnIndex can be -1

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 2f3a993f8e ReCaptchaActivity: Supress lint error for missing super call
saveCookiesAndFinish method handles back navigation

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 582f852e7a Add missing permission checks for notifications
Notifications can be disabled manually even after permission has been granted once.
Always check if they are enabled before notifying the user.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 89cb87b2a9 Introduce lint configuration and enable checks
Supress missing translation errors as they are done by volunteers

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 8c016c95d2 Enable resources shrinking
AGP 9.0.0 has introduced additional resource shrinking tasks. Its better
to enable and fix this.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Aayush Gupta 5b009453f2 Address non-final resource IDs warnings
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-20 16:16:50 +08:00
Clippy 8b28bd1a18 Merge pull request #13258 from dustdfg/regression-detailfragment-flickers-refactor
Backport #13132 to refactor branch
2026-02-19 08:58:17 +00:00
+5 feb442492b Translated using Weblate (Dutch)
Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 57.3% (51 of 89 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Somali)

Currently translated at 72.1% (551 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 66.2% (59 of 89 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 66.2% (59 of 89 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 53.9% (48 of 89 strings)

Translated using Weblate (Spanish)

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Marathi)

Currently translated at 30.4% (233 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Polish)

Currently translated at 56.1% (50 of 89 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439JBYL80IGQTF25UXNR0X1BG@users.noreply.hosted.weblate.org>
Co-authored-by: AMOL GHORPADE <amolghorpade4u@gmail.com>
Co-authored-by: Daria Szatan <bla_ke@tuta.io>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kairos <private.snazzy673@simplelogin.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Miq Maq <maqdishi@gmail.com>
Co-authored-by: Nicolás Pérez <nicoperez241@proton.me>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: delvani <del.cidrak@gmail.com>
Co-authored-by: igoroliveira-letras <igoroliveiradasilvaofc@gmail.com>
Co-authored-by: ojppe <ojppe@users.noreply.hosted.weblate.org>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2026-02-19 09:41:02 +01:00
Tobi 834f136102 Merge pull request #13247 from dustdfg/crashlog_fix
Partially revert:  ErrorActivity: Kotlin-fy buildMarkdown method
2026-02-19 00:37:56 -08:00
Tobi 01e77e2e26 Merge pull request #13256 from pierreeurope/fix/ttml-postprocessing-error-reporting
Fix subtitle post-processing error losing original exception
2026-02-19 00:21:17 -08:00
Tobi 4cfd36ce5b Merge pull request #13203 from dustdfg/video_detail_fragment_click_through
VideoDetailFragment: Forcefully catch click events uncaught by children
2026-02-18 23:45:35 -08:00
Tobi 51c9ed774c Merge pull request #13213 from aivelon/handle-comment-replies-screens
Handling Comment Replies fragments
2026-02-18 23:43:39 -08:00
vt 9f8055f018 remove existing comment replies screens before adding new one 2026-02-19 12:07:22 +05:30
Tobi 239f6c97a0 Merge pull request #13241 from aivelon/comment-replies-without-images
Fix comment replies header layout without avatar image
2026-02-18 03:20:19 -08:00
vt 7e7ad1e410 fix layout of comment replies header to work when avatar image is gone (disabled in content -> image settings) 2026-02-17 13:34:41 +05:30
Yevhen Babiichuk (DustDFG) b6c50ccca5 Partial revert: fix VideoDetailFragment flickering
Refacor port of: 5155b24ed6
Look at #13132 for more details
2026-02-16 15:16:58 +02:00
pierreeurope 95367dd338 Fix subtitle post-processing error losing original exception
Previously, TtmlConverter.process() caught all exceptions during TTML
to SRT conversion and returned opaque error codes (1 for IOException,
8 for other exceptions). These error codes were then wrapped by
Postprocessing.run() into a generic RuntimeException with the message
'post-processing algorithm returned N', losing the original exception
and its stack trace.

This made it impossible for users and developers to diagnose the root
cause of subtitle download failures, as reported in #13206.

Now, IOExceptions are re-thrown directly, and other exceptions are
wrapped in an IOException with the original exception as the cause.
This allows DownloadMission.doPostprocessing() to properly catch and
report the actual error, including the full stack trace in the crash
report.

Fixes #13206

Signed-off-by: pierreeurope <pierre.europe@pm.me>
2026-02-16 12:46:30 +01:00
Tobi 3a42827320 Merge pull request #13245 from evermind-zz/add-subscription-import-export-to-backup-restore-settings_refactor
BackupRestoreSettingsFragment: add UI options to import/export subsciptions
2026-02-15 04:03:32 -08:00
Yevhen Babiichuk (DustDFG) dd651ecc45 VideoDetailFragment: hide relatedItemsLayout in tablet mode after fullscreen
Fixes: #7617
2026-02-14 08:11:23 +02:00
Yevhen Babiichuk (DustDFG) 70cdaf5550 Partially revert: ErrorActivity: Kotlin-fy buildMarkdown method
Partially reverts c3dbed54e5
Fix the bug where collapse container for several crashlogs is created
when only one crash log present
2026-02-13 13:43:54 +02:00
evermind 941edd7aef BackupRestoreSettingsFragment: add UI options to import/export subscriptions
* create SubscriptionsImportExportHelper to share common code used in
  SubscriptionFragment and BackupRestoreSettingsFragment
* Add UI options for import/export in BackupRestoreSettingsFragment
2026-02-13 09:56:23 +01:00
Aayush Gupta 3815f5f593 Translated using Weblate (Bengali (India))
Currently translated at 39.2% (300 of 764 strings)
2026-02-12 17:25:15 +01:00
+2 6c69a5409b Translated using Weblate (Slovak)
Currently translated at 77.5% (69 of 89 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 49.4% (44 of 89 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.2% (758 of 764 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (French)

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (German)

Currently translated at 100.0% (89 of 89 strings)

Added translation using Weblate (Gaelic)

Translated using Weblate (French)

Currently translated at 77.2% (68 of 88 strings)

Translated using Weblate (Turkish)

Currently translated at 34.0% (30 of 88 strings)

Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Nico <n2778370@gmail.com>
Co-authored-by: S.B. MacQueen <macqueen1729@protonmail.com>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Thadah D. Denyse <thadahdenyse@protonmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: delvani <del.cidrak@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2026-02-12 17:25:15 +01:00
Stypox 840664a8d7 Merge pull request #13236 from dustdfg/error_activity_newlines 2026-02-12 13:15:54 +01:00
Yevhen Babiichuk (DustDFG) 54090ca7b6 ErrorActivity use normal \n instead of \\n in resource strings 2026-02-11 16:54:35 +02:00
Aayush Gupta 8d45b6b8c9 Merge pull request #13225 from dustdfg/error_activity_kotlin
Convert ErrorActivity to kotlin
2026-02-11 21:50:37 +08:00
Aayush Gupta c3dbed54e5 ErrorActivity: Kotlin-fy buildMarkdown method
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-11 21:40:02 +08:00
Aayush Gupta 8968aab578 ErrorActivity: Catch exceptions not throwables
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-11 21:40:02 +08:00
Aayush Gupta d7a4435e94 ErrorActivity: Use better variable names and encapsulation
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-11 21:39:56 +08:00
Yevhen Babiichuk (DustDFG)andAayush Gupta 4a7eaed3a7 ErrorActivity convert to kotlin
Co-authored-by: Aayush Gupta <aayushgupta219@protonmail.com>
2026-02-11 18:31:12 +08:00
Yevhen Babiichuk (DustDFG) 869a3cea9b ErrorActivity small refactor 2026-02-11 18:31:12 +08:00
Yevhen Babiichuk (DustDFG) 3a0a3a42af Misc loop/stream based player refactors 2026-02-10 11:26:23 +02:00
Yevhen Babiichuk (DustDFG) 59e5018c2d player/helper/PlayerHelper#getTimeString replace ints with longs
Duration data in the player code incosnistently typed. Half code
uses ints and half uses longs. Recieve longs in this function to
allow both halfs of player code just use the function without
nasty long to int downcasting warnings/errors in code
2026-02-10 11:24:25 +02:00
Aayush Gupta e6e0be772a Merge pull request #13026 from dustdfg/kotlin_merged2
Convert a bunch of files to kotlin
2026-02-10 16:40:51 +08:00
Aayush Gupta 224a5d0cb9 Minor improvements
- Use early return in case of nulls
- Use better variable names
- Remove non-required newlines, imports and add missing ones

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-10 16:33:43 +08:00
Yevhen Babiichuk (DustDFG) c6fc94e7bd Convert newpipe/settings/export/PreferencesObjectInputStream to kotlin 2026-02-10 10:09:18 +02:00
Yevhen Babiichuk (DustDFG) 1eedfd7eee Convert /newpipe/util/StreamTypeUtil to kotlin 2026-02-10 10:09:18 +02:00
Yevhen Babiichuk (DustDFG) 2c7654a579 Covert newpipe/util/DependentPreferenceHelper to kotlin 2026-02-10 10:09:18 +02:00
Yevhen Babiichuk (DustDFG) 09a746dd6a Convert newpipe/util/ServiceHelper to kotlin 2026-02-10 10:09:18 +02:00
Yevhen Babiichuk (DustDFG) d665a4f016 Convert newpipe/util/NewPipeTextViewHelper to kotlin 2026-02-10 10:09:18 +02:00
Yevhen Babiichuk (DustDFG) 6cf932b2a7 Convert newpipe/ExitActivity to kotlin 2026-02-10 10:09:18 +02:00
Yevhen Babiichuk (DustDFG) 48467669b6 Convert newpipe/util/PeertubeHelper to kotlin 2026-02-10 09:51:14 +02:00
Yevhen Babiichuk (DustDFG) 780e6a4848 Convert newpipe/util/PlayButtonHelper to kotlin 2026-02-10 09:51:13 +02:00
Aayush Gupta 13186c0b15 Merge pull request #13224 from dustdfg/kotlin_idiomatic
Kotlin misc idiomatic refactor
2026-02-10 15:43:15 +08:00
Yevhen Babiichuk (DustDFG) edfdbe805f Uitilize kotlin elvis operator 2026-02-10 09:23:17 +02:00
Yevhen Babiichuk (DustDFG) 451409fc3b SharedPreferences.edit applies changes automatically 2026-02-10 09:23:17 +02:00
Yevhen Babiichuk (DustDFG) 289d22eed7 Utilize kotlins ifEmpty 2026-02-10 09:23:17 +02:00
Yevhen Babiichuk (DustDFG) 21f446a78e Refactor settings/preferencesearch/PreferenceSearchItem#allRelevantSearchFields
It doesn't need to return mutable list
2026-02-10 09:23:17 +02:00
Stypox 6214ae33f3 Merge pull request #13219 from dustdfg/kotlin_check_is_not_if
Correct inverted check (error fix on dev branch)
2026-02-09 14:51:06 +01:00
Yevhen Babiichuk (DustDFG) 37cef825a2 Correct inverted check
If performs action when value is true but check when false
Fix for d6be966db3
2026-02-09 14:31:56 +02:00
Aayush Gupta dab8e056e9 Merge pull request #13137 from dustdfg/info_item_builder_dead
Remove dead code from info_list/InfoItemBuilder
2026-02-08 22:28:19 +08:00
Aayush Gupta 020dbdc82a Merge pull request #13131 from dustdfg/tabs_json_helper_refactor
TabsJsonHelper refactor
2026-02-08 22:06:25 +08:00
Aayush Gupta 5d7934249f Merge pull request #13028 from dustdfg/idiomatic_kotlin_exceptions
Replace Illegal{State,Argument} exceptions with more idiomatic kotlin code
2026-02-08 22:05:46 +08:00
Yevhen Babiichuk (DustDFG) d6be966db3 Replace Illegal{State,Argument} exceptions with more idiomatic kotlin code 2026-02-08 21:59:10 +08:00
tobigr e358867da8 Use dedicated constants for unknown image dimensions in ImageStrategy 2026-02-07 15:34:48 +01:00
TobiGr e6daf45c83 [DescriptionFragment] Fix thumbnail size: width x height
height x width was used before which is an uncommon order.
2026-02-07 12:36:09 +01:00
Stypox c2b698491b Merge pull request #13202 from TobiGr/update-refactor
Merge dev into refactor
2026-02-07 11:58:12 +01:00
Yevhen Babiichuk (DustDFG) 5e1a1989be VideoDetailFragment: Forcefully catch click events uncaught by children
Because otherwise they will be caught by underlying view and
"click through" will happen. Workaround for #5600
2026-02-06 19:03:01 +02:00
tobigr aa8aa6a241 Update hilt for compatibility with newer kotlin version 2026-02-06 12:00:02 +01:00
tobigr a430517189 Re-add PlayQueueItem.RECOVERY_UNSET
This was removed in #12347 / 31f8dd05a7
2026-02-06 11:51:15 +01:00
tobigr e006328bb1 Merge branch 'dev' into refactor 2026-02-06 11:48:59 +01:00
Tobi 56a043669a Merge pull request #13161 from jpds/outdated-subscription-shuffle
FeedLoadManager: Shuffle the order outdated subscriptions are updated in
2026-02-06 02:13:56 -08:00
tobigr 85abc58158 Merge branch 'master' into dev 2026-02-05 23:01:30 +01:00
Tobi 8578bd9f95 Merge pull request #13200 from TeamNewPipe/release-0.28.3
Release 0.28.3
2026-02-05 13:27:30 -08:00
tobigr 13577f5421 NewPipe 0.28.3 (1008) 2026-02-05 18:06:15 +01:00
tobigr dc8a62914a Add changelog for NewPipe 0.28.3 2026-02-05 18:06:15 +01:00
tobigr 74d5a8b173 Update extractor to version 0.25.2 2026-02-05 18:06:15 +01:00
+66 d2a8955964 Translated using Weblate (Basque)
Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 97.5% (745 of 764 strings)

Translated using Weblate (Kabyle)

Currently translated at 27.7% (212 of 764 strings)

Translated using Weblate (German)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.3% (759 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 97.5% (745 of 764 strings)

Translated using Weblate (French)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Icelandic)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Malay)

Currently translated at 58.7% (449 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (French)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (764 of 764 strings)

Merge branch 'origin/dev' into Weblate.

Translated using Weblate (Somali)

Currently translated at 71.5% (547 of 764 strings)

Translated using Weblate (Somali)

Currently translated at 71.5% (547 of 764 strings)

Translated using Weblate (Danish)

Currently translated at 98.5% (753 of 764 strings)

Translated using Weblate (Danish)

Currently translated at 98.5% (753 of 764 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 80.4% (615 of 764 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 73.5% (562 of 764 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 73.5% (562 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Georgian)

Currently translated at 92.5% (707 of 764 strings)

Translated using Weblate (Kurdish)

Currently translated at 60.9% (466 of 764 strings)

Translated using Weblate (Catalan)

Currently translated at 96.0% (734 of 764 strings)

Translated using Weblate (Catalan)

Currently translated at 96.0% (734 of 764 strings)

Translated using Weblate (Greek)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (French)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (French)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Japanese)

Currently translated at 95.5% (730 of 764 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.8% (755 of 764 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.3% (759 of 764 strings)

Translated using Weblate (Odia)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.1% (238 of 764 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.1% (238 of 764 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Tamazight (Central Atlas))

Currently translated at 18.5% (142 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 93.4% (714 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Lithuanian)

Currently translated at 98.9% (756 of 764 strings)

Translated using Weblate (Lithuanian)

Currently translated at 98.9% (756 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Czech)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Asturian)

Currently translated at 60.6% (463 of 764 strings)

Translated using Weblate (Asturian)

Currently translated at 60.6% (463 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Sardinian)

Currently translated at 97.9% (748 of 764 strings)

Translated using Weblate (Sardinian)

Currently translated at 97.9% (748 of 764 strings)

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

Currently translated at 95.1% (727 of 764 strings)

Translated using Weblate (Albanian)

Currently translated at 76.3% (583 of 764 strings)

Translated using Weblate (Albanian)

Currently translated at 76.3% (583 of 764 strings)

Translated using Weblate (Nepali)

Currently translated at 56.2% (430 of 764 strings)

Translated using Weblate (Nepali)

Currently translated at 56.2% (430 of 764 strings)

Translated using Weblate (Finnish)

Currently translated at 94.2% (720 of 764 strings)

Translated using Weblate (Finnish)

Currently translated at 94.2% (720 of 764 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 59.2% (453 of 764 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 59.2% (453 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 62.5% (478 of 764 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 62.5% (478 of 764 strings)

Translated using Weblate (Icelandic)

Currently translated at 98.1% (750 of 764 strings)

Translated using Weblate (N’Ko)

Currently translated at 85.7% (655 of 764 strings)

Translated using Weblate (Croatian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bengali)

Currently translated at 74.2% (567 of 764 strings)

Translated using Weblate (German)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (German)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Romanian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Romanian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (762 of 764 strings)

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

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Arabic)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Galician)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Galician)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Malayalam)

Currently translated at 73.0% (558 of 764 strings)

Translated using Weblate (Malayalam)

Currently translated at 73.0% (558 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Persian)

Currently translated at 95.6% (731 of 764 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 89.9% (687 of 764 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 89.9% (687 of 764 strings)

Translated using Weblate (Polish)

Currently translated at 99.7% (762 of 764 strings)

Co-authored-by: 2-Seol <2Seol.0117@gmail.com>
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: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andreas Westrell <andreas.westrell@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Anxhelo Lushka <github@lushka.al>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: Aurelian Ciocîltan <aurelianciociltan@gmail.com>
Co-authored-by: Bakary Kaba <mbkaba@live.fr>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Comrade KVRONV <naruto.tkntcube31@gmail.com>
Co-authored-by: D D <keptawesome@gmail.com>
Co-authored-by: David Rebolo Magariños <davidre345@hotmail.com>
Co-authored-by: Deleted User <jsdelvani@users.noreply.hosted.weblate.org>
Co-authored-by: Deleted User <noreply+10474@weblate.org>
Co-authored-by: Deleted User <noreply+19964@weblate.org>
Co-authored-by: Dormin <nkzo3d+1uozh1scczh8c@sharklasers.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Enol P. <enolp@softastur.org>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Francesco Saltori <francescosaltori@gmail.com>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Guillem <guillemglez@gmail.com>
Co-authored-by: Hakim Oubouali <hakim.oubouali.skr@gmail.com>
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: Ishwor Ghimire <ghimire.esor09@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Kiss Attila <gaxeco4855@pro5g.com>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Mathias Hamza Vedsted-Mirza <mathiashamzamirza@outlook.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mukhamadjonov <abdukodir.9507@gmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nadir Nour <dudethatwascool2@gmail.com>
Co-authored-by: Nikoloz <nukushatugushi@gmail.com>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Oymate <dhruboadittya96@gmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: SecularSteve <fairfull.playing@gmail.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Sérgio Marques <smarquespt@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Thadah D. Denyse <thadahdenyse@protonmail.com>
Co-authored-by: Theophine Savio Theodore <theophinetheodore@gmail.com>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: Vasilis K. <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Ville Rantanen <v.r@iki.fi>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: cehnemdark <cehennem1001@gmail.com>
Co-authored-by: gymka <gymka@archlinux.lt>
Co-authored-by: justcontributor <kty5663@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: winqooq <winqooq@gmail.com>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Слободан Симић(Slobodan Simić) <slsimic@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2026-02-05 18:06:15 +01:00
+66 955844b3e1 Translated using Weblate (Basque)
Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 97.5% (745 of 764 strings)

Translated using Weblate (Kabyle)

Currently translated at 27.7% (212 of 764 strings)

Translated using Weblate (German)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.3% (759 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 97.5% (745 of 764 strings)

Translated using Weblate (French)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Icelandic)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Malay)

Currently translated at 58.7% (449 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (French)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (764 of 764 strings)

Merge branch 'origin/dev' into Weblate.

Translated using Weblate (Somali)

Currently translated at 71.5% (547 of 764 strings)

Translated using Weblate (Somali)

Currently translated at 71.5% (547 of 764 strings)

Translated using Weblate (Danish)

Currently translated at 98.5% (753 of 764 strings)

Translated using Weblate (Danish)

Currently translated at 98.5% (753 of 764 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 80.4% (615 of 764 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 73.5% (562 of 764 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 73.5% (562 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Georgian)

Currently translated at 92.5% (707 of 764 strings)

Translated using Weblate (Kurdish)

Currently translated at 60.9% (466 of 764 strings)

Translated using Weblate (Catalan)

Currently translated at 96.0% (734 of 764 strings)

Translated using Weblate (Catalan)

Currently translated at 96.0% (734 of 764 strings)

Translated using Weblate (Greek)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (French)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (French)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Japanese)

Currently translated at 95.5% (730 of 764 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.8% (755 of 764 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.3% (759 of 764 strings)

Translated using Weblate (Odia)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.1% (238 of 764 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.1% (238 of 764 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Tamazight (Central Atlas))

Currently translated at 18.5% (142 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 93.4% (714 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Lithuanian)

Currently translated at 98.9% (756 of 764 strings)

Translated using Weblate (Lithuanian)

Currently translated at 98.9% (756 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Czech)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Asturian)

Currently translated at 60.6% (463 of 764 strings)

Translated using Weblate (Asturian)

Currently translated at 60.6% (463 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Sardinian)

Currently translated at 97.9% (748 of 764 strings)

Translated using Weblate (Sardinian)

Currently translated at 97.9% (748 of 764 strings)

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

Currently translated at 95.1% (727 of 764 strings)

Translated using Weblate (Albanian)

Currently translated at 76.3% (583 of 764 strings)

Translated using Weblate (Albanian)

Currently translated at 76.3% (583 of 764 strings)

Translated using Weblate (Nepali)

Currently translated at 56.2% (430 of 764 strings)

Translated using Weblate (Nepali)

Currently translated at 56.2% (430 of 764 strings)

Translated using Weblate (Finnish)

Currently translated at 94.2% (720 of 764 strings)

Translated using Weblate (Finnish)

Currently translated at 94.2% (720 of 764 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 59.2% (453 of 764 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 59.2% (453 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 62.5% (478 of 764 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 62.5% (478 of 764 strings)

Translated using Weblate (Icelandic)

Currently translated at 98.1% (750 of 764 strings)

Translated using Weblate (N’Ko)

Currently translated at 85.7% (655 of 764 strings)

Translated using Weblate (Croatian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bengali)

Currently translated at 74.2% (567 of 764 strings)

Translated using Weblate (German)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (German)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Romanian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Romanian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (762 of 764 strings)

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

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Arabic)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Galician)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Galician)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Malayalam)

Currently translated at 73.0% (558 of 764 strings)

Translated using Weblate (Malayalam)

Currently translated at 73.0% (558 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Persian)

Currently translated at 95.6% (731 of 764 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 89.9% (687 of 764 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 89.9% (687 of 764 strings)

Translated using Weblate (Polish)

Currently translated at 99.7% (762 of 764 strings)

Co-authored-by: 2-Seol <2Seol.0117@gmail.com>
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: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andreas Westrell <andreas.westrell@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Anxhelo Lushka <github@lushka.al>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: Aurelian Ciocîltan <aurelianciociltan@gmail.com>
Co-authored-by: Bakary Kaba <mbkaba@live.fr>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Comrade KVRONV <naruto.tkntcube31@gmail.com>
Co-authored-by: D D <keptawesome@gmail.com>
Co-authored-by: David Rebolo Magariños <davidre345@hotmail.com>
Co-authored-by: Deleted User <jsdelvani@users.noreply.hosted.weblate.org>
Co-authored-by: Deleted User <noreply+10474@weblate.org>
Co-authored-by: Deleted User <noreply+19964@weblate.org>
Co-authored-by: Dormin <nkzo3d+1uozh1scczh8c@sharklasers.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Enol P. <enolp@softastur.org>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Francesco Saltori <francescosaltori@gmail.com>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Guillem <guillemglez@gmail.com>
Co-authored-by: Hakim Oubouali <hakim.oubouali.skr@gmail.com>
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: Ishwor Ghimire <ghimire.esor09@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Kiss Attila <gaxeco4855@pro5g.com>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Mathias Hamza Vedsted-Mirza <mathiashamzamirza@outlook.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mukhamadjonov <abdukodir.9507@gmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nadir Nour <dudethatwascool2@gmail.com>
Co-authored-by: Nikoloz <nukushatugushi@gmail.com>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Oymate <dhruboadittya96@gmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: SecularSteve <fairfull.playing@gmail.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Sérgio Marques <smarquespt@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Thadah D. Denyse <thadahdenyse@protonmail.com>
Co-authored-by: Theophine Savio Theodore <theophinetheodore@gmail.com>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: Vasilis K. <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: Ville Rantanen <v.r@iki.fi>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: cehnemdark <cehennem1001@gmail.com>
Co-authored-by: gymka <gymka@archlinux.lt>
Co-authored-by: justcontributor <kty5663@gmail.com>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: winqooq <winqooq@gmail.com>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Слободан Симић(Slobodan Simić) <slsimic@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
2026-02-05 17:41:09 +01:00
Tobi 5112acf008 Merge pull request #13197 from TeamNewPipe/backport-13195-to-release-0.28.3
[Backport release-0.28.3] Fix 13139 resume playback
2026-02-05 08:27:48 -08:00
AbsurdlyLongUsername fcb77fed93 Fix additional setRecovery from rebase errors
(cherry picked from commit 1554f77762)
2026-02-05 14:53:19 +00:00
AbsurdlyLongUsername 2027b6dbc7 Add conditional guard to prevent useVideoAndSubtitles overwriting recovery position that was set in Player.handleIntent for RESUME_PLAYBACK when resuming playback
(cherry picked from commit 118def08b4)
2026-02-05 14:53:19 +00:00
AbsurdlyLongUsername 90d5d5f4de Update useVideoAndSubtitles rename in comment
(cherry picked from commit 725cb70cbd)
2026-02-05 14:53:19 +00:00
AbsurdlyLongUsername 045e91df1c Small refactor getPlayQueueFromCache
(cherry picked from commit 5525d206dc)
2026-02-05 14:53:19 +00:00
Tobi e74907561e Merge pull request #13195 from absurdlylongusername/fix-13139-resume-playback
Fix 13139 resume playback
2026-02-05 06:49:57 -08:00
AbsurdlyLongUsername 1554f77762 Fix additional setRecovery from rebase errors 2026-02-05 11:17:30 +00:00
AbsurdlyLongUsername 118def08b4 Add conditional guard to prevent useVideoAndSubtitles overwriting recovery position that was set in Player.handleIntent for RESUME_PLAYBACK when resuming playback 2026-02-05 05:05:41 +00:00
AbsurdlyLongUsername 725cb70cbd Update useVideoAndSubtitles rename in comment 2026-02-05 05:05:40 +00:00
AbsurdlyLongUsername 5525d206dc Small refactor getPlayQueueFromCache 2026-02-05 05:05:40 +00:00
Stypox 83f9646eec Merge pull request #13190 from TeamNewPipe/agp9fixes
Partially revert upgrade to AGP 9.0.0
2026-02-04 19:47:43 +01:00
Aayush Gupta 85d43fe45e proguard: Keep fields of generated proguard files
Inspired from https://github.com/protocolbuffers/protobuf/blob/main/java/lite.md#r8-rule-to-make-production-app-builds-work

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-04 16:22:48 +08:00
Aayush Gupta 8d6e68d6f4 Partially revert upgrade to AGP 9.0.0
Building is broken on ecrypted filesystems

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-04 15:56:44 +08:00
Yevhen Babiichuk (DustDFG) 07fe1e758a Refactor settings/tabs/TabsJsonHelper.java to use java streams 2026-02-02 23:59:01 +02:00
Stypox d859a5edc8 Merge pull request #13138 from Isira-Seneviratne/Merge-dev-to-refactor 2026-02-02 20:20:28 +01:00
Stypox f63ea4aa93 Fix ErrorInfoTest after changes to exception classifications 2026-02-02 19:54:55 +01:00
Stypox abf9a80715 Fix ErrorPanelTest after changes to exception classifications 2026-02-02 19:52:17 +01:00
Isira Seneviratne 60615e6b9e Fix compile errors 2026-02-02 05:48:22 +05:30
Aayush Gupta 15b5cef6c2 Merge pull request #13136 from TeamNewPipe/agp9
Upgrade Android Gradle Plugin to 9.0.0
2026-02-01 17:34:47 +08:00
Jonathan Davies ae60f7d7eb FeedLoadManager: Shuffle the order outdated subscriptions are updated in 2026-01-31 20:47:48 +00:00
Tobi 739b6ae57b Merge pull request #13141 from salmanmkc/upgrade-github-actions-node24-general
Upgrade GitHub Actions to latest versions
2026-01-30 02:31:40 -08:00
Tobi cc33b685a5 Merge pull request #13140 from salmanmkc/upgrade-github-actions-node24
Upgrade GitHub Actions for Node 24 compatibility
2026-01-30 02:31:29 -08:00
Salman Muin Kayser Chishti d051e8ecc8 Upgrade GitHub Actions to latest versions
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-01-30 09:16:42 +00:00
Salman Muin Kayser Chishti 51e62f09ba Upgrade GitHub Actions for Node 24 compatibility
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-01-30 09:16:36 +00:00
Isira Seneviratne 51a749a7f1 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/androidTest/java/org/schabi/newpipe/database/FeedDAOTest.kt
#	app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt
#	app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt
#	app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
#	app/src/main/java/org/schabi/newpipe/ui/components/about/LicenseDialog.kt
#	app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt
#	app/src/main/java/org/schabi/newpipe/ui/components/items/playlist/PlaylistListItem.kt
#	app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamThumbnail.kt
#	app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt
2026-01-29 19:56:59 +05:30
Isira Seneviratne 35315d02ef Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
#	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
#	app/src/main/java/org/schabi/newpipe/player/Player.java
#	app/src/main/res/values-eu/strings.xml
#	gradle/libs.versions.toml
2026-01-29 19:39:21 +05:30
Yevhen Babiichuk (DustDFG) 8a2c47bc12 Remove dead code from info_list/InfoItemBuilder
It no longer really builds any view and used only for stroing click
gesture callbacks. In the same way lik local/LocalItemBuilder does

Last usage of build functions: 2e9a860aaa
2026-01-29 15:01:40 +02:00
Aayush Gupta a7aad63bbb Upgrade Kotlin and KSP
Fixes multiple build errors. Once parcelize is fixed, we should be good
to use built-in Kotlin completely

Ref: https://issuetracker.google.com/issues/478401081

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:59:25 +08:00
Aayush Gupta fd192b4f3f Drop default properties
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta 19e94bd30c Migrate from deprecated srcDir to directories method
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta 7758a27694 Migrate from deprecated android block to ApplicationExtension
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta a3301dcfb1 Enable resValues as build feature
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta d045b27cea Migrate to built-in Kotlin
Ref: https://developer.android.com/build/migrate-to-built-in-kotlin

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:51 +08:00
Aayush Gupta 4f70235ee8 Enable proguard android optimizations
AGP 9.0+ requires enabling optimizations

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:11 +08:00
Aayush Gupta 54f9bcb03e Upgrade AGP to 9.0.0
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:11 +08:00
Tobi 50b9a7b7f6 Merge pull request #13121 from theimpulson/forwardport
Merge dev into refactor
2026-01-28 08:43:36 -08:00
Tobi 4b424bc6bd Fix comment 2026-01-28 16:56:22 +01:00
Stypox addf1e23b3 Add changelogs for v0.28.2 (1007) 2026-01-28 03:28:23 +01:00
Stypox a40d7ff70e Hotfix release v0.28.2 (1007) 2026-01-28 03:28:23 +01:00
+51 d53f7acfa4 Translated using Weblate (Somali)
Currently translated at 71.5% (547 of 764 strings)

Translated using Weblate (Somali)

Currently translated at 71.5% (547 of 764 strings)

Translated using Weblate (Danish)

Currently translated at 98.5% (753 of 764 strings)

Translated using Weblate (Danish)

Currently translated at 98.5% (753 of 764 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 80.4% (615 of 764 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 73.5% (562 of 764 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 73.5% (562 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Georgian)

Currently translated at 92.5% (707 of 764 strings)

Translated using Weblate (Kurdish)

Currently translated at 60.9% (466 of 764 strings)

Translated using Weblate (Catalan)

Currently translated at 96.0% (734 of 764 strings)

Translated using Weblate (Catalan)

Currently translated at 96.0% (734 of 764 strings)

Translated using Weblate (Greek)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (French)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (French)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Japanese)

Currently translated at 95.5% (730 of 764 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.8% (755 of 764 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.3% (759 of 764 strings)

Translated using Weblate (Odia)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 98.4% (752 of 764 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.1% (238 of 764 strings)

Translated using Weblate (Interlingua)

Currently translated at 31.1% (238 of 764 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Tamazight (Central Atlas))

Currently translated at 18.5% (142 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 93.4% (714 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Lithuanian)

Currently translated at 98.9% (756 of 764 strings)

Translated using Weblate (Lithuanian)

Currently translated at 98.9% (756 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Czech)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Asturian)

Currently translated at 60.6% (463 of 764 strings)

Translated using Weblate (Asturian)

Currently translated at 60.6% (463 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Sardinian)

Currently translated at 97.9% (748 of 764 strings)

Translated using Weblate (Sardinian)

Currently translated at 97.9% (748 of 764 strings)

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

Currently translated at 95.1% (727 of 764 strings)

Translated using Weblate (Albanian)

Currently translated at 76.3% (583 of 764 strings)

Translated using Weblate (Albanian)

Currently translated at 76.3% (583 of 764 strings)

Translated using Weblate (Nepali)

Currently translated at 56.2% (430 of 764 strings)

Translated using Weblate (Nepali)

Currently translated at 56.2% (430 of 764 strings)

Translated using Weblate (Finnish)

Currently translated at 94.2% (720 of 764 strings)

Translated using Weblate (Finnish)

Currently translated at 94.2% (720 of 764 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 59.2% (453 of 764 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 59.2% (453 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 62.5% (478 of 764 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 62.5% (478 of 764 strings)

Translated using Weblate (Icelandic)

Currently translated at 98.1% (750 of 764 strings)

Translated using Weblate (N’Ko)

Currently translated at 85.7% (655 of 764 strings)

Translated using Weblate (Croatian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bengali)

Currently translated at 74.2% (567 of 764 strings)

Translated using Weblate (German)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (German)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Romanian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Romanian)

Currently translated at 97.6% (746 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (762 of 764 strings)

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

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Arabic)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Galician)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Galician)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Malayalam)

Currently translated at 73.0% (558 of 764 strings)

Translated using Weblate (Malayalam)

Currently translated at 73.0% (558 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Persian)

Currently translated at 95.6% (731 of 764 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 89.9% (687 of 764 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 89.9% (687 of 764 strings)

Translated using Weblate (Polish)

Currently translated at 99.7% (762 of 764 strings)

Co-authored-by: 2-Seol <2Seol.0117@gmail.com>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andreas Westrell <andreas.westrell@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Anxhelo Lushka <github@lushka.al>
Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: Aurelian Ciocîltan <aurelianciociltan@gmail.com>
Co-authored-by: Bakary Kaba <mbkaba@live.fr>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: D D <keptawesome@gmail.com>
Co-authored-by: David Rebolo Magariños <davidre345@hotmail.com>
Co-authored-by: Deleted User <jsdelvani@users.noreply.hosted.weblate.org>
Co-authored-by: Deleted User <noreply+10474@weblate.org>
Co-authored-by: Deleted User <noreply+19964@weblate.org>
Co-authored-by: Dormin <nkzo3d+1uozh1scczh8c@sharklasers.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Enol P. <enolp@softastur.org>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Francesco Saltori <francescosaltori@gmail.com>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Guillem <guillemglez@gmail.com>
Co-authored-by: Hakim Oubouali <hakim.oubouali.skr@gmail.com>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Igor Nedoboy <i.nedoboy@mail.ru>
Co-authored-by: Ishwor Ghimire <ghimire.esor09@gmail.com>
Co-authored-by: Kiss Attila <gaxeco4855@pro5g.com>
Co-authored-by: Ldm Public <ldmpub@gmail.com>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Mathias Hamza Vedsted-Mirza <mathiashamzamirza@outlook.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mukhamadjonov <abdukodir.9507@gmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nadir Nour <dudethatwascool2@gmail.com>
Co-authored-by: Nikoloz <nukushatugushi@gmail.com>
Co-authored-by: Olivia Ng <uloo592@gmail.com>
Co-authored-by: Oymate <dhruboadittya96@gmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: SecularSteve <fairfull.playing@gmail.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Sérgio Marques <smarquespt@gmail.com>
Co-authored-by: Theophine Savio Theodore <theophinetheodore@gmail.com>
Co-authored-by: Ville Rantanen <v.r@iki.fi>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: cehnemdark <cehennem1001@gmail.com>
Co-authored-by: gymka <gymka@archlinux.lt>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Co-authored-by: winqooq <winqooq@gmail.com>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: Слободан Симић(Slobodan Simić) <slsimic@gmail.com>
2026-01-28 03:28:23 +01:00
+4 ee52b08546 Translated using Weblate (Basque)
Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 20.6% (18 of 87 strings)

Translated using Weblate (Latvian)

Currently translated at 96.9% (741 of 764 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.6% (761 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Thai)

Currently translated at 37.3% (285 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (French)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 33.3% (29 of 87 strings)

Translated using Weblate (Spanish)

Currently translated at 74.7% (65 of 87 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Lombard)

Currently translated at 4.9% (38 of 764 strings)

Translated using Weblate (Lombard)

Currently translated at 2.2% (2 of 87 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Arabic)

Currently translated at 97.7% (85 of 87 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 33.3% (29 of 87 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 95.8% (732 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 95.8% (732 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 95.2% (728 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 95.6% (731 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 74.7% (65 of 87 strings)

Added translation using Weblate (Yiddish)

Co-authored-by: AudricV <AudricV@users.noreply.hosted.weblate.org>
Co-authored-by: CJ Montero <cristlad@proton.me>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Hasan Kara <hasanbeytullahkara@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igi 5216 <dr.siantonov@gmail.com>
Co-authored-by: Mohammed al-Qubati <mhraqeeb@gmail.com>
Co-authored-by: Nicolás Pérez <ccnicolasperez@gmail.com>
Co-authored-by: TXRdev Archive <lckphanaf9999@gmail.com>
Co-authored-by: Thadah D. Denyse <thadahdenyse@protonmail.com>
Co-authored-by: ThaiWithNoBraincell <altofskgd@gmail.com>
Co-authored-by: What's news <fishelschonfeld@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/eu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lmo/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translation: NewPipe/Metadata
2026-01-28 03:03:51 +01:00
Stypox 864725bf0f Merge pull request #12601 from AudricV/live-prefer-dash-and-fetch-bg-audio-only 2026-01-28 02:41:09 +01:00
Stypox c2723096ab Avoid rebuilding BackgroundPlayerUi if already in place 2026-01-28 02:39:56 +01:00
Stypox eb7351c858 Remove file committed by accident 2026-01-28 02:20:58 +01:00
Stypox 216867c597 Address review comments 2026-01-28 02:20:33 +01:00
AudricV 1d8ea0181f Disable fetching video and text tracks in background player
This reduces data usage for manifest sources with demuxed audio and
video, such as livestreams, for non-HLS sources only due to an
ExoPlayer bug.
2026-01-28 02:13:14 +01:00
AudricV 4648cac9c6 Allow changing video and text tracks state without stream info
This allows disabling these track types when stream info has been not
loaded while the ExoPlayer instance is.

It is now possible to do so with the background player, in order to
disable fetching video and text tracks for manifest sources,
especially used for livestreams.

Also set the recovery first before reloading play queue manager in the
useVideoAndSubtitles method of the Player class.
2026-01-28 02:13:14 +01:00
AudricV 0578e7fde0 Rename useVideoSource to useVideoAndSubtitles in Player
As both subtitles and video tracks are disabled in this method, the
goal of this rename is to highlight disabling/enabled subtitles.
2026-01-28 02:13:14 +01:00
AudricV c670ad80ee Use DASH first instead of HLS and YouTube's DASH parser for lives 2026-01-28 02:13:14 +01:00
AudricV 077f34c922 Add a YouTube DASH manifest parser to make live DASH manifests usable
This is a hacky solution, a better one should be investigated and used.
2026-01-28 02:13:14 +01:00
Stypox 635b306de0 Merge pull request #13134 from TeamNewPipe/revert-12781-feat/similar-youtube-client-screen-rotation 2026-01-28 02:12:35 +01:00
Stypox 11af6a2902 Merge pull request #13129 from AudricV/npe_update 2026-01-28 02:08:26 +01:00
Stypox 7e3657831c Merge pull request #13133 from Stypox/missing-report-button 2026-01-28 02:05:16 +01:00
Stypox c0613b5e54 Merge pull request #13132 from Stypox/regression-detailfragment-flickers 2026-01-28 02:04:15 +01:00
AudricV bffee48bcb Update NewPipeExtractor 2026-01-28 02:01:45 +01:00
Stypox 49e95d95a1 Revert "Remember and restore orientation on fullscreen exit" 2026-01-28 01:44:39 +01:00
Stypox dc160da034 Allow reporting ContentNotAvailableException 2026-01-28 00:14:21 +01:00
Stypox 5155b24ed6 Partial revert: fix VideoDetailFragment flickering
This partially reverts commit 92a07a3445, which was needed to fix ghost notifications. There I broke the "cycle" causing the useless notifications to popup in 2 different places (see points 3 and 4 of the commit description).
However, breaking the cycle in point 4 ("`PlayerHolder::tryBindIfNeeded()` is now used to passively try to bind, instead of `PlayerHolder::startService()`" was not correct, for the following reason.
I assumed that `ACTION_PLAYER_STARTED` was used for notifying that the player was instantiated anew, while it actually is used to notify that something is now ready for use: it could be the player, but it could also just be that the bottom sheet view was just added and thus the VideoDetailFragment needs to start the player.
Therefore, when handling `ACTION_PLAYER_STARTED` it is correct to start the player service and not just try to bind to it.
The other point in which I broke the cycle (point 3) should still prevent ghost notifications, although I could not test.
2026-01-27 23:44:25 +01:00
Stypox 4e0d542994 Merge pull request #12929 from TeamNewPipe/fix/playlist-remove-watched 2026-01-27 22:07:10 +01:00
Stypox 817fccb7a3 Swap && to reduce computation 2026-01-27 22:02:01 +01:00
tobigr 8d9af62736 Extract dialog creation into its own method 2026-01-27 22:02:01 +01:00
tobigr 8f32532acd "Removed watched videos" changed to "Remove watched streams"
Playlists can also contain audio-only items. Therefore, the term "stream" is used.
2026-01-27 22:01:59 +01:00
tobigr 0611d650e7 Use checkbox to remove partially watched videos 2026-01-27 22:00:15 +01:00
tobigr d1f6337c6e Fix removing unwatched streams from playlist when using "remove watched"
The bug is caused by a wanted but forgotten inconsistency in the database.
A stream can be listed in the watch history (StreamHistoryEntity) while having no corresponding playback state (StreamStateEntity) containing the matching playback position. This is caused by the fact that NewPipe does not consider a watch time of less than five seconds to be worthy to be put into the StreamStateEntity because the video was most likely played by error. Those videos are, however, counted and stored in the watch history.
2026-01-27 22:00:15 +01:00
Stypox d7dffb7a90 Add deprecation to LocalItemListAdapter.showFooter(true) 2026-01-27 21:35:21 +01:00
Stypox d0f32b3842 Merge pull request #12996 from whistlingwoods/reapply-local-list-header-fix-by-j-haldane 2026-01-27 21:32:29 +01:00
Aayush Gupta 2376a83e0c Fix all ktlint violations and merge issues
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-26 23:08:33 +08:00
Aayush Gupta 394a7f68cd Merge branch 'dev' into refactor 2026-01-26 22:53:17 +08:00
AudricV 25b133946d [YouTube] Adapt YoutubeHttpDataSource to TVHTML5 client removal in NPE 2026-01-23 22:04:05 +01:00
Aayush Gupta 460cadf694 Merge pull request #13081 from TeamNewPipe/ktlint
Fix and enable multiple ktlint rules
2026-01-24 00:41:17 +08:00
Aayush Gupta 2fd2822053 ktlint: Drop non-required backing-property-naming supression
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:32:03 +08:00
Aayush Gupta 8ac8424cab ktlint: Drop unused chain-method-continuation violation supression
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:32:03 +08:00
Aayush Gupta 6b2a1cedef ktlint: Fix function-literal violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:32:03 +08:00
Aayush Gupta 8c5f13ab5c ktlint: Fix function-signature violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:32:03 +08:00
Aayush Gupta eb97366e41 ktlint: Drop non-required violation supression
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:32:03 +08:00
Aayush Gupta 81ddd5a115 ktlint: Fix no-empty-first-line-in-method-block violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:32:02 +08:00
Aayush Gupta 7d5647b0ba ktlint: Fix standard_argument-list-wrapping violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta 9b7874ff51 ktlint: Fix blank-line-between-when-conditions violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta ff2390b144 ktlint: Fix enum wrapping violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta 81fd089a32 ktlint: Fix block comment violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta 1466dd17b1 ktlint: Fix indentation violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta 555cd3acb7 ktlint: Fix standard_kdoc violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta ea105e9026 ktlint: Drop non-required violation supressions again
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta e86846ba6a ktlint: Fix spacing-between-declarations-with-annotations violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta dcb2460c81 ktlint: Fix spacing-between-declarations-with-comments violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:44 +08:00
Aayush Gupta 6190db7d2f ktlint: Fix violations regarding statement wrapping
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:43 +08:00
Aayush Gupta c60339fc58 ktlint: Drop non-required violation supression
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:14 +08:00
Aayush Gupta 80a543e7ab ktlint: Fix violation related to comment placement
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:14 +08:00
Aayush Gupta c76d14dfd4 ktlint: Drop unused trailing commas
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:14 +08:00
Aayush Gupta d6f3dee9f4 ktlint: Drop unused semi-colons
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:14 +08:00
Aayush Gupta 247cbf3d6f ktlint: Drop non-required violation supressions
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:14 +08:00
Aayush Gupta 0641c19388 ktlint: Fix multi-line if-else violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:14 +08:00
Aayush Gupta 2c808b0e86 ktlint: Fix ktlint_standard_annotation violations
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:14 +08:00
Aayush Gupta f23d8eff57 ktlint: Order imports in lexicographic order
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:31:13 +08:00
Aayush Gupta ed4b77b5aa ktlint: Set codestyle and ignore function naming for Composable
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-24 00:30:04 +08:00
Aayush Gupta 675dbd35d0 Merge pull request #13036 from dustdfg/kotlin_misc_refactor
Misc small kotlin based refactors
2026-01-23 14:51:15 +08:00
Aayush Gupta eec3ac166a Merge pull request #12936 from TobiGr/uddate-extractor
Update NewPipe Extractor, checkstyle and sonarqube to latest versions
2026-01-22 17:53:14 +08:00
Aayush Gupta bcb7469d30 Run checkstyle with JDK 21
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-22 17:41:08 +08:00
Aayush Gupta 40d7dcf3d3 Update Gradle to 9.3.0
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-22 17:41:08 +08:00
Aayush Gupta 16415d13ed Update more dependencies to latest stable release
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-22 17:41:08 +08:00
tobigr 3ab414252f Update NewPipe Extractor, checkstyle and sonarqube to latest versions
Add note that new webkit versions require a minSdk >= 23
2026-01-22 17:41:08 +08:00
Aayush Gupta 61c25d4589 Merge pull request #13056 from HatakeKakashri/bump_min_sdk_23
Bump minimum SDK version to API 23
2026-01-21 11:32:46 +08:00
Aayush Gupta b84395dea0 Remove non-required API M version checks
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-19 14:39:33 +08:00
Aayush Gupta 7d95ec71aa Merge pull request #13019 from dustdfg/orphaned_again
Remove file orphaned for long time
2026-01-19 14:01:48 +08:00
whistlingwoods cea5dd474b Merge branch dev into reapply-local-list-header-fix-by-j-haldane 2026-01-15 19:24:32 +05:30
Tobi 4ef5aa7b20 Merge pull request #13055 from dustdfg/playlist_add_empty
Fixed a bug that prevented the display of multiple empty playlists
2026-01-15 04:18:29 -08:00
Yevhen Babiichuk (DustDFG) 402f43e895 Fixed a bug that prevented the display of multiple empty playlists
Turned out this bug was already fixed two years ago but unfortunately
it made its way back again. It was solved in #9642 but back then another
method was used for querying playlists from db (for add playlist dialog) then in
ef4a6238c8 was introduced another method
which had the same problem as fixed in #9642 and which eventually replaced
original method in code for querying playlists from db (for add playlist dialog)
2026-01-15 12:27:07 +02:00
Hatake Kakashri 0b43dd2a62 Bump minimum SDK version to API 23
- Updated the `minSdk` in `app/build.gradle.kts`.
- Adjusted the api-level for the test-android min sdk CI workflow
  in `.github/workflows/ci.yml`.
- Simplified the `getOsString()` method in `ErrorActivity.java` by removing
  the conditional check for `Build.VERSION_CODES.M`, as API 23 is now the minimum.
2026-01-13 22:12:50 +05:30
Tobi 2f063a78ba Merge pull request #13052 from RinZ27/fix/ci-shell-injection
ci: fix shell injection in backport workflow
2026-01-13 06:36:01 -08:00
RinCodeForge927 7dc38286c0 ci: fix shell injection in backport workflow 2026-01-13 20:42:10 +07:00
Tobi 77d62deeed Merge pull request #13049 from dustdfg/playlist_bug
Fix playlist item dragging video to only neighbor positions
2026-01-12 12:17:47 -08:00
Yevhen Babiichuk (DustDFG) 914feef5e9 Fix playlist item dragging video to only neighbor positions
Call `saveImmediate` only after used actually dropped item instead
of every time View is updated which happens several times to show
user a feedback where item would be moved
2026-01-12 20:35:14 +02:00
TobiGr 4ed2b9748f Merge branch 'master' into dev 2026-01-11 22:53:11 +01:00
Tobi b8923187f9 Merge pull request #13038 from TobiGr/fix/minify
Fix R8 minify task by adding jsoup classes to proguard file
2026-01-11 13:16:53 -08:00
tobigr 6c5d58bed3 Fix minify step of release build by ignoring two classes
This fixes the following error during the minifyWithR8 task:

> Task :app:minifyReleaseWithR8 FAILED
ERROR: Missing classes detected while running R8. Please add the missing classes or apply additional keep rules that are generated in /home/runner/work/NewPipe/NewPipe/app/build/outputs/mapping/release/missing_rules.txt.
ERROR: R8: Missing class com.google.re2j.Matcher (referenced from: com.google.re2j.Matcher org.jsoup.helper.Re2jRegex$Re2jMatcher.delegate and 3 other contexts)
Missing class com.google.re2j.Pattern (referenced from: com.google.re2j.Pattern org.jsoup.helper.Re2jRegex.re2jPattern and 4 other contexts)
2026-01-11 22:06:32 +01:00
Tobi 98a1e656ce Merge pull request #12926 from TeamNewPipe/release-0-28.1
Release v0.28.1 (1006)
2026-01-11 08:58:05 -08:00
Yevhen Babiichuk (DustDFG) 6ddd4a7e63 VideoDetailFragment: hide relatedItemsLayout in tablet mode after fullscreen
Fixes: #7617
2026-01-11 18:04:26 +02:00
Yevhen Babiichuk (DustDFG) 4f0e62e599 Misc small kotlin based refactors
Java file here because it uses kotlin function which returns non null
2026-01-11 17:10:13 +02:00
56489e5ddd Translated using Weblate (Polish)
Currently translated at 56.3% (49 of 87 strings)

Translated using Weblate (French)

Currently translated at 77.0% (67 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (764 of 764 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translation: NewPipe/Metadata
2026-01-11 15:01:52 +01:00
8e389c49e6 Translated using Weblate (Polish)
Currently translated at 56.3% (49 of 87 strings)

Translated using Weblate (French)

Currently translated at 77.0% (67 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (764 of 764 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/
Translation: NewPipe/Metadata
2026-01-11 15:01:07 +01:00
tobigr 5d7a3f97cd Update extractor to v0.25.0 2026-01-11 14:58:38 +01:00
tobigr 9ba89d418b Remove script for fastlane check 2026-01-09 21:26:03 +01:00
tobigr fc39bff77e Remove script for fastlane check 2026-01-09 21:25:52 +01:00
tobigr 11467a73b8 Fixed length of changelogs 2026-01-09 21:14:38 +01:00
+9 3323868042 Translated using Weblate (Bengali (India))
Currently translated at 39.2% (300 of 764 strings)

Translated using Weblate (Lombard)

Currently translated at 0.3% (3 of 764 strings)

Translated using Weblate (French)

Currently translated at 75.8% (66 of 87 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Tigrinya)

Currently translated at 21.8% (167 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.0% (757 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 77.0% (67 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 3.4% (3 of 87 strings)

Translated using Weblate (Santali)

Currently translated at 1.1% (1 of 87 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (German)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Turkish)

Currently translated at 33.3% (29 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 78.1% (68 of 87 strings)

Translated using Weblate (German)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (French)

Currently translated at 74.7% (65 of 87 strings)

Translated using Weblate (French)

Currently translated at 74.7% (65 of 87 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Slovak)

Currently translated at 74.7% (65 of 87 strings)

Translated using Weblate (Hindi)

Currently translated at 77.0% (67 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 68.9% (60 of 87 strings)

Co-authored-by: 135 <135135@users.noreply.hosted.weblate.org>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: Ding User <dengus@users.noreply.hosted.weblate.org>
Co-authored-by: Dizro <weblate.delirium794@passmail.net>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kuko <kuko7@protonmail.ch>
Co-authored-by: MatthieuPh <matthieu.philippe@protonmail.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: STV <steeven.lombardi@gmail.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sumon Kayal <sumankayalsuman4@gmail.com>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bg/
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/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sat/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translation: NewPipe/Metadata
2026-01-09 21:14:38 +01:00
tobigr 27b8a72f19 Fixed length of changelogs 2026-01-09 21:13:57 +01:00
+9 df92431ee3 Translated using Weblate (Bengali (India))
Currently translated at 39.2% (300 of 764 strings)

Translated using Weblate (Lombard)

Currently translated at 0.3% (3 of 764 strings)

Translated using Weblate (French)

Currently translated at 75.8% (66 of 87 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Tigrinya)

Currently translated at 21.8% (167 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.0% (757 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 77.0% (67 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 3.4% (3 of 87 strings)

Translated using Weblate (Santali)

Currently translated at 1.1% (1 of 87 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (German)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Turkish)

Currently translated at 33.3% (29 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 78.1% (68 of 87 strings)

Translated using Weblate (German)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (French)

Currently translated at 74.7% (65 of 87 strings)

Translated using Weblate (French)

Currently translated at 74.7% (65 of 87 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Slovak)

Currently translated at 74.7% (65 of 87 strings)

Translated using Weblate (Hindi)

Currently translated at 77.0% (67 of 87 strings)

Translated using Weblate (Punjabi)

Currently translated at 68.9% (60 of 87 strings)

Co-authored-by: 135 <135135@users.noreply.hosted.weblate.org>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: Ding User <dengus@users.noreply.hosted.weblate.org>
Co-authored-by: Dizro <weblate.delirium794@passmail.net>
Co-authored-by: Erenay <erenaydev@proton.me>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kuko <kuko7@protonmail.ch>
Co-authored-by: MatthieuPh <matthieu.philippe@protonmail.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Prasanta-Hembram <Prasantahembram720@gmail.com>
Co-authored-by: STV <steeven.lombardi@gmail.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sumon Kayal <sumankayalsuman4@gmail.com>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bg/
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/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sat/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translation: NewPipe/Metadata
2026-01-09 20:56:42 +01:00
Siddhesh Dhainje a83762c4d7 Removed restoreInstanceState and resultServiceIntent condition
(cherry picked from commit 418e34172a)
2026-01-09 11:32:19 -08:00
Siddhesh Dhainje 381b383a43 Used requireArguments instead of getArguments
(cherry picked from commit a3673f8c3b)
2026-01-09 11:32:19 -08:00
Siddhesh Dhainje d5d92e8340 Fix crash on screen rotation while entering SoundCloud import URL
(cherry picked from commit 23b7f21d7c)
2026-01-09 11:32:19 -08:00
Tobi cafb1398cb Merge pull request #12995 from siddhesh-06/siddhesh-06/fix/crash-on-soundcloud-import
Fix crash on screen rotation while entering SoundCloud import URL
2026-01-09 10:44:03 -08:00
tobigr f5245eac91 Merge branch 'dev' into refactor 2026-01-09 14:09:14 +01:00
Justin L dc5c5b6604 Fix download resume corruption when server returns HTTP 200
When resuming a download after interruption, if the server returns
HTTP 200 (full resource) instead of HTTP 206 (partial content), the
code correctly resets mMission.done but fails to reset the 'start'
variable. This causes the subsequent file seek to use a stale offset,
writing new data at incorrect positions.

This bug causes file corruption for large downloads (>5GB) that are
interrupted and resumed, particularly when:
- Switching between WiFi networks
- Server CDN returning different responses
- Connection drops during long downloads

The corruption manifests as duplicate data regions in the file,
which for MP4 downloads results in multiple MOOV atoms and
broken seek functionality.

Fix: Reset start=0 when HTTP 200 is received, ensuring the file
write position correctly restarts from the beginning of the current
resource.

(cherry picked from commit 08008ca6f9)
2026-01-08 19:07:09 -08:00
Tobi 694124814e Merge pull request #13014 from jloutsch/fix/download-resume-corruption
Fix download resume corruption when server returns HTTP 200
2026-01-08 19:05:49 -08:00
Tobi e61bc012d9 Merge pull request #12990 from dustdfg/db_migration_kotlin
Use "factory" method for creating db migrations
2026-01-08 17:23:12 -08:00
TobiandAbsurdlyLongUsername d36a9f01d3 Add workflow to backport PRs to another branch (#12964)
The workflow can be triggered by creating a comment on a merged PR: /backport <TARGET_BRANCH>
The backport can only be triggered by people with write access to the repository.

Co-authored-by: AbsurdlyLongUsername <22662897+absurdlylongusername@users.noreply.github.com>
2026-01-08 17:06:50 -08:00
Siddhesh Dhainje 418e34172a Removed restoreInstanceState and resultServiceIntent condition 2026-01-08 21:31:31 +05:30
Clippy 2704c20fea Merge pull request #13020 from absurdlylongusername/add-note-to-material-component
Add note to upgrade material components once they fix later versions
2026-01-07 23:31:17 +00:00
AbsurdlyLongUsername a7e4afe7f7 Add note to upgrade material components once they fix later versions 2026-01-07 21:49:42 +00:00
Yevhen Babiichuk (DustDFG) 1eeba8daa7 Remove file orphaned for long time
It was not deleted by mistake, delete it now

Was orphaned at 1d69bd48be
2026-01-07 20:33:35 +02:00
Tobi 680235ad51 Merge pull request #12922 from TobiGr/enqueue-ui
[PlayQueue] Fix incorrect UI states of PlayQueue items
2026-01-07 04:01:51 -08:00
AbsurdlyLongUsername 952a1269c1 Revert Google Material Components to 1.11.0 2026-01-07 12:34:02 +01:00
Tobi 6dba8b3c44 Merge pull request #13018 from absurdlylongusername/revert-google-material-components
Revert Google Material Components to 1.11.0
2026-01-07 03:32:30 -08:00
Tobi d4a124a3c4 Merge pull request #12931 from TobiGr/fix/audio-lang
[Player] Use original audio language by default
2026-01-07 03:21:39 -08:00
AbsurdlyLongUsername 20b43b521b Revert Google Material Components to 1.11.0 2026-01-07 07:48:32 +00:00
Tobi 635e6a56c7 Merge pull request #13008 from dustdfg/playlist_normal_drag
Allow dragging boorkmarked playlist in both directions
2026-01-06 09:11:37 -08:00
Justin L 08008ca6f9 Fix download resume corruption when server returns HTTP 200
When resuming a download after interruption, if the server returns
HTTP 200 (full resource) instead of HTTP 206 (partial content), the
code correctly resets mMission.done but fails to reset the 'start'
variable. This causes the subsequent file seek to use a stale offset,
writing new data at incorrect positions.

This bug causes file corruption for large downloads (>5GB) that are
interrupted and resumed, particularly when:
- Switching between WiFi networks
- Server CDN returning different responses
- Connection drops during long downloads

The corruption manifests as duplicate data regions in the file,
which for MP4 downloads results in multiple MOOV atoms and
broken seek functionality.

Fix: Reset start=0 when HTTP 200 is received, ensuring the file
write position correctly restarts from the beginning of the current
resource.
2026-01-06 09:03:44 -05:00
TobiGr 8f071bc8f9 Update extractor to latest version
fixes media.ccc.de kiosks
2026-01-06 12:41:12 +01:00
Yevhen Babiichuk (DustDFG) 02e559e57f Commit all the playlist changes to db immediately
+ some additional minor code cleanup in the file
2026-01-06 12:36:50 +01:00
Yevhen Babiichuk (DustDFG) 9998d99a04 Allow dragging boorkmarked playlist in both directions
Ctrl+C/Ctrl+V from newpipe/local/playlist/LocalPlaylistFragment
2026-01-06 10:01:59 +02:00
Tobi 25ea75f10e Merge pull request #13005 from dustdfg/db_immediate
Commit all the playlist changes to db immediately
2026-01-05 14:03:30 -08:00
Yevhen Babiichuk (DustDFG) 61c0d134d7 Commit all the playlist changes to db immediately
+ some additional minor code cleanup in the file
2026-01-05 22:59:14 +02:00
Siddhesh Dhainje a3673f8c3b Used requireArguments instead of getArguments 2026-01-04 21:40:40 +05:30
Tobi fc66bee429 Merge pull request #13000 from dustdfg/orphaned_history_entry_adapter
Delete long orphaned file
2026-01-04 04:26:03 -08:00
Yevhen Babiichuk (DustDFG) 35eb08baf0 Delete long orphaned file
Was oprhaned at 004c2fa55a
2026-01-04 13:53:43 +02:00
Siddhesh Dhainje 23b7f21d7c Fix crash on screen rotation while entering SoundCloud import URL 2026-01-04 01:06:18 +05:30
whistlingwoodsandj-haldane 738338d092 Remove unneeded LayoutInflater from LocalItemListAdapter
Co-Authored-By: j-haldane <200528955+j-haldane@users.noreply.github.com>
2026-01-04 00:43:47 +05:30
whistlingwoodsandj-haldane e758b5f890 Adapt header handling changes from other recyclerview adapters to fix...
... Crash in lists (ViewHolder views not attached) in StatisticsPlaylistFragment

Co-Authored-By: j-haldane <200528955+j-haldane@users.noreply.github.com>
2026-01-04 00:40:25 +05:30
tobigr 5cefafa145 Prepare for version 0.28.1 (1006) 2026-01-03 11:55:59 +01:00
Mira 49aaaebd86 Feat: opus metadata encoding (#12974)
Feat: Downloading: Add opus audio metadata tags for title, author, date, and a comment tag with the originating URL

This removes the DownloadManagerService.EXTRA_SOURCE field, which is always inferred from the StreamInfo.
2026-01-03 11:55:59 +01:00
Mira 3369607b74 Feat: opus metadata encoding (#12974)
Feat: Downloading: Add opus audio metadata tags for title, author, date, and a comment tag with the originating URL

This removes the DownloadManagerService.EXTRA_SOURCE field, which is always inferred from the StreamInfo.
2026-01-03 02:54:07 -08:00
Yevhen Babiichuk (DustDFG) 0747b3a0a5 Use "factory" method for creating db migrations 2026-01-02 12:25:25 +02:00
Aayush Gupta 7283701073 Merge pull request #12978 from dustdfg/kotlin_merged
Conversion to kotlin of multiple files
2026-01-02 15:46:29 +08:00
Yevhen Babiichuk (DustDFG) 3ffcf11a3a Merge inheritors of newpipe/player/playqueue/PlayQueueEvent and
convert it to kotlin
2026-01-01 23:05:16 +02:00
Aayush Gupta 1fb2b4a42e Merge pull request #12981 from dustdfg/export_playlist_refactor
Refactor ExportPlaylist to use more idiomatic kotlin code
2026-01-01 21:40:05 +08:00
Yevhen Babiichuk (DustDFG) 83596ca907 Convert newpipe/settings/preferencesearch/PreferenceSearchResultListener to kotlin 2026-01-01 15:33:32 +02:00
Yevhen Babiichuk (DustDFG) d9682f5e0a Convert newpipe/player/PlayerType to kotlin 2026-01-01 15:33:32 +02:00
Yevhen Babiichuk (DustDFG) ab3314eb1c Convert newpipe/info_list/ItemViewMode to kotlin 2026-01-01 15:33:32 +02:00
Yevhen Babiichuk (DustDFG) 7d1d88fb87 Convert newpipe/local/playlist/PlayListShareMode to kotlin 2026-01-01 15:08:21 +02:00
Yevhen Babiichuk (DustDFG) 8379aa0a9d Refactor ExportPlaylist to use more idiomatic kotlin code 2026-01-01 15:03:29 +02:00
Tobi 7f57493da1 Merge pull request #12986 from TeamNewPipe/actions
workflows: Update actions versions
2026-01-01 02:25:30 -08:00
Yevhen Babiichuk (DustDFG) cd4cb40e6d Convert newpipe/error/UserAction.java to kotlin 2026-01-01 11:04:19 +02:00
Yevhen Babiichuk (DustDFG)andAayush Gupta f1b111212d Convert newpipe/util/FilenameUtils.java to kotlin
Co-authored-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-01 11:04:19 +02:00
Yevhen Babiichuk (DustDFG) 84c646713d Convert newpipe/settings/preferencesearch/PreferenceSearchItem.java to kotlin 2026-01-01 11:04:19 +02:00
Yevhen Babiichuk (DustDFG) 873b2be9ca Convert newpipe/util/text/TimestampLongPressClickableSpan.java to kotlin
Also convert one class used by it into java record
2026-01-01 11:04:19 +02:00
Yevhen Babiichuk (DustDFG) 4ef4ed15f1 Convert newpipe/util/image/PreferredImageQuality to kotlin 2026-01-01 11:04:19 +02:00
Yevhen Babiichuk (DustDFG) fef8a2455c Convert newpipe/util/image/ImageStrategy to kotlin 2026-01-01 11:04:19 +02:00
Yevhen Babiichuk (DustDFG) 3398b4cdc9 Convert newpipe/fragments/list/search/Suggestion{Item,ListAdapter} to kotlin 2026-01-01 11:04:19 +02:00
Yevhen Babiichuk (DustDFG) 74cf302bd6 Convert newpipe/local/playlist/RemotePlaylistManager to kotlin 2026-01-01 11:04:19 +02:00
Aayush Gupta b4f526c2a5 workflows: Update actions versions
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-01 16:51:56 +08:00
Aayush Gupta 2e8e203276 Merge pull request #12963 from dustdfg/misc_refactor
Misc small refactors (mostly replacing old switch syntax with new)
2026-01-01 16:41:25 +08:00
Yevhen Babiichuk (DustDFG) 127064d6fc Misc small refactors (mostly replacing old switch syntax with new) 2026-01-01 10:26:56 +02:00
Aayush Gupta 93166afde4 Merge pull request #12973 from dustdfg/require_preference
Replace findPreference(getString(resId) with its null safe shortcut
2026-01-01 13:36:26 +08:00
Tobi 8893a27668 Merge pull request #12977 from TeamNewPipe/minorFix
fixup! Convert newpipe/util/KioskTranslator.java to kotlin
2025-12-31 01:33:49 -08:00
Aayush Gupta 6c238fafbe fixup! Convert newpipe/util/KioskTranslator.java to kotlin
Copyright and license header are not doc-comments. Move them to the top of the file.
Also re-write it to follow the SPDX formatting as we have followed in the recent Kotlin
conversion.

Additionally, use a better name for the context parameter.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-12-31 17:05:17 +08:00
Tobi e17b1a1fcc Merge pull request #12975 from dustdfg/playlist_button_work
Correct typo in playlist db sql query
2025-12-30 22:58:06 -08:00
Yevhen Babiichuk (DustDFG) 7806a708c2 Correct typo in playlist db sql query
Solve #12855
2025-12-31 05:23:14 +02:00
Yevhen Babiichuk (DustDFG) f6085d0044 Replace findPreference(getString(resId) with its null safe shortcut 2025-12-30 13:30:37 +02:00
tobigr 0a65c862a3 Remove empty translations 2025-12-30 09:16:58 +01:00
c9339a5a03 Translated using Weblate (French)
Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Punjabi)

Currently translated at 97.2% (743 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (87 of 87 strings)

Added translation using Weblate (Romany)

Translated using Weblate (Korean)

Currently translated at 97.7% (85 of 87 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (87 of 87 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Madalin <madalin.mail09@gmail.com>
Co-authored-by: Mona Lisa <nickwick@users.noreply.hosted.weblate.org>
Co-authored-by: NormalRandomPeople <normal.scribe833@silomails.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translation: NewPipe/Metadata
2025-12-29 22:06:50 +01:00
Aayush Gupta 824fc8fbe1 Merge pull request #12952 from TeamNewPipe/fix/NaN-on-minimize
fix(player): Fix scaleX being NaN on minimize to background app switch
2025-12-30 00:41:31 +08:00
tobigr 465979e677 Do not change the aspectRation if the renderer is disabled 2025-12-29 16:41:25 +01:00
TobiGr 7101aecc98 Try to prevent invalid aspectRatio of SurfaceView
If the video's hieght is 0, the aspectRatio is set to Float.NaN which can cause further issues. Do not assign invalid values for the aspectRatio.
2025-12-29 16:30:20 +01:00
TobiGr 718335d733 fix(player): Fix scaleX being NaN on minimize to background app switch
This aims to fix the following Exception which might occour when watching a live stream and switching the app while 'minimize to backgorund' is enabled:

java.lang.IllegalArgumentException: Cannot set 'scaleX' to Float.NaN
	at android.view.View.sanitizeFloatPropertyValue(View.java:17479)
	at android.view.View.sanitizeFloatPropertyValue(View.java:17453)
	at android.view.View.setScaleX(View.java:16822)
	at org.schabi.newpipe.views.ExpandableSurfaceView.onLayout(ExpandableSurfaceView.java:71)

scaleX is set in onMeasure() in which width could be 0 in theory and this leading to a division by zero of a float which results in an assignment of Float.NaN.
2025-12-29 16:30:20 +01:00
Aayush Gupta cd056a7f7f Merge pull request #12969 from TeamNewPipe/refactor-merge
Merge dev into refactor
2025-12-29 22:48:14 +08:00
tobigr 040b4c44ca Merge branch 'dev' into refactor
Conflicts:
VideoDetailFragment (see #12781)
libs.version.toml
2025-12-29 12:15:13 +01:00
Yevhen Babiichuk (DustDFG) 2dde0fef58 Convert newpipe/util/KioskTranslator.java to kotlin
Cherry picked from #12947 / 4649e46d1e
2025-12-27 23:13:17 +01:00
Yevhen Babiichuk (DustDFG) c2f526d5b3 Use context.getString() shorthand instead of context.getResources().getString()
Cherry-picked from #12949 / 4360c1b873
2025-12-27 23:12:37 +01:00
Tobi 8ae5a55c4f Merge pull request #12947 from dustdfg/kotlin_refactor4
Convert newpipe/util/KioskTranslator.java to kotlin
2025-12-27 09:54:58 -08:00
Tobi c3c353f1b7 Merge pull request #12954 from TeamNewPipe/fix/subscriberCount
Fix retrieving incomplete data for SubscriptionEntity
2025-12-27 09:36:17 -08:00
Tobi 8b038ef1c8 Merge pull request #12949 from dustdfg/unnecessary_get_resources
Use context.getString() shorthand instead of context.getResources().getString()
2025-12-27 09:19:06 -08:00
TobiGr 58efbf4de8 Fix retrieving incomplete data for SubscriptionEntity
This fixes a visual bug in which the channels reported the the subscriber count was unavailable although stored in the db.

Closes #12953
2025-12-26 21:57:26 +01:00
Yevhen Babiichuk (DustDFG) 4360c1b873 Use context.getString() shorthand instead of context.getResources().getString() 2025-12-26 14:15:33 +02:00
Yevhen Babiichuk (DustDFG) 4649e46d1e Convert newpipe/util/KioskTranslator.java to kotlin 2025-12-26 11:21:10 +02:00
Tobi 350d08ba9c Merge pull request #12909 from TeamNewPipe/no-ai
Add AI policy to contribution guidelines and PR template
2025-12-25 01:31:45 -08:00
Tobi 2ee5f99f09 Merge pull request #12934 from TobiGr/fix/open-download
Fix insufficient permissions on opening downloaded streams
2025-12-23 00:35:10 -08:00
tobigr a7b226c354 [Player] Use original audio language by default 2025-12-22 10:15:18 +01:00
tobigr 3ffd194f78 Fix insufficient permissions on opening downloaded streams 2025-12-21 18:44:09 +01:00
3c0e6adf2e Translated using Weblate (Hungarian)
Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (French)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (German)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Polish)

Currently translated at 58.6% (51 of 87 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (87 of 87 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (87 of 87 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: tct123 <tct1234@protonmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/fr/
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/pl/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2025-12-20 21:54:22 +01:00
TobiGr 535f9da422 Fix rebase 2025-12-20 21:23:58 +01:00
TobiGr 5582eac1c4 Only show enqueue option if play queue is not empty in RouterActivity
Make enqueue option avilable for playlists as well
2025-12-20 20:59:32 +01:00
Hatake Kakashri acaaec2cde Add enqueue option to router dialog
- This allows users to enqueue a stream directly to the current player queue when sharing a link with the app, improving the user experience for queue management.
- The 'Enqueue' option is now available in the action selection dialog and can also be set as the preferred open action in the settings.
2025-12-20 20:59:25 +01:00
Tobi 8e32e7a4b4 Merge pull request #12821 from HatakeKakashri/add_to_queue_menu_option
Add enqueue option to router dialog
2025-12-20 11:59:07 -08:00
TobiGr c596476c06 Only show enqueue option if play queue is not empty in RouterActivity
Make enqueue option avilable for playlists as well
2025-12-20 20:31:30 +01:00
Hatake Kakashri 90c36cb2e8 Add enqueue option to router dialog
- This allows users to enqueue a stream directly to the current player queue when sharing a link with the app, improving the user experience for queue management.
- The 'Enqueue' option is now available in the action selection dialog and can also be set as the preferred open action in the settings.
2025-12-20 20:31:30 +01:00
TobiGr f7ff5db4b5 [PlayQueue] Fix incorrect UI states of PlayQueue items
onNext() is called after onSubscribe() when creating a PlayQueueAdapter. For that reason the last broadcasted event is applied to the UI state although it is already reflected in the PlayQueue that was used to initialize the adapter.

This is the intended behavior of the previously used event broadcaster of the type BehaviorSubject<>. The broadcaster's type was changed to PublishSubject<> which does not emit the last event after onSubscribe().

Ref: https://github.com/TeamNewPipe/NewPipe/issues/9669
2025-12-20 18:28:45 +01:00
tobigr 86fb618f61 Update NewPipe Extractor 2025-12-19 12:01:38 +01:00
Damien Hardy 0607b14bb2 add indymotion.fr peertube instance on AndroidManifest.xml 2025-12-19 09:43:14 +01:00
Tobi 01dee9dd13 Merge pull request #12913 from dhardy92/handle-indymotionFr
add indymotion.fr peertube instance on AndroidManifest.xml
2025-12-19 00:42:24 -08:00
Damien Hardy bbcf57f93a add indymotion.fr peertube instance on AndroidManifest.xml 2025-12-17 18:52:23 +01:00
Tobi 097c643d1b Add AI policy to contribution guidelines, PR and issue templates
The amount of AI generated PRs is increasing while their quality often remains poor. The introduced guidelines aim to reduce the number of poor quality AI PRs while simultaneously increasing the quality of high quality AI PRs.

New checkboxes for compliance with the AI policy and to increase the PR quality are introduced to the PR template.

Prohibit AI generated issue and PR descriptions
2025-12-16 21:34:25 +01:00
Tobi 2dc2b01d4d Merge pull request #12910 from TeamNewPipe/release-preparations
Add changelog for NewPipe 0.28.1 (1006) and update extractor
2025-12-16 00:21:58 -08:00
tobigr 698187d2d6 Update extractor to latest version 2025-12-15 22:02:53 +01:00
tobigr 16d0248039 Add fastlane changelog for NewPipe 0.28.1 (1006) 2025-12-15 22:02:53 +01:00
f3876d1c4a Translated using Weblate (Danish)
Currently translated at 98.8% (755 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Lithuanian)

Currently translated at 99.2% (758 of 764 strings)

Translated using Weblate (Bengali)

Currently translated at 74.3% (568 of 764 strings)

Co-authored-by: Agustín Cantero <brahiancantero@gmail.com>
Co-authored-by: Dual_A <alaviabdullah782@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: gymka <gymka@archlinux.lt>
2025-12-15 22:02:28 +01:00
Tobi c603c82cff Merge pull request #12781 from iampopovich/feat/similar-youtube-client-screen-rotation
Remember and restore orientation on fullscreen exit
2025-12-14 04:43:19 -08:00
Tobi 0c17956552 Merge pull request #12898 from TeamNewPipe/playQueue
Player: Enqueue next on the existing playQueue
2025-12-14 02:46:57 -08:00
Aayush Gupta 77bea1ac68 Player: Enqueue next on the existing playQueue
Fixes 150649aea9

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-12-13 17:03:54 +08:00
Tobi 40bc8c191e Merge pull request #12897 from TeamNewPipe/depUpdate
libs: Update dependencies to latest stable releases
2025-12-13 00:51:52 -08:00
Aayush Gupta 1212486adb libs: Update dependencies to latest stable releases
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-12-13 16:30:36 +08:00
Aayush Gupta 0fc2d4c948 Merge branch 'dev' into refactor
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-12-13 16:12:32 +08:00
Aayush Gupta de6dc0289a Merge pull request #12885 from TobiGr/fix/serializable
Fix deprecation of Bundle.getSerializable(String) by using BundleCompat
2025-12-13 16:04:08 +08:00
Isira Seneviratne 5e88ac4cd8 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java
#	app/src/main/java/org/schabi/newpipe/database/stream/StreamStatisticsEntry.kt
#	app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamStateDAO.kt
#	app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
#	app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamUtils.kt
#	gradle/libs.versions.toml
2025-12-12 06:44:48 +05:30
TobiGr 17ce699037 Do not change orientation on TVs when entering fullscreen 2025-12-10 14:48:45 +01:00
TobiGr d770c6fd88 Fix state access 2025-12-10 14:46:29 +01:00
TobiGr 7ffc513f46 Fix deprecation of Bundle.getSerializable(String) by using BundleCompat
This fixes the following warning during compilation:

file:app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt:36:55 'fun getSerializable(p0: String?): Serializable?' is deprecated. Deprecated in Java.
2025-12-10 14:42:52 +01:00
Aayush Gupta 3d1d7e0870 Merge pull request #12864 from TeamNewPipe/historyFixes
Fixes for history
2025-12-04 15:28:17 +08:00
Aayush Gupta 4a00dbb15d Don't swallow error when trying to mark stream as watched
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-12-03 17:23:53 +08:00
Aayush Gupta 5c9ac912ac StreamHistoryDAO: Latest entry can be null
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-12-03 16:44:41 +08:00
TobiGr d1cbc17a09 Merge branch 'dev' into feat/similar-youtube-client-screen-rotation 2025-11-29 23:30:20 +01:00
Tobi 1bca5f3d63 Merge pull request #12838 from Stypox/merge-dev-refactor
Merge dev to refactor
2025-11-29 14:04:37 -08:00
Tobi 40caeb465d Fix ktlint: remove unnecessary essary semicolons
Task :app:runKtlint
/home/runner/work/NewPipe/NewPipe/app/src/main/java/org/schabi/newpipe/player/PlayerService.kt:151:85: Unnecessary semicolon (no-semi)
/home/runner/work/NewPipe/NewPipe/app/src/main/java/org/schabi/newpipe/player/PlayerService.kt:152:72: Unnecessary semicolon (no-semi)
2025-11-29 22:24:20 +01:00
Stypox ffb82dc88c Merge pull request #12849 from TeamNewPipe/acraKSP
Fixes for ACRA with KSP
2025-11-29 19:43:23 +01:00
Aayush Gupta e91d647b27 acra: Relocate autoservice dependencies under acra block
They are only used for ACRA

Ref: https://www.acra.ch/docs/Custom-Extensions#by-annotation

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-29 19:54:36 +08:00
Aayush Gupta 6055cf2938 acra: Switch to ZacSweers's fork of autoservice
Google has no plans to officially support KSP for autoservice

Ref: https://github.com/google/auto/issues/882

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-29 19:52:17 +08:00
Tobi 22dfe9519f Merge pull request #12840 from scola/rotate_on_androidtv_issue
Always do not rotate screen when Android TV
2025-11-28 02:22:56 -08:00
shaozheng e045251b58 Always do not rotate screen when Android TV 2025-11-28 17:41:13 +08:00
Stypox 125fc38ffe Merge branch 'dev' into refactor 2025-11-27 18:13:19 +01:00
Stypox ebe07596ba Update NewPipeExtractor to fix build (Jitpack failures again) 2025-11-27 14:54:08 +01:00
Stypox 18f1cf2075 Merge pull request #12776 from TeamNewPipe/depUpdate 2025-11-26 12:53:06 +01:00
Aayush Gupta 03e963952c Ignore Kotlin compiler generated files
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-21 20:09:57 +08:00
Aayush Gupta e5ed0b529f Bump ktlint to latest stable release and maven coordinate
Disable all new rules to avoid massive file-changes. All new rules should be
enabled one by one as per requirements in separate commit to make review easier.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-21 20:08:26 +08:00
Aayush Gupta 0131bb227f Silence warnings regarding new annotation property behavior
Ref: https://kotlinlang.org/docs/annotations.html#defaults-when-no-use-site-targets-are-specified

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-21 20:08:26 +08:00
Aayush Gupta b06b7c35ca Relocate toml lint task to buildSrc and extend against default task
Fixes build errors after Gradle 9.x upgrade

Ref: https://docs.gradle.org/current/userguide/implementing_custom_tasks.html

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-21 20:08:26 +08:00
Aayush Gupta 0a0f28e801 Bump dependencies to possible stable releases
androidx has bumped minSdk to API 23 which makes us unable to use latest version of:
* room
* workmanager

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-21 20:08:22 +08:00
Aayush Gupta 4a8592c5ba Enable Gradle configuration cache
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-21 20:08:12 +08:00
Aayush Gupta 8f91f21f27 Bump Gradle to latest stable release
Also update the wrapper using the ./gradlew wrapper command

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-21 20:08:12 +08:00
Tobi 2ba87f7979 Merge pull request #12820 from TeamNewPipe/kapt
Partial-revert: Migrate from KAPT to KSP
2025-11-19 06:18:02 -08:00
Aayush Gupta f4d138d06f Partial-revert: Migrate from KAPT to KSP
statesaver has been deprecated for ~ 6 years and incompatible with KSP

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-19 16:33:43 +08:00
Stypox 6c95c9c7dd Merge pull request #12811 from TeamNewPipe/pr-template-features
Update PR template to specify target branch for features
2025-11-18 09:00:24 +01:00
Stypox 153e4820e7 Merge pull request #12816 from TeamNewPipe/dbFix
Minor fixes for database
2025-11-18 08:57:00 +01:00
Aayush Gupta 93f03bab87 Call checkpoint creation from an executor
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-18 12:03:32 +08:00
Aayush Gupta 9702189be4 Move latestEntry into SearchHistoryDao directly
The StreamHistoryDao one isn't used, so remove it and streamline the logic

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-18 12:03:32 +08:00
Aayush Gupta 85bd7c3351 HistoryDao: latestEntry can be null
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-18 12:03:32 +08:00
Tobi b7bf07d5e4 Update PR template to specify target branch for features
Added a note to target the 'refactor' branch for features.
2025-11-17 01:05:24 -08:00
Stypox 89a68d0789 Merge pull request #12801 from TeamNewPipe/weblate 2025-11-16 13:18:22 +01:00
+58 eaafdb2570 Translated using Weblate (Turkish)
Currently translated at 50.0% (43 of 86 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Spanish)

Currently translated at 98.6% (754 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Lombard)

Currently translated at 0.2% (2 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 72.0% (62 of 86 strings)

Added translation using Weblate (Lombard)

Translated using Weblate (Korean)

Currently translated at 97.6% (84 of 86 strings)

Translated using Weblate (Danish)

Currently translated at 97.9% (748 of 764 strings)

Translated using Weblate (Romanian)

Currently translated at 97.9% (748 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Latvian)

Currently translated at 95.6% (731 of 764 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 73.8% (564 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (763 of 764 strings)

Translated using Weblate (Georgian)

Currently translated at 92.8% (709 of 764 strings)

Translated using Weblate (Georgian)

Currently translated at 89.5% (77 of 86 strings)

Translated using Weblate (Thai)

Currently translated at 3.4% (3 of 86 strings)

Translated using Weblate (Thai)

Currently translated at 37.1% (284 of 764 strings)

Translated using Weblate (Catalan)

Currently translated at 96.3% (736 of 764 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Slovenian)

Currently translated at 53.0% (405 of 764 strings)

Translated using Weblate (Thai)

Currently translated at 36.7% (281 of 764 strings)

Translated using Weblate (Thai)

Currently translated at 2.3% (2 of 86 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.9% (756 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.8% (5 of 86 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.8% (5 of 86 strings)

Translated using Weblate (Bosnian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Bosnian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 90.6% (78 of 86 strings)

Translated using Weblate (Serbian)

Currently translated at 16.2% (14 of 86 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Croatian)

Currently translated at 95.4% (729 of 764 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Icelandic)

Currently translated at 98.4% (752 of 764 strings)

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

Currently translated at 36.0% (31 of 86 strings)

Translated using Weblate (German)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Belarusian)

Currently translated at 97.1% (742 of 764 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.0% (757 of 764 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.7% (762 of 764 strings)

Translated using Weblate (Korean)

Currently translated at 98.6% (754 of 764 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (French)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (German)

Currently translated at 100.0% (764 of 764 strings)

Translated using Weblate (French)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 69.7% (60 of 86 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (French)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (French)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (German)

Currently translated at 100.0% (759 of 759 strings)

Translated using Weblate (Kabyle)

Currently translated at 28.1% (212 of 754 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.2% (748 of 754 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (German)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.6% (751 of 754 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.6% (751 of 754 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Russian)

Currently translated at 99.3% (749 of 754 strings)

Translated using Weblate (Tigrinya)

Currently translated at 20.1% (152 of 754 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Tigrinya)

Currently translated at 18.1% (137 of 754 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (German)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (German)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Tigrinya)

Currently translated at 17.3% (131 of 754 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (French)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (French)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (German)

Currently translated at 99.8% (753 of 754 strings)

Translated using Weblate (German)

Currently translated at 99.8% (753 of 754 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Tigrinya)

Currently translated at 15.6% (118 of 754 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 9.3% (8 of 86 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 8.8% (67 of 754 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 98.8% (85 of 86 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Vietnamese)

Currently translated at 79.0% (68 of 86 strings)

Translated using Weblate (Portuguese)

Currently translated at 98.8% (85 of 86 strings)

Translated using Weblate (Greek)

Currently translated at 36.0% (31 of 86 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Vietnamese)

Currently translated at 97.3% (734 of 754 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 99.2% (748 of 754 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (French)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (754 of 754 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (748 of 748 strings)

Co-authored-by: 404px <limgu2010@gmail.com>
Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439JBYL80IGQTF25UXNR0X1BG@users.noreply.hosted.weblate.org>
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: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: CJ Montero <cristlad@proton.me>
Co-authored-by: DB L <deblm@tutamail.com>
Co-authored-by: Daniel Mantilla <danielmantilladiez@gmail.com>
Co-authored-by: Dizro <weblate.delirium794@passmail.net>
Co-authored-by: Drugi Sapog <dindrugi@users.noreply.hosted.weblate.org>
Co-authored-by: EESF-2 <eesf-2@users.noreply.hosted.weblate.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: FrederikFinckh <frederik.finckh@gmx.de>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hasan <hasanyildiz0@yaani.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hoàng Sơn <smgzk2000@gmail.com>
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: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Maria Dimitrova <mimidimitrova07@gmail.com>
Co-authored-by: Matija Šuklje <matija@suklje.name>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Mona Lisa <nickwick@users.noreply.hosted.weblate.org>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nick Wick <NickWick@users.noreply.hosted.weblate.org>
Co-authored-by: Nikoloz <nukushatugushi@gmail.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: René <ninso112@proton.me>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Ricard Rodríguez <rcard@insicuri.net>
Co-authored-by: Sarah O <epigenetastic@gmail.com>
Co-authored-by: SecularSteve <fairfull.playing@gmail.com>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: ThaiWithNoBraincell <altofskgd@gmail.com>
Co-authored-by: The Cats <philosoph@danwin1210.de>
Co-authored-by: Trunars <trunars@abv.bg>
Co-authored-by: Tấn Lực Trương <september122022ios16@gmail.com>
Co-authored-by: Valer <122545522+Valer100@users.noreply.github.com>
Co-authored-by: Vasilis K <skyhirules@gmail.com>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: VisionR1 <25982450+VisionR1@users.noreply.github.com>
Co-authored-by: Xiao Ping <deceased-take-mold@duck.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: delvani <del.cidrak@gmail.com>
Co-authored-by: fool <thing-sauna-cussed@duck.com>
Co-authored-by: gbpu <gui.beppu@gmail.com>
Co-authored-by: ikanakova <ikanakova@users.noreply.hosted.weblate.org>
Co-authored-by: justcontributor <kty5663@gmail.com>
Co-authored-by: late <late@users.noreply.hosted.weblate.org>
Co-authored-by: nafanz <nafanz@mail.ru>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: sudo-py-dev <sudopydev@gmail.com>
Co-authored-by: tct123 <tct1234@protonmail.com>
Co-authored-by: yummysheepouo <jerry88182821@gmail.com>
Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bg/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bs/
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/en_GB/
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/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ka/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
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/sr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/th/
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_Hans/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2025-11-14 21:09:20 +01:00
Alex Popov a4cc1d1ddf feat(player): Remember and restore orientation on fullscreen exit
- Store the original screen orientation when entering fullscreen.
- Restore the saved orientation when exiting fullscreen.
- On tablets, continue to just toggle the fullscreen UI without changing the device orientation.
2025-11-10 02:22:56 +07:00
Tobi f836f5e75d Merge pull request #12746 from TeamNewPipe/kspMigration
Migrate from KAPT to KSP
2025-11-07 07:41:56 -08:00
Aayush Gupta 4826e5b3c5 Add missing annotations for columnInfo in PlaylistDuplicatesEntry
Fixes [ksp] app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.kt:140: The columns returned by the query does not have the fields [thumbnailUrl,isThumbnailPermanent,thumbnailStreamId,displayIndex,orderingName] in org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry even though they are annotated as non-null or primitive. Columns returned by the query: [uid,streamCount,timesStreamIsContained]

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-06 16:10:15 +08:00
Aayush Gupta 97e7272151 Removed badly hacked default playlist thumbnail icon
The defaults should be supplied to the image loading software not the database library.
This would also break when we shrink resources as that would rename the resources.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-06 15:43:07 +08:00
Aayush Gupta 7c76791db3 Handle null-safety error in FeedDao
The lastUpdated parameter can be null, adjust return types to signal that too

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-03 16:14:29 +08:00
Aayush Gupta 995a92b7a4 Migrate & adapt database tests to Kotlin as well
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-03 16:14:29 +08:00
Aayush Gupta 05b9ff49a2 Migrate from KAPT to KSP
Ref: https://developer.android.com/build/migrate-to-ksp

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-03 16:14:29 +08:00
Aayush Gupta 4422b55ab4 Migrate database logic to Kotlin
Room has been convereted into a KMP library in the latest stable releases and
annotation processing requires KSP which only generates kotlin classes

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-03 16:14:29 +08:00
Tobi 2fadaffb98 Merge pull request #12765 from TransZAllen/build_error_into_NewPipeExtractor
[Build] Local NewPipeExtractor build inclusion fails in settings.gradle.kts
2025-11-02 02:02:59 -08:00
TransZAllen 1314a21f71 fix: update commented example in settings.gradle.kts for Kotlin DSL
- Resolves build issue related to local NewPipeExtractor inclusion
- Related issue: https://github.com/TeamNewPipe/NewPipe/issues/12763
2025-11-02 16:01:39 +08:00
Tobi 650b51ffec Merge pull request #12694 from mjsir911/m/on.soundcloud
Support on.soundcloud link opening
2025-11-01 15:36:03 -07:00
Tobi c03f405f8c Merge pull request #12716 from Isaac-75/12194-notification-prompt-after-rotation
Notifications are no longer requested again after rotating the phone
2025-11-01 15:24:31 -07:00
Tobi 0a89276b7a Merge pull request #12575 from TransZAllen/dev
[Bug] Fix missing subtitle text in manually downloaded *.SRT files. (issue #10030)
2025-10-30 14:27:39 -07:00
TransZAllenandTobi 300afde83d Update app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2025-10-29 22:34:47 +08:00
TransZAllen d311faea58 improve comments on TTML → SRT conversion
- update class header with proper technical references and remove author tag.
- update comments of replacing NBSP('\u00A0'), especially adding examples
  of rendering incorrectly.
2025-10-29 19:25:43 +08:00
TransZAllenandTobi 71aa6d52d3 Update app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2025-10-28 17:39:04 +08:00
Isaac 88eb32be3a moved field as requested 2025-10-26 05:32:12 +11:00
Stypox 2a9c6f0538 Merge pull request #12729 from litetex/workaround-popup-player-ui-elements-being-pushed-out 2025-10-21 18:15:13 +02:00
litetex c81148ae0a [Popup player] Workaround that UI elements are pushed off screen 2025-10-21 18:09:06 +02:00
Stypox ecd3e85d49 Merge pull request #12714 from litetex/properly-layout-player-topbar 2025-10-21 18:03:39 +02:00
Stypox f3ca5f659d Merge pull request #12684 from TobiGr/dependency-updates 2025-10-21 10:39:54 +02:00
Stypox 300f5abc70 Update NewPipeExtractor and restore Jitpack/using-locally comments
The comments were accidentally removed in #12706
2025-10-21 10:31:32 +02:00
tobigr 729702b420 Update dependencies
androidx.media:media:1.7.0 -> 1.7.1
androidx.viewpager2:viewpager2:1.1.0-beta02 -> 1.1.0
io.reactivex.rxjava3:rxjava:3.1.8 -> 3.1.12
org.jsoup:jsoup:1.17.2 -> 1.21.2
2025-10-21 10:22:37 +02:00
tobigr 42f909936b Bump checkstyle and make inner classes final
Updating checkstyle fixed a vulnerability and fixed a final class check in version 10.12.2 for local classes without constructor.  Local classes without a constructor should be marked as final. That is done in this commit.

For more info see https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.12.2
2025-10-21 10:20:57 +02:00
Stypox c8e294b1a3 Merge pull request #12706 from TeamNewPipe/buildImprovements 2025-10-21 01:32:02 +02:00
Isaac c4e6e4d4c4 Notifications are no longer requested again after rotating the phone 2025-10-19 03:41:52 +11:00
litetex 41981902ab Limit height of wrapper 2025-10-17 20:33:56 +02:00
litetex 56a09220ee Remove not needed viability control
This is done by the parent
2025-10-17 20:15:24 +02:00
litetex c4bfc119df Improve the alignment of titleTextView and audioTrackTextView
This fulfills the following:
* both should never push content outside of the view
* there should be no wasted space
* `audioTrackTextView` is always aligned to the right
* both should grow equally but also respect their respective contents size first

Caveats:
* Currently the layout weight is distributed using "NestedWeights" which require a widget to be measured twice. According to Android Studio this might cause an exponential performane impact, however there is currently just a single nested component so the effect should be not noticeable
2025-10-17 19:29:26 +02:00
litetex c49e44443c Correctly name the preview 2025-10-17 19:17:17 +02:00
litetex 1014dd563f Correctly format player.xml
Otherwise it constantly switches the attributes which makes (re) viewing changes next to impossible
2025-10-17 19:08:49 +02:00
Aayush Gupta ee01ba3209 Specify JDK toolchain directly
Specifying JDK toolchain in the java block lets us avoid specifying
same version again and again for different options while ensuring everything
is on the same version

Ref: https://developer.android.com/build/jdks#toolchain

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-10-17 16:28:00 +08:00
TransZAllen 3516667671 refactor(ttml): extract recursion into traverseChildNodesForNestedTags()
- Extracted child-node traversal logic from `extractText()`
  into a helper method `traverseChildNodesForNestedTags()`.
- No functional change.
2025-10-17 12:06:18 +08:00
TransZAllen 22ee01bcfb refactor(ttml): improve extractText() to preserve spaces and special characters
- Replaced `text()` with `getWholeText()`:
  - avoids losing whitespaces at the beginning, end, or within the text;
  - avoids merging two or more consecutive spaces into a single space ' ';
  - avoids converting '\r', '\n', and '\r\n' within the text into a single space ' ';
  For subtitle conversion, the goal is to preserve every character exactly as intended by the subtitle author.
- Normalized tabs, line breaks, and other special characters for SRT-safe output.
- Added comprehensive unit tests in `SrtFromTtmlWriterTest.java`, including cases for simple and nested tags.
2025-10-17 01:57:01 +08:00
Aayush Gupta 1bef2fdc25 Drop deprecated non-working archivesBaseName property
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-10-16 22:27:56 +08:00
Jie Li 061ce870ac Gradle script to enforce dependencies order
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-10-16 22:27:15 +08:00
Aayush Gupta 15089245bb Migrate to build version catalog
Ref: https://developer.android.com/build/migrate-to-catalogs

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-10-16 22:25:25 +08:00
Aayush Gupta d99435c4ad Migrate build scripts to kotlin DSL
Ref: https://developer.android.com/build/migrate-to-kotlin-dsl

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-10-14 23:07:07 +08:00
Aayush Gupta 320c693636 Revert "Add snippet to ensure baseline.profm file is sorted"
This reverts commit 1db1a00581.

The issue has been long resolved making this fix no longer required.
2025-10-14 23:07:07 +08:00
Tobi 09e4bea205 Merge pull request #12699 from Zer0tier/issue#12499
Fix Long Audio/Dubs text label puses UI Controls on Player Off Screen in Portrait mode.
2025-10-14 04:21:56 -07:00
jianing liu fbc664d0da [player] Prevent long audio track label from pushing controls off-screen
- audioTrackTextView: layout_width=0dp + layout_weight=1
- Make it singleLine with ellipsize="end"
- When not fullscreen, hide metadataView so an empty weighted container doesn’t reserve space
- Result: controls stay visible on small screens; longer labels can use space on larger screens
2025-10-14 12:58:51 +08:00
jianing liu 2dd4509b75 change marginRight to marginEnd 2025-10-11 21:12:57 +08:00
Tobi eee1172e8a Merge pull request #12692 from Zer0tier/issue#12433
[Bug] Long-pressing Play All-button does nothing
2025-10-10 08:08:55 -07:00
Howar 0ebd01e65e fix(playback): handle long-press on “Play All” button for issue #12433 2025-10-11 02:01:52 +11:00
m fd4f4737c2 Support on.soundcloud link opening 2025-10-10 03:22:23 -07:00
Aayush Gupta 6e0b7be15c Merge pull request #12404 from SttApollo/Create_CommentSection_ErrorPanel
Implement Compose-based Error Panel, Error UI Model, and Tests for Comments
2025-10-07 09:14:58 +08:00
Aayush Gupta 4604339583 Merge branch 'dev' into refactor
Conflicts:
	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
	app/src/main/java/org/schabi/newpipe/player/PlayerService.java
	app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java
	build.gradle
2025-10-07 08:46:03 +08:00
Su TT 25e96d584a Address reviewer changes - make previews private and remove white space 2025-10-06 11:11:13 -04:00
Su TT 9d3ac1b94f Add Compose UI tests covering ErrorPanel and comment section flows 2025-10-04 17:13:03 -04:00
Stypox 965eea2124 Merge pull request #12676 from thonsi/dev 2025-10-02 16:22:14 +02:00
Clippy d9ddc07d4b Fix failing recaptcha mapping test 2025-10-01 17:24:02 +01:00
Thonsi 59dfdda95e remove isUsingDSP 2025-10-01 15:56:08 +00:00
Stypox 8856e97c62 Reorder buttons in error panel and don't allow reporting recaptchas 2025-10-01 16:56:16 +02:00
Stypox aed4278388 Unify getString for compatibility
(read the method's javadoc for why)
2025-10-01 16:48:52 +02:00
Stypox 3a2d427a46 Merge pull request #12642 from Stypox/fireos-SAF 2025-10-01 16:28:12 +02:00
Tobi c25f83da6c Merge pull request #12671 from TransZAllen/SRT_numbering
Fix initial numbering of frames in TTML to SRT converter
2025-10-01 02:42:33 -07:00
Stypox e2026dc378 Merge pull request #12606 from Stypox/do-not-startService-randomly 2025-09-30 17:47:08 +02:00
Stypox 00f6203904 Merge pull request #12605 from TeamNewPipe/open-in-browser 2025-09-30 17:45:29 +02:00
Su TT 3ab1322425 test,ui: move comment error tests to error; remove unused ComposeView 2025-09-29 13:57:34 -04:00
TransZAllen 980e8f3951 [YouTube] *.srt numbering start at 1 instead of 0. (issue: https://github.com/TeamNewPipe/NewPipe/issues/12670)
- The SubRip (.srt) specification requires subtitle numbering to begin from 1.
- Please refer to https://en.wikipedia.org/wiki/SubRip
- Previously numbering started from 0, which is accepted by most
  players (tested on mpv, VLC, MPlayer, Totem) but not strictly compliant.
2025-09-29 18:04:35 +08:00
Su TT fab0d35269 Update ErrorPanel and retest 2025-09-24 16:47:21 -04:00
Su TT 5ba95a2c37 Add ErrorPanelPreview 2025-09-23 10:04:52 -04:00
Su TT 2d3a4b67d7 Convert CommentSectionErrorIntegrationTest to unit test
- Moved from androidTest to test directory
- Removed Android test runner dependencies
- Renamed to CommentSectionErrorTest
- Addresses PR feedback until Compose testing is in place
2025-09-23 10:04:52 -04:00
Stt_lensandIsira Seneviratne 9cbcaecb92 Update app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt
Co-authored-by: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com>
2025-09-23 10:04:52 -04:00
Stt_lensandIsira Seneviratne 848b8868fb Update app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt
Co-authored-by: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com>
2025-09-23 10:04:52 -04:00
Stt_lensandIsira Seneviratne f9222a6a68 Update app/src/main/java/org/schabi/newpipe/ui/components/common/ErrorPanel.kt
Co-authored-by: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com>
2025-09-23 10:04:52 -04:00
Su TT 8292f729ea Updated ERefactor ErrorPanel to use ErrorInfo directly and remove UI models 2025-09-23 10:04:49 -04:00
Su TT 19fb9899cd Fix CommentSectionErrorTest to use named NetworkException for instrumented test compatibility 2025-09-23 09:41:05 -04:00
Su TT da4878d264 feat(ui):Add ErrorPanel composable to Comments Section, related UI models, and tests 2025-09-23 09:40:48 -04:00
Stypox 4e9a480fdd Enforce using SAF on FireOS TVs with Android 10+
Even if SAF is bugged there, there is no other way to open a file dialog, since NewPipe does not have permissions, see #10643
2025-09-17 12:24:18 +02:00
Stypox aa2b4821e2 Post dummy notification then close player service on invalid intent
This should solve "Context.startForegroundService() did not then call Service.startForeground()" according to https://github.com/TeamNewPipe/NewPipe/issues/12489#issuecomment-3290318112
2025-09-17 11:50:46 +02:00
Stypox 92a07a3445 Use tryBindIfNeeded(), send player started only if player!=null
This commit fixes one way ghost notifications could be produced (although I don't know if there are other ways). This is the call chain that would lead to ghost notifications being created:
1. the system starts `PlayerService` to query information from it, without providing `SHOULD_START_FOREGROUND_EXTRA=true`, so NewPipe does not start the player nor show any notification, as expected
2. the `PlayerHolder::serviceConnection.onServiceConnected()` gets called by the system to inform `PlayerHolder` that the player started
3. `PlayerHolder`  notifies `MainActivity` that the player has started (although in fact only the service has started), by sending a `ACTION_PLAYER_STARTED` broadcast
4. `MainActivity` receives the `ACTION_PLAYER_STARTED` broadcast and brings up the mini-player, but then also tries to make `PlayerHolder` bind to `PlayerService` just in case it was not bound yet, but does so using `PlayerHolder::startService()` instead of the more passive `PlayerHolder::tryBindIfNeeded()`
5. `PlayerHolder::startService()` sends an intent to the `PlayerService` again, this time with `startForegroundService` and with `SHOULD_START_FOREGROUND_EXTRA=true`
6. the `PlayerService` receives the intent and due to `SHOULD_START_FOREGROUND_EXTRA=true` decides to start up the player and show a dummy notification

Steps 3 and 4 are wrong, and this commit fixes them:
3. `PlayerHolder` will now broadcast `ACTION_PLAYER_STARTED` when the service connects, only if the player is not-null
4. `PlayerHolder::tryBindIfNeeded()` is now used to passively try to bind, instead of `PlayerHolder::startService()`
2025-09-17 11:49:16 +02:00
Tobi eed09f8a1d Merge pull request #12550 from whistlingwoods/fix-downloads-lost-progress
Try to recover pending download missions when possible
2025-09-16 23:26:56 -07:00
Stypox fd3f030d0b Merge pull request #12616 from Isira-Seneviratne/Bump-AGP 2025-09-10 09:18:32 +02:00
Stypox abfde872f1 Merge pull request #12623 from Isira-Seneviratne/Merge-dev-to-refactor 2025-09-10 09:17:40 +02:00
Isira Seneviratne c98f56bf7b Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
#	app/src/main/java/org/schabi/newpipe/player/Player.java
#	app/src/main/java/org/schabi/newpipe/player/PlayerService.java
#	app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
#	app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java
2025-09-10 08:37:57 +05:30
Profpatsch 45c22c0db8 Merge pull request #12615 from Isira-Seneviratne/Player-intent-refactor
Refactor player intent logic
2025-09-09 10:24:42 +02:00
Isira Seneviratne 2b7c72eb69 Update AGP to 8.13.0 2025-09-08 08:08:07 +05:30
Isira Seneviratne 89c4eb5237 Refactor player intent logic 2025-09-08 07:56:13 +05:30
Stypox 759342fa62 Merge pull request #12612 from Profpatsch/rename-fullscreen-buttons 2025-09-07 18:00:47 +02:00
Profpatsch 0382cfd2ae MainPlayerUi: separate enter/exit fullscreen methods
Most usages of `toggleFullscreen` are clear about whether they want to
enter or exit fullscreen, so let’s split the setup into two functions
for easier debugging.

The two remaining uses of `toggleFullscreen` *should* really toggle,
but I’m not 100% sure.

Also rename `onScreenRotationButtonClicked` to
`onFullscreenToggleButtonClicked`, since we renamed the button id
earlier.
2025-09-07 13:23:15 +02:00
Profpatsch 753a3e68d5 player.xml: Rename fullscreen buttons
Their names must have come from a very old version of the app, they
both toggle the fullscreen mode and rotate the screen depending on
circumstances.

So
`fullscreenButton` -> `fullscreenToggleButtonSecondaryMenu` because it
is only visible in the secondary video menu on some configurations.
and
`screenRotationButton` -> `fullscreenToggleButton` because it is the main
fullscreen button next to the video progress bar.
2025-09-07 12:25:56 +02:00
Isira Seneviratne d7953d620c Merge branch 'refactor' into History-Compose 2025-09-07 09:55:44 +05:30
Stypox b6bd87a4dc Merge pull request #12609 from Stypox/image-vector-app-icon 2025-09-06 18:01:10 +02:00
Stypox b36201442d Use ImageVector to render NewPipe squircle app icon 2025-09-06 17:59:42 +02:00
Stypox 803aba4935 Merge pull request #12254 from TeamNewPipe/timestamp-keep-current-player 2025-09-06 17:51:36 +02:00
Profpatsch 1723bf0e8a Player/handleIntent: keep current player when clicking timestamp
This was always a bit weird, that clicking a timestamp would
unconditionally switch to the popup player.

With the new enum, it’s trivial to change it to always stay at the
selected player now ;)
2025-09-06 17:40:18 +02:00
Stypox 9d3775f132 Rewrite logo SVGs to make them line-only
Also optimize them with svgo
2025-09-06 17:23:31 +02:00
whistlingwoods 21e24c9e34 Apply review suggestions 2025-09-06 19:14:15 +05:30
Stypox b2d89a41fb Merge pull request #12604 from Isira-Seneviratne/Refactor-EmptyState 2025-09-06 15:33:49 +02:00
Profpatsch eb277fe14b Player/handleIntent: call handleIntentPost unconditionally
We always need to handleIntentPost otherwise the VideoDetailFragment
is not setup correctly.
2025-09-06 15:31:14 +02:00
Profpatsch d77771a60c Player/handleIntent: fix enqueue if player not running
In 063dcd41e57c46721f1691cd57d21fbde75a35ea I falsely claimed that the
fallthrough case is always degenerate, but it kinda somehow still
worked because if you long-click on e.g. the popup button, it would
call enqueue, but if nothing was running yet it would fallthrough to
the very last case and start the player with the video.

So let’s return to that and add a TODO for further refactoring in the
future.
2025-09-06 15:09:11 +02:00
Profpatsch 01f9a3de33 Fix Checkstyle & remove unused fields 2025-09-06 15:09:11 +02:00
Profpatsch 150649aea9 Player/handleIntent: Don’t delete queue when clicking on timestamp
Fixes https://github.com/TeamNewPipe/NewPipe/issues/11013

We finally are at the point where we can have good logic around
clicking on timestamps.

This is pretty straightforward:

1) if we are already playing the stream (usual case), we skip to the
   correct second directly
2) If we don’t have a queue yet, create a trivial one with the stream
3) If we have a queue, we insert the video as next item and start
  playing it.

The skipping logic in 1) is similar to the one further down in the old
optimization block, but will always correctly fire for timestamps now.
I copied it because it’s not quite the same code, and moving into a
separate method at this stage would complicate the code too much.
2025-09-06 15:09:11 +02:00
Profpatsch 3803d49489 Player/handleIntent: separate out the timestamp request into enum
Instead of implicitely reconstructing whether the intent was
intended (lol) to be a timestamp change, we create a new kind of
intent that *only* sets the data we need to switch to a new timestamp.

This means that the logic of what to do (opening a popup player) gets
moved from `InternalUrlsHandler.playOnPopup` to the
`Player.handleIntent` method, we only pass that we want to jump to a
new timestamp. Thus, the stream is now loaded *after* sending the
intent instead of before sending.

This is somewhat messy right now and still does not fix the issue of
queue deletion, but from now on the queue logic should get more
straightforward to implement.

In the end, everything should be a giant switch. Thus we don’t
fall-through anymore, but run the post-setup code manually by calling
`handeIntentPost` and then returning.
2025-09-06 15:06:53 +02:00
Profpatsch 25a4a9a253 Player/handleIntent: move prefs parameters into initPlayback
They are just read from the player preferences and don’t influence the
branching, no need to read them in the intent parsing logic.
2025-09-06 15:04:06 +02:00
Profpatsch d534946550 Player: inline repeat mode cycling 2025-09-06 15:04:06 +02:00
Profpatsch 8fb3e90fe1 Player: remove unused REPEAT_MODE intent key 2025-09-06 15:04:06 +02:00
Profpatsch 5750ef6aa8 Player/handleIntent: start converting intent data to enum
The goal here is to convert all player intents to use a single enum
with extra data for each case. The queue ones are pretty easy, they
don’t carry any extra data. We fall through for everything else for
now.
2025-09-06 15:04:06 +02:00
Profpatsch ab7d1377e5 Player/handleIntent: always early return on ENQUEUE an ENQUEUE_NEXT
We can do this, because:

1. if `playQueue` is not null, we return early
2. if `playQueue` is null and we need to enqueue:
  - the only “proper” case that could be triggered is
    the `RESUME_PLAYBACK` case, which is never `true` for the queuing
    intents, see the comment in `NavigationHelper.enqueueOnPlayer`
  - the generic `else` case is degenerate, because it would crash on
  `playQueue` being `null`.

This makes some sense, because there is no way to trigger the
enqueueing logic via the UI currently if there is no video playing
yet, in which case `playQueue` is not `null`.

So we need to transform this whole if desaster into a big switch.
2025-09-06 15:04:06 +02:00
Profpatsch fd24c08529 Player/handleIntent: de morgan samePlayQueue
Okay, so this is the … only? branch in this if-chain that will
conditionally fire if `playQueue` *is* `null`, sometimes.

This is why the unconditional `initPlayback` in `else` is not passed a
`null` in many cases … because `RESUME_PLAYBACK` is `true` and
`playQueue` is `null`.

It’s gonna be hard to figure out which parts of that are intentional,
I say.
2025-09-06 15:04:06 +02:00
Profpatsch e14ec3a4f9 NavigationHelper: inline trivial getPlayerIntent use 2025-09-06 15:04:06 +02:00
Profpatsch b592403a66 NavigationHelper: push out resumePlayback one layer 2025-09-06 15:04:06 +02:00
Profpatsch 90e1ac56ef NavigationHelper: inline getPlayerEnqueueIntent
Funnily enough, I’m pretty sure that whole comment will be not
necessary, because we never check `resumePlayback` on handling the
intent anyway.
2025-09-06 15:04:06 +02:00
Profpatsch 32eb3afe16 Player/handleIntent: a few comments 2025-09-06 15:04:06 +02:00
Fynn Godau 83a0abddcc Fix and simplify openUrlInBrowser
The code was not previously working in case no default browser is set[1]
AND NewPipe is set as default handler for the link in question.

We improve it by telling the system to choose the target app as if the
URI was `http://`, which works even if the user has not set a default
browser.

[1]: also the case if platform refuses to tell an app what the user's
default browser is, which I observed on CalyxOS.
2025-09-05 17:49:58 +02:00
Profpatsch 35c7f2f5d1 Player: Remove unused IS_MUTED intent key
The only use of the key was removed in commit
2a2c82e73b
but the handling logic stayed around. So let’s get rid of it.
2025-09-05 16:57:27 +02:00
Isira Seneviratne 01a8c4e584 Clean up EmptyStateComposable code 2025-09-05 18:22:19 +05:30
Stypox 2ee7cc4344 Merge branch 'dev' into refactor 2025-09-05 13:34:53 +02:00
Stypox 8afb00d2f0 Merge pull request #12603 from Stypox/better-error-panel 2025-09-05 13:31:39 +02:00
Stypox f27ec53c08 Even more centralized error handling in ErrorInfo 2025-09-05 12:39:16 +02:00
Stypox 2cb465c89d Merge branch 'dev' into refactor 2025-09-04 15:25:45 +02:00
Stypox a3ddd616f9 Merge pull request #12578 from Stypox/better-error-messages 2025-09-04 13:18:40 +02:00
Stypox 79980e2078 Address PR reviews 2025-09-04 13:17:45 +02:00
Isira Seneviratne ccca89dc8a Merge pull request #12585 from Isira-Seneviratne/Merge-dev-to-refactor
Merge dev to refactor
2025-09-01 05:37:29 +05:30
Isira Seneviratne 9bf23abcd1 Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/src/main/AndroidManifest.xml
2025-09-01 05:22:51 +05:30
Isira Seneviratne b204fad9d5 Merge pull request #12471 from Isira-Seneviratne/Fix-notifications
Fix foreground service issues
2025-09-01 05:05:47 +05:30
Isira Seneviratne 08f51abefb Added comments 2025-08-31 22:25:12 +05:30
Stypox 204df4c45a Fix test 2025-08-30 14:58:08 +02:00
Stypox 989c0cfd28 Fix REPORT in snackbar not opening ErrorActivity if MainActivity not shown
Bug caused by https://github.com/TeamNewPipe/NewPipe/pull/11789
2025-08-30 14:39:23 +02:00
Stypox a369deeef4 Allow ErrorInfo messages with formatArgs
- ErrorInfo.getMessage() now returns an ErrorMessage instance that can be formatted into a string using a context (this allows the construction of an ErrorInfo to remain independent of a Context)
- now the service ID is used in ErrorInfo.getMessage() to customize some messages based on the currently selected service
- player HTTP invalid statuses are now included in the message
- building a custom error message for AccountTerminatedException was moved from ErrorPanelHelper to ErrorInfo
2025-08-30 14:36:27 +02:00
Stypox 1bde2dcd9f Fix ordering of error messages conditions 2025-08-28 17:06:10 +02:00
Stypox 29a3ca83b5 Show better information about player errors 2025-08-28 17:06:09 +02:00
Stypox 38064be702 Add more specific error messages and deduplicate their handling 2025-08-28 17:05:52 +02:00
Isira Seneviratne 44538cea30 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
2025-08-27 18:51:49 +05:30
Tobi d17eae9bad Merge pull request #12253 from Profpatsch/popup-overlay-alert-dialog
Overlay Permission: display explanatory dialog for Android > R
2025-08-27 02:50:45 -07:00
TobiGr 74562db965 Use androidx compat alert dialog 2025-08-27 11:45:31 +02:00
Profpatsch 386d5197d8 Permission: display explanatory dialog for Android > R
On Android > R, ACTION_MANAGE_OVERLAY_PERMISSION always brings the
user to the app selection screen.

https://developer.android.com/about/versions/11/privacy/permissions#manage_overlay

This is highly confusing behaviour from the system, so let’s add an
instruction before navigating to the settings menu.
2025-08-27 11:38:25 +02:00
Tobi ccd76dea1f Merge pull request #12544 from Stypox/download-options
Add option to delete a download without also deleting file
2025-08-27 02:31:14 -07:00
Tobi 10c5a5d02c Merge pull request #12569 from Isira-Seneviratne/Fix-import
Fix database import
2025-08-27 01:55:55 -07:00
TobiGr e1888ede87 Fix JDoc and apply suggestions 2025-08-27 10:38:13 +02:00
TransZAllen 2c35db7a07 [Bug] Fix missing subtitle text in manually downloaded *.SRT files. (issue #10030)
- Previously, *.SRT files only contained timestamps and sequence numbers, without the actual text content.
- Added recursive text extraction to handle nested tags in TTML
  files.(e.g.: <span> tags)
2025-08-27 14:03:42 +08:00
Isira Seneviratne cf4b5e17c5 Fix database import 2025-08-25 14:32:19 +05:30
whistlingwoods 9282cce6a8 fix: unfinished downloads disappear from the downloads list after app gets killed
Author: InfinityLoop1308
Adapted for NewPipe from a fork's this commit https://github.com/InfinityLoop1308/PipePipeClient/commit/1cf059ce5e947aadb6dacd93b2ac9ca81b79cf46
2025-08-22 01:14:24 +05:30
Stypox deb5425871 Merge branch 'dev' into refactor 2025-08-17 12:48:30 +02:00
Stypox 7644066c5a Add option to delete a download without also deleting file 2025-08-16 16:50:01 +02:00
Stypox 9bc8139b8c Merge pull request #12483 from TeamNewPipe/ignore-picasso-update 2025-08-11 17:48:30 +02:00
Isira Seneviratne 48d0ebff9b Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/settings/BackupRestoreSettingsFragment.java
#	gradle/libs.versions.toml
2025-08-04 19:58:25 +05:30
Tobi ff3526b28d Merge pull request #12460 from Isira-Seneviratne/Short-count-refactor
Fix short count formatting for Android versions below 7.0
2025-08-01 10:56:41 -07:00
TobiGr d6c0dc32d1 Correctly ignore new version check for picasso 2025-08-01 10:50:54 +02:00
Stypox 124ab56c5f Merge branch 'master' into dev 2025-07-31 23:52:01 +02:00
Stypox 95a0e0ca39 Merge pull request #12435 from TeamNewPipe/release-0.28.0 2025-07-31 23:51:10 +02:00
Stypox 4d97a7653d Merge pull request #12450 from TeamNewPipe/yt-trending-migration 2025-07-31 23:48:53 +02:00
Hosted Weblateandfool 5aefa4aff2 Translated using Weblate (Tigrinya)
Currently translated at 12.7% (95 of 748 strings)

Co-authored-by: fool <thing-sauna-cussed@duck.com>
2025-07-31 23:43:24 +02:00
Stypox b846746119 Update NewPipeExtractor to v0.24.8 2025-07-31 23:43:19 +02:00
Stypox b7b836e941 Update the names of YT kiosks 2025-07-31 23:43:19 +02:00
Stypox d96c0aebb1 Show tabs above kiosks in drawer 2025-07-31 23:43:19 +02:00
Stypox 8400a9ae8e Remove DEBUG statements and don't replace yt trending with live
You can use this command to test instead:

adb shell run-as org.schabi.newpipe.debug.pr12450 'sed -i '"'"'s#<int name="last_used_preferences_version" value="8" />#<int name="last_used_preferences_version" value="6" />#'"'"' shared_prefs/org.schabi.newpipe.debug.pr12450_preferences.xml' && adb shell run-as org.schabi.newpipe.debug.pr12450 'sed -i '"'"'s#\]}</string>#,{\&quot;tab_id\&quot;:5,\&quot;service_id\&quot;:0,\&quot;kiosk_id\&quot;:\&quot;Trending\&quot;},{\&quot;tab_id\&quot;:5,\&quot;service_id\&quot;:1,\&quot;kiosk_id\&quot;:\&quot;Top 50\&quot;}]}</string>#'"'"' shared_prefs/org.schabi.newpipe.debug.pr12450_preferences.xml'
2025-07-31 23:43:19 +02:00
Stypox 7cecd11f72 [YouTube] Add icons and strings for new trending pages 2025-07-31 23:43:19 +02:00
TobiGr ed93603815 WIP: Add SettingsMigration to change YouTube trending kiosk tab 2025-07-31 23:43:19 +02:00
Stypox 56f79fac13 Merge branch 'release-0.28.0' into dev 2025-07-30 11:42:06 +02:00
Stypox 86efde5996 Merge pull request #12476 from TeamNewPipe/weblate 2025-07-29 20:23:08 +02:00
Stypox ca9fc14c2a Fix name of nepali language (there was a leftover N) 2025-07-29 20:19:31 +02:00
tobigr 7130adb4ec Clean strings 2025-07-29 20:19:31 +02:00
tobigr e08d2d8726 Add new locals to the in-app language chooser 2025-07-29 20:19:31 +02:00
Isira Seneviratne ef29c318b0 Remove NewApi suppression 2025-07-29 06:18:27 +05:30
+27 6516fb96fd Translated using Weblate (Romanian)
Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (French)

Currently translated at 100.0% (748 of 748 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (745 of 749 strings)

Translated using Weblate (Estonian)

Currently translated at 18.6% (16 of 86 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.5% (746 of 749 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.7% (747 of 749 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.7% (747 of 749 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Slovak)

Currently translated at 99.7% (747 of 749 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.5% (746 of 749 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (German)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (German)

Currently translated at 100.0% (749 of 749 strings)

Translated using Weblate (Tamazight (Central Atlas))

Currently translated at 19.2% (144 of 749 strings)

Translated using Weblate (Macedonian)

Currently translated at 79.3% (594 of 749 strings)

Translated using Weblate (Slovenian)

Currently translated at 54.6% (409 of 749 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.7% (95 of 747 strings)

Translated using Weblate (Tigrinya)

Currently translated at 3.4% (3 of 86 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (743 of 747 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 90.6% (78 of 86 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (French)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Polish)

Currently translated at 58.1% (50 of 86 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (German)

Currently translated at 100.0% (86 of 86 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (German)

Currently translated at 100.0% (747 of 747 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439JBYL80IGQTF25UXNR0X1BG@users.noreply.hosted.weblate.org>
Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Drugi Sapog <dindrugi@users.noreply.hosted.weblate.org>
Co-authored-by: Dual Natan <dvapatinatan@gmail.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hakim Oubouali <hakim.oubouali.skr@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: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Marian Hanzel <marulinko@gmail.com>
Co-authored-by: Matej U <mateju@src.gnome.org>
Co-authored-by: Michael Moroni <michaelmoroni@disroot.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NTFSynergy <ntfsynergy@gmail.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Stypox <stypox@pm.me>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Trunars <trunars@abv.bg>
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: Yassin Amir <y6b5@proton.me>
Co-authored-by: erti <erti@users.noreply.hosted.weblate.org>
Co-authored-by: ikanakova <ikanakova@users.noreply.hosted.weblate.org>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Максим Горпиніч <gorpinicmaksim5@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: 赖诚俊 <cosmic.universe.glitch@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/et/
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/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/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ti/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2025-07-28 23:05:05 +02:00
Stypox e9922fe162 Merge pull request #12470 from litetex/cleanup-PlayerHelper-localization 2025-07-28 15:30:07 +02:00
Stypox eea2b7417e Fix player formatters resetting too early on language change
formatters() is called again by the player before the user has a chance to click on the language in the language chooser.

So the correct solution would probably be to attach to https://developer.android.com/reference/android/content/Intent#ACTION_LOCALE_CHANGED, but let's keep it simple. I added `PlayerHelper.resetFormat();` in `ContentSettingsFragment.onDestroy()` and it works. It will mean the player formatters will be reset every time the user exits content settings, but whatever.
2025-07-28 15:29:06 +02:00
litetex 893a1cb699 Encapsulate Formatters in PlayerHelper
and reset them when the language is changed/changing.
This way they will be re-initialized on the next call.

Also Remove a bunch of outdated/non-thread safe code (STRING_FORMATTER)
2025-07-28 15:11:27 +02:00
litetex ebd5e1a318 Remove unused method 2025-07-28 15:11:27 +02:00
litetex 70841db92f Cleanup `Localization` formatting 2025-07-28 15:11:27 +02:00
litetex 859555e129 Use regions 2025-07-28 15:11:27 +02:00
Stypox c1cef19b33 Merge pull request #12455 from TobiGr/nextPage-nullable 2025-07-28 14:52:08 +02:00
Stypox 9ba30887f9 Improve null checking further in SearchFragment.handleNextItems 2025-07-28 14:43:46 +02:00
Stypox 0ef38e3a4d Merge pull request #12472 from TeamNewPipe/user-agent-140 2025-07-28 14:03:42 +02:00
Isira Seneviratne 9f11db8e06 Improve scale display 2025-07-28 09:02:52 +05:30
Isira Seneviratne fece0741e5 Suppress NewApi 2025-07-27 15:47:06 +05:30
TobiGr a9ce2e9605 Update USER_AGENT to Firefox 140 ESR 2025-07-27 09:39:53 +02:00
Isira Seneviratne b9b47fc520 Update manifest, startForeground call 2025-07-27 11:58:01 +05:30
Isira Seneviratne 59db955493 Fix new streams notification issue 2025-07-27 11:31:23 +05:30
Isira Seneviratne 2915ab6aef Merge pull request #12462 from Isira-Seneviratne/Merge-dev-to-refactor
Merge dev to refactor
2025-07-25 06:23:19 +05:30
Isira Seneviratne be662a9f1a Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/build.gradle
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
#	app/src/main/java/org/schabi/newpipe/player/mediabrowser/PackageValidator.kt
2025-07-24 19:28:41 +05:30
Isira Seneviratne 22a709d53b Merge pull request #12388 from mikooomich/sdk35
Target SDK 35
2025-07-24 08:18:32 +05:30
Michael Zh 329d76c857 Bump emulator target 33 -> 35 2025-07-23 22:30:34 -04:00
Isira Seneviratne 9f526e8e8f Fix short count formatting for Android versions below 7.0 2025-07-24 07:56:44 +05:30
Michael ZhandIsira Seneviratne 50caba6606 Fix compile
Co-Authored-By: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com>
2025-07-23 18:49:28 -04:00
Michael Zh 26443f9f14 WIP: Fix compile 2025-07-23 18:45:30 -04:00
Michael Zh 366129eee2 Fix error toast crash 2025-07-23 18:45:30 -04:00
Michael Zh 4c8d44b6ba Bump compileSdk to 36 and targetSdk to 35
* Sdk 36 requires edge to edge, so use 35 so we can opt out for now
2025-07-23 18:45:30 -04:00
Michael Zh 14cd562ebd Update manifest for sdk34 FGS changes 2025-07-23 18:45:30 -04:00
Michael Zh 04ef608f7a Specify RECEIVER_EXPORTED/RECEIVER_NOT_EXPORTED for sdk34 2025-07-23 18:45:30 -04:00
Stypox 71fcc5ebce Release v0.28.0 (1005) 2025-07-23 14:22:07 +02:00
TobiGr 30e33d59e8 Use correct fix for nextPage being null while creating error report in SearchFragment.handleNextItems() 2025-07-22 16:12:02 +02:00
Kouki BadrandSiddhesh Naik a4bd82be8a fix: handle nullable nextPage behavior when searching albums #12401 (#12408)
* fix: handle nullable nextPage behavior when searching albums #12401

* feat: add nullable annotation to newPage attribute in SearchFragment

* Updated more usages of InfoItemsPage#getNextPage. Nullability is already handled in these areas so no other changes needed

---------

Co-authored-by: Siddhesh Naik <siddheshnaik20@protonmail.com>
2025-07-22 08:58:56 +05:30
Isira Seneviratne 1534c880ef Fix compilation errors 2025-07-21 09:11:24 +05:30
Isira Seneviratne ded7205588 Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/App.java
#	app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
#	app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
#	app/src/main/java/org/schabi/newpipe/local/subscription/ImportConfirmationDialog.java
#	app/src/main/java/org/schabi/newpipe/player/Player.java
#	app/src/main/java/org/schabi/newpipe/player/PlayerService.java
#	app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
2025-07-21 09:05:32 +05:30
litetex 45589dbf26 Merge pull request #12444 from Isira-Seneviratne/Per-app-language
Enable per-app language preferences for Android < 13
2025-07-20 22:20:12 +02:00
litetex 99ae3fdd4e Removed no longer needed translation key 2025-07-20 22:05:05 +02:00
litetex f48e73eb2a Cleaned up some code related to app language
* Use build constants when possible
* Inline variables
* Don't use var for normal-sized types (that way it's easier to review)
* Split code into methods
2025-07-20 21:52:07 +02:00
Isira Seneviratne 99003bab07 Clean up imports 2025-07-20 16:43:37 +05:30
Isira Seneviratne 9e14f93186 Properly handle when system language is selected 2025-07-20 16:27:07 +05:30
Isira Seneviratne 1f2e5799f7 Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
#	app/src/main/res/values/strings.xml
2025-07-20 05:57:27 +05:30
Isira Seneviratne abd9aade87 Update AppCompat 2025-07-20 05:24:56 +05:30
Isira Seneviratne b8f9c125cd Add link for future reference 2025-07-20 05:03:20 +05:30
Isira Seneviratne 893a227ab1 Enable per-app language preferences for Android < 13 2025-07-20 04:50:49 +05:30
Stypox 0db859e225 Merge pull request #12438 from TeamNewPipe/soundcloud/top_50 2025-07-19 20:53:44 +02:00
Stypox e61f98bd47 Merge pull request #12434 from TeamNewPipe/fix-new-badge-links-on-readme 2025-07-19 20:44:03 +02:00
Stypox 991d9ea3df Fix state not saved 2025-07-19 20:39:55 +02:00
Stypox f94892166d Improve comment 2025-07-19 20:34:09 +02:00
Stypox 9697112db6 Show error panel in EmptyFragment 2025-07-19 19:41:13 +02:00
litetex f64dba0107 Fix new badge links on Readme being rendered incorrectly
For all non default Readmes
2025-07-19 22:45:32 +05:30
litetex 9bf01e1241 Fix new badge links on Readme being rendered incorrectly 2025-07-19 22:45:32 +05:30
Stypox 474efbebc1 Merge pull request #12437 from TeamNewPipe/localization-main-page-content 2025-07-19 19:04:34 +02:00
tobigr fe58ec85ed Fix error detection when loading main page tabs
Do not crash if something unexpected happens.
2025-07-19 13:37:54 +02:00
tobigr 941f85781b Display dialog informing the user about the removal of the Top 50 kiosk 2025-07-19 13:37:54 +02:00
tobigr 7e0ee4eb7a Update Extractor and add migration to remove SoundCloud Top 50 kiosk 2025-07-18 18:59:28 +02:00
Isira Seneviratne f93806293a Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/player/PlayerService.java
#	app/src/main/res/values-ca/strings.xml
2025-07-18 19:14:58 +05:30
Isira Seneviratne fa75283d48 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt
2025-07-18 14:17:21 +05:30
tobigr 4a41214df4 Do not capitalize "page" for main page content options 2025-07-18 10:28:59 +02:00
Stypox 938265d127 Update NewPipeExtractor 2025-07-17 23:57:03 +02:00
Stypox ba4e7a3c7f Add changelog for v0.28.0 (1005) 2025-07-17 10:18:10 +02:00
+68 58b5ccb66f Translated using Weblate (Czech)
Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (745 of 745 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (745 of 745 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (745 of 745 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (745 of 745 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Serbian)

Currently translated at 99.7% (745 of 747 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (French)

Currently translated at 100.0% (747 of 747 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (Tigrinya)

Currently translated at 12.6% (94 of 744 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 8.2% (7 of 85 strings)

Translated using Weblate (Serbian)

Currently translated at 16.4% (14 of 85 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Azerbaijani (Southern))

Currently translated at 1.1% (1 of 85 strings)

Added translation using Weblate (Azerbaijani (Southern))

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Serbian)

Currently translated at 16.4% (14 of 85 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (Romanian)

Currently translated at 99.7% (742 of 744 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (French)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (German)

Currently translated at 100.0% (744 of 744 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 97.6% (83 of 85 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (Kabyle)

Currently translated at 29.0% (215 of 741 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (741 of 741 strings)

Added translation using Weblate (Luri (Bakhtiari))

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Persian)

Currently translated at 94.3% (699 of 741 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Persian)

Currently translated at 94.1% (698 of 741 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Finnish)

Currently translated at 98.5% (730 of 741 strings)

Translated using Weblate (French)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (French)

Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (German)

Currently translated at 100.0% (85 of 85 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (German)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 69.4% (59 of 85 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Latvian)

Currently translated at 21.4% (18 of 84 strings)

Translated using Weblate (Latvian)

Currently translated at 99.7% (739 of 741 strings)

Translated using Weblate (Latvian)

Currently translated at 20.2% (17 of 84 strings)

Translated using Weblate (Latvian)

Currently translated at 16.6% (14 of 84 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (French)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Greek)

Currently translated at 32.1% (27 of 84 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Urdu)

Currently translated at 69.2% (513 of 741 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Catalan)

Currently translated at 90.2% (669 of 741 strings)

Translated using Weblate (Estonian)

Currently translated at 17.8% (15 of 84 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 76.7% (569 of 741 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 76.2% (565 of 741 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Kabyle)

Currently translated at 28.8% (214 of 741 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Spanish)

Currently translated at 99.8% (740 of 741 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 67.8% (57 of 84 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (737 of 741 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (French)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (German)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Breton)

Currently translated at 13.6% (101 of 741 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (741 of 741 strings)

Translated using Weblate (Breton)

Currently translated at 12.1% (90 of 741 strings)

Translated using Weblate (Breton)

Currently translated at 7.6% (57 of 741 strings)

Translated using Weblate (Breton)

Currently translated at 7.4% (55 of 741 strings)

Translated using Weblate (Breton)

Currently translated at 7.4% (55 of 741 strings)

Translated using Weblate (Breton)

Currently translated at 7.4% (55 of 741 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.8% (740 of 741 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (741 of 741 strings)

Added translation using Weblate (Breton)

Translated using Weblate (Romanian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Finnish)

Currently translated at 98.5% (729 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (740 of 740 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439JBYL80IGQTF25UXNR0X1BG@users.noreply.hosted.weblate.org>
Co-authored-by: AP <kubanto@users.noreply.hosted.weblate.org>
Co-authored-by: Abu Sarim Hindi <sarfaraz.ahmed78@gmail.com>
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: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: Bastian <basti.anderl774@gmail.com>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Darth23G (DarthGamer23) <fref2329@gmail.com>
Co-authored-by: Deleted User <noreply+48943@weblate.org>
Co-authored-by: Dream X <nodem49316@daupload.com>
Co-authored-by: Drugi Sapog <dindrugi@users.noreply.hosted.weblate.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Fareedar Islami <fareedar.islami@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Fjuro <git@alius.cz>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: GiannosOB <giannos2105@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: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jay Tromp <jaytromp@pm.me>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Jordi Cambrells <cambrells@users.noreply.hosted.weblate.org>
Co-authored-by: Jordi Cambrells <hanta.hrabal@gmail.com>
Co-authored-by: Juzé <dedakir923@exoular.com>
Co-authored-by: KaGaster <mohamed.kooli@medtech.tn>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Mandeep <mandeeps708@gmail.com>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Michael Moroni <michaelmoroni@disroot.org>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Mohammed al-Qubati <mhraqeeb@gmail.com>
Co-authored-by: Mücteba <muctebanesiri@gmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nick Wick <NickWick@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: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Q. Boii <sf1hks@marketmail.info>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: THANOS SIOURDAKIS <siourdakisthanos@gmail.com>
Co-authored-by: Trunars <trunars@abv.bg>
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: Yasser Althuwaini <ymth2000@outlook.com>
Co-authored-by: Yauhen <bugomol@users.noreply.hosted.weblate.org>
Co-authored-by: ab_09 <ab_09@users.noreply.hosted.weblate.org>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: dekiw39846 <dekiw39846@bariswc.com>
Co-authored-by: elid <shopisrael12@gmail.com>
Co-authored-by: gfbdrgn <erfvvgtyhbnjhyuu@wireconnected.com>
Co-authored-by: late <late@users.noreply.hosted.weblate.org>
Co-authored-by: moton03 <moton.cat@outlook.com>
Co-authored-by: rehork <cooky@e.email>
Co-authored-by: rimasx <riks_12@hot.ee>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: Максим Горпиніч <gorpinicmaksim5@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Co-authored-by: Саша Петровић <salepetronije@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: 李恩霆 <timothylee0802@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/azb/
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/en_GB/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/et/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lv/
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/sr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ta/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translation: NewPipe/Metadata
2025-07-17 09:16:38 +02:00
Stypox da36b8a140 Merge pull request #12337 from Profpatsch/video-detail-fragment-kotlin-conversion 2025-07-16 15:56:39 +02:00
Stypox 046ea7301b Apply Kotlin suggestion by @Isira-Seneviratne 2025-07-16 15:53:05 +02:00
Stypox 3998982002 Smaller style fixes 2025-07-16 15:53:05 +02:00
Stypox a4182b474b More improve Kotlin converted from java in various places 2025-07-16 15:53:05 +02:00
Stypox 317db719db Fix comments in PlayerUiList 2025-07-16 15:47:00 +02:00
Stypox 3f62ec7e53 Improve Kotlin converted from java in various places 2025-07-16 15:46:59 +02:00
Profpatsch 7330541499 PlayerUIList: remove remaining uses of getOpt
mediaSession is now `@NonNull`, so the getter is as well.
2025-07-16 15:42:59 +02:00
Profpatsch ed0051a3f6 Player: small class comment 2025-07-16 15:42:59 +02:00
Profpatsch 91aed1e240 VideoDetailFragment: replace every getOpt() with get() 2025-07-16 15:42:59 +02:00
Profpatsch 38ed1da79e PlayerHolder: use object class to implement singleton pattern 2025-07-16 15:42:59 +02:00
Profpatsch cc3ecd4169 VideoDetailFragment: convert to kotlin (mechanical, fixup)
Mostly 1:1, I had to fix a few places where the automatic conversion
did not infer the right kotlin types, and places where it tried to
convert to `double` instead of using `float` like the original.

Everything else is the result of automatic conversion.
2025-07-16 15:42:58 +02:00
Profpatsch bf72fd1fa5 VideoDetailFragment: convert to kotlin (mechanical, failing)
Just the conversion, errors still there for easier rebasing later.
2025-07-16 15:42:12 +02:00
Profpatsch 86b27cf77d PlayerHolder: kotlinify optional calls 2025-07-16 15:15:28 +02:00
Profpatsch 4fd3ddf392 PlayerHolder: kotlinify setListener 2025-07-16 15:14:50 +02:00
Profpatsch fc7daa96e9 PlayerHolder: kotlinify getters 2025-07-16 15:14:50 +02:00
Profpatsch 9b22773070 PlayerHolder: convert to kotlin (mechanical) 2025-07-16 15:14:49 +02:00
Profpatsch 86063fda6a PlayQueueActivity: inline getServiceConnection() and bind()
Both are only used once, and it’s easier to see what’s going on if
they are just inlined in `onCreate`.
2025-07-16 15:07:43 +02:00
Profpatsch c9be4066f2 PlayerService: inline init block & make non-optional 2025-07-16 15:07:43 +02:00
Profpatsch 462f0ad5c0 PlayerUIList: inline init block 2025-07-16 15:07:43 +02:00
Stypox 4e94b2602d Merge pull request #12258 from Profpatsch/show-service-name-in-search 2025-07-16 15:02:43 +02:00
Stypox 4ddc0648ef Merge pull request #12412 from Stypox/fix-ghost-notifications 2025-07-14 21:56:22 +02:00
Tobi 4c920a4406 Merge pull request #12367 from watermelon42/3783_Import_Soundcloud_likes
Support Soundcloud likes in channel and feed
2025-07-14 01:01:02 -07:00
watermelon42 1c0eabf75c Updated extractor version to latest commit 2025-07-13 16:21:42 +02:00
Isira Seneviratne 8b2c61432e Cleanup 2025-07-12 06:14:42 +05:30
Isira Seneviratne eac0dcd70d Run database checkpoint on IO coroutine dispatcher 2025-07-12 06:06:29 +05:30
Isira Seneviratne f89ee3b376 Rename .java to .kt 2025-07-12 06:06:24 +05:30
Isira Seneviratne b0427ec0d0 Merge branch 'refactor' into History-Compose 2025-07-12 06:06:15 +05:30
Isira Seneviratne 183cc580fc Merge pull request #12415 from Isira-Seneviratne/Zip-migrate-Path
Migrate zip import/export to use Path
2025-07-12 06:04:31 +05:30
Isira Seneviratne 4eae345057 Combine history ordered query methods into one 2025-07-11 22:26:33 +05:30
watermelon42 f119a368d8 Added support for importing Soundcloud likes as a new tab before About in a user's channel.
The likes are also retrieved in the feed if the user is subscribed to.
2025-07-11 09:50:33 +02:00
Isira Seneviratne 52ff052d6a Merge pull request #12347 from Isira-Seneviratne/PlayQueueItem-equals
Add PlayQueueItem equals and hashCode
2025-07-10 02:18:43 +05:30
Isira Seneviratne 0e422a42ee Merge branch 'refactor' into History-Compose 2025-07-09 07:49:26 +05:30
Isira Seneviratne 31f8dd05a7 Convert play queue classes to Kotlin 2025-07-09 07:48:08 +05:30
Isira Seneviratne c2b6c71947 Rename .java to .kt 2025-07-09 07:48:07 +05:30
Isira Seneviratne bb7873d157 Fix Sonar warning 2025-07-09 07:48:07 +05:30
Isira Seneviratne e2a02a1f86 Fix some issues 2025-07-09 07:48:07 +05:30
Isira Seneviratne 690af88db9 Add PlayQueueItem equals and hashCode 2025-07-09 07:48:07 +05:30
Isira Seneviratne 4ffadc2057 Inline variable 2025-07-09 07:47:25 +05:30
Isira Seneviratne 225cb91105 Use InputStream#transferTo() 2025-07-09 07:47:25 +05:30
Isira Seneviratne 3e106b5e4f Fix DB import/export issue 2025-07-09 07:47:25 +05:30
Isira Seneviratne 72b67ab5d4 Refactor zip import/export using Path 2025-07-09 07:47:25 +05:30
Isira Seneviratne 840084d2c9 Merge pull request #12419 from Isira-Seneviratne/Merge-dev-to-refactor
Merge dev to refactor
2025-07-09 07:46:11 +05:30
Isira Seneviratne 2acfefd0bc Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java
2025-07-09 07:10:18 +05:30
Stypox f3c20d43be Merge pull request #12410 from Stypox/fix-android-auto-thumbnails 2025-07-08 11:42:20 +02:00
Tobi c9559fa801 Merge pull request #12416 from Stypox/fix-fullscreen-clear-queue-prompt
Fix fullscreen eliciting "clear queue" prompt
2025-07-07 11:47:27 -07:00
Stypox 8ab79488e9 Merge pull request #12409 from Stypox/readme-badge-size-fix 2025-07-07 16:26:20 +02:00
Stypox f0b26e208b Update notice about rewrite in the README 2025-07-07 16:25:38 +02:00
Stypox 79084568f2 Fix fullscreen eliciting "clear queue" prompt 2025-07-07 15:07:46 +02:00
Stypox 705b5e5580 Fix ghost notifications on Android 10
Fixes #12400, see there for explanation. Citing from there:

So apparently the problem is onGetRoot always returning a BrowserRoot instance. Making it return null solved the issue (but again, breaks Android Auto compatibility). It turns out (see https://stackoverflow.com/q/63818988/) that onGetRoot is also used for media resumption https://developer.android.com/media/implement/surfaces/mobile#mediabrowserservice_implementation, which causes a new notification to pop up (in this case a useless notification because our onGetRoot does not return something that can be used for resumption). So what needs to be done is to check if rootHints?.getBoolean(EXTRA_RECENT) == true and if that's the case not return anything (as EXTRA_RECENT is used by the system for resumption).

The PackageValidator file is taken from https://github.com/android/uamp/blob/329a21b63c247e9bd35f6858d4fc0e448fa38603/common/src/main/java/com/example/android/uamp/media/PackageValidator.kt .
2025-07-07 01:06:59 +02:00
Stypox a4d457b2b2 Use Kotlin's .toUri() instead of Uri.parse() 2025-07-06 15:05:30 +02:00
Stypox 834c93f22a Fix thumbnails appearing on Android Auto even if disabled 2025-07-06 14:49:09 +02:00
Stypox a0adeb0099 Fix "Get it on F-Droid" appearing giant in README 2025-07-06 13:51:59 +02:00
Isira Seneviratne 2dd11f70a3 Merge pull request #12356 from dev-victoria/fix-json-importing-oldandroid
check if the JSON MimeType is supported
2025-07-04 07:06:34 +05:30
Stypox d048bca8b4 Temporarily disable sonarcloud CI step 2025-06-28 15:17:18 +02:00
Diana Victoria Furrer 0c9f5ddcaf change according to Isira-Seneviratne suggestion 2025-06-17 15:42:01 +02:00
Diana Victoria Furrer aa75a1449f use MimeTypeMap from android webkit to check if the json MimeType is unsupported 2025-06-15 02:19:56 +02:00
Stypox d81b571d05 Merge pull request #12348 from Isira-Seneviratne/Merge-dev-to-refactor 2025-06-11 10:13:36 +02:00
Isira Seneviratne 6efb92a38f Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/MainActivity.java
2025-06-11 08:20:00 +05:30
Isira Seneviratne 12a97c6439 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt
#	app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt
2025-06-07 06:23:10 +05:30
Stypox f16becc872 Merge pull request #12217 from SttApollo/Update-EmptyStateComposable 2025-06-06 15:49:13 +02:00
Stypox 60ea317e61 NoComments -> NoVideos in related items screen 2025-06-06 15:36:07 +02:00
Su TT e53f0ff94a Update EmptyStateComposable usages to include modifiers 2025-06-05 12:33:21 -04:00
Profpatsch 16e32dfc96 SearchFragment: show filter in brackets behind service name
This is still not perfect, but it will show the selected search filter
in addition to the service name, like: “Search YouTube (Playlists)”.

It will not distinguish between a YouTube Music and Youtube filter, so
it will display the same thing. Could be improved, but then the text
gets too long! :(
2025-06-05 14:30:04 +02:00
Profpatsch 006b4c9ae1 Merge pull request #11965 from Profpatsch/player-classes-kotlin-conversion
Some simple refactors & beginning of kotlin conversions of the player classes
2025-06-05 14:13:33 +02:00
Profpatsch 73fef268fc PlayerService/PlayerUIList: some small improvements 2025-06-05 13:52:04 +02:00
Profpatsch cf8fe95abf PlayerService: runtime-assert that we get passed a service
We directly call the `getService` function after receiving the
argument, so resolving the WeakPointer should never return `null` in
our case.
Of course there could be a race condition in theory, but I feel like
if that happens we have bigger problems?
2025-06-05 13:52:04 +02:00
Profpatsch 36115c3164 PlayerService: remove !! where possible
It’s a bit unwieldy in places, but should improve the safety of the
code in the face of possible race conditions.
2025-06-05 13:52:04 +02:00
Profpatsch be373dca8d PlayerUIList: make UI list private 2025-06-05 13:52:04 +02:00
Profpatsch f5a4af2d67 Player: destroy -> saveAndShutdown 2025-06-05 13:52:04 +02:00
Profpatsch 06cf511188 PlayerHolder: improve interface docstrings 2025-06-05 13:52:04 +02:00
Profpatsch 26050d808e VideoPlayerUi: suppress warnings
The `R.id` link from the comment cannot be resolved, so let’s not link
it for now.

We are using some exoplayer2 resources, let’s silence the warning.
2025-06-05 13:52:04 +02:00
Profpatsch 0b32738d42 VideoDetailFragment: remove duplicate code in startLoading 2025-06-05 13:52:04 +02:00
Profpatsch c37db85b97 VideoDetailFragment: apply more IDE suggestions 2025-06-05 13:52:04 +02:00
Profpatsch 4d6e1a4ecf VideoDetailFragment: apply visibility suggestions
Because the class is final, protected does not make sense (Android
Studio auto-suggestions)
2025-06-05 13:52:04 +02:00
Profpatsch b5dd49ecd3 PlayerService: simplify nullable calls, getters 2025-06-05 13:52:04 +02:00
Profpatsch 945fbd884b PlayerService: Convert to kotlin (mechanical) 2025-06-05 13:52:04 +02:00
Profpatsch 545c4f078f PlayerUIList: restrict superclasses a little 2025-06-05 13:52:04 +02:00
Stypox 8c4a789f78 Merge pull request #12302 from davidasunmo/update-readmes 2025-06-04 11:59:56 +02:00
Stypox 769e98acd0 Show search filter in search bar hint 2025-06-04 11:54:31 +02:00
Stypox 8e036b5e69 Merge pull request #12325 from dev-victoria/FeedGroupTab 2025-06-04 11:24:32 +02:00
Stypox 571b7bc74b Improve layout of select_feed_group_item 2025-06-04 11:18:04 +02:00
Stt_lensandStypox d4cd54fd7b Update app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt
Co-authored-by: Stypox <stypox@pm.me>
2025-05-31 12:27:16 -04:00
Stt_lensandStypox 15ab3df511 Update app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt
Co-authored-by: Stypox <stypox@pm.me>
2025-05-31 12:07:37 -04:00
Audric V. 033cc08c26 Merge pull request #12322 from dev-victoria/tiny-code-fixes
Fix equality comparison in Tab class
2025-05-31 16:56:37 +02:00
Diana Victoria Furrer 205d18f4c4 Use GroupName for the Settings Text.
The Tabname displays the default Feed title.
2025-05-31 14:11:26 +02:00
Diana Victoria Furrer 712724211c added FeedGroup to Tab Settings UnitTest 2025-05-31 01:41:06 +02:00
Diana Victoria Furrer fd09e6147f # Fixed Feed Group Titlebar
- use default fragment_feed_title for TabName
- only clear FeedFragment bar subtitle when it matches the groupName to clear.
2025-05-31 01:30:49 +02:00
Diana Victoria Furrer 279caac915 # Change
Layout select_feed_group_item (FeedGroup Picker in the Settings)
Remove rounded style from the icons
2025-05-30 21:00:37 +02:00
Diana Victoria Furrer f8ed8e575e # Change
Added FEEDGROUP Tab Code to
 - ChooseTabsFragment
 - Tab

Added strings:
- feed_group_page_summary
2025-05-30 20:47:37 +02:00
Diana Victoria Furrer 436626fa83 # Change
Adjusted select_feed_group_fragment Layout
 - reference select_feed_group_item layout
 - use new Strings

Added strings:
- select_a_feed_group
- no_feed_group_created_yet
2025-05-30 17:54:49 +02:00
Diana Victoria Furrer 7c3989ff93 # Change
Adjusted the new Class SelectFeedGroupFragment for its Role
- Renamed Variables
- adjusted Imports
- adjusted Interface with FeedGroupEntity Values
2025-05-30 17:45:51 +02:00
Diana Victoria Furrer e6c4690e7d # Copied Layouts
Copied select_channel_fragment to select_feed_group_fragment

Copied select_channel_item to select_feed_group_item

# Change
Replaced the Layout references in the new Class SelectFeedGroupFragment
2025-05-30 17:07:19 +02:00
Diana Victoria Furrer 86869f0a14 Copied SelectFeedGroupFragment from SelectChannelFragment 2025-05-30 16:55:07 +02:00
Diana Victoria Furrer aa0b45c05f ChannelTab.equals fix comparison 2025-05-30 13:21:45 +02:00
David Asunmo 55bf74b4a7 Fix CI status badge 2025-05-24 02:15:45 +01:00
David Asunmo de3d11568d Add nightly builds to all readmes
Add matrix to .ru
2025-05-22 03:29:12 +01:00
David Asunmo 16077dee80 Add matrix chat link to all READMEs 2025-05-22 03:29:12 +01:00
Stypox c9155f7834 Merge pull request #12298 from davidasunmo/add-dev-refactor-nightly-badges
Add dev and refactor nightly build badges
2025-05-20 11:13:38 +02:00
David 7dd1abdf9c Add dev and refactor nightly build badges
bottom text
2025-05-20 02:22:47 +01:00
Isira Seneviratne f41b34c090 Merge pull request #11759 from Isira-Seneviratne/Import-export-worker
Rewrite import and export subscriptions functionality using coroutines
2025-05-16 01:16:34 +05:30
Isira Seneviratne eb6a03aa1e Merge branch 'refactor' into History-Compose
# Conflicts:
#	gradle/libs.versions.toml
2025-05-14 21:05:16 +05:30
Stypox d1954baf29 Merge pull request #11829 from Profpatsch/PlayerUIList-to-kotlin
Player UI list to kotlin
2025-05-13 23:36:16 +02:00
Profpatsch a8da9946d1 PlayerUiList: guard list actions with mutex
The new implementation would throw `ConcurrentModificationExceptions`
when destroying the UIs. So let’s play it safe and put the list behind
a mutex.

Adds a helper class `GuardedByMutex` that can be wrapped around a
property to force all use-sites to acquire the lock before doing
anything with the data.
2025-05-11 15:23:03 +02:00
Profpatsch 3d069cdf5b PlayerUIList: rename get to getOpt and make get nullable
In Kotlin, dealing with nulls works better so we don’t need optional.
2025-05-11 15:12:37 +02:00
Profpatsch eccedc0ab0 PlayerUIList: transform to kotlin
And simplify the code a little
2025-05-11 15:06:52 +02:00
Profpatsch f3858e70a3 Merge pull request #11789 from Thompson3142/fix_background_crash_focus
Fix background crash focus
2025-05-09 23:41:38 +02:00
Thompson3142 76202e6b4b Remove no longer needed dependency 2025-05-09 22:29:05 +02:00
Thompson3142 90e2f234e7 Initial commit for better handling of background crashes
Fix crashing behaviour with entry in SharedPreferences

A few minor improvements

Added docs for isInBackground

Some more minor changes

Overwrite methods in MainActivity instead of creating a new class
2025-05-09 22:29:00 +02:00
Profpatsch 42a52b7118 Merge pull request #12259 from Profpatsch/put-@-on-right-side-of-rtl-usernames
Comments: Put @ on the right side of right-to-left usernames
2025-05-08 21:46:00 +02:00
Stypox 7cecda5713 Merge branch 'dev' into refactor
Had to make some adjustments to make https://github.com/TeamNewPipe/NewPipe/pull/12188 work
2025-05-08 15:34:00 +02:00
Stypox d9dccfa8af Merge branch 'master' into dev 2025-05-08 15:04:06 +02:00
Profpatsch e554c77f2e Comments: Put @ on the right side of right-to-left usernames
From the discussion in
https://github.com/TeamNewPipe/NewPipe/pull/12188 it reads more
natural for RTL readers.
2025-05-07 14:20:44 +02:00
Stypox 81b4e3f970 Hotfix release v0.27.7 (1004) 2025-05-07 12:52:43 +02:00
TobiGr ef068e1eca Update NewPipe Extractor and add new proguard rules
New rules are required since Rhino and Rhino Engine 1.8.0
2025-05-07 12:50:37 +02:00
Stypox 8407b5aefd Add translated changelogs for v0.27.7
Copied from 985.txt
2025-05-07 12:49:31 +02:00
Stypox b6aa07545a Add changelog for v0.26.7 (1004) 2025-05-07 12:48:59 +02:00
Stypox 1dcb1953ba Update NewPipeExtractor to v0.24.6
For some reason
com.github.TeamNewPipe.NewPipeExtractor:v0.24.6
didn't work, but
com.github.TeamNewPipe.NewPipeExtractor:NewPipeExtractor:v0.24.6
as suggested on https://jitpack.io/#TeamNewPipe/NewPipeExtractor/v0.24.6 worked...
2025-05-07 12:36:08 +02:00
Profpatsch 862a8e8f26 Merge pull request #12188 from VougJo23/commentsfix
fix: support RTL usernames in comment header
2025-05-07 12:20:23 +02:00
Profpatsch 88395fa852 Merge pull request #12202 from AndrianaBilali/fix/timestamp-clicks-in-replies
Fix timestamps not working in comment replies
2025-05-07 12:07:03 +02:00
VougJo23 8d679626f0 fix: support RTL usernames in comment header
The `@` gets added by the youtube API and thus is a fixed member of
the username, so we do some simple detection logic to handle that
case (otherwise the `@` will be at the right side of a RTL username,
which is different of how Youtube displays these usernames in the
browser).

Fixes https://github.com/TeamNewPipe/NewPipe/issues/12141
2025-05-07 12:05:09 +02:00
Profpatsch d2dc20c551 SearchFragment: show service name in search hint
The only hint (haha) which service one is searching in is currently
the color of the background. This is super confusing, yesterday a
friend tried to search for a video on youtube and the app was set to
Bandcamp, and they were super confused why nothing turned up.

So let’s put the name of the service in the hint!

The `updateService()` thing is a little confused, but I didn’t want
to refactor to improve the logic. It’s not doing anything
computationally intensive anyway.

For PeerTube, the sidebar calls it FramaTube but the service name is
PeerTube, I’m not sure why that is the case. Looks like the string
depends on the name of the instance? Hm, can be improved later I
think.
2025-05-07 10:12:41 +02:00
AndrianaandIsira Seneviratne e7f3750f5e Fix timestamps not working in comment replies
Use LinkMovementMethodCompat for comment links

Co-authored-by: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com>

Update import

Use LongPressLinkMovementMethod
2025-05-06 17:12:17 +02:00
j-haldane 48e826e912 Fix header crash in History List view (#12214)
* Adapt header handling changes from other recyclerview adapters to fix issue #4475 in StatisticsPlaylistFragment

* Remove unneeded LayoutInflater

* Revert "Remove unneeded LayoutInflater"

This reverts commit ab73dc1e72.

* Revert "Adapt header handling changes from other recyclerview adapters to fix issue #4475 in StatisticsPlaylistFragment"

This reverts commit 2abe71cc98.

* Remove header animation causing view recycling issue
2025-05-06 17:07:45 +02:00
Profpatsch 088cb8353e Merge pull request #12256 from Profpatsch/improve-jitpack-workaraund-docs
build.gradle: Improve jitpack workaround doc & fix hash
2025-05-06 12:56:38 +02:00
Profpatsch 5ca544bc42 build.gradle: Improve jitpack workaround doc & fix hash 2025-05-06 10:48:20 +02:00
Isira Seneviratne b6144d01f3 Merge branch 'refactor' into Import-export-worker 2025-04-29 10:18:13 +05:30
Su TT df11e53a74 Refactor EmptyStateComposable to remove modifier from EmptyStateSpec and fix modifier usage ... 2025-04-28 17:24:11 -04:00
Stypox aa1b7f8584 Merge pull request #12215 from naveensingh/fix-image-minimizer
Fix image minimizer pattern
2025-04-28 07:34:06 +02:00
Naveen Singh ce16c6df5f Fix image minimizer pattern
Added non-capturing group that matches either:

 - `user-attachments/assets`
 - `owner/repo/assets/digits`
2025-04-27 19:35:31 -04:00
Isira Seneviratne 1d94fd1582 Merge pull request #12195 from Isira-Seneviratne/Merge-dev-to-refactor
Merge dev to refactor
2025-04-27 08:01:33 +05:30
Isira Seneviratne c9542ad6fd Update extractor 2025-04-27 07:43:52 +05:30
Isira Seneviratne cbe598182a Merge branch 'refactor' into Import-export-worker 2025-04-20 07:10:07 +05:30
Isira Seneviratne 27a29aefc2 Update Jetpack Compose and Room 2025-04-17 07:32:41 +05:30
Isira Seneviratne 7615f79aca Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
2025-04-14 07:29:30 +05:30
Stypox 276bf390b2 Merge pull request #12117 from malania02/dev
Show download date of downloaded videos
2025-04-11 20:17:27 +02:00
Isira Seneviratne 451c89b8e2 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/res/values-be/strings.xml
2025-04-10 11:29:02 +05:30
malania02 f39eda086f Fix for overlapping 2025-04-09 23:40:14 +02:00
Stypox 756327da39 Merge pull request #12093 from mileskrell/mileskrell/support-per-app-language-preferences
Support per-app language preferences
2025-04-08 23:13:07 +02:00
Stypox 5840d3a437 Merge pull request #12150 from FineFindus/fix/potoken-index
[YouTube] Access first element if array size is one
2025-04-08 23:06:04 +02:00
Stypox 47299c9184 Merge pull request #12164 from Isira-Seneviratne/Merge-dev-to-refactor
Merge dev to refactor
2025-04-08 10:55:28 +02:00
Isira Seneviratne 6486f2de56 Merge branch 'dev' into Merge-dev-to-refactor
# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
#	app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt
#	app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
#	app/src/main/java/org/schabi/newpipe/player/PlayerService.java
#	app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceExtendedEventListener.java
#	app/src/main/java/org/schabi/newpipe/player/helper/PlayerHolder.java
#	app/src/main/res/values-is/strings.xml
2025-04-08 05:42:31 +05:30
FineFindus e1dedd45ed [YouTube] Access first element if array size is one
Fixes a regression, where if the challenge data array size was one, the second element
would be accessed, leading to a crash.
This was introduced when porting the challenge parsing from JS to
Kotlin.

Ref: https://github.com/TeamNewPipe/NewPipe/commit/53b599b042ff23cf2e74e3e8d90609b574a30fd1
2025-04-02 22:14:01 +02:00
Isira Seneviratne 287db90f62 Sort using last played by default 2025-03-31 07:15:36 +05:30
malania02 912f07a1dd Missing lines added 2025-03-30 14:50:05 +02:00
Miles Krell 205466c56a Move call to setApplicationLocales 2025-03-27 19:14:41 -04:00
Isira Seneviratne 6f9af2638b Update dependencies 2025-03-26 07:57:30 +05:30
Isira Seneviratne fe563f3307 Improve DAO method calls 2025-03-26 07:57:20 +05:30
Isira Seneviratne e00e2562fb Rename .java to .kt 2025-03-26 07:57:19 +05:30
Isira Seneviratne a58b0da2c1 Fix actions 2025-03-25 07:42:10 +05:30
Isira Seneviratne 54c2b499af Restore clear menu 2025-03-25 05:48:42 +05:30
Isira Seneviratne 2bafa2c715 Add loading indicator in ItemList 2025-03-24 07:10:18 +05:30
Miles Krell 7f10312d0a Move migration to NewPipeSettings 2025-03-23 17:39:21 -04:00
malania02 63be3220e7 Show download date 2025-03-22 16:19:26 +01:00
malania02 536b78f2e6 textview for download date added 2025-03-22 16:13:45 +01:00
malania02 6d6b73ef73 textview for download date added 2025-03-22 16:09:58 +01:00
Stypox 196c27792b Merge pull request #12044 from TeamNewPipe/android-auto
Add support for Android Auto *(season 2)*
2025-03-21 11:21:58 +01:00
Stypox b3789315ad Merge pull request #12104 from TeamNewPipe/update-npe
Update NewPipe Extractor and add new proguard rules
2025-03-21 10:52:37 +01:00
Miles Krell c7bf498c04 Don't show toast because of changing content language or country 2025-03-16 20:27:05 -04:00
Miles Krell 35abb99dac Only show toast on Android <13 2025-03-16 20:15:38 -04:00
Miles Krell 70416e73f3 Move app language setting migration to SettingMigrations 2025-03-16 19:24:04 -04:00
TobiGr a0b76c3385 Update NewPipe Extractor and add new proguard rules
New rules are required since Rhino and Rhino Engine 1.8.0
2025-03-16 22:08:10 +01:00
Tobi c232193a46 Merge pull request #12083 from har-123/bugfix/11894_fix_duplicate_menu_options
Fix duplicate menu options in ChannelFragment
2025-03-16 10:34:52 +01:00
Isira Seneviratne 033d288077 Use "Sort By" label, remove outdated translations 2025-03-16 12:49:16 +05:30
Siddhesh Naik f289bea6b3 Fix sonar warning 2025-03-16 12:44:05 +05:30
Harshita 48b200868a BF-11894 : Fix the menu disappearing on performing backGesture 2025-03-16 12:44:05 +05:30
Harshita 54bf7f0ced BF-11894 : Fix the Duplicate menu options in ChannelFragment 2025-03-16 12:44:05 +05:30
Miles Krell 980a35a708 Move migration to separate method 2025-03-15 23:00:31 -04:00
Miles Krell da106e2361 Don't try to migrate "system" app language 2025-03-15 22:54:17 -04:00
Miles Krell 3532ac96b4 Migrate from pre-Android 13 app language pref 2025-03-15 22:13:01 -04:00
Miles Krell 87693a2ad1 Redirect to per-app language settings on Android 13+ 2025-03-15 21:56:02 -04:00
+19 d321e57620 Translated using Weblate (Czech)
Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Catalan)

Currently translated at 88.2% (653 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 90.4% (76 of 84 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Croatian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Greek)

Currently translated at 25.0% (21 of 84 strings)

Translated using Weblate (Greek)

Currently translated at 23.8% (20 of 84 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 99.5% (737 of 740 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439JBYL80IGQTF25UXNR0X1BG@users.noreply.hosted.weblate.org>
Co-authored-by: Andrey F <firsan777@mail.ru>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Antonin Del Fabbro <message@antonin.one>
Co-authored-by: Christian Eichert <c@zp1.net>
Co-authored-by: Drugi Sapog <dindrugi@users.noreply.hosted.weblate.org>
Co-authored-by: Eduardo Calixto <eduardogubertcalixto@gmail.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.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: Jan Layola <gilajan@protonmail.com>
Co-authored-by: Kevin Wang <wmk153024@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Petr Kadlec <mormegil@centrum.cz>
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: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: XxVictoriaxX <evakonoob@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@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/el/
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/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2025-03-15 17:43:36 +01:00
Tobi fb4a65a14a Merge pull request #12043 from TeamNewPipe/hide-view-logs
Disable logs about view animations by default
2025-03-15 17:17:59 +01:00
Stypox 3047704e1c Merge pull request #12089 from mileskrell/mileskrell/fix-audio-track-labels
Disambiguate audio track labels
2025-03-15 12:45:20 +01:00
Stypox 3dcfdaf510 Merge pull request #12065 from tfga/YouTubeTemporaryPlaylist
Share as YouTube temporary playlist
2025-03-15 10:11:59 +01:00
Thiago F. G. Albuquerque 2ceb70236e sharePlaylist(): converting javadoc from Markdown back to "classic javadoc"
(request from @Stypox)
2025-03-14 21:56:42 -03:00
Thiago F. G. Albuquerque be097f26c8 Deleting the "explanatory text" bellow the title
<string name="share_playlist_with_titles_message">Share playlist with details such as playlist name and video titles or as a simple list of video URLs</string>
    Share playlist with details such as playlist name and video titles or as a simple list of video URLs</string>

(Discussion: https://github.com/TeamNewPipe/NewPipe/pull/12065#discussion_r1994349485)
2025-03-13 19:10:26 -03:00
Thiago F. G. Albuquerque 098f60d593 Don't add the title when sharing as YouTube temp playlist 2025-03-13 18:16:09 -03:00
Thiago F. G. Albuquerque eb0568044a R.string.share_playlist_as_youtube_temporary_playlist: pt-BR
+ Minor fixes to related translations
2025-03-12 19:09:31 -03:00
Thiago F. G. Albuquerque f3b3d5c3e7 R.string.share_playlist_as_youtube_temporary_playlist 2025-03-12 19:08:09 -03:00
Miles Krell b888dc72cf Support per-app language preferences 2025-03-11 23:29:23 -04:00
Thiago F. G. Albuquerque 599d86151a Making ktLint happy 2025-03-11 21:26:58 -03:00
tfgaandStypox 587df093ea YouTube video IDs are 11 characters long
Co-authored-by: Stypox <stypox@pm.me>
2025-03-11 20:35:41 -03:00
tfgaandStypox 8830e87242 YouTube video IDs are 11 characters long
Co-authored-by: Stypox <stypox@pm.me>
2025-03-11 20:35:18 -03:00
Thiago F. G. Albuquerque f96b8f7b2a Comment: maximum length of 50 items
(PR review from @Stypox)
2025-03-11 20:19:54 -03:00
Thiago F. G. Albuquerque c28478ae53 getYouTubeId(): Changing implementation to use YoutubeStreamLinkHandler
(PR review from @Stypox)
2025-03-11 20:12:25 -03:00
Isira Seneviratne 851ba4bf1d Use segmented button for sort options 2025-03-11 11:04:58 +05:30
Miles Krell 10110397fd Use display name instead of only the language 2025-03-10 22:01:09 -04:00
tfgaandStypox d81244e77c YT temp playlist URL: http => https
Co-authored-by: Stypox <stypox@pm.me>
2025-03-10 19:11:20 -03:00
Isira Seneviratne 975ba3c067 Reduce button size, fix alignment 2025-03-08 19:13:31 +05:30
Isira Seneviratne d805679a5e Use workDataOf 2025-03-08 09:18:30 +05:30
Isira Seneviratne e877997603 Improve constructors 2025-03-06 06:11:49 +05:30
Stypox ea20ca9e72 Merge pull request #12067 from Isira-Seneviratne/Fix-notification-grouping
Fix stream notification grouping
2025-02-28 11:51:11 +01:00
Isira Seneviratne f0c89494dd Fix stream notification grouping 2025-02-27 09:15:40 +05:30
Thiago F. G. Albuquerque 0fd2d4fed6 [#11930] Removing Apache Commons Collections
It's no longer needed after the conversion to Kotlin.
2025-02-26 21:29:48 -03:00
Stypox c1bdffd917 Merge pull request #11978 from Profpatsch/fix-back-button-on-remaining-stack
MainActivity: Fix onBackPressed handling for open player
2025-02-26 16:56:04 +01:00
Thiago F. G. Albuquerque 3c7b026d7d [#11930] Updating javadoc 2025-02-25 20:23:07 -03:00
Thiago F. G. Albuquerque 998d84de6c [#11930] Converting to Kotlin 2025-02-25 18:56:12 -03:00
Thiago F. G. Albuquerque 76a02d5858 [#11930] Extracting to a separate file 2025-02-24 20:16:40 -03:00
Thiago F. G. Albuquerque 24bb71a23f [#11930] Making it more efficient: Reverse iteration + limit(50) + reverse 2025-02-24 19:22:36 -03:00
Stypox 49b71942ad Fix style and add comment about null player 2025-02-24 14:21:05 +01:00
Isira Seneviratne abaf16e12b Merge branch 'refactor' into Import-export-worker
# Conflicts:
#	gradle/libs.versions.toml
2025-02-23 05:10:34 +05:30
Isira Seneviratne 491b433f32 Merge branch 'refactor' into History-Compose 2025-02-23 05:06:57 +05:30
Thompson3142andStypox c9ec257a5e Ugly fix for broken text colors in dark mode (#12035)
* Ugly fix for broken text colors in dark mode

* Add comment for clarification

* Added error prevention

* Update app/src/main/java/org/schabi/newpipe/MainActivity.java

---------

Co-authored-by: Stypox <stypox@pm.me>
2025-02-21 09:38:58 +00:00
Thiago F. G. Albuquerque b1f995a78c [#11930] Playlist with more than 50 items 2025-02-20 16:26:03 -03:00
Thiago F. G. Albuquerque acac50a1d1 [#11930] Non-Youtube URLs should be ignored 2025-02-19 16:29:34 -03:00
Thiago F. G. Albuquerque c6b87cd316 [#11930] Making CheckStyle happy 2025-02-18 20:59:13 -03:00
Thiago F. G. Albuquerque 94d4c21cc7 [#11930] @Test export_justUrls() 2025-02-18 17:47:22 -03:00
Stypox a7a7dc5363 Handle player and player service separately
This is, again, a consequence of the commit "Drop some assumptions on how PlayerService is started and reused".
This commit notified VideoDetailFragment of player starting and stopping independently of the player.
Read the comments in the code changes for more information.
2025-02-18 19:27:46 +01:00
Stypox 126f4b0e30 Fix crash when closing video detail fragment
This bug started appearing because the way to close the player is now unified in PlayerHolder.stopService(), which causes the player to reach back to the video detail fragment with a notification of the shutdown (i.e. onServiceStopped() is called). This is fixed by adding a nullability check on the binding.
2025-02-18 18:03:10 +01:00
Stypox 6558794d26 Try to bind to PlayerService when MainActivity starts
Fixes mini-player not appearing on app start if the player service is already playing something.

The PlayerService (and the player) may be started from an external intent that does not involve the MainActivity (e.g. RouterActivity or Android Auto's media browser interface).
This PR tries to bind to the PlayerService as soon as the MainActivity starts, but only does so in a passive way, i.e. if the service is not already running it is not started.
Once the connection between PlayerHolder and PlayerService is setup, the ACTION_PLAYER_STARTED broadcast is sent to MainActivity so that it can setup the bottom mini-player.
Another important thing this commit does is to check whether the player is open before actually adding the mini-player view, since the PlayerService could be bound even without a running player (e.g. Android Auto's media browser is being used). This is a consequence of commit "Drop some assumptions on how PlayerService is started and reused".
2025-02-18 17:49:38 +01:00
Stypox 1d12874937 Merge pull request #12046 from TobiGr/weblate
Update translations
2025-02-16 22:01:41 +01:00
Stypox 1d98518bfa Fix loading remote playlists in media browser 2025-02-16 21:44:50 +01:00
Stypox e5458bcb14 Properly handle item errors during media browser loading
Non-item errors, i.e. critical parsing errors of the page, are still handled properly.
2025-02-16 21:44:50 +01:00
Stypox dc62d211f5 Properly stop PlayerService
This commit is a consequence of the commit "Drop some assumptions on how PlayerService is started and reused". Since the assumptions on how the PlayerService is started and reused have changed, we also need to adapt the way it is stopped. This means allowing the service to remain alive even after the player is destroyed, in case the system is still accessing PlayerService e.g. through the media browser interface. The foreground service needs to be stopped and the notification removed in any case.
2025-02-16 21:44:49 +01:00
Stypox ec6612dd71 Call exoPlayer.prepare() on PlaybackPreparer.onPrepare()
If a playbackPreparer is set, then instead of calling `player.prepare()`, the MediaSessionConnector will call `playbackPreparer.onPrepare(true)` instead, as seen below.
This commit makes it so that playbackPreparer.onPrepare(true) restores the original behavior of just calling player.prepare().

From MediaSessionConnector -> MediaSessionCompat.Callback implementation:
```java
    @Override
    public void onPlay() {
      if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PLAY)) {
        if (player.getPlaybackState() == Player.STATE_IDLE) {
          if (playbackPreparer != null) {
            playbackPreparer.onPrepare(/* playWhenReady= */ true);
          } else {
            player.prepare();
          }
        } else if (player.getPlaybackState() == Player.STATE_ENDED) {
          seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET);
        }
        Assertions.checkNotNull(player).play();
      }
    }
```
2025-02-16 21:44:49 +01:00
StypoxandHaggai Eran 064e1d39c7 Use the media browser implementation in PlayerService
Now the media browser queries are replied to by MediaBrowserImpl

Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:44:05 +01:00
4c88a193bd Add MediaBrowserImpl
This class implements the media browser service interface as a standalone class for clearer separation of concerns (otherwise everything would need to go in PlayerService, since PlayerService overrides MediaBrowserServiceCompat)

Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
Co-authored-by: Profpatsch <mail@profpatsch.de>
2025-02-16 21:43:46 +01:00
3fcac10e7f Add MediaBrowserPlaybackPreparer
This class will receive the media URLs generated by [MediaBrowserImpl] and will start playback of the corresponding streams or playlists.

Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
Co-authored-by: Profpatsch <mail@profpatsch.de>
2025-02-16 21:43:35 +01:00
StypoxandHaggai Eran 6cedd117fe Add StreamHistoryEntry.toStreamInfoItem()
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:55 +01:00
StypoxandHaggai Eran 5eabcb52b5 Add getThumbnailUrl() to PlaylistLocalItem interface
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:48 +01:00
Stypox 690b40d0c4 Allow creating PlayQueue from ListInfo and index 2025-02-16 21:40:47 +01:00
StypoxandHaggai Eran 9bb2c0b484 Add getPlaylist(id) to RemotePlaylistManager
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:36 +01:00
StypoxandHaggai Eran 1e08cc8c8f Add MediaBrowserCommon with info item's and pages' IDs
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:29 +01:00
Stypox 7d17468266 Instantiate media session and connector in PlayerService
This changes significantly how the MediaSessionCompat and MediaSessionConnector objects are used:
- now they are tied to the service and not to the player, and so they might be reused with multiple players (which should be allowed)
- now they can exist even if there is no player (which is fundamental to be able to answer media browser queries)
2025-02-16 21:40:13 +01:00
StypoxandHaggai Eran 5819546ea9 Have PlayerService implement MediaBrowserServiceCompat
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:36:59 +01:00
Stypox cfb6e114d6 Disable logs about view animations by default 2025-02-16 10:31:42 +01:00
Stypox b764ad33c4 Drop some assumptions on how PlayerService is started and reused
Read the comments in the lines changed to understand more
2025-02-15 17:48:19 +01:00
+6 430b4eb916 Translated using Weblate (Persian)
Currently translated at 92.7% (686 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Georgian)

Currently translated at 83.3% (70 of 84 strings)

Translated using Weblate (Estonian)

Currently translated at 16.6% (14 of 84 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Mainfränkisch)

Currently translated at 1.0% (8 of 740 strings)

Translated using Weblate (Bavarian)

Currently translated at 3.9% (29 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (84 of 84 strings)

Added translation using Weblate (Mainfränkisch)

Translated using Weblate (Thai)

Currently translated at 36.6% (271 of 740 strings)

Translated using Weblate (Armenian)

Currently translated at 28.2% (209 of 740 strings)

Translated using Weblate (Georgian)

Currently translated at 85.7% (72 of 84 strings)

Translated using Weblate (Thai)

Currently translated at 34.3% (254 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 11.3% (84 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Nepali)

Currently translated at 1.1% (1 of 84 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (French)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 11.0% (82 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Davit Mayilyan <davit.mayilyan@protonmail.ch>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Garfield2150 <knd.garfield@gmail.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Goudarz Jafari <goudarz.jafari@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kchenik Poudel <Kakapoudel7@gmail.com>
Co-authored-by: Kuko <kuko7@protonmail.ch>
Co-authored-by: Paul Sibila <p.aul@mail.de>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: freddyLovesUs <compteperso@outlook.com>
Co-authored-by: રાજ ભાતેલીઆ <rajbhatelia@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/et/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
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/ne/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translation: NewPipe/Metadata
2025-02-15 13:08:00 +01:00
Thiago F. G. Albuquerque 2339f51ad4 [#11930] Share as YouTube temporary playlist
Initial commit.
2025-02-14 21:14:42 -03:00
Isira Seneviratne dbe30116e3 Remove Unknown class 2025-02-09 20:38:34 +05:30
Stypox 99aae7eb28 Merge branch 'dev' into refactor 2025-02-05 15:15:41 +01:00
Stypox c6e1721884 Add translated changelogs for v0.27.6 (1003)
Copied from 1002.txt
2025-02-05 11:30:37 +01:00
Stypox 94684fe380 Merge branch 'weblate-dev' into dev 2025-02-05 11:29:14 +01:00
Hosted Weblate 398a2f55ce Merge branch 'origin/dev' into Weblate. 2025-02-05 11:28:09 +01:00
Stypox 1f7b3b5b06 Add changelog for v0.27.6 (1003) 2025-02-05 11:25:58 +01:00
Stypox 909ed616c4 Hotfix release v0.27.6 (1003) 2025-02-05 11:14:17 +01:00
Stypox dd223af28d Merge pull request #11955 from Stypox/po-token
[YouTube] Add support for poTokens
2025-02-05 10:52:16 +01:00
Stypox dbee8d8128 Update NewPipeExtractor to v0.24.5
Using commit 9f83b385a since JitPack is buggy...
2025-02-05 10:24:34 +01:00
Isira Seneviratne 695c05bd8e Add confirmation dialog for clearing watch history 2025-02-05 08:52:28 +05:30
Stypox b62a09b5b3 Use WebSettingsCompat.setSafeBrowsingEnabled 2025-02-04 21:50:10 +01:00
Stypox 87317c6faf Reorder functions in PoTokenWebView 2025-02-04 21:38:01 +01:00
Stypox 53b599b042 Make JavaScript code compatible with older WebViews 2025-02-04 21:38:01 +01:00
Stypox 21df24abfd Detect when WebView is broken and return null poToken
Some old Android devices have a broken WebView implementation, that can't execute the poToken code. This is now detected and the getWebClientPoToken return null instead of throwing an error in such a case, to allow the extractor to try to extract the video data even without a poToken.
2025-02-04 11:22:50 +01:00
+46 ca4592a935 Translated using Weblate (Russian)
Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 13.2% (11 of 83 strings)

Translated using Weblate (Latin)

Currently translated at 8.6% (64 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Turkish)

Currently translated at 48.1% (40 of 83 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 96.3% (80 of 83 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Vietnamese)

Currently translated at 78.3% (65 of 83 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (German)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (740 of 740 strings)

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

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (N’Ko)

Currently translated at 89.4% (662 of 740 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 19.8% (147 of 740 strings)

Translated using Weblate (Georgian)

Currently translated at 89.1% (660 of 740 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.0% (733 of 740 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 65.4% (484 of 740 strings)

Translated using Weblate (Somali)

Currently translated at 75.1% (556 of 740 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 62.0% (459 of 740 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 7.4% (55 of 740 strings)

Translated using Weblate (Odia)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Santali)

Currently translated at 14.5% (108 of 740 strings)

Translated using Weblate (Bengali)

Currently translated at 76.7% (568 of 740 strings)

Translated using Weblate (Sardinian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Bengali (India))

Currently translated at 40.1% (297 of 740 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 84.0% (622 of 740 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 97.7% (723 of 740 strings)

Translated using Weblate (Malayalam)

Currently translated at 76.4% (566 of 740 strings)

Translated using Weblate (Interlingua)

Currently translated at 32.2% (239 of 740 strings)

Translated using Weblate (Filipino)

Currently translated at 31.3% (232 of 740 strings)

Translated using Weblate (Thai)

Currently translated at 30.0% (222 of 740 strings)

Translated using Weblate (Nepali)

Currently translated at 59.0% (437 of 740 strings)

Translated using Weblate (Danish)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Galician)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Malay)

Currently translated at 57.9% (429 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.0% (696 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Punjabi)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Albanian)

Currently translated at 79.8% (591 of 740 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 75.1% (556 of 740 strings)

Translated using Weblate (Urdu)

Currently translated at 68.2% (505 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Catalan)

Currently translated at 87.0% (644 of 740 strings)

Translated using Weblate (Kurdish)

Currently translated at 63.7% (472 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Telugu)

Currently translated at 58.1% (430 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Finnish)

Currently translated at 97.9% (725 of 740 strings)

Translated using Weblate (Croatian)

Currently translated at 98.9% (732 of 740 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Lithuanian)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Bengali (Bangladesh))

Currently translated at 54.3% (402 of 740 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Asturian)

Currently translated at 63.3% (469 of 740 strings)

Translated using Weblate (Persian)

Currently translated at 92.4% (684 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Turkish)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Czech)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Esperanto)

Currently translated at 71.4% (529 of 740 strings)

Translated using Weblate (Slovak)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Romanian)

Currently translated at 99.8% (739 of 740 strings)

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

Currently translated at 99.3% (735 of 740 strings)

Translated using Weblate (Basque)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Korean)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Japanese)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Serbian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Dutch)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (German)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Macedonian)

Currently translated at 6.0% (5 of 82 strings)

Translated using Weblate (Macedonian)

Currently translated at 80.6% (597 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (French)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Esperanto)

Currently translated at 71.7% (531 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Kabyle)

Currently translated at 28.7% (213 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 12.1% (10 of 82 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 11.0% (82 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 10.9% (9 of 82 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 98.7% (81 of 82 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Undetermined)

Currently translated at 2.4% (2 of 82 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (82 of 82 strings)

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

Currently translated at 28.0% (23 of 82 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 9.4% (70 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 9.7% (8 of 82 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Hungarian)

Currently translated at 74.3% (61 of 82 strings)

Translated using Weblate (Russian)

Currently translated at 98.7% (81 of 82 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 64.6% (53 of 82 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Russian)

Currently translated at 97.5% (80 of 82 strings)

Translated using Weblate (German)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Andrey F <firsan777@mail.ru>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Anthony Romero <dagazcii@gmail.com>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Bảo Nam (Namm) <namb20994@gmail.com>
Co-authored-by: C. Rüdinger <Mail-an-CR@web.de>
Co-authored-by: Ding User <dengus@users.noreply.hosted.weblate.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: GeoCup <geokapaniaris@gmail.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: H Tamás <hovanszki@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jaidyn Ann <jadedctrl@posteo.net>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: MatthieuPh <matthieu.philippe@protonmail.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Miguel <mp0187595@tutamail.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nicolas SALMIERI <1salmieri.nicolas@gmail.com>
Co-authored-by: NormalRandomPeople <normal.scribe833@silomails.com>
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: Rex_sa <rex.sa@pm.me>
Co-authored-by: Rijolo <rijolo4790@gholar.com>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Szia Tomi <sziatomi01@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: VisionR1 <25982450+VisionR1@users.noreply.github.com>
Co-authored-by: Vtrytobe <vtrytobe@gmail.com>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: gfbdrgng <hnaofegnp@hldrive.com>
Co-authored-by: hajayad577 <hajayad577@numerobo.com>
Co-authored-by: jpkaster 77 <jpkaster81@gmail.com>
Co-authored-by: polarwood <wreckfitzgerald@proton.me>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: yummysheepouo <jerry88182821@gmail.com>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Валентин Барсуков <valikbars04@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: મેબીરાજ <rajbhatelia@gmail.com>
Co-authored-by: રાજ ભાતેલીઆ <rajbhatelia@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/et/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
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/mk/
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/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ta/
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/und/
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
2025-02-04 10:59:39 +01:00
Stypox 3fc487310b Use Runnable instead of () -> Unit if converted to Runnable anyway 2025-02-04 10:23:45 +01:00
Stypox 056809cb0d Use "this" instead of "globalThis" as global scope
globalThis was introduced only on newer versions of JS
2025-02-04 10:22:10 +01:00
AudricV a60bb3e7af [YouTube] Change BotGuard endpoint to youtube.com's one
This prevents non-abilities to fetch BotGuard challenge and send its
result with the jnn-pa.googleapis.com domain (domain block like done
on Pi-hole lists or DNS servers).

That's what the official website uses to send the challenge execution
result, however it uses InnerTube to fetch the challenge. Embeds
still use the jnn-pa.googleapis.com domain.

Also rename the makeJnnPaGoogleapisRequest method appropriately.
2025-02-03 13:05:39 +01:00
AudricV ecd3f6c2ee [YouTube] Clarify BotGuard API key's origin and disable related Sonar warning 2025-02-01 15:40:16 +01:00
AudricV 70ff47b810 [YouTube] Get visitorData from the service to get valid responses 2025-02-01 15:39:07 +01:00
Isira Seneviratne ac34ada7c9 Make DropdownTextMenuItem non-restartable 2025-02-01 05:41:02 +05:30
Isira Seneviratne 212c67838a Remove manual text color specification 2025-02-01 05:29:14 +05:30
Isira Seneviratne 61bb81f2d6 Fix black theme, remove manual background definition 2025-02-01 05:25:48 +05:30
Isira Seneviratne 6de5ce77c8 Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt
2025-02-01 05:05:07 +05:30
AudricV b8e050f6c4 Adapt YoutubeHttpDataSource to extractor changes and improve requests
Always use POST requests and the same body that official HTML5 clients
use for a while.
2025-01-31 22:50:10 +01:00
AudricV 46d0bc1004 Update NewPipeExtractor 2025-01-31 22:28:08 +01:00
Stypox e7fe84f2c7 Make sure downloadAndRunBotguard() is called after <script> loaded 2025-01-31 21:47:46 +01:00
Stypox 2b183a0576 Wrap logs in BuildConfig.DEBUG 2025-01-31 21:47:46 +01:00
Stypox f856bd9306 Recreate poToken generator if current is broken
This will be tried only once, and afterwards an error will be thrown
2025-01-31 21:47:45 +01:00
Stypox 0066b322e1 Unify running on main thread 2025-01-31 21:47:45 +01:00
Stypox 3bdae81c0a Fix checkstyle 2025-01-31 21:47:45 +01:00
Stypox 6010c4ea7f Connect poToken generation to extractor 2025-01-31 21:47:45 +01:00
Stypox 690b3410e9 Interfaces for poTokens + WebView implementation 2025-01-31 21:47:44 +01:00
Profpatsch ba86ce137b Merge pull request #11969 from neosis91/dev
DownloaderImpl: Auto-close resources and simplify header setting
2025-01-31 15:56:39 +01:00
Bertrand Jaunet 410c01547c DownloaderImpl: Auto-close resources and simplify header setting
The headers should be overwritten in the same way, based on how
`.header` is the same as `.removeHeader().addHeader()`.

We weren’t closing the request resources after using them, potentially
leaking file handles. This will add autoclosing for both the request
and the body objects.
2025-01-31 12:36:27 +01:00
Profpatsch fd99c5e461 MainActivity: Fix onBackPressed handling for open player
The change
b9dd7078ad
accidentally moved the `return` into the `{}`, so the logic would fall
through to

```
if (fragmentManager.getBackStackEntryCount() == 1) {`
```

and close the app even though there are still items on the
`VideoFragmentDetail` stack.

To reproduce:
Start video, enqueue another video, then start a third video (which
adds one entry to the stack), and press `back` on the expanded video.

This should keep the player open and go back to the first 2-video
queue, but it actually closes the app before this fix.
2025-01-30 19:40:44 +01:00
Stypox 407d2d768d Merge pull request #11539 from Isira-Seneviratne/Compose-theme-improvements
Compose theme improvements
2025-01-28 14:02:50 +01:00
Stypox 47263f5254 Merge pull request #11959 from Stypox/fix-loading-stream-twice
Fix loading StreamInfo twice on first VideoDetailFragment opening
2025-01-27 14:56:51 +01:00
Stypox 01bf855015 Fix naming in VideoDetailFragment: video->stream, videoUrl->url 2025-01-27 14:52:35 +01:00
Profpatsch ebf3008729 Merge pull request #11870 from TeamNewPipe/sidebar_donations
Add link to donation page on app drawer
2025-01-27 13:59:29 +01:00
Christian Schabesberger 33ecfb757e Sidebar: Add donation link to app drawer
This creates a donation link that leads to our donation page on the
NewPipe website.
2025-01-27 13:43:34 +01:00
Profpatsch b109e4d3cc Merge pull request #11867 from Profpatsch/player-holder-refactor
PlayerHolder refactor
2025-01-27 13:29:53 +01:00
Profpatsch 137ade24ff Adjust javadoc format 2025-01-27 12:45:30 +01:00
Isira Seneviratne 5abc03c4cf Extract thumbnail into common composable 2025-01-27 07:36:48 +05:30
Stypox ffe26d882b Fix loading StreamInfo twice on first VideoDetailFragment opening 2025-01-26 12:39:07 +01:00
Stypox 83f8141fe7 Merge pull request #11806 from Thompson3142/fix_subtitle_size
Fix caption sizes not being changed
2025-01-25 18:10:56 +01:00
Isira Seneviratne 48d682016e Rm ViewPager version 2025-01-25 11:21:58 +05:30
Isira Seneviratne 21caa6ce13 Update Compose BOM 2025-01-25 11:19:28 +05:30
Isira Seneviratne b78e0b2da8 Merge branch 'refactor' into Compose-theme-improvements 2025-01-25 09:41:29 +05:30
Isira Seneviratne 32a88ab890 Truncate existing file in export 2025-01-25 07:57:01 +05:30
Isira Seneviratne af3ed992e5 Add error handling for import 2025-01-25 05:40:00 +05:30
Isira Seneviratne a79516dfff Use fragment arguments 2025-01-25 05:30:14 +05:30
Isira Seneviratne 59a748d0ba Merge branch 'refactor' into History-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
2025-01-24 09:40:28 +05:30
Profpatsch 9253640fae Merge pull request #11887 from Nikunj-Aggarwal/bg-iso-timestamp
Convert error report timestamps to ISO format
2025-01-23 19:51:18 +01:00
Profpatsch dbd11a6a8d SubscriptionImportWorker: inputs as sealed class 2025-01-22 15:34:04 +01:00
Isira Seneviratne 21973b362a Use Kotlin Pair 2025-01-22 13:03:57 +01:00
Isira Seneviratne 60586c90d6 Improve subscription upsert methods 2025-01-22 13:03:57 +01:00
Isira Seneviratne 095155d35f Only get subscription extractor when needed 2025-01-22 13:03:57 +01:00
Isira Seneviratne af8e5646a6 Remove LocalBroadcastManager 2025-01-22 13:03:57 +01:00
Isira Seneviratne c9d155a335 Combine notification and ForegroundInfo creation methods 2025-01-22 13:03:57 +01:00
Isira Seneviratne 4e31ccebf8 Moved Kotlinx Serialization to library catalog 2025-01-22 13:03:57 +01:00
Isira Seneviratne c0965a42a1 Added success toasts 2025-01-22 13:03:57 +01:00
Isira Seneviratne fc7f1b0af0 Convert subscription import service to a worker 2025-01-22 13:03:57 +01:00
Isira Seneviratne dfb035dfa5 Improve import/export tests 2025-01-22 13:03:57 +01:00
Isira Seneviratne 8e9503cfe4 Convert subscription export service to a worker 2025-01-22 13:03:55 +01:00
Isira Seneviratne 82516dd75c Rename .java to .kt 2025-01-22 13:02:36 +01:00
Stypox 3e6e980362 Merge branch 'dev' into refactor 2025-01-22 11:12:51 +01:00
Stypox 8b5aa5cd9b Merge branch 'master' into dev 2025-01-22 11:10:22 +01:00
Stypox 58393ad4ef Release v0.27.5 (1002) 2025-01-21 23:34:42 +01:00
Stypox 977f7e28b5 Add changelogs for hotfix release v0.27.5 (1002) 2025-01-21 23:34:12 +01:00
Stypox 99e77249de Update NewPipeExtractor to v0.24.4 2025-01-21 23:19:49 +01:00
Profpatsch 1890fbb19a Merge pull request #11809 from Isira-Seneviratne/Merge-dev
Merge dev to refactor
2025-01-21 17:56:00 +01:00
Profpatsch a955408053 Merge pull request #11928 from LeMeuble/bug-checksum-deleted-file
Fix the issue of getting the checksum of a removed file
2025-01-21 17:40:50 +01:00
Thompson3142 86203d6800 MainPlayer/PopupPlayer: Use system settings for subtitle size
This will use the exact subtitle sizes the user requested, both for
the main and the popup player. They will always be the same fraction
of the video, even if the popup player is resized.
2025-01-21 17:23:08 +01:00
Profpatsch edd19641ac ErrorActivity: add Timestamp and Package/Service to markdown export
These were displayed in the UI, but not added into the markdown export
string.
2025-01-21 16:25:54 +01:00
Nikunj-Aggarwal 65749cbac0 ErrorActivity: Use a proper zoned ISO timestamp
Will have a timezone offset and be parsable as valid ISO8601
timestamp.

Also change the label in the UI to just say “Timestamp”
2025-01-21 16:24:07 +01:00
Isira Seneviratne bf0692377c Reuse dropdown composable 2025-01-21 11:05:33 +05:30
Isira Seneviratne 3943a87966 Fix preview issue 2025-01-21 06:23:23 +05:30
Isira Seneviratne aeb4548a5a Add delete button for history items 2025-01-21 05:59:01 +05:30
Isira Seneviratne 6621b7feaa Fix NPE 2025-01-20 11:36:53 +05:30
Isira Seneviratne fa98a92415 Cache history in view model 2025-01-20 11:08:57 +05:30
Isira Seneviratne 38645b02df Implement playback action buttons 2025-01-20 10:51:00 +05:30
LeMeuble 658ddfc921 Fix issue of checksum for removed file 2025-01-16 10:46:25 +01:00
Isira Seneviratne 48ad12375b Implement clear history functionality 2025-01-15 08:56:42 +05:30
Isira Seneviratne 3f1bd58751 Rm unused file 2025-01-15 08:13:29 +05:30
Isira Seneviratne 38a533f9a8 Combine ItemList code 2025-01-15 07:48:56 +05:30
Isira Seneviratne 4ec5492814 Update dependencies 2025-01-15 07:35:21 +05:30
Isira Seneviratne f99fc13326 Reuse ItemList composable 2025-01-15 07:28:42 +05:30
Isira Seneviratne 3d9394e69b Reuse stream composables 2025-01-14 14:45:11 +05:30
Isira Seneviratne 77e254b2be Remove unused classes 2025-01-14 12:44:30 +05:30
Isira Seneviratne 169eff809e Rewrite history fragment using Compose 2025-01-14 10:17:45 +05:30
Isira Seneviratne efb3aa530d Merge branch 'dev' into Merge-dev 2025-01-11 18:45:51 +05:30
Stypox f7d0fd545d Merge pull request #11879 from tom93/pr/fix-image-minimizer-multiple-images
Fix image-minimizer on lines containing multiple images
2025-01-04 09:34:07 +01:00
Tom Levy 27e6be792f Fix image-minimizer on lines containing multiple images 2025-01-04 08:15:44 +00:00
Profpatsch ce919215fb PlayerHolder: Separate holder and service event interface
Should make it easier to seperate the two further later, both of them
are only implemented by VideoDetailFragment anyway, which is kind of a
code smell!
2024-12-26 01:31:17 +01:00
Profpatsch 6a4aaba431 PlayerHolder: add some more docstrings 2024-12-26 01:02:59 +01:00
Profpatsch 83d93e16e7 PlayerHolder: move unbind right next to stopService 2024-12-26 00:36:49 +01:00
Profpatsch 8d15a141b1 PlayerHolder: invert isBound 2024-12-26 00:26:59 +01:00
Profpatsch a78bed700a PlayerHolder: inline bind
Only used once. Now the code looks weird … why is the service started
twice??
2024-12-26 00:26:22 +01:00
Profpatsch ef3c76645f PlayerHolder/PlayerService: inline & remove duplicate player passing
The player in playerHolder is exactly the player inside the
`PlayerService`, which in turn is exactly passed through the IBinder
interface. Thus we don’t have to pass both.

Instead add `PlayerService.getPlayer()`.

Also inline a few methods of `PlayerHolder` and simplify.
2024-12-25 22:14:22 +01:00
Isira Seneviratne d4ed18bf08 Merge branch 'dev' into Merge-dev
# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/App.java
#	app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
#	app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
#	app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
#	app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java
#	app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
#	app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
#	app/src/main/res/values-bg/strings.xml
#	app/src/main/res/values-da/strings.xml
#	app/src/main/res/values-is/strings.xml
#	app/src/main/res/values-lv/strings.xml
#	app/src/main/res/values-zh-rTW/strings.xml
#	build.gradle
2024-12-21 07:45:20 +05:30
Stypox 3fc0147f47 Merge pull request #11784 from Rishi2003Das/typo_change
Correct a Typo in Contributing.md
2024-12-17 10:42:34 +01:00
Isira Seneviratne fbafdeb2ca Merge pull request #11767 from tsiflimagas/remove_viewpager2
Remove ViewPager2 dependency
2024-12-17 08:49:01 +05:30
Rishi Das c6b05c6094 Update CONTRIBUTING.md 2024-12-08 02:00:41 +05:30
Rishi Das 240a2fe36b Update CONTRIBUTING.md 2024-12-08 02:00:04 +05:30
Rishi Das de46e3abb3 Update CONTRIBUTING.md 2024-12-08 01:59:14 +05:30
Stypox 70748fa0bc Use JDK 21 in build-release-apk.yml
See https://github.com/TeamNewPipe/NewPipe/issues/11754
2024-12-02 13:49:30 +01:00
Kostas Giapis 781040efaa Remove ViewPager2 dependency 2024-12-01 22:24:39 +02:00
Stypox 3847b32c11 Release v0.27.4 (1001) 2024-11-30 15:11:23 +01:00
Stypox 9054575f6c Add changelog for v0.27.4 (1001) 2024-11-30 15:10:38 +01:00
Stypox 0dca92dd59 Merge branch 'master' into dev 2024-11-30 14:55:31 +01:00
+15 b19cd00dba Translated using Weblate (Malay)
Currently translated at 9.8% (8 of 81 strings)

Translated using Weblate (Malay)

Currently translated at 57.9% (429 of 740 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 96.2% (78 of 81 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 64.1% (52 of 81 strings)

Translated using Weblate (Hungarian)

Currently translated at 50.6% (41 of 81 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 4.9% (4 of 81 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Hungarian)

Currently translated at 32.0% (26 of 81 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Turkish)

Currently translated at 46.9% (38 of 81 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Japanese)

Currently translated at 12.3% (10 of 81 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (740 of 740 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Aliberk Sandıkçı <git@aliberksandikci.com.tr>
Co-authored-by: Dampuzakura <dampuzakura@users.noreply.hosted.weblate.org>
Co-authored-by: Eder Etxebarria Rojo <eder@betxepare.eus>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: H Tamás <hovanszki@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: LeoL <leonardo.lapa.04@protonmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Shafiq Jamzuri <shafiq.joe@yandex.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar_LY/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ms/
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/tr/
Translation: NewPipe/Metadata
2024-11-30 14:55:05 +01:00
Stypox 88d8d90bbd Merge pull request #11765 from Stypox/release-workflow
Add build-release-apk workflow
2024-11-30 14:53:16 +01:00
Stypox c569f08a32 Add build-release-apk workflow 2024-11-30 13:39:18 +01:00
Stypox 246fc034c1 Add build-release-apk workflow 2024-11-30 13:29:38 +01:00
Isira Seneviratne 1547b50b4e Merge branch 'refactor' into Compose-theme-improvements 2024-11-28 06:12:33 +05:30
Stypox 3f7ef49979 NewPipe license is GPLv3-or-later, not -only, in AboutScreen 2024-11-27 22:15:23 +01:00
Stypox 52942ffd30 Merge pull request #11738 from cillyvms/a13-player-notifs
Always allow changing player notification preferences on Android 13+
2024-11-27 19:12:19 +01:00
Stypox e4b0245530 Merge pull request #11734 from Thompson3142/fix_timestamp_popup_time
Fix player resuming from start when clicking on a timestamp
2024-11-27 18:38:49 +01:00
Tobi c6b8bcf0f4 Merge pull request #11745 from Stypox/truncate-before-export
Fix downloading/exporting when overwriting file would not truncate
2024-11-27 17:37:53 +01:00
Stypox dab0148a78 Merge pull request #11750 from Isira-Seneviratne/Fix-image-loading
Fix image loading
2024-11-27 16:50:38 +01:00
Stypox c0c08a4f63 Merge pull request #11282 from Isira-Seneviratne/About-Compose
Migrate about activity to Jetpack Compose
2024-11-27 16:42:35 +01:00
Stypox e31a8ad7a2 Mock openAndTruncateStream instead of getStream in test 2024-11-27 16:37:25 +01:00
Stypox b21981a9c7 Add comments to explain why openAndTruncateStream() 2024-11-27 16:34:50 +01:00
Stypox aaf337421d Merge branch 'refactor' into pr11282 2024-11-27 16:20:49 +01:00
Stypox 79a0edacd7 Merge pull request #11752 from JL0000/sort-dependencies
Sort dependencies in `libs.versions.toml`
2024-11-27 16:10:31 +01:00
Stypox d56eef6ece Use content padding instead of padding on container 2024-11-27 15:59:20 +01:00
Stypox 72f054a4fa Library should not be clickable if spdx is blank 2024-11-27 15:46:39 +01:00
Jie Li 172c3c92ac gradle script to enforce dependencies order 2024-11-26 18:32:44 +00:00
Isira Seneviratne 137ef3fee4 Fix image loading 2024-11-26 10:08:27 +05:30
Stypox e49156fb11 Merge pull request #11684 from JL0000/version-catalogs
Migrate build to version catalogs
2024-11-25 19:05:52 +01:00
Jie Li de5d45849f migrated to version catalogs 2024-11-25 23:12:29 +05:30
Stypox a25034b898 Fix toolbar colors in light theme 2024-11-25 04:43:43 +01:00
Stypox ae9e82b2c1 Implement showing libraries and licenses 2024-11-25 04:43:43 +01:00
Stypox a70b38a8e5 Minor updates to some libraries 2024-11-25 03:56:13 +01:00
Isira Seneviratne 08f3dba42c Merge branch 'refactor' into Compose-theme-improvements
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/ui/components/common/NoItemsMessage.kt
#	app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt
2024-11-25 07:22:03 +05:30
Thompson3142 f9711a3402 Removed call to setRecovery() entirely 2024-11-24 22:12:25 +01:00
Stypox df941670a8 Fix downloading/exporting when overwriting file would not truncate 2024-11-24 18:36:54 +01:00
Stypox 57e66b17c6 Merge branch 'master' into dev 2024-11-24 17:43:45 +01:00
Stypox d298a12533 Merge pull request #11712 from TeamNewPipe/release-0.27.3
Release v0.27.3 (1000)
2024-11-24 17:41:05 +01:00
+6 a79bc3db14 Translated using Weblate (Italian)
Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hungarian)

Currently translated at 28.3% (23 of 81 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (735 of 739 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Hungarian)

Currently translated at 23.4% (19 of 81 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Hungarian)

Currently translated at 23.4% (19 of 81 strings)

Translated using Weblate (Punjabi (Pakistan))

Currently translated at 16.9% (125 of 739 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 66.6% (54 of 81 strings)

Translated using Weblate (Albanian)

Currently translated at 1.2% (1 of 81 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Polish)

Currently translated at 60.4% (49 of 81 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Hungarian)

Currently translated at 19.7% (16 of 81 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (German)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (German)

Currently translated at 100.0% (739 of 739 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: D <dici.handy@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: H Tamás <hovanszki@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Szymon Siemieniuk <szymonsiemieniuk01@gmail.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: Vladi69 <vladimirogalante@yahoo.it>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Максим Горпиніч <mgorpinic2005@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/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/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sq/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/
Translation: NewPipe/Metadata
2024-11-24 17:32:32 +01:00
Stypox 661e6155c1 Update NewPipeExtractor to v0.24.3 2024-11-24 17:32:27 +01:00
Stypox 12558172d1 Merge pull request #11714 from AudricV/yt_more-audio-track-types-support
Add support for secondary audio track type
2024-11-24 17:01:03 +01:00
AudricV dc3f55674f Add support for secondary audio track type 2024-11-24 16:43:22 +01:00
Stypox acf2e88cb3 Merge pull request #11743 from TeamNewPipe/slower-feed
Throttle feed loading to avoid YouTube rate limits
2024-11-24 16:35:13 +01:00
Stypox 726c12e934 Only throttle YouTube feed loading 2024-11-24 16:22:19 +01:00
Stypox 0cff3a6ecd Improve AboutTab spacing 2024-11-24 16:06:21 +01:00
Stypox 33b96d238a Throttle loading subscriptions feed to avoid YouTube rate limits 2024-11-24 14:06:53 +01:00
cillyvms 213f49f5c4 Allow changing player notification preferences regardless of system settings on Android 13 and above. 2024-11-22 14:21:46 +01:00
Profpatsch 9b78e49e45 Merge pull request #11725 from Profpatsch/lwj.compose_migrate_empty_state_view
Migrate empty_state_view xml/view to Jetpack Compose
2024-11-22 11:49:22 +01:00
Thompson3142 16c79c8219 Fixed player resuming from start when clicking on a timestamp 2024-11-21 22:42:42 +01:00
Isira Seneviratne e6eea8f851 Merge branch 'refactor' into Compose-theme-improvements 2024-11-21 21:26:03 +05:30
Isira Seneviratne 4e55f1bee6 Merge branch 'refactor' into About-Compose 2024-11-21 21:11:52 +05:30
Stypox cff3834fde Fix setEmptyStateComposable dark theme 2024-11-21 13:17:33 +01:00
Stypox c8b01a06b0 Use empty state view in compose 2024-11-21 13:14:39 +01:00
Stypox 414b1a8344 Remove unused methods in EmptyStateUtil 2024-11-21 13:14:19 +01:00
Stypox 404d9f3fac Use empty state view in a few more places 2024-11-21 12:42:58 +01:00
Stypox 55e4014036 Use custom EmptyStateSpec for bookmark fragment 2024-11-21 12:24:11 +01:00
Stypox 1cd5563b27 All empty states now have the same style 2024-11-21 12:14:40 +01:00
Stypox 1abced992b Use normal colors for empty state view 2024-11-21 12:07:03 +01:00
Stypox 46b9243661 Remove unneeded empty state changes in ChannelFragment 2024-11-21 11:53:48 +01:00
toliuweijing ad72b2cb31 boost error hint color 2024-11-21 11:52:42 +01:00
toliuweijing 8b79d0ee29 Migrate empty_state_view to Jetpack Compose 2024-11-21 11:52:42 +01:00
Stypox 295f719b77 Merge pull request #11723 from Isira-Seneviratne/Coil-3
Migrate to Coil 3
2024-11-21 10:56:07 +01:00
Stypox b584353f4d Small fixes to code style 2024-11-21 10:52:15 +01:00
Isira Seneviratne d73314b4dd Make App instance variable immutable outside class 2024-11-21 08:09:57 +05:30
Isira Seneviratne 9f4a33c7a8 Fix lint 2024-11-21 06:56:10 +05:30
Isira SeneviratneandTobi 3a9540b042 Update app/src/main/java/org/schabi/newpipe/App.kt
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2024-11-20 16:04:39 +05:30
ShareASmile 14081505cd Update backup and restore explanation & improve hindi, punjabi and assamese READMEs (#11243)
* update backup and restore explanation in punjabi README

* Update backup and restore explanation in hindi README

* add_matrix_link to hindi and punjabi README

also translate Warning in hindi & punjabi language Readme's

* improve hindi and punjabi readme

add missing link #supported-services in hindi readme (that is #समर्थित-सेवाएँ}
improve translation of supported services in punjabi
Use Fdroid Hindi badge instead of english in hindi readme

* revert translate Warning in hindi & punjabi language Readme's

* update backup and restore explanation in assamese README

* fix assamese readme librapay donate button not showing and fix weird formating

* add matrix chat link to assamese readme & fix Newpipe logo not showing

* Update Matrix room URL to new link

oh! I missed this one earlier

* remove references to Bitcoin and Bountysource donation options in hindi readme

* more improvements in punjabi README

* fix CONTRIBUTING.md link in punjabi readme

* fix CONTRIBUTING.md link in assamese readme

* add missing paragraphs in hindi translation for hi readme

* revert localisation of app name NewPipe as it stands out

* address the review and place supported-services at correct place in hindi readme

do required changes for punjabi
do much needed improvements in assamese readme

* fix formatting issues in assamese readme

* fix link to releases in punjabi readme

* resolve conflicts
2024-11-20 10:42:29 +01:00
Isira Seneviratne ca855cbca0 Migrate to Coil 3 2024-11-20 09:28:20 +05:30
Isira Seneviratne 6a98b1dac7 Rename .java to .kt 2024-11-20 08:44:16 +05:30
Tobi ebd4880188 Merge pull request #10969 from yosrinajar/Read-Me-Translation
Readme translation to arabic
2024-11-19 14:38:52 +01:00
Profpatsch ffcba175ff Merge pull request #11330 from Isira-Seneviratne/Java-10-URL-NP
Apply URL encode/decode changes
2024-11-19 14:05:04 +01:00
Isira Seneviratne c7848e5e86 Apply URL encode/decode changes 2024-11-19 13:17:10 +01:00
yosrinajar 6d686b93cb fixed all readme files 2024-11-19 12:17:25 +01:00
yosrinajar 2cc38f59d3 Readme translation to arabic 2024-11-19 12:16:30 +01:00
Stypox 8bf24e6b14 Merge branch 'dev' into release-0.27.3 2024-11-18 17:09:27 +01:00
Stypox 10e7a5cf9c Merge pull request #11268 from TeamNewPipe/user-agent
Update user agent to Firefox ESR 128
2024-11-18 17:06:31 +01:00
Stypox 9f2f219613 Merge branch 'dev' into release-0.27.3 2024-11-18 17:01:58 +01:00
Stypox 841471bf85 Merge pull request #10892 from KaGaster/el-koko
update README.fr.md
2024-11-18 16:59:50 +01:00
Stypox 06d25b0310 Merge pull request #11244 from Isira-Seneviratne/Android-elapsed-time
Use Android's elapsed time formatting
2024-11-18 16:56:41 +01:00
Mohamed Kooli 3c8d81a3c2 add README.fr.md 2024-11-18 16:55:23 +01:00
Stypox cf870add49 Release v0.27.3 (1000) 2024-11-17 20:45:45 +01:00
Stypox a962e6d633 Add changelog for v0.27.3 (1000) 2024-11-17 17:11:40 +01:00
Hosted Weblate 970ef9357b Merge branch 'origin/dev' into Weblate. 2024-11-17 16:58:51 +01:00
H Tamás 4ba961fe7a Translated using Weblate (Hungarian)
Currently translated at 18.7% (15 of 80 strings)

Translation: NewPipe/Metadata
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
2024-11-17 16:58:43 +01:00
Stypox e6c03bf4ac Merge pull request #11711 from Stypox/prepare-for-0.27.3
Actually fix playlist bookmark layout
2024-11-17 16:55:02 +01:00
Stypox 1f39523429 Update NewPipeExtractor 2024-11-16 14:17:37 +01:00
Stypox b43031fb99 Ellipsize uploader text in playlist bookmark 2024-11-16 14:17:37 +01:00
Stypox 986cd52da0 Fix crash because of no height set on playlist bookmark
This is a consequence of https://github.com/TeamNewPipe/NewPipe/pull/11024

x
2024-11-16 14:17:32 +01:00
Isira Seneviratne 7d4a2836fc Use existing scrollbar theme method 2024-11-16 16:45:35 +05:30
Isira Seneviratne 6ea715a18d Clean up unnecessary manual color specification in Compose code 2024-11-16 16:09:10 +05:30
Isira Seneviratne a56debfce6 Merge branch 'refs/heads/refactor' into Compose-theme-improvements
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.kt
#	app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt
2024-11-16 15:50:48 +05:30
Isira Seneviratne 226b6de34f Merge branch 'refs/heads/refactor' into About-Compose
# Conflicts:
#	app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
#	build.gradle
2024-11-16 15:41:50 +05:30
+92 bcd4579187 Translated using Weblate (Hebrew)
Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.3% (734 of 739 strings)

Translated using Weblate (Welsh)

Currently translated at 3.7% (3 of 80 strings)

Translated using Weblate (Bulgarian)

Currently translated at 5.0% (4 of 80 strings)

Added translation using Weblate (Welsh)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 99.7% (737 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 99.4% (735 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 98.1% (725 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 97.8% (723 of 739 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Galician)

Currently translated at 98.5% (728 of 739 strings)

Translated using Weblate (Burmese)

Currently translated at 2.9% (22 of 739 strings)

Translated using Weblate (Tagalog)

Currently translated at 8.1% (60 of 739 strings)

Translated using Weblate (French)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Tamil)

Currently translated at 23.7% (19 of 80 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Dutch)

Currently translated at 62.5% (50 of 80 strings)

Translated using Weblate (Persian)

Currently translated at 92.9% (687 of 739 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Catalan)

Currently translated at 87.1% (644 of 739 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 66.2% (53 of 80 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Albanian)

Currently translated at 79.8% (590 of 739 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (French)

Currently translated at 90.0% (72 of 80 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Telugu)

Currently translated at 58.5% (433 of 739 strings)

Translated using Weblate (Esperanto)

Currently translated at 70.2% (519 of 739 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 97.8% (723 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 17.5% (14 of 80 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 65.0% (52 of 80 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Tamil)

Currently translated at 25.0% (20 of 80 strings)

Translated using Weblate (Hungarian)

Currently translated at 18.7% (15 of 80 strings)

Translated using Weblate (Galician)

Currently translated at 98.3% (727 of 739 strings)

Translated using Weblate (Finnish)

Currently translated at 98.3% (727 of 739 strings)

Translated using Weblate (German)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Bulgarian)

Currently translated at 80.2% (593 of 739 strings)

Translated using Weblate (Hungarian)

Currently translated at 18.7% (15 of 80 strings)

Translated using Weblate (Tamil)

Currently translated at 47.0% (348 of 739 strings)

Translated using Weblate (Tatar)

Currently translated at 6.4% (48 of 739 strings)

Added translation using Weblate (Tatar)

Translated using Weblate (Slovak)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Hungarian)

Currently translated at 16.2% (13 of 80 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Icelandic)

Currently translated at 97.5% (721 of 739 strings)

Translated using Weblate (Tamil)

Currently translated at 21.2% (17 of 80 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (German)

Currently translated at 100.0% (80 of 80 strings)

Translated using Weblate (French)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 97.1% (718 of 739 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (79 of 79 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 11.3% (9 of 79 strings)

Translated using Weblate (Finnish)

Currently translated at 11.3% (9 of 79 strings)

Translated using Weblate (German)

Currently translated at 100.0% (79 of 79 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Finnish)

Currently translated at 97.4% (720 of 739 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (79 of 79 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.8% (738 of 739 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 96.6% (714 of 739 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (79 of 79 strings)

Translated using Weblate (Latvian)

Currently translated at 92.1% (681 of 739 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Latvian)

Currently translated at 91.0% (673 of 739 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Vietnamese)

Currently translated at 76.9% (60 of 78 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Tagalog)

Currently translated at 1.2% (1 of 78 strings)

Translated using Weblate (Latvian)

Currently translated at 87.1% (644 of 739 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Bulgarian)

Currently translated at 80.1% (592 of 739 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Lithuanian)

Currently translated at 98.3% (727 of 739 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Basque)

Currently translated at 42.3% (33 of 78 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Marathi)

Currently translated at 31.9% (236 of 739 strings)

Translated using Weblate (Odia)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Bulgarian)

Currently translated at 66.0% (488 of 739 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (French)

Currently translated at 99.8% (738 of 739 strings)

Translated using Weblate (Interlingua)

Currently translated at 32.4% (240 of 739 strings)

Translated using Weblate (Mongolian)

Currently translated at 5.5% (41 of 739 strings)

Added translation using Weblate (Mongolian)

Translated using Weblate (Interlingua)

Currently translated at 32.0% (237 of 739 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.8% (738 of 739 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.8% (738 of 739 strings)

Translated using Weblate (Tigrinya)

Currently translated at 9.4% (70 of 739 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.8% (738 of 739 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Icelandic)

Currently translated at 3.8% (3 of 78 strings)

Translated using Weblate (Icelandic)

Currently translated at 96.0% (710 of 739 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Turkish)

Currently translated at 44.8% (35 of 78 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 94.3% (697 of 739 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Vietnamese)

Currently translated at 75.6% (59 of 78 strings)

Translated using Weblate (Albanian)

Currently translated at 78.7% (582 of 739 strings)

Translated using Weblate (Dutch)

Currently translated at 61.5% (48 of 78 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Armenian)

Currently translated at 27.8% (206 of 739 strings)

Translated using Weblate (German)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Burmese)

Currently translated at 2.5% (19 of 739 strings)

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

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Tigrinya)

Currently translated at 9.3% (69 of 739 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (78 of 78 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Tamil)

Currently translated at 46.5% (344 of 739 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (German)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Turkish)

Currently translated at 44.8% (35 of 78 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.9% (731 of 739 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.8% (738 of 739 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Romanian)

Currently translated at 99.8% (738 of 739 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (739 of 739 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (739 of 739 strings)

Co-authored-by: --//-- <htetoh2006@outlook.com>
Co-authored-by: 09pulse <junis.mednis@gmail.com>
Co-authored-by: Adrien N <adriennathaniel1999@gmail.com>
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: Andrés Paredes <andresparedeszaa@gmail.com>
Co-authored-by: AntonAkovP <anton.akov@gmail.com>
Co-authored-by: Anxhelo Lushka <anxhelo1995@gmail.com>
Co-authored-by: Balázs Meskó <meskobalazs@mailbox.org>
Co-authored-by: BennyBeat <bennybeat@gmail.com>
Co-authored-by: Bálint Katona <katonabalint0901@gmail.com>
Co-authored-by: Coool (github.com/Coool) <coool@mail.lv>
Co-authored-by: D D <keptawesome@gmail.com>
Co-authored-by: Danial Behzadi <dani.behzi@ubuntu.com>
Co-authored-by: Daniels Gaho <mouth_many452@slmails.com>
Co-authored-by: Davit Mayilyan <davit.mayilyan@protonmail.ch>
Co-authored-by: DevMikey123 <minecraftmikey20yt@gmail.com>
Co-authored-by: Faeh jaekhan <hooby.facsimile081@simplelogin.com>
Co-authored-by: Femini <Olpi@users.noreply.hosted.weblate.org>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Flavian <3zorro.1@gmail.com>
Co-authored-by: Flo P <florian@policnik.de>
Co-authored-by: Francesco James Fanti <francescojamesfanti@gmail.com>
Co-authored-by: Freddy Morán Jr <freddynic159@gmail.com>
Co-authored-by: GET100PERCENT <eraofphysics@yahoo.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Gold Ayan <thangaayyanar@gmail.com>
Co-authored-by: Gontzal Manuel Pujana Onaindia <thadahdenyse@gmail.com>
Co-authored-by: Gonzalo Vidal <idigbacon@gmail.com>
Co-authored-by: Gustavo A <gustavo.shortage796@slmails.com>
Co-authored-by: H Tamás <hovanszki@gmail.com>
Co-authored-by: Hoseok Seo <ddinghoya@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hydra3 <hydra3black@gmail.com>
Co-authored-by: Hứa Đức Quân <huaducquan14@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Inn Charge <inncharge@abv.bg>
Co-authored-by: Jan Novotny <aplikace62@gmail.com>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: Jimi Sainio <kitsu193@gmail.com>
Co-authored-by: Jose Delvani <delvani.eletricista@gmail.com>
Co-authored-by: Jose Delvani <jsdelvani@users.noreply.hosted.weblate.org>
Co-authored-by: Kartik Jivane <jivanekartik21@gmail.com>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: LuanaBanana29 <luana.baron@protonmail.com>
Co-authored-by: Luna <social.pvxuu@slmail.me>
Co-authored-by: MS-PC <MSPCtranslator@gmail.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Onebyone <onebyone222@ccmail.uk>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: PepeV_nRT <pepev.nrt@gmail.com>
Co-authored-by: Phi Huynh <huynhkhaphi.ltp20@gmail.com>
Co-authored-by: Philip Goto <philip.goto@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: Rhoslyn Prys <rprys@posteo.net>
Co-authored-by: Riku <riksu9000@gmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Sandeep Balaji <besandeep21@gmail.com>
Co-authored-by: SejeroDev <sejerodev@gmail.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Teoman <teoteot1122@gmail.com>
Co-authored-by: Timur Seber <seber.tatsoft@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: Tzvika <mmm_45@walla.com>
Co-authored-by: Tấn Lực Trương <september122022ios16@gmail.com>
Co-authored-by: Vas R <mrkomododragon1234@gmail.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: W L <wl@mailhole.de>
Co-authored-by: WB <dln0@proton.me>
Co-authored-by: Wydow <wydow@protonmail.com>
Co-authored-by: X <dieeeazpnnqbpddh@cock.email>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Zorro <3zorro.1@gmail.com>
Co-authored-by: abfreeman <freemanab@protonmail.com>
Co-authored-by: algimantas <algimantas@margevicius.lt>
Co-authored-by: billy appetie <billy_appetie@users.noreply.hosted.weblate.org>
Co-authored-by: dulgun <dulguun.tuguldur11@gmail.com>
Co-authored-by: fsbat0 <fsbat0@users.noreply.hosted.weblate.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: gfbdrgng <hnaofegnp@hldrive.com>
Co-authored-by: j <jonas84@infocus.lt>
Co-authored-by: justcontributor <dumkty5663@gmail.com>
Co-authored-by: kuragehime <kuragehime641@gmail.com>
Co-authored-by: kuriokurio <kuriokurio@proton.me>
Co-authored-by: mamarama9904 <mamarama9904@gmail.com>
Co-authored-by: nick vurgaft <slipperygate@gmail.com>
Co-authored-by: p3nguin-kun.png <p3nguinkun@proton.me>
Co-authored-by: rakijagamer-2003 <rakijaisthebest@abv.bg>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: triaza <triazatriborinane@gmail.com>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: weldu <fsbat0@users.noreply.hosted.weblate.org>
Co-authored-by: ε <aaypkzixad@outlook.com>
Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
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/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cy/
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/fi/
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/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/is/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/lv/
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/ru/
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/ta/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tl/
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/
Translation: NewPipe/Metadata
2024-11-14 17:37:31 +01:00
Stypox 6fe417abc6 Merge pull request #11024 from AbdeltwabMF/fix/rtl_lang_adjustment_bookmark
Adjust the playlist bookmark item layout for RTL languages
2024-11-14 16:26:25 +01:00
Stypox a229ab68d5 Merge pull request #11696 from codyit/history-remove-dialog-override
Remove history dialog override so clicking "Start playing in the background" would only enqueue the current item instead of the full history which is usually massive
2024-11-12 10:43:01 +01:00
Stypox 544b30290d Merge pull request #11694 from VishramKidPG123/fix-typo-in-readme
Fix a typo in README
2024-11-12 10:32:01 +01:00
Cody T.-H. Chiu cb300724da Remove history dialog override so clicking "Start playing in the background" would only enqueue the current item instead of the full history which is usually massive 2024-11-12 18:24:23 +13:00
VishramKidPG123 0ac5a269ff Update README.md 2024-11-11 22:40:29 -05:00
Stypox 13585ca0be Avoid drawing surface background twice for comments fragment 2024-11-11 16:15:36 +01:00
Stypox 62ab9bd740 Merge pull request #11060 from Isira-Seneviratne/Comments-Compose
Migrate comment fragments to Jetpack Compose
2024-11-11 16:12:53 +01:00
Stypox fdf36cbad6 Deduplicate and improve Scrollbar theme 2024-11-11 15:20:38 +01:00
Stypox aea2b7c7f3 Show correct reply count in dialog 2024-11-11 14:58:54 +01:00
Stypox 37d1c784fa Create utilities to copy to clipboard in Compose code 2024-11-11 14:58:54 +01:00
Stypox cea149f852 Add .kotlin/ to gitignore 2024-11-11 14:26:01 +01:00
Stypox a92a28517e Use Icons.Default.* instead of vector assets 2024-11-11 14:25:28 +01:00
Stypox 800961c3d7 Unexpand bottom sheet dialog when clicking on a channel 2024-11-11 13:51:24 +01:00
Stypox 9d8a79b0bd Slightly improve comment replies header spacing 2024-11-11 13:34:18 +01:00
Tobi 0009613608 Merge pull request #11140 from shrimprugbysnowowl/dev
Adding Hash of Signing Key to README
2024-11-11 07:38:13 +01:00
Tobi 7c18d4dd01 Update README.md 2024-11-11 07:35:37 +01:00
Tobi fe1c538f9c Update README.md 2024-11-11 07:34:45 +01:00
Stypox ef56dea817 Fix content color in comment replies fragment 2024-11-11 00:29:29 +01:00
Stypox 23b3835af0 Fix PagingSource for comments
The previous implementation was skipping the first page of comments
2024-11-11 00:16:32 +01:00
Stypox 412e1d602a Better handle unknown values for comment & like count 2024-11-10 23:45:10 +01:00
Stypox 802a094154 Improve comment replies dialog layout 2024-11-10 23:28:59 +01:00
Stypox e6b1341246 Improve Comment layout 2024-11-10 23:09:29 +01:00
Stypox 36ede243e3 Update compose bom and fix navigation compose without version 2024-11-10 20:53:49 +01:00
Stypox bac9f7eebf Merge branch 'refactor' into pr11060 2024-11-10 16:50:46 +01:00
litetex 8ada566bf1 Replaced `Icepick with Bridge and Android-State`
* IcePick fails on Java 21 (default in Android Studio 2024.2)
* Bridge is the most modern alternative that is currently available. It is backed by ``Android-State`` and can be configured with various frameworks
* In the long term this should be replaced with something better
2024-11-10 16:42:42 +01:00
litetex 5bd4ed77df Fix Android Gradle plugin warning 2024-11-10 16:42:42 +01:00
litetex 97652ac015 Update Gradle to latest version 2024-11-10 16:42:41 +01:00
litetexandThompson3142 6dd24033a4 Replace symlink with original
Co-Authored-By: Thompson3142 <115718208+thompson3142@users.noreply.github.com>
2024-11-10 16:42:41 +01:00
litetex 4de3ef20be Delete symlink 2024-11-10 16:42:41 +01:00
litetex 702f74291d Use working Extractor version
The tag can't be resolved by Jitpack so use the commit-hash instead
2024-11-10 16:42:41 +01:00
litetex d8759993a9 CI: Use Java 21 2024-11-10 16:42:41 +01:00
litetex 7787eafd3a Fix build failing locally due to outdated kotlin version 2024-11-10 16:42:41 +01:00
Stypox f08e07873a Merge pull request #11566 from nicholasala/fix/#10993-strange-playlist-order
Fixed playlist order
2024-11-10 15:45:33 +01:00
TobiGr 1193b02ca1 Update user agent to Firefox ESR128 2024-11-03 11:52:31 +01:00
Tobi c0b36b86b9 Merge pull request #11614 from rmtilde/fix-related-items-enque-popup-crash
Fix related items list enqueue popup crash
2024-11-03 10:13:45 +01:00
rmtildeandTobi 66ec596f67 Update app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2024-11-03 18:26:38 +11:00
Tobi 90404a23ce Merge pull request #11621 from u7656655/fixing-ui-crash-11468
Fix UI crash when user navigates away before the download dialog appears
2024-11-02 23:30:35 +01:00
Tobi 64ad05d813 Merge pull request #11629 from Two-Ai/kotlin-getStringSafe
Add null-safe SharedPreferences.getStringSafe
2024-10-27 20:58:25 +01:00
TwoAi 734b6e2b67 Add null-safe SharedPreferences.getStringSafe
Null-safe alternative to SharedPreferences.getString that guarantees the return value is non-null when defValue is non-null.
2024-10-27 20:38:28 +01:00
Tobi 94f992a2e2 Merge pull request #11656 from litetex/better-control-over-version
[Build] Make it possible control the version code and name
2024-10-27 20:05:53 +01:00
litetex c8550695aa Make it possible control the version code and name 2024-10-27 17:51:22 +01:00
Tobi cdac50bab3 Merge pull request #11596 from Thompson3142/fix_scrubbing_seekbar_preview_crash
Fix seekbar crashing on drag with faulty frameset
2024-10-27 16:19:44 +01:00
Thompson3142 23961548c0 Formatting changes (back to original) 2024-10-27 14:38:25 +01:00
Thompson3142andTobi ba1e9c8e1b Update comment
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2024-10-27 14:17:32 +01:00
Tobi f4baf4628e Update app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java 2024-10-27 09:41:45 +01:00
Tobi 05a87da827 Merge pull request #11651 from u7656655/fix-addtoplaylist-crash
Fix crash after adding item to a playlist caused by null thumbnail URL
2024-10-27 09:15:49 +01:00
Jacob Hawkins fef40014a0 Added not null check for thumbnail URL before performing comparison 2024-10-27 17:38:57 +11:00
rmtilde 1996c1176c Merge branch 'TeamNewPipe:dev' into fix-related-items-enque-popup-crash 2024-10-26 20:33:17 +11:00
Elva Kang 0190bcee25 Fix line length violation 2024-10-24 16:04:53 +11:00
Elva Kang 1ed4928f40 Add comment for fragment lifecycle checks before showing DownloadDialog 2024-10-24 11:47:23 +11:00
Elva Kang 63bc982cb2 Merge branch 'TeamNewPipe:dev' into fixing-ui-crash-11468 2024-10-24 11:11:37 +11:00
Stypox 3a286515f2 Merge pull request #11636 from litetex/fix-build-2024-10
Fix compilation
2024-10-23 22:18:48 +02:00
litetex 2e96b65fda Replaced `Icepick with Bridge and Android-State`
* IcePick fails on Java 21 (default in Android Studio 2024.2)
* Bridge is the most modern alternative that is currently available. It is backed by ``Android-State`` and can be configured with various frameworks
* In the long term this should be replaced with something better
2024-10-23 21:28:07 +02:00
litetex 2482615460 Fix Android Gradle plugin warning 2024-10-22 21:40:16 +02:00
litetex 9384365061 Update Gradle to latest version 2024-10-22 21:39:44 +02:00
litetexandThompson3142 b1d4b66aa6 Replace symlink with original
Co-Authored-By: Thompson3142 <115718208+thompson3142@users.noreply.github.com>
2024-10-22 21:24:10 +02:00
litetex ea0da5fdbd Delete symlink 2024-10-22 21:24:09 +02:00
litetex d80b6a759c Use working Extractor version
The tag can't be resolved by Jitpack so use the commit-hash instead
2024-10-22 21:23:34 +02:00
litetex 8106ba68b5 CI: Use Java 21 2024-10-22 21:23:26 +02:00
litetex ee15a72e4f Fix build failing locally due to outdated kotlin version 2024-10-22 21:03:08 +02:00
Isira Seneviratne 4f4136c6e9 Merge branch 'refs/heads/refactor' into About-Compose
# Conflicts:
#	app/build.gradle
#	build.gradle
2024-10-22 20:15:07 +05:30
Siddhesh Naik b399030e19 Settings redesign debug page (#10876)
Initial Work for Settings Page with Jetpack Compose

- Implemented a new settings page using Jetpack Compose.
- Added a new settings option to enable the redesigned settings page.
- This option allows for gradual integration and testing of the new
  settings page, minimizing disruptions to current functionality.

Plan for Settings Items:
- Jetpack Compose does not have a direct equivalent to the
  Preference/settings library.
- We could consider using third-party libraries that offer preference
  items as composables.
- However, these libraries may be incomplete or lack active development.
- Given our specific needs for only a subset of preference types,
  creating custom composables would be beneficial.
- This approach allows for fine-tuning the components to our specific
  use case.
2024-10-22 00:47:26 +05:30
Elva Kang 2eb256799d Revert "Project now runs"
This reverts commit 53edd054aa.
2024-10-20 10:29:48 +11:00
Elva Kang 0cf4732d8a Fix UI crash when user navigates away before the download dialog appears 2024-10-19 19:43:34 +11:00
Jacob Hawkins 53edd054aa Project now runs 2024-10-17 15:14:15 +11:00
rmtilde 678f0a786a Merge pull request #1 from rmtilde/fix-related-items-enqueue-on-video-change
Fix Crash on Related Items Modal
2024-10-17 13:37:19 +11:00
rmtilde b14f65804d Added comments to explain changes 2024-10-16 23:58:32 +11:00
u7310752 781a69d60d Chanegd related videos enqueue modal to attach to parent fragment instead 2024-10-16 20:52:43 +11:00
Thompson3142 eb9f300e60 Fix seekbar preview crashes (#11584)
Fixed crashes from recycled bitmaps by creating real copies of bitmaps if necessary + some minor refactoring
2024-10-10 10:32:06 +02:00
Nicholas Sala 063568b620 Fixed playlist order between "Bookmarked Playlists" list and "add to playlist" dialog list. Now both lists are sorted using case insensitive order if the user has not yet adjusted manually the order. 2024-09-26 13:24:26 +00:00
Isira Seneviratne 0991461d04 Merge branch 'refs/heads/refactor' into About-Compose 2024-09-26 07:01:03 +05:30
Stypox 49bcf2c41b Merge branch 'dev' into refactor 2024-09-24 14:45:59 +02:00
Isira Seneviratne c00c6c460c Add scaffold preview, use container color in about screen and scaffold 2024-09-17 04:26:36 +05:30
Isira Seneviratne 4c4fe3f511 Add scrollbar color scheme 2024-09-16 16:28:49 +05:30
Isira Seneviratne db485c3d77 Remove unnecessary annotation 2024-09-16 16:15:37 +05:30
Isira Seneviratne c0388d948b Add colors for Compose scrollbars 2024-09-16 15:33:41 +05:30
Isira Seneviratne 43bbddcc26 Add theme generated from the Material Theme Builder 2024-09-16 15:27:21 +05:30
Isira Seneviratne 6471b64ab6 Update dependencies 2024-09-16 12:53:16 +05:30
Isira Seneviratne b9fcf0dff8 Enable edge-to-edge display 2024-09-16 12:45:03 +05:30
Isira Seneviratne 3177ca6e8a Avoid issues if context is a ContextWrapper 2024-09-11 21:57:51 +05:30
Isira Seneviratne 5017f4f05a Update dependencies 2024-09-05 09:23:00 +05:30
Mihael_RiverandTobi 035c394cf6 Fixing the 404 page not found, when clicking on "contribution notes" in multiple README.md's translated into different languages (#11487)
Link to contribution notes wasn't working

* Update README.de.md, fix grammar in README.de.md
* Update README.asm.md
* Update README.fr.md
* Update README.hi.md
* Update README.it.md
* Update README.pa.md
* Update README.pt_BR.md
* Update README.ru.md
* Update README.sr.md

---------

Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2024-08-30 16:32:42 +02:00
Isira Seneviratne 823d4a041f Improve loading indicator positioning 2024-08-30 16:59:15 +05:30
Isira Seneviratne 62d4044d6c Make lazy column scrollbars red 2024-08-30 09:02:56 +05:30
Isira Seneviratne 3785404618 Display number of comments 2024-08-30 08:46:02 +05:30
Isira Seneviratne c98ad62163 Implement black theme in Compose 2024-08-29 08:06:56 +05:30
Isira Seneviratne 4cac111b66 Reduce preview count 2024-08-29 07:46:37 +05:30
Isira Seneviratne 941b8eb194 Implement copy on long click 2024-08-29 07:24:03 +05:30
Isira Seneviratne b1add13bfd Address code review comments 2024-08-28 18:15:11 +05:30
Isira Seneviratne 5fffee2c7d Fix text color in bottom sheet 2024-08-28 17:59:38 +05:30
Isira Seneviratne f9340ae604 Improve compose function organisation 2024-08-27 08:19:37 +05:30
Isira Seneviratne d3a6991fd4 Use Fragment.content extension, improve comment composables 2024-08-26 19:29:46 +05:30
Isira Seneviratne b0bfd4a807 Merge branch 'refs/heads/refactor' into About-Compose
# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt
#	build.gradle
2024-08-23 20:16:19 +05:30
Isira Seneviratne 3641698379 Merge branch 'refs/heads/refactor' into Comments-Compose
# Conflicts:
#	app/build.gradle
2024-08-23 20:13:03 +05:30
Isira Seneviratne 2836191fb3 Migrate related items fragment to Jetpack Compose (#11383)
* Rename .java to .kt

* Migrate related items fragment to Jetpack Compose

* Specify mode parameter explicitly

* Rm unused class

* Fix list item size

* Added stream progress bar, separate stream and playlist thumbnails

* Display message if no related streams are available

* Dispose of related items when closing the video player

* Add modifiers for no items message function

* Implement remaining stream menu items

* Improved stream composables

* Use view model lifecycle scope

* Make live color solid red

* Use nested scroll modifier

* Simplify determineItemViewMode()
2024-08-23 19:51:32 +05:30
Isira Seneviratne 0df6c7fc2c Remove unused assets 2024-08-23 14:48:41 +05:30
Isira Seneviratne b1ebd3ecd9 Update Compose BOM 2024-08-23 14:22:45 +05:30
Isira Seneviratne 4758244cf5 Use AboutLibraries to display library information 2024-08-23 14:05:50 +05:30
Isira Seneviratne 294b9cf347 Rm unused declaration 2024-08-17 08:25:39 +05:30
Tobi fad3120b00 Merge pull request #11428 from Two-Ai/remove-returnActivity-test
Remove outdated returnActivity test code
2024-08-15 01:53:28 +02:00
Isira Seneviratne 6d05af484e Use int array 2024-08-11 09:31:09 +05:30
TwoAi 38c823a042 Remove outdated returnActivity test code
returnActivity was removed in 463dd8e
2024-08-10 23:09:54 -04:00
Isira Seneviratne e082bca5e0 Use nested scroll modifier 2024-08-11 08:23:13 +05:30
Isira Seneviratne f9dae9078e Always show comment thumbnails, even if placeholders 2024-08-11 08:23:13 +05:30
Isira Seneviratne e955beeef1 Update Kotlin to 2.0, update dependencies and fix issues 2024-08-11 08:23:10 +05:30
Isira Seneviratne eaac7f3f85 Improved component organisation 2024-08-11 08:21:53 +05:30
Isira Seneviratne ea414f57d4 Added DescriptionText composable 2024-08-11 08:21:53 +05:30
Isira Seneviratne f984b26626 Fix some modifiers 2024-08-11 08:21:53 +05:30
Isira Seneviratne edab9a6a1f Fix alignment of comment message 2024-08-11 08:21:53 +05:30
Isira Seneviratne 4740e3be86 Make parsed links clickable, visible 2024-08-11 08:21:53 +05:30
Isira Seneviratne e639b02fed Animate comment expand/collapse 2024-08-11 08:21:53 +05:30
Isira Seneviratne ac1ca1412d Improve comment loading smoothness 2024-08-11 08:21:52 +05:30
Isira Seneviratne d131d3399a Rm unused method 2024-08-11 08:21:52 +05:30
Isira Seneviratne 1009dc4d4e Added loading indicator 2024-08-11 08:21:52 +05:30
Isira Seneviratne 42cb914616 Replace padding modifier with verticalArrangement in comment header 2024-08-11 08:21:52 +05:30
Isira Seneviratne e72da94eb1 Rm extra padding in header 2024-08-11 08:21:52 +05:30
Isira Seneviratne c5d94a5b60 Add comment view model 2024-08-11 08:21:52 +05:30
Isira Seneviratne 02c5f2607a Cache paging data using the cachedIn() extension 2024-08-11 08:21:52 +05:30
Isira Seneviratne 369a46f8fe Improve code organization 2024-08-11 08:21:52 +05:30
Isira Seneviratne 909d214002 Rm redundant Surface 2024-08-11 08:21:52 +05:30
Isira Seneviratne 5e7e14ee4d Handle no comments and comments disabled scenarios 2024-08-11 08:21:52 +05:30
Isira Seneviratne b092fe2c76 Replace Spacers with the horizontalArrangement parameter 2024-08-11 08:21:52 +05:30
Isira Seneviratne b9dd7078ad Replace CommentRepliesFragment with bottom sheet composable, improve previews 2024-08-11 08:21:52 +05:30
Isira Seneviratne 93310955f2 Added scrollbar to comment section 2024-08-11 08:21:52 +05:30
Isira Seneviratne 9c52e039ee Migrate comments fragment to Jetpack Compose 2024-08-11 08:21:52 +05:30
Isira Seneviratne be037e0756 Rename .java to .kt 2024-08-11 08:21:52 +05:30
Isira Seneviratne 5bfb0449cf Fixed fragment title 2024-08-11 08:21:52 +05:30
Isira Seneviratne 0ec81c9e52 Fixed like count display 2024-08-11 08:21:52 +05:30
Isira Seneviratne 5841eaa6d7 Set view strategy 2024-08-11 08:21:52 +05:30
Isira Seneviratne e92ba8f5d1 Add replies button 2024-08-11 08:21:52 +05:30
Isira Seneviratne 1908e18dc4 Use AnnotatedString to handle HTML parsing 2024-08-11 08:21:52 +05:30
Isira Seneviratne e30d5e4305 Fixed some comment issues 2024-08-11 08:21:52 +05:30
Isira Seneviratne 11bb2495ba Improve previews, display date of comment 2024-08-11 08:21:52 +05:30
Isira Seneviratne 341cc37ce7 Update replies fragment to use the comment composable as well 2024-08-11 08:21:52 +05:30
Isira Seneviratne 1620668966 Add comment ellipsis 2024-08-11 08:21:51 +05:30
Isira Seneviratne 56c80ce6dd Added missing comment features, fixed theming 2024-08-11 08:21:51 +05:30
Isira Seneviratne 8ce9a7e43c Added like count 2024-08-11 08:21:51 +05:30
Isira Seneviratne e05d97732e Use reply header composable in fragment 2024-08-11 08:21:51 +05:30
Isira Seneviratne 644a345b55 Rename .java to .kt 2024-08-11 08:21:51 +05:30
Isira Seneviratne bda961a04c Convert comment replies views to Jetpack Compose 2024-08-11 08:21:51 +05:30
Isira Seneviratne ba2efded76 Replace Picasso with Coil in about 2024-08-11 08:13:21 +05:30
Isira Seneviratne b05b98ca61 Improved component organisation 2024-08-11 08:13:21 +05:30
Isira Seneviratne 7a7f81ac7f Fix tab text color 2024-08-11 08:13:21 +05:30
Isira Seneviratne 6e6c171dd7 Added new icon 2024-08-11 08:13:21 +05:30
Isira Seneviratne 8a41c8cf66 Added buttons to alert dialog 2024-08-11 08:13:21 +05:30
Isira Seneviratne 05271d95a9 Migrate about activity to Jetpack Compose 2024-08-11 08:13:21 +05:30
9d04a73c85 Merge dev to refactor (#11427)
* add NP icon for Android Studio's NewUI

* Fix NPE in MediaSessionPlayerUi while destroying player

* Update NewPipeExtractor to v0.24.1

* Add changelogs for hotfix release v0.27.1 (998)

* Hotfix release v0.27.1 (998)

* Update README.pt_BR.md (#11275)

* Update Matrix room link, and prioritise it (#11350)

* Update Matrix room link, and prioritise it

* Update Matrix room link in CONTRIBUTING.md

* Prioritise Matrix in contribution doc too

* Update NewPipeExtractor to v0.24.2

* Hotfix release v0.27.2 (999)

* Add changelogs for hotfix release v0.27.2 (999)

* Don't warn about rhino class in proguard

Likely related to https://github.com/mozilla/rhino/commit/01a7b20655602f7e2df59af744b47b77f678b6cf but I am not completely sure.
I tested the app and it works well, so I think that org.mozilla.javascript.JavaToJSONConverters is not used really.

This is the full list of errors:
Missing class java.beans.BeanDescriptor (referenced from: java.lang.Object org.mozilla.javascript.JavaToJSONConverters.lambda$static$4(java.lang.Object))
Missing class java.beans.BeanInfo (referenced from: java.lang.Object org.mozilla.javascript.JavaToJSONConverters.lambda$static$4(java.lang.Object))
Missing class java.beans.IntrospectionException (referenced from: java.lang.Object org.mozilla.javascript.JavaToJSONConverters.lambda$static$4(java.lang.Object))
Missing class java.beans.Introspector (referenced from: java.lang.Object org.mozilla.javascript.JavaToJSONConverters.lambda$static$4(java.lang.Object))
Missing class java.beans.PropertyDescriptor (referenced from: java.lang.Object org.mozilla.javascript.JavaToJSONConverters.lambda$static$4(java.lang.Object))

* Remove code committed accidentally

---------

Co-authored-by: Christian Schabesberger <chris.schabesberger@mailbox.org>
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
Co-authored-by: Stypox <stypox@pm.me>
Co-authored-by: #27 <68751594+tag27@users.noreply.github.com>
Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com>
2024-08-11 08:11:50 +05:30
Stypox d336f4cef2 Merge pull request #11238 from Isira-Seneviratne/Coil
Migrate image loading from Picasso to Coil
2024-08-07 18:45:16 +02:00
Stypox 51ee2f8d1e Merge branch 'master' into dev 2024-07-25 21:20:44 +02:00
opusforlife2 0e8303f13a Update Matrix room link, and prioritise it (#11350)
* Update Matrix room link, and prioritise it

* Update Matrix room link in CONTRIBUTING.md

* Prioritise Matrix in contribution doc too
2024-07-25 16:21:21 +02:00
Isira Seneviratne 4ec7532126 Addressed code review comments 2024-07-23 05:25:55 +05:30
Isira Seneviratne da83646303 Update Coil 2024-07-22 08:12:37 +05:30
Stypox 72e9f7f9cf Merge branch 'master' into dev 2024-07-15 10:17:27 +02:00
#27 ad6b676c81 Update README.pt_BR.md (#11275) 2024-07-13 19:32:24 +02:00
Isira Seneviratne 07c63f794e Update documentation 2024-07-07 14:25:02 +05:30
Isira Seneviratne 26dd86e967 Use Android's elapsed time formatting 2024-07-07 10:46:17 +05:30
Isira Seneviratne 5062d38b65 Merge pull request #11237 from TeamNewPipe/revert-11201-Coil
Revert "Migrate image loading from Picasso to Coil"
2024-07-05 08:40:34 +05:30
Isira Seneviratne 82b492c050 Revert "Migrate image loading from Picasso to Coil (#11201)"
This reverts commit 73e3a69aaf.
2024-07-05 08:29:21 +05:30
Isira Seneviratne 73e3a69aaf Migrate image loading from Picasso to Coil (#11201)
* Load notification icons using Coil

* Migrate to Coil from Picasso

* Clean up Picasso leftovers

* Enable RGB-565 for low-end devices

* Added Coil helper method

* Add annotation

* Simplify newImageLoader implementation

* Use Coil's default disk and memory cache config

* Enable crossfade animation

* Correct method name

* Fix thumbnail not being displayed in media notification
2024-07-03 18:53:04 +05:30
Isira Seneviratne 348a79f91d Fix thumbnail not being displayed in media notification 2024-07-03 14:41:47 +05:30
Tobi 5e5e77f746 Merge pull request #11230 from TeamNewPipe/idea_icon
add NP icon for Android Studio's NewUI
2024-07-03 09:50:40 +02:00
Isira Seneviratne c4ada7ff6e Correct method name 2024-07-03 09:30:47 +05:30
Isira Seneviratne 39d0691c7e Enable crossfade animation 2024-07-03 09:10:57 +05:30
Isira Seneviratne 71361de8ee Use Coil's default disk and memory cache config 2024-07-03 09:10:54 +05:30
Isira Seneviratne 8aa2590fd3 Simplify newImageLoader implementation 2024-07-03 09:10:52 +05:30
Isira Seneviratne e3b7bf467e Add annotation 2024-07-03 09:10:49 +05:30
Isira Seneviratne f74402bc94 Added Coil helper method 2024-07-03 09:10:46 +05:30
Isira Seneviratne 4d3b4a7b20 Enable RGB-565 for low-end devices 2024-07-03 09:10:44 +05:30
Isira Seneviratne e6302cc868 Clean up Picasso leftovers 2024-07-03 09:10:40 +05:30
Isira Seneviratne 844b4edf48 Migrate to Coil from Picasso 2024-07-03 09:10:37 +05:30
Isira Seneviratne 92a7f22d3c Load notification icons using Coil 2024-07-03 09:10:34 +05:30
Isira Seneviratne 03167a1e9c Merge pull request #11234 from TeamNewPipe/dev
Merge dev to refactor
2024-07-03 09:05:32 +05:30
Stypox 1f309854bc Run CI on pull requests to refactor branch, too 2024-07-02 17:37:09 +02:00
Christian Schabesberger 2ac0d1f13a add NP icon for Android Studio's NewUI 2024-07-02 09:31:34 +02:00
Stypox 4eeea7b787 Merge pull request #11209 from EricDriussi/kotlin-contributing
Remove kotlin code restriction from contribution guidelines
2024-06-25 08:26:13 +02:00
Eric Driussi e64c01d2da Remove kotlin restriction 2024-06-24 09:47:29 +01:00
Tobi 0c7a91f852 Merge pull request #11067 from snaik20/fix_rss_button_visibility
Fix RSS button visibility
2024-06-17 11:49:33 +02:00
Tobi a2d93b389c Merge pull request #11110 from Neznak/add-peertube-instance
[Peertube] Handle `subscribeto.me` instance links automatically
2024-06-17 11:48:25 +02:00
Tobi c795214abb Merge pull request #11112 from aryn-ydv/extend-playlist-description
Make playlist description clickable to show more / less content
2024-06-17 11:32:10 +02:00
shrimprugbysnowowl 71822a47a5 Update README.md 2024-06-07 14:24:59 +00:00
shrimprugbysnowowl e1bf67c676 Update README.md 2024-06-07 14:20:06 +00:00
Aryan Yadav 8583c48264 fixes #11093 2024-05-28 10:14:46 +05:30
Neznak 2a3d133bcf Add missing Peertube instance subscribeto.me to the links Newpipe handles 2024-05-27 14:08:18 +03:00
Isira Seneviratne 3e3d1fd265 Merge pull request #11075 from Isira-Seneviratne/Comment-touch-lambda
Convert comment touch listener to a lambda
2024-05-26 04:34:49 +05:30
Tobi 8645618f1a Merge pull request #11094 from moontoaster/update-prettytime-to-fix-ukrainian
Update PrettyTime to 5.0.8
2024-05-23 21:24:40 +02:00
moontoaster e48ce5a103 Update PrettyTime to 5.0.8
This version contains a fix for Ukrainian locale which fixes #11092.
2024-05-23 20:57:05 +03:00
Abd El-Twab M. Fakhry c02ceda22f Use layout constraints instead of static height 2024-05-18 16:47:41 +03:00
Isira Seneviratne 46139340fe Convert comment touch listener to a lambda 2024-05-15 06:51:57 +05:30
Stypox d479f29e9b Merge pull request #10875 from snaik20/intro-jetpack-compose
Introducing Jetpack Compose in NewPipe
2024-05-13 21:17:11 +02:00
Siddhesh Naik 1af798b04b Introducing Jetpack Compose in NewPipe
This pull request integrates Jetpack Compose into NewPipe by:
- Adding the necessary dependencies and setup.
- This is part of the NewPipe rewrite and fulfils the requirement for
  the planned settings page redesign.
- Introducing a Toolbar composable with theming that aligns with
  NewPipe's design.

Note:
- Theme colors are generated using the Material Theme builder (https://m3.material.io/styles/color/overview).
2024-05-13 03:53:35 +05:30
Siddhesh Naik 7204407690 Fix RSS button visibility
- The `onPrepareMenu` callback is invoked after setting the visibility
  of the menu items.
- Due to this, the menu item resets to it's default visibility.
- Now updating the menu item within the callback.
- Also migrated to the MenuHost framework to reduce dependency on
  deprecated APIs.
2024-05-13 02:28:21 +05:30
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
Abd El-Twab M. Fakhry cf21b9feaf Revert "Fix compilation error when parsing unsupported file format"
This reverts commit 8267d325ed.
2024-05-01 17:21:24 +03:00
Abd El-Twab M. Fakhry b74cab6642 Adjust the playlist bookmark item layout for RTL languages 2024-05-01 01:38:46 +03:00
Abd El-Twab M. Fakhry 8267d325ed Fix compilation error when parsing unsupported file format 2024-04-30 23:41:02 +03: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 f4fb960c62 Migrate to non-transitive R classes 2024-03-29 00:17:13 +01:00
2103 changed files with 57553 additions and 24820 deletions
+18
View File
@@ -0,0 +1,18 @@
#
# SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
# SPDX-License-Identifier: GPL-3.0-or-later
#
root = true
[*.{kt,kts}]
ktlint_code_style = android_studio
# https://pinterest.github.io/ktlint/latest/rules/standard/#function-naming
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_class-signature = disabled
ktlint_standard_function-expression-body = disabled
ktlint_standard_max-line-length = disabled
ktlint_standard_mixed-condition-operators = disabled
ktlint_standard_package-name = disabled
ktlint_standard_property-naming = disabled
+17 -8
View File
@@ -3,10 +3,23 @@
NewPipe contribution guidelines
===============================
## AI policy
* Using generative AI to develop new features or making larger code changes is generally prohibited. Please refrain from contributions which are heavily depending on AI generated source code because they are usually lacking a fundamental understanding of the overall project structure and thus come with poor quality. However, you are allowed to use gen. AI if you
* are aware of the project structure,
* ensure that the generated code follows the project structure,
* fully understand the generated code, and
* review the generated code completely.
* Using AI to find the root cause of bugs and generating small fixes might be acceptable. However, gen. AI often does not fix the underlying problem but is trying to fix the symptoms. If you are using AI to fix bugs, ensure that the root cause is tackled.
* The use of AI to generate documentation is allowed. We ask you to thoroughly check the quality of generated documentation wrong, misleading or uninformative documentation is useless and wastes the reader's time. Ensure that reasoning is documented.
* Using generative AI to write or fill in PR or issue templates is prohibited. Those texts are often lengthy and miss critical information.
* PRs and issues that do not follow this AI policy can be closed without further explanation.
## Crash reporting
Report crashes through the **automated crash report system** of NewPipe.
This way all the data needed for debugging is included in your bugreport for GitHub.
This way all the data needed for debugging is included in your bug report for GitHub.
You'll see *exactly* what is sent, be able to add **your comments**, and then send it.
## Issue reporting/feature requests
@@ -42,10 +55,6 @@ You'll see *exactly* what is sent, be able to add **your comments**, and then se
* Create PRs that cover only **one specific issue/solution/bug**. Do not create PRs that are huge monoliths and could have been split into multiple independent contributions.
* NewPipe uses [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor) to fetch data from services. If you need to change something there, you must test your changes in NewPipe. Telling NewPipe to use your extractor version can be accomplished by editing the `app/build.gradle` file: the comments under the "NewPipe libraries" section of `dependencies` will help you out.
### Kotlin in NewPipe
* NewPipe will remain mostly Java for time being
* Contributions containing a simple conversion from Java to Kotlin should be avoided. Conversions to Kotlin should only be done if Kotlin actually brings improvements like bug fixes or better performance which are not, or only with much more effort, implementable in Java. The core team sees Java as an easier to learn and generally well adopted programming language.
### Creating a Pull Request (PR)
* Make changes on a **separate branch** with a meaningful name, not on the _master_ branch or the _dev_ branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request (PR) on GitHub.
@@ -83,6 +92,6 @@ The [ktlint](https://github.com/pinterest/ktlint) plugin does the same job as ch
## Communication
* The #newpipe channel on Libera Chat (`ircs://irc.libera.chat:6697/newpipe`) has the core team and other developers in it. [Click here for webchat](https://web.libera.chat/#newpipe)!
* You can also use a Matrix account to join the NewPipe channel at [#newpipe:libera.chat](https://matrix.to/#/#newpipe:libera.chat). Some convenient clients, available both for phone and desktop, are listed at that link.
* You can post your suggestions, changes, ideas etc. on either GitHub or IRC.
* You can use a Matrix account to join the NewPipe channel at [#newpipe:matrix.newpipe-ev.de](https://matrix.to/#/#newpipe:matrix.newpipe-ev.de). Some convenient clients, available both for phone and desktop, are listed at that link.
* Alternatively, the #newpipe channel on Libera Chat (`ircs://irc.libera.chat:6697/newpipe`) can also be joined, as it is bridged to the Matrix room. [Click here for webchat](https://web.libera.chat/#newpipe)!
* You can post your suggestions, changes, ideas etc. on either GitHub or Matrix (including via IRC).
+2
View File
@@ -26,6 +26,8 @@ body:
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md)."
required: true
- label: "I have read and understood the [AI policy](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md#ai-policy). The content of this bug report is not generated by AI."
required: true
- type: input
id: app-version
+3 -3
View File
@@ -3,9 +3,9 @@ contact_links:
- name: ❓ Question
url: https://github.com/TeamNewPipe/NewPipe/discussions/new?category=questions
about: Ask about anything NewPipe-related
- name: 💬 Matrix
url: https://matrix.to/#/#newpipe:matrix.newpipe-ev.de
about: Chat with us via Matrix for quick Q/A
- name: 💬 IRC
url: https://web.libera.chat/#newpipe
about: Chat with us via IRC for quick Q/A
- name: 💬 Matrix
url: https://matrix.to/#/#newpipe:libera.chat
about: Chat with us via Matrix for quick Q/A
@@ -25,6 +25,8 @@ body:
required: true
- label: "I have read and understood the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md)."
required: true
- label: "I have read and understood the [AI policy](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md#ai-policy). The content of this request is not generated by AI."
required: true
- type: textarea
+3 -1
View File
@@ -2,7 +2,7 @@
#### What is it?
- [ ] Bugfix (user facing)
- [ ] Feature (user facing)
- [ ] Feature (user facing) ⚠️ **Your PR must target the [`refactor`](https://github.com/TeamNewPipe/NewPipe/tree/refactor) branch**
- [ ] Codebase improvement (dev facing)
- [ ] Meta improvement to the project (dev facing)
@@ -32,3 +32,5 @@ The APK can be found by going to the "Checks" tab below the title. On the left p
#### Due diligence
- [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).
- [ ] The proposed changes follow the [AI policy](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md#ai-policy).
- [ ] I tested the changes using an emulator or a physical device.
+48
View File
@@ -0,0 +1,48 @@
name: Backport merged pull request
on:
issue_comment:
types: [created]
permissions:
contents: write # for comment creation on original PR
pull-requests: write
jobs:
backport:
name: Backport pull request
runs-on: ubuntu-latest
# Only run when the comment starts with the `/backport` command on a PR and
# the commenter has write access to the repository. We do not want to allow
# everybody to trigger backports and create branches in our repository.
if: >
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/backport ') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'COLLABORATOR' ||
github.event.comment.author_association == 'MEMBER'
)
steps:
- uses: actions/checkout@v6
- name: Get backport metadata
# the target branch is the first argument after `/backport`
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
body="$COMMENT_BODY"
line=${body%%$'\n'*} # Get the first line
if [[ $line =~ ^/backport[[:space:]]+([^[:space:]]+) ]]; then
echo "BACKPORT_TARGET=${BASH_REMATCH[1]}" >> "$GITHUB_ENV"
else
echo "Usage: /backport <target-branch>" >&2
exit 1
fi
- name: Create backport pull request
uses: korthout/backport-action@v4
with:
add_labels: 'backport'
copy_labels_pattern: '.*'
label_pattern: ''
target_branches: ${{ env.BACKPORT_TARGET }}
+38
View File
@@ -0,0 +1,38 @@
name: "Build unsigned release APK on master"
on:
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: 'master'
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: "Build release APK"
run: ./gradlew assembleRelease --stacktrace
- name: "Rename APK"
run: |
VERSION_NAME="$(jq -r ".elements[0].versionName" "app/build/outputs/apk/release/output-metadata.json")"
echo "Version name: $VERSION_NAME" >> "$GITHUB_STEP_SUMMARY"
echo '```json' >> "$GITHUB_STEP_SUMMARY"
cat "app/build/outputs/apk/release/output-metadata.json" >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
# assume there is only one APK in that folder
mv app/build/outputs/apk/release/*.apk "app/build/outputs/apk/release/NewPipe_v$VERSION_NAME.apk"
- name: "Upload APK"
uses: actions/upload-artifact@v7
with:
name: app
path: app/build/outputs/apk/release/*.apk
+30 -23
View File
@@ -6,6 +6,7 @@ on:
branches:
- dev
- master
- refactor
- release**
paths-ignore:
- 'README.md'
@@ -36,8 +37,8 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@v6
- name: create and checkout branch
# push events already checked out the branch
@@ -46,46 +47,51 @@ jobs:
BRANCH: ${{ github.head_ref }}
run: git checkout -B "$BRANCH"
- name: set up JDK 17
uses: actions/setup-java@v4
- name: set up JDK
uses: actions/setup-java@v5
with:
java-version: 17
java-version: 21
distribution: "temurin"
cache: 'gradle'
- name: Build debug APK and run jvm tests
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint
run: ./gradlew assembleContinuous lintContinuous testDebugUnitTest --stacktrace -DskipFormatKtlint
- name: Upload APK
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: app
path: app/build/outputs/apk/debug/*.apk
path: app/build/outputs/apk/continuous/*.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:
include:
- api-level: 21
- api-level: 23
target: default
arch: x86
- api-level: 33
target: google_apis # emulator API 33 only exists with Google APIs
- api-level: 35
target: default
arch: x86_64
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: set up JDK 17
uses: actions/setup-java@v4
- 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
uses: actions/setup-java@v5
with:
java-version: 17
java-version: 21
distribution: "temurin"
cache: 'gradle'
@@ -98,32 +104,33 @@ jobs:
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@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: android-test-report-api${{ matrix.api-level }}
path: app/build/reports/androidTests/connected/**
sonar:
if: ${{ false }} # the key has expired and needs to be regenerated by the sonar admins
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
uses: actions/setup-java@v4
- name: Set up JDK
uses: actions/setup-java@v5
with:
java-version: 17
java-version: 21
distribution: "temurin"
cache: 'gradle'
- name: Cache SonarCloud packages
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
+4 -4
View File
@@ -32,12 +32,12 @@ module.exports = async ({github, context}) => {
}
// Regex for finding images (simple variant) ![ALT_TEXT](https://*.githubusercontent.com/<number>/<variousHexStringsAnd->.<fileExtension>)
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;
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_LOOKUP = /\!\[([^\]]*)\]\((https:\/\/github\.com\/(?:user-attachments\/assets|[-\w\d]+\/[-\w\d]+\/assets\/\d+)\/[\-0-9a-f]{32,512})\)/gm;
// Check if we found something
let foundSimpleImages = REGEX_USER_CONTENT_IMAGE_LOOKUP.test(initialBody)
|| REGEX_ASSETS_IMAGE_LOCKUP.test(initialBody);
|| REGEX_ASSETS_IMAGE_LOOKUP.test(initialBody);
if (!foundSimpleImages) {
console.log('Found no simple images to process');
return;
@@ -52,7 +52,7 @@ module.exports = async ({github, context}) => {
// Try to find and replace the images with minimized ones
let newBody = await replaceAsync(initialBody, REGEX_USER_CONTENT_IMAGE_LOOKUP, minimizeAsync);
newBody = await replaceAsync(newBody, REGEX_ASSETS_IMAGE_LOCKUP, minimizeAsync);
newBody = await replaceAsync(newBody, REGEX_ASSETS_IMAGE_LOOKUP, minimizeAsync);
if (!wasMatchModified) {
console.log('Nothing was modified. Skipping update');
+3 -3
View File
@@ -17,9 +17,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 16
@@ -27,7 +27,7 @@ jobs:
run: npm i probe-image-size@7.2.3 --ignore-scripts
- name: Minimize simple images
uses: actions/github-script@v7
uses: actions/github-script@v9
timeout-minutes: 3
with:
script: |
+19 -1
View File
@@ -1,15 +1,18 @@
.gradle/
local.properties
.DS_Store
build/
**/build/
!src/**/build/
captures/
.idea/
*.iml
*~
.weblate
.kotlin
*.class
app/debug/
app/release/
.kotlin/
# vscode / eclipse files
*.classpath
@@ -18,3 +21,18 @@ app/release/
bin/
.vscode/
*.code-workspace
# xcode files
xcuserdata
.externalNativeBuild
.cxx
node_modules/
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcodeproj/project.xcworkspace/
!*.xcworkspace/contents.xcworkspacedata
**/xcshareddata/WorkspaceSettings.xcsettings
# logs
*.log
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#CD201F;}
.st1{fill:#FFFFFF;}
</style>
<g id="Alapkör">
<circle id="XMLID_23_" class="st0" cx="50" cy="50" r="50"/>
</g>
<g id="Elemek">
<path id="XMLID_19_" class="st1" d="M47,28.2c-9-5.3-15.3-9-15.3-9v61.7c0,0,30.4-18,52.3-30.9C72.1,43,57.7,34.5,47,28.2z"/>
</g>
<g id="Fedő">
<path id="XMLID_5_" class="st0" d="M48.4,40.1c-4.1-2.4-7-4.1-7-4.1V64c0,0,13.9-8.2,23.8-14C59.8,46.8,53.3,42.9,48.4,40.1z"/>
<rect id="XMLID_4_" x="41.4" y="55.6" class="st0" width="6.2" height="21"/>
</g>
<g id="Vonalak">
</g>
</svg>

After

Width:  |  Height:  |  Size: 850 B

+24 -10
View File
@@ -1,26 +1,32 @@
<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>
<h3 align="center">We are <i>rewriting</i> large chunks of the codebase, to bring about <a href="https://newpipe.net/blog/pinned/announcement/newpipe-0.27.6-rewrite-team-states/#the-refactor">a modern and stable NewPipe</a>! You can download nightly builds <a href="https://github.com/TeamNewPipe/NewPipe-refactor-nightly/releases">here</a>.</h3>
<h4 align="center">Please work on the <code>refactor</code> branch if you want to contribute <i>new features</i>. The current codebase is in maintenance mode and will only receive <i>bugfixes</i>.</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>
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-en.svg" alt="Get it on F-Droid" height=80/></a></p>
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-en.svg" alt="Get it on F-Droid" width=206/></a></p>
<p align="center">
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub NewPipe releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://github.com/TeamNewPipe/NewPipe-nightly/releases" alt="GitHub NewPipe nightly releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-nightly.svg?labelColor=purple&label=dev%20nightly"></a>
<a href="https://github.com/TeamNewPipe/NewPipe-refactor-nightly/releases" alt="GitHub NewPipe refactor nightly releases"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe-refactor-nightly.svg?labelColor=purple&label=refactor%20nightly"></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<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://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/actions/workflows/ci.yml/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>
</p>
<p align="center">
<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://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 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)*
*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), [العربية](README.ar.md)*
> [!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>
@@ -96,7 +102,7 @@ Also, since they are free and open source software, neither the app nor the Extr
## Installation and updates
You can install NewPipe using one of the following methods:
1. Add our custom repo to F-Droid and install it from there. The instructions are here: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
2. Download the APK from [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
2. Download the APK from [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases), [compare the signing key](#apk-info) and install it.
3. Update via F-Droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, and then push the update to users.
4. Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
5. If you're interested in a specific feature or bugfix provided in a Pull Request in this repo, you can also download its APK from within the PR. Read the PR description for instructions. The great thing about PR-specific APKs is that they're installed side-by-side the official app, so you don't have to worry about losing your data or messing anything up.
@@ -104,12 +110,20 @@ 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>
> [!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.
### APK Info
This is the SHA fingerprint of NewPipe's signing key to verify downloaded APKs which are signed by us. The fingerprint is also available on [NewPipe's website](https://newpipe.net#download). This is relevant for method 2.
```
CB:84:06:9B:D6:81:16:BA:FA:E5:EE:4E:E5:B0:8A:56:7A:A6:D8:98:40:4E:7C:B1:2F:9E:75:6D:F5:CF:5C:AB
```
## Contribution
Whether you have ideas, translations, design changes, code cleaning, or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
-342
View File
@@ -1,342 +0,0 @@
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 "4.0.0.2929"
}
android {
compileSdk 34
namespace 'org.schabi.newpipe'
defaultConfig {
applicationId "org.schabi.newpipe"
resValue "string", "app_name", "NewPipe"
minSdk 21
targetSdk 33
versionCode 999
versionName "0.27.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildTypes {
debug {
debuggable true
// suffix the app id and the app name with git branch name
def workingBranch = getGitWorkingBranch()
def normalizedWorkingBranch = workingBranch.replaceFirst("^[^A-Za-z]+", "").replaceAll("[^0-9A-Za-z]+", "")
if (normalizedWorkingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") {
// default values when branch name could not be determined or is master or dev
applicationIdSuffix ".debug"
resValue "string", "app_name", "NewPipe Debug"
} else {
applicationIdSuffix ".debug." + normalizedWorkingBranch
resValue "string", "app_name", "NewPipe " + workingBranch
archivesBaseName = 'NewPipe_' + normalizedWorkingBranch
}
}
release {
if (System.properties.containsKey('packageSuffix')) {
applicationIdSuffix System.getProperty('packageSuffix')
resValue "string", "app_name", "NewPipe " + System.getProperty('packageSuffix')
archivesBaseName = 'NewPipe_' + System.getProperty('packageSuffix')
}
minifyEnabled true
shrinkResources false // disabled to fix F-Droid's reproducible build
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
archivesBaseName = 'app'
}
}
lint {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
// suppress false warning ("Resource IDs will be non-final in Android Gradle Plugin version
// 5.0, avoid using them in switch case statements"), which affects only library projects
disable 'NonConstantResourceId'
}
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
encoding 'utf-8'
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
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.12.1'
androidxLifecycleVersion = '2.6.2'
androidxRoomVersion = '2.6.1'
androidxWorkVersion = '2.8.1'
icepickVersion = '3.2.0'
exoPlayerVersion = '2.18.7'
googleAutoServiceVersion = '1.1.1'
groupieVersion = '2.10.1'
markwonVersion = '4.6.2'
leakCanaryVersion = '2.12'
stethoVersion = '1.6.0'
}
configurations {
checkstyle
ktlint
}
checkstyle {
getConfigDirectory().set(rootProject.file("checkstyle"))
ignoreFailures false
showViolations true
toolVersion = checkstyleVersion
}
tasks.register('runCheckstyle', Checkstyle) {
source 'src'
include '**/*.java'
exclude '**/gen/**'
exclude '**/R.java'
exclude '**/BuildConfig.java'
exclude 'main/java/us/shandian/giga/**'
classpath = configurations.checkstyle
showViolations true
reports {
xml.getRequired().set(true)
html.getRequired().set(true)
}
}
def outputDir = "${project.buildDir}/reports/ktlint/"
def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
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")
}
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 {
if (!System.properties.containsKey('skipFormatKtlint')) {
preDebugBuild.dependsOn formatKtlint
}
preDebugBuild.dependsOn runCheckstyle, runKtlint
}
sonar {
properties {
property "sonar.projectKey", "TeamNewPipe_NewPipe"
property "sonar.organization", "teamnewpipe"
property "sonar.host.url", "https://sonarcloud.io"
}
}
dependencies {
/** Desugaring **/
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
// Or you can use a commit you pushed to GitHub by just replacing TeamNewPipe with your GitHub
// 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:v0.24.2'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
/** Checkstyle **/
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
ktlint 'com.pinterest:ktlint:0.45.2'
/** Kotlin **/
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
/** AndroidX **/
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.12.0'
implementation 'androidx.documentfile:documentfile:1.0.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.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-beta02'
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
implementation 'com.google.android.material:material:1.11.0'
/** Third-party libraries **/
// Instance state boilerplate elimination
implementation "frankiesardo:icepick:${icepickVersion}"
kapt "frankiesardo:icepick-processor:${icepickVersion}"
// HTML parser
implementation "org.jsoup:jsoup:1.17.2"
// HTTP client
implementation "com.squareup.okhttp3:okhttp:4.12.0"
// Media player
implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:exoplayer-dash:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:exoplayer-database:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:exoplayer-datasource:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:exoplayer-hls:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:exoplayer-ui:${exoPlayerVersion}"
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
// Metadata generator for service descriptors
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
// Manager for complex RecyclerView layouts
implementation "com.github.lisawray.groupie:groupie:${groupieVersion}"
implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}"
// Image loading
//noinspection GradleDependency --> 2.8 is the last version, not 2.71828!
implementation "com.squareup.picasso:picasso:2.8"
// Markdown library for Android
implementation "io.noties.markwon:core:${markwonVersion}"
implementation "io.noties.markwon:linkify:${markwonVersion}"
// Crash reporting
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.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.7.Final"
/** Debugging **/
// Memory leak detection
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:5.6.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:3.24.2"
}
static String getGitWorkingBranch() {
try {
def gitProcess = "git rev-parse --abbrev-ref HEAD".execute()
gitProcess.waitFor()
if (gitProcess.exitValue() == 0) {
return gitProcess.text.trim()
} else {
// not a git repository
return ""
}
} catch (IOException ignored) {
// git was not found
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, "")
}
}
}
}
}
+397
View File
@@ -0,0 +1,397 @@
/*
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import com.android.build.api.dsl.ApplicationExtension
import com.mikepenz.aboutlibraries.plugin.DuplicateMode
import java.util.regex.Pattern
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.android.legacy.kapt)
alias(libs.plugins.google.ksp)
alias(libs.plugins.jetbrains.kotlin.compose)
alias(libs.plugins.jetbrains.kotlin.parcelize)
alias(libs.plugins.jetbrains.kotlinx.serialization)
alias(libs.plugins.sonarqube)
alias(libs.plugins.hilt)
alias(libs.plugins.about.libraries)
checkstyle
}
val gitWorkingBranch = providers.exec {
commandLine("git", "rev-parse", "--abbrev-ref", "HEAD")
}.standardOutput.asText.map { it.trim() }
val defaultBranches = listOf("master", "dev")
val workingBranch = gitWorkingBranch.getOrElse("")
val normalizedWorkingBranch = workingBranch
.replaceFirst("^[^A-Za-z]+".toRegex(), "")
.replace("[^0-9A-Za-z]+".toRegex(), "")
kotlin {
jvmToolchain(21)
compilerOptions {
// TODO: Drop annotation default target when it is stable
freeCompilerArgs.addAll(
"-Xannotation-default-target=param-property"
)
}
}
configure<ApplicationExtension> {
compileSdk {
version = release(NEWPIPE_VERSION_SDK_COMPILE_MAJOR) {
minorApiLevel = NEWPIPE_VERSION_SDK_COMPILE_MINOR
}
}
namespace = NEWPIPE_APPLICATION_ID_OLD
defaultConfig {
applicationId = NEWPIPE_APPLICATION_ID_OLD
resValue("string", "app_name", "NewPipe")
minSdk {
version = release(NEWPIPE_VERSION_SDK_MIN)
}
targetSdk {
version = release(NEWPIPE_VERSION_SDK_TARGET)
}
versionCode = System.getProperty("versionCodeOverride")?.toInt() ?: NEWPIPE_VERSION_CODE
versionName = NEWPIPE_VERSION_NAME
System.getProperty("versionNameSuffix")?.let { versionNameSuffix = it }
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
isDebuggable = true
// suffix the app id and the app name with git branch name
if (normalizedWorkingBranch.isEmpty() || workingBranch in defaultBranches) {
applicationIdSuffix = ".debug"
resValue("string", "app_name", "NewPipe Debug")
} else {
applicationIdSuffix = ".debug.$normalizedWorkingBranch"
resValue("string", "app_name", "NewPipe $workingBranch")
}
}
release {
System.getProperty("packageSuffix")?.let { suffix ->
applicationIdSuffix = suffix
resValue("string", "app_name", "NewPipe $suffix")
}
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
register("continuous") {
initWith(getByName("release"))
signingConfig = signingConfigs.getByName("debug")
isDefault = true
// suffix the app id and the app name with git branch name
if (normalizedWorkingBranch.isEmpty() || workingBranch in defaultBranches) {
applicationIdSuffix = ".continuous"
resValue("string", "app_name", "NewPipe Continuous")
} else {
applicationIdSuffix = ".continuous.$normalizedWorkingBranch"
resValue("string", "app_name", "NewPipe $workingBranch")
}
}
}
lint {
lintConfig = file("lint.xml")
// Continue the debug build even when errors are found
abortOnError = false
}
compileOptions {
// Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true
encoding = "utf-8"
}
sourceSets {
getByName("androidTest") {
assets.directories += "$projectDir/schemas"
}
}
androidResources {
generateLocaleConfig = true
}
buildFeatures {
viewBinding = true
compose = true
buildConfig = true
resValues = true
}
packaging {
resources {
// remove two files which belong to jsoup
// no idea how they ended up in the META-INF dir...
excludes += setOf(
"META-INF/README.md",
"META-INF/CHANGES",
"META-INF/COPYRIGHT" // "COPYRIGHT" belongs to RxJava...
)
}
}
}
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
// Custom dependency configuration for ktlint
val ktlint by configurations.creating
checkstyle {
configDirectory = rootProject.file("checkstyle")
isIgnoreFailures = false
isShowViolations = true
toolVersion = libs.versions.checkstyle.get()
}
tasks.register<Checkstyle>("runCheckstyle") {
source("src")
include("**/*.java")
exclude("**/gen/**")
exclude("**/R.java")
exclude("**/BuildConfig.java")
exclude("main/java/us/shandian/giga/**")
classpath = configurations.getByName("checkstyle")
isShowViolations = true
reports {
xml.required = true
html.required = true
}
}
val outputDir = project.layout.buildDirectory.dir("reports/ktlint/")
val inputFiles = fileTree("src") { include("**/*.kt") }
tasks.register<JavaExec>("runKtlint") {
inputs.files(inputFiles)
outputs.dir(outputDir)
mainClass.set("com.pinterest.ktlint.Main")
classpath = configurations.getByName("ktlint")
args = listOf("--editorconfig=../.editorconfig", "src/**/*.kt")
jvmArgs = listOf("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
tasks.register<JavaExec>("formatKtlint") {
inputs.files(inputFiles)
outputs.dir(outputDir)
mainClass.set("com.pinterest.ktlint.Main")
classpath = configurations.getByName("ktlint")
args = listOf("--editorconfig=../.editorconfig", "-F", "src/**/*.kt")
jvmArgs = listOf("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
tasks.register<CheckDependenciesOrder>("checkDependenciesOrder") {
tomlFile = layout.projectDirectory.file("../gradle/libs.versions.toml")
}
afterEvaluate {
tasks.named("preDebugBuild").configure {
if (!System.getProperties().containsKey("skipFormatKtlint")) {
dependsOn("formatKtlint")
}
dependsOn("runCheckstyle", "runKtlint", "checkDependenciesOrder")
}
}
sonar {
properties {
property("sonar.projectKey", "TeamNewPipe_NewPipe")
property("sonar.organization", "teamnewpipe")
property("sonar.host.url", "https://sonarcloud.io")
}
}
aboutLibraries {
// note: offline mode prevents the plugin from fetching licenses at build time, which would be
// harmful for reproducible builds
offlineMode = true
}
dependencies {
// Desugaring
coreLibraryDesugaring(libs.android.desugar)
// NewPipe libraries
implementation(projects.shared)
implementation(libs.newpipe.nanojson)
implementation(libs.newpipe.extractor)
implementation(libs.newpipe.filepicker)
// Checkstyle
checkstyle(libs.puppycrawl.checkstyle)
ktlint(libs.pinterest.ktlint)
/** Kotlin **/
implementation(libs.kotlin.stdlib)
/** AndroidX **/
implementation(libs.androidx.appcompat)
implementation(libs.androidx.cardview)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.documentfile)
implementation(libs.androidx.fragment.compose)
implementation(libs.androidx.lifecycle.livedata)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.media)
implementation(libs.androidx.preference)
implementation(libs.androidx.recyclerview)
implementation(libs.androidx.room.paging)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.rxjava3)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.work.rxjava3)
implementation(libs.google.android.material)
implementation(libs.androidx.webkit)
/** Compose & other modern patterns **/
// Jetpack Compose
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.adaptive)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.compose.ui.text) // Needed for parsing HTML to AnnotatedString
implementation(libs.androidx.compose.material.icons.extended)
// Jetpack navigatio3
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.viewmodel)
// Jetpack Compose related dependencies
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.hilt.navigation.compose)
// Coroutines interop
implementation(libs.kotlinx.coroutines.rx3)
// Library loading for About screen
implementation(libs.about.libraries.compose.m3)
// Hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
// Scroll
implementation(libs.lazy.column.scrollbar)
// Kotlinx Serialization
implementation(libs.kotlinx.serialization.json)
// Third-party libraries
implementation(libs.livefront.bridge)
implementation(libs.evernote.statesaver.core)
kapt(libs.evernote.statesaver.compiler)
// HTML parser
implementation(libs.jsoup)
// HTTP client
implementation(libs.squareup.okhttp)
// Media player
implementation(libs.google.exoplayer.core)
implementation(libs.google.exoplayer.dash)
implementation(libs.google.exoplayer.database)
implementation(libs.google.exoplayer.datasource)
implementation(libs.google.exoplayer.hls)
implementation(libs.google.exoplayer.mediasession)
implementation(libs.google.exoplayer.smoothstreaming)
implementation(libs.google.exoplayer.ui)
// Manager for complex RecyclerView layouts
implementation(libs.lisawray.groupie.core)
implementation(libs.lisawray.groupie.viewbinding)
// Image loading
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
// Markdown library for Android
implementation(libs.noties.markwon.core)
implementation(libs.noties.markwon.linkify)
// Crash reporting
implementation(libs.acra.core)
compileOnly(libs.google.autoservice.annotations)
ksp(libs.zacsweers.autoservice.compiler)
// Properly restarting
implementation(libs.jakewharton.phoenix)
// Reactive extensions for Java VM
implementation(libs.reactivex.rxjava)
implementation(libs.reactivex.rxandroid)
// RxJava binding APIs for Android UI widgets
implementation(libs.jakewharton.rxbinding)
// Date and time formatting
implementation(libs.ocpsoft.prettytime)
// Debugging and memory leak detection
debugImplementation(libs.squareup.leakcanary.watcher)
debugImplementation(libs.squareup.leakcanary.plumber)
debugImplementation(libs.squareup.leakcanary.core)
// Debug bridge for Android
debugImplementation(libs.facebook.stetho.core)
debugImplementation(libs.facebook.stetho.okhttp3)
// Jetpack Compose
debugImplementation(libs.androidx.compose.ui.tooling)
/** Testing **/
testImplementation(libs.junit)
testImplementation(libs.mockito.core)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.runner)
androidTestImplementation(libs.androidx.room.testing)
androidTestImplementation(libs.assertj.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}
aboutLibraries {
collect {
configPath = file("../config/aboutlibraries")
}
export {
outputFile = file("../shared/src/androidMain/assets/aboutlibraries.json")
prettyPrint = true
excludeFields.addAll("organization", "scm", "funding")
}
library {
exclusionPatterns = listOf(
Pattern.compile("^com\\.github\\.TeamNewPipe:NewPipeExtractor$"),
Pattern.compile("^com\\.evernote:android-state$")
)
}
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<lint>
<issue id="MissingTranslation" severity="ignore" />
<issue id="MissingQuantity" severity="ignore" />
<issue id="ImpliedQuantity" severity="ignore" />
</lint>
+45 -9
View File
@@ -5,23 +5,21 @@
## Rules for NewPipeExtractor
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
## Rules for Rhino and Rhino Engine
-keep class org.mozilla.javascript.* { *; }
-keep class org.mozilla.javascript.** { *; }
-keep class org.mozilla.javascript.engine.** { *; }
-keep class org.mozilla.classfile.ClassFileWriter
-dontwarn org.mozilla.javascript.JavaToJSONConverters
-dontwarn org.mozilla.javascript.tools.**
-keep class javax.script.** { *; }
-dontwarn javax.script.**
-keep class jdk.dynalink.** { *; }
-dontwarn jdk.dynalink.**
## 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 { *; }
-keepclasseswithmembernames class * {
@icepick.* <fields>;
}
-keepnames class * { @icepick.State *;}
## Rules for OkHttp. Copy pasted from https://github.com/square/okhttp
-dontwarn okhttp3.**
-dontwarn okio.**
@@ -36,3 +34,41 @@
## For some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
-keep class org.schabi.newpipe.settings.notifications.** { *; }
## Keep Kotlinx Serialization classes
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class org.schabi.newpipe.**$$serializer { *; }
-keepclassmembers class org.schabi.newpipe.** {
*** Companion;
}
-keepclasseswithmembers class org.schabi.newpipe.** {
kotlinx.serialization.KSerializer serializer(...);
}
# Prevent R8 from stripping or renaming Protobuf internal fields
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite {
<fields>;
}
## Keep Kotlinx Serialization classes
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class org.schabi.newpipe.**$$serializer { *; }
-keepclassmembers class org.schabi.newpipe.** {
*** Companion;
}
-keepclasseswithmembers class org.schabi.newpipe.** {
kotlinx.serialization.KSerializer serializer(...);
}
# See https://github.com/TeamNewPipe/NewPipe/issues/13508
-keep class org.ocpsoft.prettytime.i18n.Resources* { *; }
@@ -458,7 +458,7 @@
"notNull": true
},
{
"fieldPath": "name",
"fieldPath": "orderingName",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
@@ -129,7 +129,7 @@ class DatabaseMigrationTest {
)
val migratedDatabaseV3 = getMigratedDatabase()
val listFromDB = migratedDatabaseV3.streamDAO().all.blockingFirst()
val listFromDB = migratedDatabaseV3.streamDAO().getAll().blockingFirst()
// Only expect 2, the one with the null url will be ignored
assertEquals(2, listFromDB.size)
@@ -176,28 +176,32 @@ class DatabaseMigrationTest {
databaseInV7.run {
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
"search_history",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", serviceId)
put("search", defaultSearch1)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
"search_history",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", serviceId)
put("search", defaultSearch2)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
"search_history",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", otherServiceId)
put("search", defaultSearch1)
}
)
insert(
"search_history", SQLiteDatabase.CONFLICT_FAIL,
"search_history",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", otherServiceId)
put("search", defaultSearch2)
@@ -207,17 +211,21 @@ class DatabaseMigrationTest {
}
testHelper.runMigrationsAndValidate(
AppDatabase.DATABASE_NAME, Migrations.DB_VER_8,
true, Migrations.MIGRATION_7_8
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
AppDatabase.DATABASE_NAME,
Migrations.DB_VER_9,
true,
Migrations.MIGRATION_8_9
)
val migratedDatabaseV8 = getMigratedDatabase()
val listFromDB = migratedDatabaseV8.searchHistoryDAO().all.blockingFirst()
val listFromDB = migratedDatabaseV8.searchHistoryDAO().getAll().blockingFirst()
assertEquals(2, listFromDB.size)
assertEquals("abc", listFromDB[0].search)
@@ -235,7 +243,8 @@ class DatabaseMigrationTest {
val remoteUid2: Long
databaseInV8.run {
localUid1 = insert(
"playlists", SQLiteDatabase.CONFLICT_FAIL,
"playlists",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("name", DEFAULT_NAME + "1")
put("is_thumbnail_permanent", false)
@@ -243,7 +252,8 @@ class DatabaseMigrationTest {
}
)
localUid2 = insert(
"playlists", SQLiteDatabase.CONFLICT_FAIL,
"playlists",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("name", DEFAULT_NAME + "2")
put("is_thumbnail_permanent", false)
@@ -251,25 +261,29 @@ class DatabaseMigrationTest {
}
)
delete(
"playlists", "uid = ?",
"playlists",
"uid = ?",
Array(1) { localUid1 }
)
remoteUid1 = insert(
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
"remote_playlists",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SERVICE_ID)
put("url", DEFAULT_URL)
}
)
remoteUid2 = insert(
"remote_playlists", SQLiteDatabase.CONFLICT_FAIL,
"remote_playlists",
SQLiteDatabase.CONFLICT_FAIL,
ContentValues().apply {
put("service_id", DEFAULT_SECOND_SERVICE_ID)
put("url", DEFAULT_SECOND_URL)
}
)
delete(
"remote_playlists", "uid = ?",
"remote_playlists",
"uid = ?",
Array(1) { remoteUid2 }
)
close()
@@ -283,8 +297,8 @@ class DatabaseMigrationTest {
)
val migratedDatabaseV9 = getMigratedDatabase()
var localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
var localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst()
var remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst()
assertEquals(1, localListFromDB.size)
assertEquals(localUid2, localListFromDB[0].uid)
@@ -294,17 +308,27 @@ class DatabaseMigrationTest {
assertEquals(-1, remoteListFromDB[0].displayIndex)
val localUid3 = migratedDatabaseV9.playlistDAO().insert(
PlaylistEntity(DEFAULT_NAME + "3", false, -1, -1)
PlaylistEntity(
name = "${DEFAULT_NAME}3",
isThumbnailPermanent = false,
thumbnailStreamId = -1,
displayIndex = -1
)
)
val remoteUid3 = migratedDatabaseV9.playlistRemoteDAO().insert(
PlaylistRemoteEntity(
DEFAULT_THIRD_SERVICE_ID, DEFAULT_NAME, DEFAULT_THIRD_URL,
DEFAULT_THUMBNAIL, DEFAULT_UPLOADER_NAME, -1, 10
serviceId = DEFAULT_THIRD_SERVICE_ID,
orderingName = DEFAULT_NAME,
url = DEFAULT_THIRD_URL,
thumbnailUrl = DEFAULT_THUMBNAIL,
uploader = DEFAULT_UPLOADER_NAME,
displayIndex = -1,
streamCount = 10
)
)
localListFromDB = migratedDatabaseV9.playlistDAO().all.blockingFirst()
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().all.blockingFirst()
localListFromDB = migratedDatabaseV9.playlistDAO().getAll().blockingFirst()
remoteListFromDB = migratedDatabaseV9.playlistRemoteDAO().getAll().blockingFirst()
assertEquals(2, localListFromDB.size)
assertEquals(localUid3, localListFromDB[1].uid)
assertEquals(-1, localListFromDB[1].displayIndex)
@@ -4,6 +4,11 @@ import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import io.reactivex.rxjava3.core.Single
import java.io.IOException
import java.time.OffsetDateTime
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.rx3.await
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
@@ -20,9 +25,6 @@ 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
@@ -41,14 +43,21 @@ class FeedDAOTest {
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
stream1,
stream2,
stream3,
stream4,
stream5,
stream6,
stream7
)
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(
context, AppDatabase::class.java
context,
AppDatabase::class.java
).build()
feedDAO = db.feedDAO()
streamDAO = db.streamDAO()
@@ -65,7 +74,10 @@ class FeedDAOTest {
fun testUnlinkStreamsOlderThan_KeepOne() {
setupUnlinkDelete("2023-08-15T00:00:00Z")
val streams = feedDAO.getStreams(
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
FeedGroupEntity.GROUP_ALL_ID,
includePlayed = true,
includePartiallyPlayed = true,
null
)
.blockingGet()
val allowedStreams = listOf(stream3, stream5, stream6, stream7)
@@ -76,7 +88,10 @@ class FeedDAOTest {
fun testUnlinkStreamsOlderThan_KeepMultiple() {
setupUnlinkDelete("2023-08-01T00:00:00Z")
val streams = feedDAO.getStreams(
FeedGroupEntity.GROUP_ALL_ID, includePlayed = true, includePartiallyPlayed = true, null
FeedGroupEntity.GROUP_ALL_ID,
includePlayed = true,
includePartiallyPlayed = true,
null
)
.blockingGet()
val allowedStreams = listOf(stream3, stream4, stream5, stream6, stream7)
@@ -94,14 +109,10 @@ class FeedDAOTest {
)
}
private fun setupUnlinkDelete(time: String) {
private fun setupUnlinkDelete(time: String) = runBlocking(Dispatchers.IO) {
clearAndFillTables()
Single.fromCallable {
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
}.blockingSubscribe()
Single.fromCallable {
streamDAO.deleteOrphans()
}.blockingSubscribe()
feedDAO.unlinkStreamsOlderThan(OffsetDateTime.parse(time))
streamDAO.deleteOrphans().await()
}
private fun clearAndFillTables() {
@@ -112,7 +123,7 @@ class FeedDAOTest {
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")),
SubscriptionEntity.from(ChannelInfo(serviceId, "4", "https://youtube.com/channel/4", "https://youtube.com/channel/4", "channel-4"))
)
)
feedDAO.insertAll(
@@ -123,7 +134,7 @@ class FeedDAOTest {
FeedEntity(4, 2),
FeedEntity(5, 2),
FeedEntity(6, 3),
FeedEntity(7, 4),
FeedEntity(7, 4)
)
)
}
@@ -1,46 +0,0 @@
package org.schabi.newpipe.error;
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Instrumented tests for {@link ErrorInfo}.
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ErrorInfoTest {
@Test
public void errorInfoTestParcelable() {
final ErrorInfo info = new ErrorInfo(new ParsingException("Hello"),
UserAction.USER_REPORT, "request", ServiceList.YouTube.getServiceId());
// Obtain a Parcel object and write the parcelable object to it:
final Parcel parcel = Parcel.obtain();
info.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
final ErrorInfo infoFromParcel = (ErrorInfo) ErrorInfo.CREATOR.createFromParcel(parcel);
assertTrue(Arrays.toString(infoFromParcel.getStackTraces())
.contains(ErrorInfoTest.class.getSimpleName()));
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
assertEquals(ServiceList.YouTube.getServiceInfo().getName(),
infoFromParcel.getServiceName());
assertEquals("request", infoFromParcel.getRequest());
assertEquals(R.string.parsing_error, infoFromParcel.getMessageStringId());
parcel.recycle();
}
}
@@ -0,0 +1,127 @@
package org.schabi.newpipe.error
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import java.io.IOException
import java.net.SocketTimeoutException
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.extractor.exceptions.ParsingException
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
/**
* Instrumented tests for {@link ErrorInfo}.
*/
@RunWith(AndroidJUnit4::class)
@LargeTest
class ErrorInfoTest {
private val context: Context by lazy { ApplicationProvider.getApplicationContext() }
/**
* @param errorInfo the error info to access
* @return the private field errorInfo.message.stringRes using reflection
*/
@Throws(NoSuchFieldException::class, IllegalAccessException::class)
private fun getMessageFromErrorInfo(errorInfo: ErrorInfo): Int {
val message = ErrorInfo::class.java.getDeclaredField("message")
message.isAccessible = true
val messageValue = message.get(errorInfo) as ErrorInfo.Companion.ErrorMessage
val stringRes = ErrorInfo.Companion.ErrorMessage::class.java.getDeclaredField("stringRes")
stringRes.isAccessible = true
return stringRes.get(messageValue) as Int
}
@Test
@Throws(NoSuchFieldException::class, IllegalAccessException::class)
fun errorInfoTestParcelable() {
val info = ErrorInfo(
ParsingException("Hello"),
UserAction.USER_REPORT,
"request",
ServiceList.YouTube.serviceId
)
// Obtain a Parcel object and write the parcelable object to it:
val parcel = Parcel.obtain()
info.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
val creatorField = ErrorInfo::class.java.getDeclaredField("CREATOR")
val creator = creatorField.get(null)
check(creator is Parcelable.Creator<*>)
val infoFromParcel = requireNotNull(
creator.createFromParcel(parcel) as? ErrorInfo
)
assertTrue(
infoFromParcel.stackTraces.contentToString()
.contains(ErrorInfoTest::class.java.simpleName)
)
assertEquals(UserAction.USER_REPORT, infoFromParcel.userAction)
assertEquals(
ServiceList.YouTube.serviceInfo.name,
infoFromParcel.getServiceName()
)
assertEquals("request", infoFromParcel.request)
assertEquals(R.string.parsing_error, getMessageFromErrorInfo(infoFromParcel))
parcel.recycle()
}
/**
* Test: Network error on initial load (Resource.Error)
*/
@Test
fun testInitialCommentNetworkError() {
val errorInfo = ErrorInfo(
throwable = SocketTimeoutException("Connection timeout"),
userAction = UserAction.REQUESTED_COMMENTS,
request = "comments"
)
assertEquals(context.getString(R.string.network_error), errorInfo.getMessage(context))
assertTrue(errorInfo.isReportable)
assertTrue(errorInfo.isRetryable)
assertNull(errorInfo.recaptchaUrl)
}
/**
* Test: Network error on paging (LoadState.Error)
*/
@Test
fun testPagingNetworkError() {
val errorInfo = ErrorInfo(
throwable = IOException("Paging failed"),
userAction = UserAction.REQUESTED_COMMENTS,
request = "comments"
)
assertEquals(context.getString(R.string.network_error), errorInfo.getMessage(context))
assertTrue(errorInfo.isReportable)
assertTrue(errorInfo.isRetryable)
assertNull(errorInfo.recaptchaUrl)
}
/**
* Test: ReCaptcha during comments load
*/
@Test
fun testReCaptchaDuringComments() {
val url = "https://www.google.com/recaptcha/api/fallback?k=test"
val errorInfo = ErrorInfo(
throwable = ReCaptchaException("ReCaptcha needed", url),
userAction = UserAction.REQUESTED_COMMENTS,
request = "comments"
)
assertEquals(context.getString(R.string.recaptcha_request_toast), errorInfo.getMessage(context))
assertEquals(url, errorInfo.recaptchaUrl)
assertTrue(errorInfo.isReportable)
assertTrue(errorInfo.isRetryable)
}
}
@@ -1,6 +1,9 @@
package org.schabi.newpipe.local.history
import androidx.test.core.app.ApplicationProvider
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Assert.assertEquals
@@ -11,9 +14,6 @@ import org.schabi.newpipe.database.AppDatabase
import org.schabi.newpipe.database.history.model.SearchHistoryEntry
import org.schabi.newpipe.testUtil.TestDatabase
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
class HistoryRecordManagerTest {
@@ -41,7 +41,7 @@ class HistoryRecordManagerTest {
// For some reason the Flowable returned by getAll() never completes, so we can't assert
// that the number of Lists it returns is exactly 1, we can only check if the first List is
// correct. Why on earth has a Flowable been used instead of a Single for getAll()?!?
val entities = database.searchHistoryDAO().all.blockingFirst()
val entities = database.searchHistoryDAO().getAll().blockingFirst()
assertThat(entities).hasSize(1)
assertThat(entities[0].id).isEqualTo(1)
assertThat(entities[0].serviceId).isEqualTo(0)
@@ -51,54 +51,53 @@ class HistoryRecordManagerTest {
@Test
fun deleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(time.minusSeconds(1), 0, "A"),
SearchHistoryEntry(time.minusSeconds(2), 2, "A"),
SearchHistoryEntry(time.minusSeconds(3), 1, "B"),
SearchHistoryEntry(time.minusSeconds(4), 0, "B"),
SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 0, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 2, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 1, search = "B"),
SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 0, search = "B")
)
// make sure all 4 were inserted
database.searchHistoryDAO().insertAll(entries)
assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries)
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).hasSameSizeAs(entries)
// try to delete only "A" entries, "B" entries should be untouched
manager.deleteSearchHistory("A").test().await().assertValue(2)
val entities = database.searchHistoryDAO().all.blockingFirst()
val entities = database.searchHistoryDAO().getAll().blockingFirst()
assertThat(entities).hasSize(2)
assertThat(entities).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
.containsExactly(*entries.subList(2, 4).toTypedArray())
// assert that nothing happens if we delete a search query that does exist in the db
manager.deleteSearchHistory("A").test().await().assertValue(0)
val entities2 = database.searchHistoryDAO().all.blockingFirst()
val entities2 = database.searchHistoryDAO().getAll().blockingFirst()
assertThat(entities2).hasSize(2)
assertThat(entities2).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
.containsExactly(*entries.subList(2, 4).toTypedArray())
// delete all remaining entries
manager.deleteSearchHistory("B").test().await().assertValue(2)
assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty()
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).isEmpty()
}
@Test
fun deleteCompleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(time.minusSeconds(1), 1, "A"),
SearchHistoryEntry(time.minusSeconds(2), 2, "B"),
SearchHistoryEntry(time.minusSeconds(3), 0, "C"),
SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 1, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 2, search = "B"),
SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 0, search = "C")
)
// make sure all 3 were inserted
database.searchHistoryDAO().insertAll(entries)
assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries)
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).hasSameSizeAs(entries)
// should remove everything
manager.deleteCompleteSearchHistory().test().await().assertValue(entries.size)
assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty()
assertThat(database.searchHistoryDAO().getAll().blockingFirst()).isEmpty()
}
private fun insertShuffledRelatedSearches(relatedSearches: Collection<SearchHistoryEntry>) {
// shuffle to make sure the order of items returned by queries depends only on
// SearchHistoryEntry.creationDate, not on the actual insertion time, so that we can
// verify that the `ORDER BY` clause does its job
@@ -107,7 +106,7 @@ class HistoryRecordManagerTest {
// make sure all entries were inserted
assertEquals(
relatedSearches.size,
database.searchHistoryDAO().all.blockingFirst().size
database.searchHistoryDAO().getAll().blockingFirst().size
)
}
@@ -121,25 +120,24 @@ class HistoryRecordManagerTest {
RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places)
RELATED_SEARCHES_ENTRIES[4].search, // B
RELATED_SEARCHES_ENTRIES[5].search, // AA
RELATED_SEARCHES_ENTRIES[2].search, // BA
RELATED_SEARCHES_ENTRIES[2].search // BA
)
}
@Test
fun getRelatedSearches_emptyQuery_manyDuplicates() {
insertShuffledRelatedSearches(
listOf(
SearchHistoryEntry(time.minusSeconds(9), 3, "A"),
SearchHistoryEntry(time.minusSeconds(8), 3, "AB"),
SearchHistoryEntry(time.minusSeconds(7), 3, "A"),
SearchHistoryEntry(time.minusSeconds(6), 3, "A"),
SearchHistoryEntry(time.minusSeconds(5), 3, "BA"),
SearchHistoryEntry(time.minusSeconds(4), 3, "A"),
SearchHistoryEntry(time.minusSeconds(3), 3, "A"),
SearchHistoryEntry(time.minusSeconds(2), 0, "A"),
SearchHistoryEntry(time.minusSeconds(1), 2, "AA"),
)
val relatedSearches = listOf(
SearchHistoryEntry(creationDate = time.minusSeconds(9), serviceId = 3, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(8), serviceId = 3, search = "AB"),
SearchHistoryEntry(creationDate = time.minusSeconds(7), serviceId = 3, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(6), serviceId = 3, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(5), serviceId = 3, search = "BA"),
SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 3, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 3, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 0, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 2, search = "AA")
)
insertShuffledRelatedSearches(relatedSearches)
val searches = manager.getRelatedSearches("", 9, 3).blockingFirst()
assertThat(searches).containsExactly("AA", "A", "BA")
@@ -154,7 +152,7 @@ class HistoryRecordManagerTest {
assertThat(searches).containsExactly(
RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places)
RELATED_SEARCHES_ENTRIES[5].search, // AA
RELATED_SEARCHES_ENTRIES[1].search, // BA
RELATED_SEARCHES_ENTRIES[1].search // BA
)
// also make sure that the string comparison is case insensitive
@@ -166,13 +164,13 @@ class HistoryRecordManagerTest {
private val time = OffsetDateTime.of(LocalDateTime.of(2000, 1, 1, 1, 1), ZoneOffset.UTC)
private val RELATED_SEARCHES_ENTRIES = listOf(
SearchHistoryEntry(time.minusSeconds(7), 2, "AC"),
SearchHistoryEntry(time.minusSeconds(6), 0, "ABC"),
SearchHistoryEntry(time.minusSeconds(5), 1, "BA"),
SearchHistoryEntry(time.minusSeconds(4), 3, "A"),
SearchHistoryEntry(time.minusSeconds(2), 0, "B"),
SearchHistoryEntry(time.minusSeconds(3), 2, "AA"),
SearchHistoryEntry(time.minusSeconds(1), 1, "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(7), serviceId = 2, search = "AC"),
SearchHistoryEntry(creationDate = time.minusSeconds(6), serviceId = 0, search = "ABC"),
SearchHistoryEntry(creationDate = time.minusSeconds(5), serviceId = 1, search = "BA"),
SearchHistoryEntry(creationDate = time.minusSeconds(4), serviceId = 3, search = "A"),
SearchHistoryEntry(creationDate = time.minusSeconds(2), serviceId = 0, search = "B"),
SearchHistoryEntry(creationDate = time.minusSeconds(3), serviceId = 2, search = "AA"),
SearchHistoryEntry(creationDate = time.minusSeconds(1), serviceId = 1, search = "A")
)
}
}
@@ -33,8 +33,12 @@ class LocalPlaylistManagerTest {
fun createPlaylist() {
val NEWPIPE_URL = "https://newpipe.net/"
val stream = StreamEntity(
serviceId = 1, url = NEWPIPE_URL, title = "title",
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader",
serviceId = 1,
url = NEWPIPE_URL,
title = "title",
streamType = StreamType.VIDEO_STREAM,
duration = 1,
uploader = "uploader",
uploaderUrl = NEWPIPE_URL
)
@@ -58,20 +62,28 @@ class LocalPlaylistManagerTest {
@Test()
fun createPlaylist_nonExistentStreamsAreUpserted() {
val stream = StreamEntity(
serviceId = 1, url = "https://newpipe.net/", title = "title",
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader",
serviceId = 1,
url = "https://newpipe.net/",
title = "title",
streamType = StreamType.VIDEO_STREAM,
duration = 1,
uploader = "uploader",
uploaderUrl = "https://newpipe.net/"
)
database.streamDAO().insert(stream)
val upserted = StreamEntity(
serviceId = 1, url = "https://newpipe.net/2", title = "title2",
streamType = StreamType.VIDEO_STREAM, duration = 1, uploader = "uploader",
serviceId = 1,
url = "https://newpipe.net/2",
title = "title2",
streamType = StreamType.VIDEO_STREAM,
duration = 1,
uploader = "uploader",
uploaderUrl = "https://newpipe.net/"
)
val result = manager.createPlaylist("name", listOf(stream, upserted))
result.test().await().assertComplete()
database.streamDAO().all.test().awaitCount(1).assertValue(listOf(stream, upserted))
database.streamDAO().getAll().test().awaitCount(1).assertValue(listOf(stream, upserted))
}
}
@@ -17,21 +17,20 @@ class TrampolineSchedulerRule : TestRule {
private val scheduler = Schedulers.trampoline()
override fun apply(base: Statement, description: Description): Statement =
object : Statement() {
override fun evaluate() {
try {
RxJavaPlugins.setComputationSchedulerHandler { scheduler }
RxJavaPlugins.setIoSchedulerHandler { scheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
RxJavaPlugins.setSingleSchedulerHandler { scheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
override fun apply(base: Statement, description: Description): Statement = object : Statement() {
override fun evaluate() {
try {
RxJavaPlugins.setComputationSchedulerHandler { scheduler }
RxJavaPlugins.setIoSchedulerHandler { scheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
RxJavaPlugins.setSingleSchedulerHandler { scheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
base.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
base.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
@@ -0,0 +1,126 @@
package org.schabi.newpipe.ui.components.common
import androidx.activity.ComponentActivity
import androidx.annotation.StringRes
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import java.net.UnknownHostException
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.schabi.newpipe.R
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.exceptions.UnsupportedContentInCountryException
import org.schabi.newpipe.ui.theme.AppTheme
@RunWith(AndroidJUnit4::class)
class ErrorPanelTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()
private fun setErrorPanel(errorInfo: ErrorInfo, onRetry: (() -> Unit)? = null) {
composeRule.setContent {
AppTheme {
ErrorPanel(errorInfo = errorInfo, onRetry = onRetry)
}
}
}
private fun text(@StringRes id: Int) = composeRule.activity.getString(id)
/**
* Test Network Error
*/
@Test
fun testNetworkErrorShowsRetryWithoutReportButton() {
val networkErrorInfo = ErrorInfo(
throwable = UnknownHostException("offline"),
userAction = UserAction.REQUESTED_STREAM,
request = "https://example.com/watch?v=foo"
)
setErrorPanel(networkErrorInfo, onRetry = {})
composeRule.onNodeWithText(text(R.string.network_error)).assertIsDisplayed()
composeRule.onNodeWithText(text(R.string.retry), ignoreCase = true).assertIsDisplayed()
composeRule.onNodeWithText(text(R.string.error_snackbar_action), ignoreCase = true)
.assertDoesNotExist()
composeRule.onNodeWithText(text(R.string.recaptcha_solve), ignoreCase = true)
.assertDoesNotExist()
}
/**
* Test Unexpected Error, Shows Report and Retry buttons
*/
@Test
fun unexpectedErrorShowsReportAndRetryButtons() {
val unexpectedErrorInfo = ErrorInfo(
throwable = RuntimeException("Unexpected error"),
userAction = UserAction.REQUESTED_STREAM,
request = "https://example.com/watch?v=bar"
)
setErrorPanel(unexpectedErrorInfo, onRetry = {})
composeRule.onNodeWithText(text(R.string.error_snackbar_message)).assertIsDisplayed()
composeRule.onNodeWithText(text(R.string.retry), ignoreCase = true).assertIsDisplayed()
composeRule.onNodeWithText(text(R.string.error_snackbar_action), ignoreCase = true)
.assertIsDisplayed()
}
/**
* Test Recaptcha Error shows all buttons: solve, retry, open in browser, report
*/
@Test
fun recaptchaErrorShowsAllButtons() {
var retryClicked = false
val recaptchaErrorInfo = ErrorInfo(
throwable = ReCaptchaException(
"Recaptcha required",
"https://example.com/captcha"
),
userAction = UserAction.REQUESTED_STREAM,
request = "https://example.com/watch?v=baz",
openInBrowserUrl = "https://example.com/watch?v=baz"
)
setErrorPanel(
errorInfo = recaptchaErrorInfo,
onRetry = { retryClicked = true }
)
composeRule.onNodeWithText(text(R.string.recaptcha_solve), ignoreCase = true)
.assertIsDisplayed()
composeRule.onNodeWithText(text(R.string.retry), ignoreCase = true)
.assertIsDisplayed()
.performClick()
composeRule.onNodeWithText(text(R.string.open_in_browser), ignoreCase = true)
.assertIsDisplayed()
composeRule.onNodeWithText(text(R.string.error_snackbar_action), ignoreCase = true)
.assertIsDisplayed()
assert(retryClicked) { "onRetry callback should have been invoked" }
}
/**
* Test Content Not Available Error hides retry button
*/
@Test
fun testNonRetryableErrorHidesRetryAndReportButtons() {
val contentNotAvailable = ErrorInfo(
throwable = UnsupportedContentInCountryException("Not available here"),
userAction = UserAction.REQUESTED_STREAM,
request = "https://example.com/watch?v=qux"
)
setErrorPanel(contentNotAvailable)
composeRule.onNodeWithText(text(R.string.unsupported_content_in_country))
.assertIsDisplayed()
composeRule.onNodeWithText(text(R.string.retry), ignoreCase = true)
.assertDoesNotExist()
composeRule.onNodeWithText(text(R.string.error_snackbar_action), ignoreCase = true)
.assertDoesNotExist()
}
}
@@ -0,0 +1,364 @@
package org.schabi.newpipe.ui.components.video.comment
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performScrollToNode
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.LoadStates
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import java.net.UnknownHostException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.schabi.newpipe.R
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.Page
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.stream.Description
import org.schabi.newpipe.ui.components.common.ErrorPanel
import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar
import org.schabi.newpipe.ui.components.common.LoadingIndicator
import org.schabi.newpipe.ui.emptystate.EmptyStateComposable
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec
import org.schabi.newpipe.ui.theme.AppTheme
import org.schabi.newpipe.viewmodels.util.Resource
class CommentSectionInstrumentedTest {
@get:Rule
val composeRule = createAndroidComposeRule<androidx.activity.ComponentActivity>()
private val uiStateFlow = MutableStateFlow<Resource<CommentInfo>>(Resource.Loading)
private val pagingFlow = MutableStateFlow(PagingData.empty<CommentsInfoItem>())
private fun string(@StringRes resId: Int) = composeRule.activity.getString(resId)
@Before
fun setUp() {
composeRule.setContent {
AppTheme {
TestCommentSection(uiStateFlow = uiStateFlow, commentsFlow = pagingFlow)
}
}
}
private fun successState(commentCount: Int) = Resource.Success(
CommentInfo(
serviceId = 0,
url = "",
comments = emptyList(),
nextPage = null,
commentCount = commentCount,
isCommentsDisabled = false
)
)
@Test
fun commentListLoadsAndScrolls() {
val comments = (1..25).map { index ->
CommentsInfoItem(
commentText = Description("Comment $index", Description.PLAIN_TEXT),
uploaderName = "Uploader $index",
replies = Page(""),
replyCount = 0
)
}
uiStateFlow.value = successState(comments.size)
pagingFlow.value = PagingData.from(comments)
composeRule.waitForIdle()
composeRule.onNodeWithText("Comment 1").assertIsDisplayed()
composeRule.onNodeWithTag("comment_list")
.performScrollToNode(hasText("Comment 25"))
composeRule.onNodeWithText("Comment 25").assertIsDisplayed()
}
@OptIn(ExperimentalTestApi::class)
@Test
fun pagingErrorShowsErrorPanelAndAllowsRetry() {
uiStateFlow.value = successState(10)
pagingFlow.value = PagingData.from(
data = emptyList(),
sourceLoadStates = LoadStates(
refresh = LoadState.Error(ReCaptchaException("captcha required", "https://example.com")),
prepend = LoadState.NotLoading(true),
append = LoadState.NotLoading(true)
)
)
composeRule.waitForIdle()
val solveMatcher = hasText(string(R.string.recaptcha_solve), ignoreCase = true)
.and(hasClickAction())
val retryMatcher = hasText(string(R.string.retry), ignoreCase = true)
.and(hasClickAction())
composeRule.waitUntil(timeoutMillis = 5_000) {
composeRule.onAllNodes(solveMatcher).fetchSemanticsNodes().isNotEmpty()
}
composeRule.waitUntil(timeoutMillis = 5_000) {
composeRule.onAllNodes(retryMatcher).fetchSemanticsNodes().isNotEmpty()
}
composeRule.onNode(retryMatcher)
.performScrollTo()
.performClick()
val recoveredComment = CommentsInfoItem(
commentText = Description("Recovered comment", Description.PLAIN_TEXT),
uploaderName = "Uploader",
replies = Page(""),
replyCount = 0
)
uiStateFlow.value = successState(1)
pagingFlow.value = PagingData.from(
data = listOf(recoveredComment),
sourceLoadStates = LoadStates(
refresh = LoadState.NotLoading(false),
prepend = LoadState.NotLoading(true),
append = LoadState.NotLoading(true)
)
)
composeRule.waitForIdle()
composeRule.waitUntil(timeoutMillis = 5_000) {
composeRule.onAllNodes(hasText("Recovered comment"))
.fetchSemanticsNodes()
.isNotEmpty()
}
composeRule.onNodeWithText("Recovered comment").assertIsDisplayed()
composeRule.onNode(solveMatcher).assertDoesNotExist()
composeRule.onNode(retryMatcher).assertDoesNotExist()
}
@OptIn(ExperimentalTestApi::class)
@Test
fun resourceErrorShowsErrorPanelAndRetry() {
uiStateFlow.value = Resource.Error(UnknownHostException("offline"))
composeRule.waitForIdle()
composeRule.onNodeWithText(string(R.string.network_error)).assertIsDisplayed()
val retryMatcher = hasText(string(R.string.retry), ignoreCase = true)
.and(hasClickAction())
composeRule.waitUntil(timeoutMillis = 5_000) {
composeRule.onAllNodes(retryMatcher).fetchSemanticsNodes().isNotEmpty()
}
composeRule.onNode(retryMatcher)
.performScrollTo()
.performClick()
val recoveredComment = CommentsInfoItem(
commentText = Description("Recovered comment", Description.PLAIN_TEXT),
uploaderName = "Uploader",
replies = Page(""),
replyCount = 0
)
uiStateFlow.value = successState(1)
pagingFlow.value = PagingData.from(
data = listOf(recoveredComment),
sourceLoadStates = LoadStates(
refresh = LoadState.NotLoading(false),
prepend = LoadState.NotLoading(true),
append = LoadState.NotLoading(true)
)
)
composeRule.waitForIdle()
composeRule.waitUntil(timeoutMillis = 5_000) {
composeRule.onAllNodes(hasText("Recovered comment"))
.fetchSemanticsNodes()
.isNotEmpty()
}
composeRule.onNodeWithText("Recovered comment").assertIsDisplayed()
composeRule.onNodeWithText(string(R.string.network_error))
.assertDoesNotExist()
composeRule.onNode(retryMatcher).assertDoesNotExist()
}
@OptIn(ExperimentalTestApi::class)
@Test
fun retryAfterErrorRecoversList() {
uiStateFlow.value = Resource.Error(RuntimeException("boom"))
composeRule.waitForIdle()
val retryMatcher = hasText(string(R.string.retry), ignoreCase = true)
.and(hasClickAction())
composeRule.waitUntil(timeoutMillis = 5_000) {
composeRule.onAllNodes(retryMatcher).fetchSemanticsNodes().isNotEmpty()
}
composeRule.onNode(retryMatcher)
.performScrollTo()
.performClick()
val firstComment = CommentsInfoItem(
commentText = Description("First comment", Description.PLAIN_TEXT),
uploaderName = "Uploader",
replies = Page(""),
replyCount = 0
)
uiStateFlow.value = successState(1)
pagingFlow.value = PagingData.from(
data = listOf(firstComment),
sourceLoadStates = LoadStates(
refresh = LoadState.NotLoading(false),
prepend = LoadState.NotLoading(true),
append = LoadState.NotLoading(true)
)
)
composeRule.waitForIdle()
composeRule.waitUntil(timeoutMillis = 5_000) {
composeRule.onAllNodes(hasText("First comment"))
.fetchSemanticsNodes()
.isNotEmpty()
}
composeRule.onNodeWithText("First comment").assertIsDisplayed()
composeRule.onNodeWithText(string(R.string.network_error))
.assertDoesNotExist()
composeRule.onNode(retryMatcher).assertDoesNotExist()
}
}
@Composable
private fun TestCommentSection(
uiStateFlow: StateFlow<Resource<CommentInfo>>,
commentsFlow: Flow<PagingData<CommentsInfoItem>>
) {
val uiState by uiStateFlow.collectAsState()
val comments = commentsFlow.collectAsLazyPagingItems()
val nestedScrollInterop = rememberNestedScrollInteropConnection()
val listState = rememberLazyListState()
val COMMENT_LIST_TAG = "comment_list"
LazyColumnThemedScrollbar(state = listState) {
LazyColumn(
modifier = Modifier
.testTag(COMMENT_LIST_TAG)
.nestedScroll(nestedScrollInterop),
state = listState
) {
when (uiState) {
is Resource.Loading -> item {
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
}
is Resource.Success -> {
val commentInfo = (uiState as Resource.Success<CommentInfo>).data
val count = commentInfo.commentCount
when {
commentInfo.isCommentsDisabled -> item {
EmptyStateComposable(
spec = EmptyStateSpec.DisabledComments,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 128.dp)
)
}
count == 0 -> item {
EmptyStateComposable(
spec = EmptyStateSpec.NoComments,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 128.dp)
)
}
else -> {
if (count >= 0) {
item {
Text(
modifier = Modifier
.padding(start = 12.dp, end = 12.dp, bottom = 4.dp),
text = pluralStringResource(R.plurals.comments, count, count),
maxLines = 1,
style = MaterialTheme.typography.titleMedium
)
}
}
when (val refresh = comments.loadState.refresh) {
is LoadState.Loading -> item {
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
}
is LoadState.Error -> item {
Box(
modifier = Modifier.fillMaxWidth()
) {
ErrorPanel(
errorInfo = ErrorInfo(
throwable = refresh.error,
userAction = UserAction.REQUESTED_COMMENTS,
request = "comments"
),
onRetry = { comments.retry() },
modifier = Modifier.align(Alignment.Center)
)
}
}
else -> items(comments.itemCount) { index ->
Comment(comment = comments[index]!!) {}
}
}
}
}
}
is Resource.Error -> item {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
ErrorPanel(
errorInfo = ErrorInfo(
throwable = (uiState as Resource.Error).throwable,
userAction = UserAction.REQUESTED_COMMENTS,
request = "comments"
),
onRetry = { comments.retry() },
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
}
}
@@ -156,41 +156,51 @@ class StreamItemAdapterTest {
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0)
helper.assertInvalidResponse(
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.png\""))), 1
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.png\""))),
1
)
helper.assertInvalidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"data.csv\""))), 2
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"data.csv\""))),
2
)
helper.assertInvalidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; filename=\"data.csv\""))), 3
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
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
5,
MediaFormat.OGG
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "some-form-data; filename=\"audio.flac\""))),
6, MediaFormat.FLAC
6,
MediaFormat.FLAC
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.aiff\"; filename=\"audio.aiff\""))),
7, MediaFormat.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
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
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
10,
MediaFormat.OPUS
)
}
@@ -213,16 +223,24 @@ class StreamItemAdapterTest {
helper.assertInvalidResponse(getResponse(mapOf()), 7)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/flac"))), 8, MediaFormat.FLAC
getResponse(mapOf(Pair("Content-Type", "audio/flac"))),
8,
MediaFormat.FLAC
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/wav"))), 9, MediaFormat.WAV
getResponse(mapOf(Pair("Content-Type", "audio/wav"))),
9,
MediaFormat.WAV
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/opus"))), 10, MediaFormat.OPUS
getResponse(mapOf(Pair("Content-Type", "audio/opus"))),
10,
MediaFormat.OPUS
)
helper.assertValidResponse(
getResponse(mapOf(Pair("Content-Type", "audio/aiff"))), 11, MediaFormat.AIFF
getResponse(mapOf(Pair("Content-Type", "audio/aiff"))),
11,
MediaFormat.AIFF
)
}
@@ -230,39 +248,37 @@ class StreamItemAdapterTest {
* @return a list of video streams, in which their video only property mirrors the provided
* [videoOnly] vararg.
*/
private fun getVideoStreams(vararg videoOnly: Boolean) =
StreamItemAdapter.StreamInfoWrapper(
videoOnly.map {
VideoStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com", true)
.setMediaFormat(MediaFormat.MPEG_4)
.setResolution("720p")
.setIsVideoOnly(it)
.build()
},
context
)
private fun getVideoStreams(vararg videoOnly: Boolean) = StreamInfoWrapper(
videoOnly.map {
VideoStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com", true)
.setMediaFormat(MediaFormat.MPEG_4)
.setResolution("720p")
.setIsVideoOnly(it)
.build()
},
context
)
/**
* @return a list of audio streams, containing valid and null elements mirroring the provided
* [shouldBeValid] vararg.
*/
private fun getAudioStreams(vararg shouldBeValid: Boolean) =
getSecondaryStreamsFromList(
shouldBeValid.map {
if (it) {
AudioStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com", true)
.setMediaFormat(MediaFormat.OPUS)
.setAverageBitrate(192)
.build()
} else {
null
}
private fun getAudioStreams(vararg shouldBeValid: Boolean) = getSecondaryStreamsFromList(
shouldBeValid.map {
if (it) {
AudioStream.Builder()
.setId(Stream.ID_UNKNOWN)
.setContent("https://example.com", true)
.setMediaFormat(MediaFormat.OPUS)
.setAverageBitrate(192)
.build()
} else {
null
}
)
}
)
private fun getIncompleteAudioStreams(size: Int): List<AudioStream> {
val list = ArrayList<AudioStream>(size)
@@ -292,7 +308,7 @@ class StreamItemAdapterTest {
Assert.assertEquals(
"normal visibility (pos=[$position]) is not correct",
findViewById<View>(R.id.wo_sound_icon).visibility,
normalVisibility,
normalVisibility
)
}
spinner.adapter.getDropDownView(position, null, spinner).run {
@@ -307,18 +323,17 @@ class StreamItemAdapterTest {
/**
* Helper function that builds a secondary stream list.
*/
private fun <T : Stream> getSecondaryStreamsFromList(streams: List<T?>) =
SparseArrayCompat<SecondaryStreamHelper<T>?>(streams.size).apply {
streams.forEachIndexed { index, stream ->
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
SecondaryStreamHelper(
StreamItemAdapter.StreamInfoWrapper(streams, context),
it
)
}
put(index, secondaryStreamHelper)
private fun <T : Stream> getSecondaryStreamsFromList(streams: List<T?>) = SparseArrayCompat<SecondaryStreamHelper<T>?>(streams.size).apply {
streams.forEachIndexed { index, stream ->
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
SecondaryStreamHelper(
StreamItemAdapter.StreamInfoWrapper(streams, context),
it
)
}
put(index, secondaryStreamHelper)
}
}
private fun getResponse(headers: Map<String, String>): Response {
val listHeaders = HashMap<String, List<String>>()
@@ -345,7 +360,8 @@ class StreamItemAdapterTest {
index: Int
) {
assertFalse(
"invalid header returns valid value", retrieveMediaFormat(streams[index], response)
"invalid header returns valid value",
retrieveMediaFormat(streams[index], response)
)
assertNull("Media format extracted although stated otherwise", wrapper.getFormat(index))
}
@@ -359,7 +375,8 @@ class StreamItemAdapterTest {
format: MediaFormat
) {
assertTrue(
"header was not recognized", retrieveMediaFormat(streams[index], response)
"header was not recognized",
retrieveMediaFormat(streams[index], response)
)
assertEquals("Wrong media format extracted", format, wrapper.getFormat(index))
}
@@ -1,20 +0,0 @@
package org.schabi.newpipe.settings;
import android.content.Intent;
import leakcanary.LeakCanary;
/**
* Build variant dependent (BVD) leak canary API implementation for the debug settings fragment.
* This class is loaded via reflection by
* {@link DebugSettingsFragment.DebugSettingsBVDLeakCanaryAPI}.
*/
@SuppressWarnings("unused") // Class is used but loaded via reflection
public class DebugSettingsBVDLeakCanary
implements DebugSettingsFragment.DebugSettingsBVDLeakCanaryAPI {
@Override
public Intent getNewLeakDisplayActivityIntent() {
return LeakCanary.INSTANCE.newLeakDisplayActivityIntent();
}
}
@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.settings
import android.content.Intent
import leakcanary.LeakCanary.newLeakDisplayActivityIntent
/**
* Build variant dependent (BVD) leak canary API implementation for the debug settings fragment.
* This class is loaded via reflection by
* [DebugSettingsBVDLeakCanaryAPI].
*/
@Suppress("unused") // Class is used but loaded via reflection
class DebugSettingsBVDLeakCanary :
DebugSettingsBVDLeakCanaryAPI {
override fun getNewLeakDisplayActivityIntent(): Intent {
return newLeakDisplayActivityIntent()
}
}
+37 -12
View File
@@ -9,6 +9,8 @@
<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.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- We need to be able to open links in the browser on API 30+ -->
@@ -57,6 +59,15 @@
</intent-filter>
</receiver>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
<service
android:name=".player.PlayerService"
android:exported="true"
@@ -64,8 +75,15 @@
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<activity
android:name=".ComposeActivity"
android:exported="false" />
<activity
android:name=".player.PlayQueueActivity"
android:exported="false"
@@ -77,14 +95,14 @@
android:exported="false"
android:label="@string/settings" />
<activity
android:name=".about.AboutActivity"
android:exported="false"
android:label="@string/title_activity_about" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
tools:node="merge" />
<service android:name=".local.subscription.services.SubscriptionsImportService" />
<service android:name=".local.subscription.services.SubscriptionsExportService" />
<service android:name=".local.feed.service.FeedLoadService" />
<service
android:name=".local.feed.service.FeedLoadService"
android:foregroundServiceType="dataSync" />
<activity
android:name=".PanicResponderActivity"
@@ -105,10 +123,6 @@
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".error.ErrorActivity"
android:exported="false" />
<!-- giga get related -->
<activity
android:name=".download.DownloadActivity"
@@ -116,7 +130,9 @@
android:label="@string/app_name"
android:launchMode="singleTask" />
<service android:name="us.shandian.giga.service.DownloadManagerService" />
<service
android:name="us.shandian.giga.service.DownloadManagerService"
android:foregroundServiceType="dataSync" />
<activity
android:name=".util.FilePickerActivityHelper"
@@ -312,6 +328,7 @@
<data android:scheme="https" />
<data android:host="soundcloud.com" />
<data android:host="m.soundcloud.com" />
<data android:host="on.soundcloud.com" />
<data android:host="www.soundcloud.com" />
<data android:pathPrefix="/" />
</intent-filter>
@@ -356,6 +373,7 @@
<data android:host="eduvid.org" />
<data android:host="framatube.org" />
<data android:host="indymotion.fr" />
<data android:host="media.assassinate-you.net" />
<data android:host="media.fsfe.org" />
<data android:host="peertube.co.uk" />
@@ -367,6 +385,7 @@
<data android:host="tilvids.com" />
<data android:host="video.lqdn.fr" />
<data android:host="video.ploud.fr" />
<data android:host="subscribeto.me" />
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
<data android:pathPrefix="/w/" /> <!-- short video URLs -->
@@ -408,6 +427,7 @@
</activity>
<service
android:name=".RouterActivity$FetcherService"
android:foregroundServiceType="dataSync"
android:exported="false" />
<!-- opting out of sending metrics to Google in Android System WebView -->
@@ -423,5 +443,10 @@
<meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="true" />
<!-- Android Auto -->
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@mipmap/ic_launcher" />
</application>
</manifest>
-162
View File
@@ -1,162 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Apache License - Version 2.0, January 2004</title>
</head>
<body>
<p>Apache License<br>Version 2.0, January 2004<br>
<a href="http://www.apache.org/licenses/">http://www.apache.org/licenses/</a> </p>
<p>TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION</p>
<p><strong><a name="definitions">1. Definitions</a></strong>.</p>
<p>"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.</p>
<p>"Licensor" shall mean the copyright owner or entity authorized by the
copyright owner that is granting the License.</p>
<p>"Legal Entity" shall mean the union of the acting entity and all other
entities that control, are controlled by, or are under common control with
that entity. For the purposes of this definition, "control" means (i) the
power, direct or indirect, to cause the direction or management of such
entity, whether by contract or otherwise, or (ii) ownership of fifty
percent (50%) or more of the outstanding shares, or (iii) beneficial
ownership of such entity.</p>
<p>"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.</p>
<p>"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source,
and configuration files.</p>
<p>"Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled
object code, generated documentation, and conversions to other media types.</p>
<p>"Work" shall mean the work of authorship, whether in Source or Object form,
made available under the License, as indicated by a copyright notice that
is included in or attached to the work (an example is provided in the
Appendix below).</p>
<p>"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial
revisions, annotations, elaborations, or other modifications represent, as
a whole, an original work of authorship. For the purposes of this License,
Derivative Works shall not include works that remain separable from, or
merely link (or bind by name) to the interfaces of, the Work and Derivative
Works thereof.</p>
<p>"Contribution" shall mean any work of authorship, including the original
version of the Work and any modifications or additions to that Work or
Derivative Works thereof, that is intentionally submitted to Licensor for
inclusion in the Work by the copyright owner or by an individual or Legal
Entity authorized to submit on behalf of the copyright owner. For the
purposes of this definition, "submitted" means any form of electronic,
verbal, or written communication sent to the Licensor or its
representatives, including but not limited to communication on electronic
mailing lists, source code control systems, and issue tracking systems that
are managed by, or on behalf of, the Licensor for the purpose of discussing
and improving the Work, but excluding communication that is conspicuously
marked or otherwise designated in writing by the copyright owner as "Not a
Contribution."</p>
<p>"Contributor" shall mean Licensor and any individual or Legal Entity on
behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.</p>
<p><strong><a name="copyright">2. Grant of Copyright License</a></strong>. Subject to the
terms and conditions of this License, each Contributor hereby grants to You
a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of, publicly
display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.</p>
<p><strong><a name="patent">3. Grant of Patent License</a></strong>. Subject to the terms
and conditions of this License, each Contributor hereby grants to You a
perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made, use,
offer to sell, sell, import, and otherwise transfer the Work, where such
license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by
combination of their Contribution(s) with the Work to which such
Contribution(s) was submitted. If You institute patent litigation against
any entity (including a cross-claim or counterclaim in a lawsuit) alleging
that the Work or a Contribution incorporated within the Work constitutes
direct or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate as of the
date such litigation is filed.</p>
<p><strong><a name="redistribution">4. Redistribution</a></strong>. You may reproduce and
distribute copies of the Work or Derivative Works thereof in any medium,
with or without modifications, and in Source or Object form, provided that
You meet the following conditions:</p>
<ol style="list-style: lower-latin;">
<li>You must give any other recipients of the Work or Derivative Works a
copy of this License; and</li>
<li>You must cause any modified files to carry prominent notices stating
that You changed the files; and</li>
<li>You must retain, in the Source form of any Derivative Works that You
distribute, all copyright, patent, trademark, and attribution notices from
the Source form of the Work, excluding those notices that do not pertain to
any part of the Derivative Works; and</li>
<li>If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable copy
of the attribution notices contained within such NOTICE file, excluding
those notices that do not pertain to any part of the Derivative Works, in
at least one of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or documentation,
if provided along with the Derivative Works; or, within a display generated
by the Derivative Works, if and wherever such third-party notices normally
appear. The contents of the NOTICE file are for informational purposes only
and do not modify the License. You may add Your own attribution notices
within Derivative Works that You distribute, alongside or as an addendum to
the NOTICE text from the Work, provided that such additional attribution
notices cannot be construed as modifying the License.
<br/>
<br/>
You may add Your own copyright statement to Your modifications and may
provide additional or different license terms and conditions for use,
reproduction, or distribution of Your modifications, or for any such
Derivative Works as a whole, provided Your use, reproduction, and
distribution of the Work otherwise complies with the conditions stated in
this License.
</li>
</ol>
<p><strong><a name="contributions">5. Submission of Contributions</a></strong>. Unless You
explicitly state otherwise, any Contribution intentionally submitted for
inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the
terms of any separate license agreement you may have executed with Licensor
regarding such Contributions.</p>
<p><strong><a name="trademarks">6. Trademarks</a></strong>. This License does not grant
permission to use the trade names, trademarks, service marks, or product
names of the Licensor, except as required for reasonable and customary use
in describing the origin of the Work and reproducing the content of the
NOTICE file.</p>
<p><strong><a name="no-warranty">7. Disclaimer of Warranty</a></strong>. Unless required by
applicable law or agreed to in writing, Licensor provides the Work (and
each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including,
without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You
are solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise
of permissions under this License.</p>
<p><strong><a name="no-liability">8. Limitation of Liability</a></strong>. In no event and
under no legal theory, whether in tort (including negligence), contract, or
otherwise, unless required by applicable law (such as deliberate and
grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a result
of this License or out of the use or inability to use the Work (including
but not limited to damages for loss of goodwill, work stoppage, computer
failure or malfunction, or any and all other commercial damages or losses),
even if such Contributor has been advised of the possibility of such
damages.</p>
<p><strong><a name="additional">9. Accepting Warranty or Additional Liability</a></strong>.
While redistributing the Work or Derivative Works thereof, You may choose
to offer, and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this License.
However, in accepting such obligations, You may act only on Your own behalf
and on Your sole responsibility, not on behalf of any other Contributor,
and only if You agree to indemnify, defend, and hold each Contributor
harmless for any liability incurred by, or claims asserted against, such
Contributor by reason of your accepting any such warranty or additional
liability.</p>
</body>
</html>
-245
View File
@@ -1,245 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- saved from url=(0050)https://www.eclipse.org/org/documents/epl-v10.html -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Eclipse Public License - Version 1.0</title>
</head>
<body cz-shortcut-listen="true" lang="EN-US">
<h2>Eclipse Public License - v 1.0</h2>
<p>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
AGREEMENT.</p>
<p><b>1. DEFINITIONS</b></p>
<p>"Contribution" means:</p>
<p class="list">a) in the case of the initial Contributor, the initial
code and documentation distributed under this Agreement, and</p>
<p class="list">b) in the case of each subsequent Contributor:</p>
<p class="list">i) changes to the Program, and</p>
<p class="list">ii) additions to the Program;</p>
<p class="list">where such changes and/or additions to the Program
originate from and are distributed by that particular Contributor. A
Contribution 'originates' from a Contributor if it was added to the
Program by such Contributor itself or anyone acting on such
Contributor's behalf. Contributions do not include additions to the
Program which: (i) are separate modules of software distributed in
conjunction with the Program under their own license agreement, and (ii)
are not derivative works of the Program.</p>
<p>"Contributor" means any person or entity that distributes
the Program.</p>
<p>"Licensed Patents" mean patent claims licensable by a
Contributor which are necessarily infringed by the use or sale of its
Contribution alone or when combined with the Program.</p>
<p>"Program" means the Contributions distributed in accordance
with this Agreement.</p>
<p>"Recipient" means anyone who receives the Program under
this Agreement, including all Contributors.</p>
<p><b>2. GRANT OF RIGHTS</b></p>
<p class="list">a) Subject to the terms of this Agreement, each
Contributor hereby grants Recipient a non-exclusive, worldwide,
royalty-free copyright license to reproduce, prepare derivative works
of, publicly display, publicly perform, distribute and sublicense the
Contribution of such Contributor, if any, and such derivative works, in
source code and object code form.</p>
<p class="list">b) Subject to the terms of this Agreement, each
Contributor hereby grants Recipient a non-exclusive, worldwide,
royalty-free patent license under Licensed Patents to make, use, sell,
offer to sell, import and otherwise transfer the Contribution of such
Contributor, if any, in source code and object code form. This patent
license shall apply to the combination of the Contribution and the
Program if, at the time the Contribution is added by the Contributor,
such addition of the Contribution causes such combination to be covered
by the Licensed Patents. The patent license shall not apply to any other
combinations which include the Contribution. No hardware per se is
licensed hereunder.</p>
<p class="list">c) Recipient understands that although each Contributor
grants the licenses to its Contributions set forth herein, no assurances
are provided by any Contributor that the Program does not infringe the
patent or other intellectual property rights of any other entity. Each
Contributor disclaims any liability to Recipient for claims brought by
any other entity based on infringement of intellectual property rights
or otherwise. As a condition to exercising the rights and licenses
granted hereunder, each Recipient hereby assumes sole responsibility to
secure any other intellectual property rights needed, if any. For
example, if a third party patent license is required to allow Recipient
to distribute the Program, it is Recipient's responsibility to acquire
that license before distributing the Program.</p>
<p class="list">d) Each Contributor represents that to its knowledge it
has sufficient copyright rights in its Contribution, if any, to grant
the copyright license set forth in this Agreement.</p>
<p><b>3. REQUIREMENTS</b></p>
<p>A Contributor may choose to distribute the Program in object code
form under its own license agreement, provided that:</p>
<p class="list">a) it complies with the terms and conditions of this
Agreement; and</p>
<p class="list">b) its license agreement:</p>
<p class="list">i) effectively disclaims on behalf of all Contributors
all warranties and conditions, express and implied, including warranties
or conditions of title and non-infringement, and implied warranties or
conditions of merchantability and fitness for a particular purpose;</p>
<p class="list">ii) effectively excludes on behalf of all Contributors
all liability for damages, including direct, indirect, special,
incidental and consequential damages, such as lost profits;</p>
<p class="list">iii) states that any provisions which differ from this
Agreement are offered by that Contributor alone and not by any other
party; and</p>
<p class="list">iv) states that source code for the Program is available
from such Contributor, and informs licensees how to obtain it in a
reasonable manner on or through a medium customarily used for software
exchange.</p>
<p>When the Program is made available in source code form:</p>
<p class="list">a) it must be made available under this Agreement; and</p>
<p class="list">b) a copy of this Agreement must be included with each
copy of the Program.</p>
<p>Contributors may not remove or alter any copyright notices contained
within the Program.</p>
<p>Each Contributor must identify itself as the originator of its
Contribution, if any, in a manner that reasonably allows subsequent
Recipients to identify the originator of the Contribution.</p>
<p><b>4. COMMERCIAL DISTRIBUTION</b></p>
<p>Commercial distributors of software may accept certain
responsibilities with respect to end users, business partners and the
like. While this license is intended to facilitate the commercial use of
the Program, the Contributor who includes the Program in a commercial
product offering should do so in a manner which does not create
potential liability for other Contributors. Therefore, if a Contributor
includes the Program in a commercial product offering, such Contributor
("Commercial Contributor") hereby agrees to defend and
indemnify every other Contributor ("Indemnified Contributor")
against any losses, damages and costs (collectively "Losses")
arising from claims, lawsuits and other legal actions brought by a third
party against the Indemnified Contributor to the extent caused by the
acts or omissions of such Commercial Contributor in connection with its
distribution of the Program in a commercial product offering. The
obligations in this section do not apply to any claims or Losses
relating to any actual or alleged intellectual property infringement. In
order to qualify, an Indemnified Contributor must: a) promptly notify
the Commercial Contributor in writing of such claim, and b) allow the
Commercial Contributor to control, and cooperate with the Commercial
Contributor in, the defense and any related settlement negotiations. The
Indemnified Contributor may participate in any such claim at its own
expense.</p>
<p>For example, a Contributor might include the Program in a commercial
product offering, Product X. That Contributor is then a Commercial
Contributor. If that Commercial Contributor then makes performance
claims, or offers warranties related to Product X, those performance
claims and warranties are such Commercial Contributor's responsibility
alone. Under this section, the Commercial Contributor would have to
defend claims against the other Contributors related to those
performance claims and warranties, and if a court requires any other
Contributor to pay any damages as a result, the Commercial Contributor
must pay those damages.</p>
<p><b>5. NO WARRANTY</b></p>
<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
responsible for determining the appropriateness of using and
distributing the Program and assumes all risks associated with its
exercise of rights under this Agreement , including but not limited to
the risks and costs of program errors, compliance with applicable laws,
damage to or loss of data, programs or equipment, and unavailability or
interruption of operations.</p>
<p><b>6. DISCLAIMER OF LIABILITY</b></p>
<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</p>
<p><b>7. GENERAL</b></p>
<p>If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this Agreement, and without further action
by the parties hereto, such provision shall be reformed to the minimum
extent necessary to make such provision valid and enforceable.</p>
<p>If Recipient institutes patent litigation against any entity
(including a cross-claim or counterclaim in a lawsuit) alleging that the
Program itself (excluding combinations of the Program with other
software or hardware) infringes such Recipient's patent(s), then such
Recipient's rights granted under Section 2(b) shall terminate as of the
date such litigation is filed.</p>
<p>All Recipient's rights under this Agreement shall terminate if it
fails to comply with any of the material terms or conditions of this
Agreement and does not cure such failure in a reasonable period of time
after becoming aware of such noncompliance. If all Recipient's rights
under this Agreement terminate, Recipient agrees to cease use and
distribution of the Program as soon as reasonably practicable. However,
Recipient's obligations under this Agreement and any licenses granted by
Recipient relating to the Program shall continue and survive.</p>
<p>Everyone is permitted to copy and distribute copies of this
Agreement, but in order to avoid inconsistency the Agreement is
copyrighted and may only be modified in the following manner. The
Agreement Steward reserves the right to publish new versions (including
revisions) of this Agreement from time to time. No one other than the
Agreement Steward has the right to modify this Agreement. The Eclipse
Foundation is the initial Agreement Steward. The Eclipse Foundation may
assign the responsibility to serve as the Agreement Steward to a
suitable separate entity. Each new version of the Agreement will be
given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version
of the Agreement is published, Contributor may elect to distribute the
Program (including its Contributions) under the new version. Except as
expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
rights or licenses to the intellectual property of any Contributor under
this Agreement, whether expressly, by implication, estoppel or
otherwise. All rights in the Program not expressly granted under this
Agreement are reserved.</p>
<p>This Agreement is governed by the laws of the State of New York and
the intellectual property laws of the United States of America. No party
to this Agreement will bring a legal action under this Agreement more
than one year after the cause of action arose. Each party waives its
rights to a jury trial in any resulting litigation.</p>
</body>
</html>
-639
View File
@@ -1,639 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>GNU General Public License v3.0 - GNU Project - Free Software Foundation (FSF)</title>
<link rel="alternate" type="application/rdf+xml"
href="http://www.gnu.org/licenses/gpl-3.0.rdf" />
</head>
<body>
<h3 style="text-align: center;">GNU GENERAL PUBLIC LICENSE</h3>
<p style="text-align: center;">Version 3, 29 June 2007</p>
<p>Copyright &copy; 2007 Free Software Foundation, Inc.
&lt;<a href="http://fsf.org/">http://fsf.org/</a>&gt;</p><p>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.</p>
<h3><a name="preamble"></a>Preamble</h3>
<p>The GNU General Public License is a free, copyleft license for
software and other kinds of works.</p>
<p>The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.</p>
<p>When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.</p>
<p>To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.</p>
<p>For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.</p>
<p>Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.</p>
<p>For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.</p>
<p>Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.</p>
<p>Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.</p>
<p>The precise terms and conditions for copying, distribution and
modification follow.</p>
<h3><a name="terms"></a>TERMS AND CONDITIONS</h3>
<h4><a name="section0"></a>0. Definitions.</h4>
<p>&ldquo;This License&rdquo; refers to version 3 of the GNU General Public License.</p>
<p>&ldquo;Copyright&rdquo; also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.</p>
<p>&ldquo;The Program&rdquo; refers to any copyrightable work licensed under this
License. Each licensee is addressed as &ldquo;you&rdquo;. &ldquo;Licensees&rdquo; and
&ldquo;recipients&rdquo; may be individuals or organizations.</p>
<p>To &ldquo;modify&rdquo; a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a &ldquo;modified version&rdquo; of the
earlier work or a work &ldquo;based on&rdquo; the earlier work.</p>
<p>A &ldquo;covered work&rdquo; means either the unmodified Program or a work based
on the Program.</p>
<p>To &ldquo;propagate&rdquo; a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.</p>
<p>To &ldquo;convey&rdquo; a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.</p>
<p>An interactive user interface displays &ldquo;Appropriate Legal Notices&rdquo;
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.</p>
<h4><a name="section1"></a>1. Source Code.</h4>
<p>The &ldquo;source code&rdquo; for a work means the preferred form of the work
for making modifications to it. &ldquo;Object code&rdquo; means any non-source
form of a work.</p>
<p>A &ldquo;Standard Interface&rdquo; means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.</p>
<p>The &ldquo;System Libraries&rdquo; of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
&ldquo;Major Component&rdquo;, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.</p>
<p>The &ldquo;Corresponding Source&rdquo; for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.</p>
<p>The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.</p>
<p>The Corresponding Source for a work in source code form is that
same work.</p>
<h4><a name="section2"></a>2. Basic Permissions.</h4>
<p>All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.</p>
<p>You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.</p>
<p>Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.</p>
<h4><a name="section3"></a>3. Protecting Users' Legal Rights From Anti-Circumvention Law.</h4>
<p>No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.</p>
<p>When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.</p>
<h4><a name="section4"></a>4. Conveying Verbatim Copies.</h4>
<p>You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.</p>
<p>You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.</p>
<h4><a name="section5"></a>5. Conveying Modified Source Versions.</h4>
<p>You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:</p>
<ul>
<li>a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.</li>
<li>b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
&ldquo;keep intact all notices&rdquo;.</li>
<li>c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.</li>
<li>d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.</li>
</ul>
<p>A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
&ldquo;aggregate&rdquo; if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.</p>
<h4><a name="section6"></a>6. Conveying Non-Source Forms.</h4>
<p>You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:</p>
<ul>
<li>a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.</li>
<li>b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.</li>
<li>c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.</li>
<li>d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.</li>
<li>e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.</li>
</ul>
<p>A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.</p>
<p>A &ldquo;User Product&rdquo; is either (1) a &ldquo;consumer product&rdquo;, which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, &ldquo;normally used&rdquo; refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.</p>
<p>&ldquo;Installation Information&rdquo; for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.</p>
<p>If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).</p>
<p>The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.</p>
<p>Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.</p>
<h4><a name="section7"></a>7. Additional Terms.</h4>
<p>&ldquo;Additional permissions&rdquo; are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.</p>
<p>When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.</p>
<p>Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:</p>
<ul>
<li>a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or</li>
<li>b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or</li>
<li>c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or</li>
<li>d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or</li>
<li>e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or</li>
<li>f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.</li>
</ul>
<p>All other non-permissive additional terms are considered &ldquo;further
restrictions&rdquo; within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.</p>
<p>If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.</p>
<p>Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.</p>
<h4><a name="section8"></a>8. Termination.</h4>
<p>You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).</p>
<p>However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.</p>
<p>Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.</p>
<p>Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.</p>
<h4><a name="section9"></a>9. Acceptance Not Required for Having Copies.</h4>
<p>You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.</p>
<h4><a name="section10"></a>10. Automatic Licensing of Downstream Recipients.</h4>
<p>Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.</p>
<p>An &ldquo;entity transaction&rdquo; is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.</p>
<p>You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.</p>
<h4><a name="section11"></a>11. Patents.</h4>
<p>A &ldquo;contributor&rdquo; is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's &ldquo;contributor version&rdquo;.</p>
<p>A contributor's &ldquo;essential patent claims&rdquo; are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, &ldquo;control&rdquo; includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.</p>
<p>Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.</p>
<p>In the following three paragraphs, a &ldquo;patent license&rdquo; is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To &ldquo;grant&rdquo; such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.</p>
<p>If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. &ldquo;Knowingly relying&rdquo; means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.</p>
<p>If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.</p>
<p>A patent license is &ldquo;discriminatory&rdquo; if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.</p>
<p>Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.</p>
<h4><a name="section12"></a>12. No Surrender of Others' Freedom.</h4>
<p>If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.</p>
<h4><a name="section13"></a>13. Use with the GNU Affero General Public License.</h4>
<p>Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.</p>
<h4><a name="section14"></a>14. Revised Versions of this License.</h4>
<p>The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.</p>
<p>Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License &ldquo;or any later version&rdquo; applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.</p>
<p>If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.</p>
<p>Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.</p>
<h4><a name="section15"></a>15. Disclaimer of Warranty.</h4>
<p>THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM &ldquo;AS IS&rdquo; WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</p>
<h4><a name="section16"></a>16. Limitation of Liability.</h4>
<p>IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.</p>
<h4><a name="section17"></a>17. Interpretation of Sections 15 and 16.</h4>
<p>If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.</p>
</body></html>
-26
View File
@@ -1,26 +0,0 @@
<html>
<head></head>
<body>
<p>Copyright (c) &lt;year&gt; &lt;copyright holders&gt;</p>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:</p>
<p>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.</p>
<p>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.<br />
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
</body>
</html>
-261
View File
@@ -1,261 +0,0 @@
<!DOCTYPE html>
<!-- saved from url=(0038)https://www.mozilla.org/en-US/MPL/2.0/ -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Mozilla Public License, version 2.0</title>
</head>
<body>
<h1 id="mozilla-public-license-version-2.0">Mozilla Public License<br>Version 2.0</h1>
<h2 id="definitions">1. Definitions</h2>
<dl>
<dt>1.1. “Contributor”</dt>
<dd><p>means each individual or legal entity that creates, contributes to the creation of, or
owns Covered Software.</p>
</dd>
<dt>1.2. “Contributor Version”</dt>
<dd><p>means the combination of the Contributions of others (if any) used by a Contributor and
that particular Contributors Contribution.</p>
</dd>
<dt>1.3. “Contribution”</dt>
<dd><p>means Covered Software of a particular Contributor.</p>
</dd>
<dt>1.4. “Covered Software”</dt>
<dd><p>means Source Code Form to which the initial Contributor has attached the notice in
Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source
Code Form, in each case including portions thereof.</p>
</dd>
<dt>1.5. “Incompatible With Secondary Licenses”</dt>
<dd><p>means</p>
<ol type="a">
<li><p>that the initial Contributor has attached the notice described in Exhibit B to
the Covered Software; or</p></li>
<li><p>that the Covered Software was made available under the terms of version 1.1 or
earlier of the License, but not also under the terms of a Secondary License.</p>
</li>
</ol>
</dd>
<dt>1.6. “Executable Form”</dt>
<dd><p>means any form of the work other than Source Code Form.</p>
</dd>
<dt>1.7. “Larger Work”</dt>
<dd><p>means a work that combines Covered Software with other material, in a separate file or
files, that is not Covered Software.</p>
</dd>
<dt>1.8. “License”</dt>
<dd><p>means this document.</p>
</dd>
<dt>1.9. “Licensable”</dt>
<dd><p>means having the right to grant, to the maximum extent possible, whether at the time of
the initial grant or subsequently, any and all of the rights conveyed by this License.</p>
</dd>
<dt>1.10. “Modifications”</dt>
<dd><p>means any of the following:</p>
<ol type="a">
<li><p>any file in Source Code Form that results from an addition to, deletion from, or
modification of the contents of Covered Software; or</p></li>
<li><p>any new file in Source Code Form that contains any Covered Software.</p></li>
</ol>
</dd>
<dt>1.11. “Patent Claims” of a Contributor</dt>
<dd><p>means any patent claim(s), including without limitation, method, process, and apparatus
claims, in any patent Licensable by such Contributor that would be infringed, but for the
grant of the License, by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.</p>
</dd>
<dt>1.12. “Secondary License”</dt>
<dd><p>means either the GNU General Public License, Version 2.0, the GNU Lesser General Public
License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later
versions of those licenses.</p>
</dd>
<dt>1.13. “Source Code Form”</dt>
<dd><p>means the form of the work preferred for making modifications.</p>
</dd>
<dt>1.14. “You” (or “Your”)</dt>
<dd><p>means an individual or a legal entity exercising rights under this License. For legal
entities, “You” includes any entity that controls, is controlled by, or is under common
control with You. For purposes of this definition, “control” means (a) the power, direct or
indirect, to cause the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or
beneficial ownership of such entity.</p>
</dd>
</dl>
<h2 id="license-grants-and-conditions">2. License Grants and Conditions</h2>
<h3 id="grants">2.1. Grants</h3>
<p>Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:</p>
<ol type="a">
<li><p>under intellectual property rights (other than patent or trademark) Licensable by such
Contributor to use, reproduce, make available, modify, display, perform, distribute, and
otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and</p></li>
<li><p>under Patent Claims of such Contributor to make, use, sell, offer for sale, have made,
import, and otherwise transfer either its Contributions or its Contributor Version.</p></li>
</ol>
<h3 id="effective-date">2.2. Effective Date</h3>
<p>The licenses granted in Section&nbsp;2.1 with respect to any Contribution become effective for
each Contribution on the date the Contributor first distributes such Contribution.</p>
<h3 id="limitations-on-grant-scope">2.3. Limitations on Grant Scope</h3>
<p>The licenses granted in this Section&nbsp;2 are the only rights granted under this License. No
additional rights or licenses will be implied from the distribution or licensing of Covered
Software under this License. Notwithstanding Section&nbsp;2.1(b) above, no patent license is
granted by a Contributor:</p>
<ol type="a">
<li><p>for any code that a Contributor has removed from Covered Software; or</p></li>
<li><p>for infringements caused by: (i) Your and any other third partys modifications of
Covered Software, or (ii) the combination of its Contributions with other software (except
as part of its Contributor Version); or</p></li>
<li><p>under Patent Claims infringed by Covered Software in the absence of its
Contributions.</p></li>
</ol>
<p>This License does not grant any rights in the trademarks, service marks, or logos of any
Contributor (except as may be necessary to comply with the notice requirements in Section&nbsp;3.4).</p>
<h3 id="subsequent-licenses">2.4. Subsequent Licenses</h3>
<p>No Contributor makes additional grants as a result of Your choice to distribute the Covered
Software under a subsequent version of this License (see Section&nbsp;10.2) or under the terms
of a Secondary License (if permitted under the terms of Section&nbsp;3.3).</p>
<h3 id="representation">2.5. Representation</h3>
<p>Each Contributor represents that the Contributor believes its Contributions are its original
creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by
this License.</p>
<h3 id="fair-use">2.6. Fair Use</h3>
<p>This License is not intended to limit any rights You have under applicable copyright doctrines of
fair use, fair dealing, or other equivalents.</p>
<h3 id="conditions">2.7. Conditions</h3>
<p>Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section&nbsp;2.1.</p>
<h2 id="responsibilities">3. Responsibilities</h2>
<h3 id="distribution-of-source-form">3.1. Distribution of Source Form</h3>
<p>All distribution of Covered Software in Source Code Form, including any Modifications that You
create or to which You contribute, must be under the terms of this License. You must inform
recipients that the Source Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.</p>
<h3 id="distribution-of-executable-form">3.2. Distribution of Executable Form</h3>
<p>If You distribute Covered Software in Executable Form then:</p>
<ol type="a">
<li><p>such Covered Software must also be made available in Source Code Form, as described in
Section&nbsp;3.1, and You must inform recipients of the Executable Form how they can obtain
a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and</p></li>
<li><p>You may distribute such Executable Form under the terms of this License, or sublicense it
under different terms, provided that the license for the Executable Form does not attempt to
limit or alter the recipients rights in the Source Code Form under this License.</p></li>
</ol>
<h3 id="distribution-of-a-larger-work">3.3. Distribution of a Larger Work</h3>
<p>You may create and distribute a Larger Work under terms of Your choice, provided that You also
comply with the requirements of this License for the Covered Software. If the Larger Work is a
combination of Covered Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this License permits You to
additionally distribute such Covered Software under the terms of such Secondary License(s), so
that the recipient of the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary License(s).</p>
<h3 id="notices">3.4. Notices</h3>
<p>You may not remove or alter the substance of any license notices (including copyright notices,
patent notices, disclaimers of warranty, or limitations of liability) contained within the
Source Code Form of the Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.</p>
<h3 id="application-of-additional-terms">3.5. Application of Additional Terms</h3>
<p>You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability
obligations to one or more recipients of Covered Software. However, You may do so only on Your
own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by You alone, and You
hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a
result of warranty, support, indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any jurisdiction.</p>
<h2 id="inability-to-comply-due-to-statute-or-regulation">4. Inability to Comply Due to Statute or
Regulation</h2>
<p>If it is impossible for You to comply with any of the terms of this License with respect to some
or all of the Covered Software due to statute, judicial order, or regulation then You must: (a)
comply with the terms of this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a text file included
with all distributions of the Covered Software under this License. Except to the extent
prohibited by statute or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.</p>
<h2 id="termination">5. Termination</h2>
<p>5.1. The rights granted under this License will terminate automatically if You fail to comply
with any of its terms. However, if You become compliant, then the rights granted under this
License from a particular Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such
Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days
after You have come back into compliance. Moreover, Your grants from a particular Contributor
are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of non-compliance with
this License from such Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.</p>
<p>5.2. If You initiate litigation against any entity by asserting a patent infringement claim
(excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a
Contributor Version directly or indirectly infringes any patent, then the rights granted to You
by any and all Contributors for the Covered Software under Section&nbsp;2.1 of this License
shall terminate.</p>
<p>5.3. In the event of termination under Sections&nbsp;5.1 or 5.2 above, all end user license
agreements (excluding distributors and resellers) which have been validly granted by You or Your
distributors under this License prior to termination shall survive termination.</p>
<h2 id="disclaimer-of-warranty">6. Disclaimer of Warranty</h2>
<p><em>Covered Software is provided under this License on an “as is” basis, without warranty of any
kind, either expressed, implied, or statutory, including, without limitation, warranties that
the Covered Software is free of defects, merchantable, fit for a particular purpose or
non-infringing. The entire risk as to the quality and performance of the Covered Software is
with You. Should any Covered Software prove defective in any respect, You (not any Contributor)
assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty
constitutes an essential part of this License. No use of any Covered Software is authorized
under this License except under this disclaimer.</em></p>
<h2 id="limitation-of-liability">7. Limitation of Liability</h2>
<p><em>Under no circumstances and under no legal theory, whether tort (including negligence),
contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as
permitted above, be liable to You for any direct, indirect, special, incidental, or
consequential damages of any character including, without limitation, damages for lost profits,
loss of goodwill, work stoppage, computer failure or malfunction, or any and all other
commercial damages or losses, even if such party shall have been informed of the possibility of
such damages. This limitation of liability shall not apply to liability for death or personal
injury resulting from such partys negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.</em></p>
<h2 id="litigation">8. Litigation</h2>
<p>Any litigation relating to this License may be brought only in the courts of a jurisdiction where
the defendant maintains its principal place of business and such litigation shall be governed by
laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this
Section shall prevent a partys ability to bring cross-claims or counter-claims.</p>
<h2 id="miscellaneous">9. Miscellaneous</h2>
<p>This License represents the complete agreement concerning the subject matter hereof. If any
provision of this License is held to be unenforceable, such provision shall be reformed only to
the extent necessary to make it enforceable. Any law or regulation which provides that the
language of a contract shall be construed against the drafter shall not be used to construe this
License against a Contributor.</p>
<h2 id="versions-of-the-license">10. Versions of the License</h2>
<h3 id="new-versions">10.1. New Versions</h3>
<p>Mozilla Foundation is the license steward. Except as provided in Section&nbsp;10.3, no one other
than the license steward has the right to modify or publish new versions of this License. Each
version will be given a distinguishing version number.</p>
<h3 id="effect-of-new-versions">10.2. Effect of New Versions</h3>
<p>You may distribute the Covered Software under the terms of the version of the License under which
You originally received the Covered Software, or under the terms of any subsequent version
published by the license steward.</p>
<h3 id="modified-versions">10.3. Modified Versions</h3>
<p>If you create software not governed by this License, and you want to create a new license for
such software, you may create and use a modified version of this License if you rename the
license and remove any references to the name of the license steward (except to note that such
modified license differs from this License).</p>
<h3 id="distributing-source-code-form-that-is-incompatible-with-secondary-licenses">10.4.
Distributing Source Code Form that is Incompatible With Secondary Licenses</h3>
<p>If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under
the terms of this version of the License, the notice described in Exhibit B of this License must
be attached.</p>
<h2 id="exhibit-a---source-code-form-license-notice">Exhibit A - Source Code Form License
Notice</h2>
<blockquote>
<p>This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a
copy of the MPL was not distributed with this file, You can obtain one at
https://mozilla.org/MPL/2.0/.</p>
</blockquote>
<p>If it is not possible or desirable to put the notice in a particular file, then You may include
the notice in a location (such as a LICENSE file in a relevant directory) where a recipient
would be likely to look for such a notice.</p>
<p>You may add additional accurate notices of copyright ownership.</p>
<h2 id="exhibit-b---incompatible-with-secondary-licenses-notice">Exhibit B - “Incompatible With
Secondary Licenses” Notice</h2>
<blockquote>
<p>This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla
Public License, v. 2.0.</p>
</blockquote>
</body>
</html>
+127
View File
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en"><head><title></title><script>
/**
* Factory method to create and load a BotGuardClient instance.
* @param options - Configuration options for the BotGuardClient.
* @returns A promise that resolves to a loaded BotGuardClient instance.
*/
function loadBotGuard(challengeData) {
this.vm = this[challengeData.globalName];
this.program = challengeData.program;
this.vmFunctions = {};
this.syncSnapshotFunction = null;
if (!this.vm)
throw new Error('[BotGuardClient]: VM not found in the global object');
if (!this.vm.a)
throw new Error('[BotGuardClient]: Could not load program');
const vmFunctionsCallback = function (
asyncSnapshotFunction,
shutdownFunction,
passEventFunction,
checkCameraFunction
) {
this.vmFunctions = {
asyncSnapshotFunction: asyncSnapshotFunction,
shutdownFunction: shutdownFunction,
passEventFunction: passEventFunction,
checkCameraFunction: checkCameraFunction
};
};
this.syncSnapshotFunction = this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, function () {/** no-op */ }, [ [], [] ])[0]
// an asynchronous function runs in the background and it will eventually call
// `vmFunctionsCallback`, however we need to manually tell JavaScript to pass
// control to the things running in the background by interrupting this async
// function in any way, e.g. with a delay of 1ms. The loop is most probably not
// needed but is there just because.
return new Promise(function (resolve, reject) {
i = 0
refreshIntervalId = setInterval(function () {
if (!!this.vmFunctions.asyncSnapshotFunction) {
resolve(this)
clearInterval(refreshIntervalId);
}
if (i >= 10000) {
reject("asyncSnapshotFunction is null even after 10 seconds")
clearInterval(refreshIntervalId);
}
i += 1;
}, 1);
})
}
/**
* Takes a snapshot asynchronously.
* @returns The snapshot result.
* @example
* ```ts
* const result = await botguard.snapshot({
* contentBinding: {
* c: "a=6&a2=10&b=SZWDwKVIuixOp7Y4euGTgwckbJA&c=1729143849&d=1&t=7200&c1a=1&c6a=1&c6b=1&hh=HrMb5mRWTyxGJphDr0nW2Oxonh0_wl2BDqWuLHyeKLo",
* e: "ENGAGEMENT_TYPE_VIDEO_LIKE",
* encryptedVideoId: "P-vC09ZJcnM"
* }
* });
*
* console.log(result);
* ```
*/
function snapshot(args) {
return new Promise(function (resolve, reject) {
if (!this.vmFunctions.asyncSnapshotFunction)
return reject(new Error('[BotGuardClient]: Async snapshot function not found'));
this.vmFunctions.asyncSnapshotFunction(function (response) { resolve(response) }, [
args.contentBinding,
args.signedTimestamp,
args.webPoSignalOutput,
args.skipPrivacyBuffer
]);
});
}
function runBotGuard(challengeData) {
const interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;
if (interpreterJavascript) {
new Function(interpreterJavascript)();
} else throw new Error('Could not load VM');
const webPoSignalOutput = [];
return loadBotGuard({
globalName: challengeData.globalName,
globalObj: this,
program: challengeData.program
}).then(function (botguard) {
return botguard.snapshot({ webPoSignalOutput: webPoSignalOutput })
}).then(function (botguardResponse) {
return { webPoSignalOutput: webPoSignalOutput, botguardResponse: botguardResponse }
})
}
function obtainPoToken(webPoSignalOutput, integrityToken, identifier) {
const getMinter = webPoSignalOutput[0];
if (!getMinter)
throw new Error('PMD:Undefined');
const mintCallback = getMinter(integrityToken);
if (!(mintCallback instanceof Function))
throw new Error('APF:Failed');
const result = mintCallback(identifier);
if (!result)
throw new Error('YNJ:Undefined');
if (!(result instanceof Uint8Array))
throw new Error('ODM:Invalid');
return result;
}
</script></head><body></body></html>
@@ -1,269 +0,0 @@
package org.schabi.newpipe;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.preference.PreferenceManager;
import com.jakewharton.processphoenix.ProcessPhoenix;
import org.acra.ACRA;
import org.acra.config.CoreConfigurationBuilder;
import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
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.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;
import java.net.SocketException;
import java.util.List;
import java.util.Objects;
import io.reactivex.rxjava3.exceptions.CompositeException;
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
/*
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
* App.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 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
public static App getApp() {
return app;
}
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
initACRA();
}
@Override
public void onCreate() {
super.onCreate();
app = this;
if (ProcessPhoenix.isPhoenixProcess(this)) {
Log.i(TAG, "This is a phoenix process! "
+ "Aborting initialization of App[onCreate]");
return;
}
// 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(),
Localization.getPreferredLocalization(this),
Localization.getPreferredContentCountry(this));
Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
StateSaver.init(this);
initNotificationChannels();
ServiceHelper.initServices(this);
// Initialize image loader
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
PicassoHelper.init(this);
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));
configureRxJavaErrorHandler();
}
@Override
public void onTerminate() {
super.onTerminate();
PicassoHelper.terminate();
}
protected Downloader getDownloader() {
final DownloaderImpl downloader = DownloaderImpl.init(null);
setCookiesToDownloader(downloader);
return downloader;
}
protected void setCookiesToDownloader(final DownloaderImpl downloader) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
getApplicationContext());
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, null));
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
}
private void configureRxJavaErrorHandler() {
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
@Override
public void accept(@NonNull final Throwable throwable) {
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : "
+ "throwable = [" + throwable.getClass().getName() + "]");
final Throwable actualThrowable;
if (throwable instanceof UndeliverableException) {
// As UndeliverableException is a wrapper,
// get the cause of it to get the "real" exception
actualThrowable = Objects.requireNonNull(throwable.getCause());
} else {
actualThrowable = throwable;
}
final List<Throwable> errors;
if (actualThrowable instanceof CompositeException) {
errors = ((CompositeException) actualThrowable).getExceptions();
} else {
errors = List.of(actualThrowable);
}
for (final Throwable error : errors) {
if (isThrowableIgnored(error)) {
return;
}
if (isThrowableCritical(error)) {
reportException(error);
return;
}
}
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
// When exception is not reported, log it
if (isDisposedRxExceptionsReported()) {
reportException(actualThrowable);
} else {
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable);
}
}
private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
// Don't crash the application over a simple network problem
return ExceptionUtils.hasAssignableCause(throwable,
// network api cancellation
IOException.class, SocketException.class,
// blocking code disposed
InterruptedException.class, InterruptedIOException.class);
}
private boolean isThrowableCritical(@NonNull final Throwable throwable) {
// Though these exceptions cannot be ignored
return ExceptionUtils.hasAssignableCause(throwable,
NullPointerException.class, IllegalArgumentException.class, // bug in app
OnErrorNotImplementedException.class, MissingBackpressureException.class,
IllegalStateException.class); // bug in operator
}
private void reportException(@NonNull final Throwable throwable) {
// Throw uncaught exception that will trigger the report system
Thread.currentThread().getUncaughtExceptionHandler()
.uncaughtException(Thread.currentThread(), throwable);
}
});
}
/**
* Called in {@link #attachBaseContext(Context)} after calling the {@code super} method.
* Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
*/
protected void initACRA() {
if (ACRA.isACRASenderServiceProcess()) {
return;
}
final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder()
.withBuildConfigClass(BuildConfig.class);
ACRA.init(this, acraConfig);
}
private void initNotificationChannels() {
// Keep the importance below DEFAULT to avoid making noise on every notification update for
// the main and update channels
final List<NotificationChannelCompat> notificationChannelCompats = List.of(
new NotificationChannelCompat.Builder(getString(R.string.notification_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.notification_channel_name))
.setDescription(getString(R.string.notification_channel_description))
.build(),
new NotificationChannelCompat
.Builder(getString(R.string.app_update_notification_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.app_update_notification_channel_name))
.setDescription(
getString(R.string.app_update_notification_channel_description))
.build(),
new NotificationChannelCompat.Builder(getString(R.string.hash_channel_id),
NotificationManagerCompat.IMPORTANCE_HIGH)
.setName(getString(R.string.hash_channel_name))
.setDescription(getString(R.string.hash_channel_description))
.build(),
new NotificationChannelCompat.Builder(getString(R.string.error_report_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW)
.setName(getString(R.string.error_report_channel_name))
.setDescription(getString(R.string.error_report_channel_description))
.build(),
new NotificationChannelCompat
.Builder(getString(R.string.streams_notification_channel_id),
NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setName(getString(R.string.streams_notification_channel_name))
.setDescription(
getString(R.string.streams_notification_channel_description))
.build()
);
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.createNotificationChannelsCompat(notificationChannelCompats);
}
protected boolean isDisposedRxExceptionsReported() {
return false;
}
public boolean isFirstRun() {
return isFirstRun;
}
}
+295
View File
@@ -0,0 +1,295 @@
package org.schabi.newpipe
import android.app.ActivityManager
import android.app.Application
import android.content.Context
import android.util.Log
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.allowRgb565
import coil3.request.crossfade
import coil3.util.DebugLogger
import com.jakewharton.processphoenix.ProcessPhoenix
import dagger.hilt.android.HiltAndroidApp
import io.reactivex.rxjava3.exceptions.CompositeException
import io.reactivex.rxjava3.exceptions.MissingBackpressureException
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException
import io.reactivex.rxjava3.exceptions.UndeliverableException
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.plugins.RxJavaPlugins
import java.io.IOException
import java.io.InterruptedIOException
import java.net.SocketException
import org.acra.ACRA.init
import org.acra.ACRA.isACRASenderServiceProcess
import org.acra.config.CoreConfigurationBuilder
import org.schabi.newpipe.error.ReCaptchaActivity
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.downloader.Downloader
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor
import org.schabi.newpipe.ktx.hasAssignableCause
import org.schabi.newpipe.settings.NewPipeSettings
import org.schabi.newpipe.util.BridgeStateSaverInitializer
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.StateSaver
import org.schabi.newpipe.util.image.ImageStrategy
import org.schabi.newpipe.util.image.PreferredImageQuality
import org.schabi.newpipe.util.potoken.PoTokenProviderImpl
/*
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
* App.kt 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/>.
*/
@HiltAndroidApp
open class App :
Application(),
SingletonImageLoader.Factory {
var isFirstRun = false
private set
var notificationsRequested = false
private set
fun setNotificationsRequested() {
notificationsRequested = true
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
initACRA()
}
override fun onCreate() {
super.onCreate()
instance = this
if (ProcessPhoenix.isPhoenixProcess(this)) {
Log.i(TAG, "This is a phoenix process! Aborting initialization of App[onCreate]")
return
}
// check if the last used preference version is set
// to determine whether this is the first app run
val 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(),
Localization.getPreferredLocalization(this),
Localization.getPreferredContentCountry(this)
)
Localization.initPrettyTime(Localization.resolvePrettyTime())
BridgeStateSaverInitializer.init(this)
StateSaver.init(this)
initNotificationChannels()
ServiceHelper.initServices(this)
// Initialize image loader
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
ImageStrategy.setPreferredImageQuality(
PreferredImageQuality.fromPreferenceKey(
this,
prefs.getString(
getString(R.string.image_quality_key),
getString(R.string.image_quality_default)
)
)
)
configureRxJavaErrorHandler()
YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl)
}
override fun newImageLoader(context: Context): ImageLoader = ImageLoader
.Builder(this)
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
.allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
.crossfade(true)
.components {
add(OkHttpNetworkFetcherFactory(callFactory = DownloaderImpl.getInstance().client))
}.build()
protected open fun getDownloader(): Downloader {
val downloader = DownloaderImpl.init(null)
setCookiesToDownloader(downloader)
return downloader
}
protected fun setCookiesToDownloader(downloader: DownloaderImpl) {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val key = getString(R.string.recaptcha_cookies_key)
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, null))
downloader.updateYoutubeRestrictedModeCookies(this)
}
private fun configureRxJavaErrorHandler() {
// https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling
RxJavaPlugins.setErrorHandler(
object : Consumer<Throwable> {
override fun accept(throwable: Throwable) {
Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : throwable = [${throwable.javaClass.getName()}]")
// As UndeliverableException is a wrapper,
// get the cause of it to get the "real" exception
val actualThrowable = (throwable as? UndeliverableException)?.cause ?: throwable
val errors = (actualThrowable as? CompositeException)?.exceptions ?: listOf(actualThrowable)
for (error in errors) {
if (isThrowableIgnored(error)) {
return
}
if (isThrowableCritical(error)) {
reportException(error)
return
}
}
// Out-of-lifecycle exceptions should only be reported if a debug user wishes so,
// When exception is not reported, log it
if (isDisposedRxExceptionsReported()) {
reportException(actualThrowable)
} else {
Log.e(TAG, "RxJavaPlugin: Undeliverable Exception received: ", actualThrowable)
}
}
fun isThrowableIgnored(throwable: Throwable): Boolean {
// Don't crash the application over a simple network problem
return throwable // network api cancellation
.hasAssignableCause(
IOException::class.java,
SocketException::class.java, // blocking code disposed
InterruptedException::class.java,
InterruptedIOException::class.java
)
}
fun isThrowableCritical(throwable: Throwable): Boolean {
// Though these exceptions cannot be ignored
return throwable
.hasAssignableCause(
// bug in app
NullPointerException::class.java,
IllegalArgumentException::class.java,
OnErrorNotImplementedException::class.java,
MissingBackpressureException::class.java,
// bug in operator
IllegalStateException::class.java
)
}
fun reportException(throwable: Throwable) {
// Throw uncaught exception that will trigger the report system
Thread
.currentThread()
.uncaughtExceptionHandler
.uncaughtException(Thread.currentThread(), throwable)
}
}
)
}
/**
* Called in [.attachBaseContext] after calling the `super` method.
* Should be overridden if MultiDex is enabled, since it has to be initialized before ACRA.
*/
protected fun initACRA() {
if (isACRASenderServiceProcess()) {
return
}
val acraConfig =
CoreConfigurationBuilder()
.withBuildConfigClass(BuildConfig::class.java)
init(this, acraConfig)
}
private fun initNotificationChannels() {
// Keep the importance below DEFAULT to avoid making noise on every notification update for
// the main and update channels
val mainChannel =
NotificationChannelCompat
.Builder(
getString(R.string.notification_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW
).setName(getString(R.string.notification_channel_name))
.setDescription(getString(R.string.notification_channel_description))
.build()
val appUpdateChannel =
NotificationChannelCompat
.Builder(
getString(R.string.app_update_notification_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW
).setName(getString(R.string.app_update_notification_channel_name))
.setDescription(getString(R.string.app_update_notification_channel_description))
.build()
val hashChannel =
NotificationChannelCompat
.Builder(
getString(R.string.hash_channel_id),
NotificationManagerCompat.IMPORTANCE_HIGH
).setName(getString(R.string.hash_channel_name))
.setDescription(getString(R.string.hash_channel_description))
.build()
val errorReportChannel =
NotificationChannelCompat
.Builder(
getString(R.string.error_report_channel_id),
NotificationManagerCompat.IMPORTANCE_LOW
).setName(getString(R.string.error_report_channel_name))
.setDescription(getString(R.string.error_report_channel_description))
.build()
val newStreamChannel =
NotificationChannelCompat
.Builder(
getString(R.string.streams_notification_channel_id),
NotificationManagerCompat.IMPORTANCE_DEFAULT
).setName(getString(R.string.streams_notification_channel_name))
.setDescription(getString(R.string.streams_notification_channel_description))
.build()
val channels = listOf(mainChannel, appUpdateChannel, hashChannel, errorReportChannel, newStreamChannel)
NotificationManagerCompat.from(this).createNotificationChannelsCompat(channels)
}
protected open fun isDisposedRxExceptionsReported(): Boolean = false
companion object {
const val PACKAGE_NAME: String = BuildConfig.APPLICATION_ID
private val TAG = App::class.java.toString()
@JvmStatic
lateinit var instance: App
private set
}
}
@@ -0,0 +1,22 @@
package org.schabi.newpipe
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
@Singleton
fun providesSharedPreference(@ApplicationContext context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
}
@@ -10,8 +10,9 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import icepick.Icepick;
import icepick.State;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
public abstract class BaseFragment extends Fragment {
protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
@@ -48,7 +49,7 @@ public abstract class BaseFragment extends Fragment {
+ "savedInstanceState = [" + savedInstanceState + "]");
}
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
Bridge.restoreInstanceState(this, savedInstanceState);
if (savedInstanceState != null) {
onRestoreInstanceState(savedInstanceState);
}
@@ -70,7 +71,7 @@ public abstract class BaseFragment extends Fragment {
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.navigation3.runtime.NavKey
import dagger.hilt.android.AndroidEntryPoint
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.navigation.NavDisplay
import org.schabi.newpipe.navigation.Screen
import org.schabi.newpipe.ui.theme.AppTheme
/**
* Single host activity for all Compose-based screens.
* Other parts of the app (including legacy View-based code) launch this activity
* via Intent with extras specifying which screen to display.
*/
@AndroidEntryPoint
class ComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
super.onCreate(savedInstanceState)
val startDestination: NavKey = resolveStartDestination(intent)
setContent {
AppTheme {
NavDisplay(startDestination)
}
}
}
private fun resolveStartDestination(intent: Intent): NavKey {
return when (intent.getStringExtra(EXTRA_SCREEN)) {
SCREEN_ABOUT -> Screen.About
SCREEN_ERROR -> Screen.Error
SCREEN_SETTINGS -> Screen.Settings.Home
else -> throw IllegalArgumentException(
"Unknown screen: ${intent.getStringExtra(EXTRA_SCREEN)}"
)
}
}
companion object {
const val SCREEN_ABOUT = "about"
const val EXTRA_SCREEN = "extra_screen"
const val EXTRA_ERROR_INFO = "extra_error_info"
const val SCREEN_ERROR = "error"
const val SCREEN_SETTINGS = "settings"
fun aboutIntent(context: Context): Intent {
return Intent(context, ComposeActivity::class.java).apply {
putExtra(EXTRA_SCREEN, SCREEN_ABOUT)
}
}
fun errorIntent(context: Context, errorInfo: ErrorInfo): Intent {
return Intent(context, ComposeActivity::class.java).apply {
putExtra(EXTRA_SCREEN, SCREEN_ERROR)
putExtra(EXTRA_ERROR_INFO, errorInfo)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
}
fun settingsIntent(context: Context): Intent {
return Intent(context, ComposeActivity::class.java).apply {
putExtra(EXTRA_SCREEN, SCREEN_SETTINGS)
}
}
}
}
@@ -29,7 +29,7 @@ import okhttp3.ResponseBody;
public final class DownloaderImpl extends Downloader {
public static final String USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0";
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY =
"youtube_restricted_mode_key";
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
@@ -48,6 +48,11 @@ public final class DownloaderImpl extends Downloader {
this.mCookies = new HashMap<>();
}
@NonNull
public OkHttpClient getClient() {
return client;
}
/**
* It's recommended to call exactly once in the entire lifetime of the application.
*
@@ -137,7 +142,8 @@ public final class DownloaderImpl extends Downloader {
}
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method(httpMethod, requestBody).url(url)
.method(httpMethod, requestBody)
.url(url)
.addHeader("User-Agent", USER_AGENT);
final String cookies = getCookies(url);
@@ -145,38 +151,31 @@ public final class DownloaderImpl extends Downloader {
requestBuilder.addHeader("Cookie", cookies);
}
for (final Map.Entry<String, List<String>> pair : headers.entrySet()) {
final String headerName = pair.getKey();
final List<String> headerValueList = pair.getValue();
headers.forEach((headerName, headerValueList) -> {
requestBuilder.removeHeader(headerName);
headerValueList.forEach(headerValue ->
requestBuilder.addHeader(headerName, headerValue));
});
if (headerValueList.size() > 1) {
requestBuilder.removeHeader(headerName);
for (final String headerValue : headerValueList) {
requestBuilder.addHeader(headerName, headerValue);
}
} else if (headerValueList.size() == 1) {
requestBuilder.header(headerName, headerValueList.get(0));
try (
okhttp3.Response response = client.newCall(requestBuilder.build()).execute()
) {
if (response.code() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested", url);
}
String responseBodyToReturn = null;
try (ResponseBody body = response.body()) {
responseBodyToReturn = body.string();
}
final String latestUrl = response.request().url().toString();
return new Response(
response.code(),
response.message(),
response.headers().toMultimap(),
responseBodyToReturn,
latestUrl);
}
final okhttp3.Response response = client.newCall(requestBuilder.build()).execute();
if (response.code() == 429) {
response.close();
throw new ReCaptchaException("reCaptcha Challenge requested", url);
}
final ResponseBody body = response.body();
String responseBodyToReturn = null;
if (body != null) {
responseBodyToReturn = body.string();
}
final String latestUrl = response.request().url().toString();
return new Response(response.code(), response.message(), response.headers().toMultimap(),
responseBodyToReturn, latestUrl);
}
}
@@ -1,50 +0,0 @@
package org.schabi.newpipe;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import org.schabi.newpipe.util.NavigationHelper;
/*
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
* ExitActivity.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 ExitActivity extends Activity {
public static void exitAndRemoveFromRecentApps(final Activity activity) {
final Intent intent = new Intent(activity, ExitActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
activity.startActivity(intent);
}
@SuppressLint("NewApi")
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finishAndRemoveTask();
NavigationHelper.restartApp(this);
}
}
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2016-2026 NewPipe contributors <https://newpipe.net>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import org.schabi.newpipe.util.NavigationHelper
class ExitActivity : Activity() {
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
finishAndRemoveTask()
NavigationHelper.restartApp(this)
}
companion object {
@JvmStatic
fun exitAndRemoveFromRecentApps(activity: Activity) {
val intent = Intent(activity, ExitActivity::class.java)
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
or Intent.FLAG_ACTIVITY_CLEAR_TASK
or Intent.FLAG_ACTIVITY_NO_ANIMATION
)
activity.startActivity(intent)
}
}
}
@@ -20,14 +20,14 @@
package org.schabi.newpipe;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -38,21 +38,21 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
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;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
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;
@@ -66,13 +66,11 @@ 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;
@@ -80,6 +78,7 @@ 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.settings.migration.MigrationManager;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.KioskTranslator;
@@ -92,8 +91,11 @@ import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.FocusOverlayView;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -120,10 +122,14 @@ public class MainActivity extends AppCompatActivity {
private static final int ITEM_ID_DOWNLOADS = -4;
private static final int ITEM_ID_HISTORY = -5;
private static final int ITEM_ID_SETTINGS = 0;
private static final int ITEM_ID_ABOUT = 1;
private static final int ITEM_ID_DONATION = 1;
private static final int ITEM_ID_ABOUT = 2;
private static final int ORDER = 0;
public static final String KEY_IS_IN_BACKGROUND = "is_in_background";
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor sharedPrefEditor;
/*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@@ -135,11 +141,26 @@ public class MainActivity extends AppCompatActivity {
+ "savedInstanceState = [" + savedInstanceState + "]");
}
Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
ThemeHelper.setDayNightMode(this);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
assureCorrectAppLanguage(this);
// Fixes text color turning black in dark/black mode:
// https://github.com/TeamNewPipe/NewPipe/issues/12016
// For further reference see: https://issuetracker.google.com/issues/37124582
if (DeviceUtils.supportsWebView()) {
try {
new WebView(this);
} catch (final Throwable e) {
if (DEBUG) {
Log.e(TAG, "Failed to create WebView", e);
}
}
}
super.onCreate(savedInstanceState);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
sharedPrefEditor = sharedPreferences.edit();
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
drawerLayoutBinding = mainBinding.drawerLayout;
@@ -170,27 +191,49 @@ public class MainActivity extends AppCompatActivity {
NotificationWorker.initialize(this);
}
if (!UpdateSettingsFragment.wasUserAskedForConsent(this)
&& !App.getApp().isFirstRun()
&& !App.getInstance().isFirstRun()
&& ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
UpdateSettingsFragment.askForConsentToUpdateChecks(this);
}
// ReleaseVersionUtil.INSTANCE.isReleaseApk() will be true only for main official build
// We want every release build (nightly, nightly-refactor) to show the popup
if (!DEBUG) {
showKeepAndroidDialog();
showApi23RequirementDialog();
}
MigrationManager.showUserInfoIfPresent(this);
}
@Override
protected void onPostCreate(final Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
final App app = App.getApp();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
final App app = App.getInstance();
if (prefs.getBoolean(app.getString(R.string.update_app_key), false)
&& prefs.getBoolean(app.getString(R.string.update_check_consent_key), false)) {
if (sharedPreferences.getBoolean(app.getString(R.string.update_app_key), false)
&& sharedPreferences
.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, false);
}
}
@Override
protected void onStart() {
super.onStart();
sharedPrefEditor.putBoolean(KEY_IS_IN_BACKGROUND, false).apply();
Log.d(TAG, "App moved to foreground");
}
@Override
protected void onStop() {
super.onStop();
sharedPrefEditor.putBoolean(KEY_IS_IN_BACKGROUND, true).apply();
Log.d(TAG, "App moved to background");
}
private void setupDrawer() throws ExtractionException {
addDrawerMenuForCurrentService();
@@ -228,19 +271,6 @@ public class MainActivity extends AppCompatActivity {
*/
private void addDrawerMenuForCurrentService() throws ExtractionException {
//Tabs
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
int kioskMenuItemId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, kioskMenuItemId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks));
kioskMenuItemId++;
}
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
R.string.tab_subscriptions)
@@ -258,32 +288,49 @@ public class MainActivity extends AppCompatActivity {
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
.setIcon(R.drawable.ic_history);
//Kiosks
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
final StreamingService service = NewPipe.getService(currentServiceId);
int kioskMenuItemId = 0;
for (final String ks : service.getKioskList().getAvailableKiosks()) {
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_kiosks_group, kioskMenuItemId, 0, KioskTranslator
.getTranslatedKioskName(ks, this))
.setIcon(KioskTranslator.getKioskIcon(ks));
kioskMenuItemId++;
}
//Settings and About
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(R.drawable.ic_settings);
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_DONATION, ORDER,
R.string.donation_title)
.setIcon(R.drawable.volunteer_activism_ic);
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(R.drawable.ic_info_outline);
}
private boolean drawerItemSelected(final MenuItem item) {
switch (item.getGroupId()) {
case R.id.menu_services_group:
changeService(item);
break;
case R.id.menu_tabs_group:
try {
tabSelected(item);
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Selecting main page tab", e);
}
break;
case R.id.menu_options_about_group:
optionsAboutSelected(item);
break;
default:
return false;
final int groupId = item.getGroupId();
if (groupId == R.id.menu_services_group) {
changeService(item);
} else if (groupId == R.id.menu_tabs_group) {
tabSelected(item);
} else if (groupId == R.id.menu_kiosks_group) {
try {
kioskSelected(item);
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Selecting drawer kiosk", e);
}
} else if (groupId == R.id.menu_options_about_group) {
optionsAboutSelected(item);
} else {
return false;
}
mainBinding.getRoot().closeDrawers();
@@ -300,7 +347,7 @@ public class MainActivity extends AppCompatActivity {
.setChecked(true);
}
private void tabSelected(final MenuItem item) throws ExtractionException {
private void tabSelected(final MenuItem item) {
switch (item.getItemId()) {
case ITEM_ID_SUBSCRIPTIONS:
NavigationHelper.openSubscriptionFragment(getSupportFragmentManager());
@@ -317,18 +364,19 @@ public class MainActivity extends AppCompatActivity {
case ITEM_ID_HISTORY:
NavigationHelper.openStatisticFragment(getSupportFragmentManager());
break;
default:
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;
}
kioskMenuItemId++;
}
}
}
private void kioskSelected(final MenuItem item) throws ExtractionException {
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;
}
kioskMenuItemId++;
}
}
@@ -337,6 +385,9 @@ public class MainActivity extends AppCompatActivity {
case ITEM_ID_SETTINGS:
NavigationHelper.openSettings(this);
break;
case ITEM_ID_DONATION:
ShareUtils.openUrlInBrowser(this, getString(R.string.donation_url));
break;
case ITEM_ID_ABOUT:
NavigationHelper.openAbout(this);
break;
@@ -366,6 +417,7 @@ public class MainActivity extends AppCompatActivity {
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_services_group);
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_kiosks_group);
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
// Show up or down arrow
@@ -459,9 +511,8 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onResume() {
assureCorrectAppLanguage(this);
// Change the date format to match the selected language on resume
Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext()));
Localization.initPrettyTime(Localization.resolvePrettyTime());
super.onResume();
// Close drawer on return, and don't show animation,
@@ -483,13 +534,11 @@ public class MainActivity extends AppCompatActivity {
ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e);
}
final SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) {
if (DEBUG) {
Log.d(TAG, "Theme has changed, recreating activity...");
}
sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
sharedPrefEditor.putBoolean(Constants.KEY_THEME_CHANGE, false).apply();
ActivityCompat.recreate(this);
}
@@ -497,7 +546,7 @@ public class MainActivity extends AppCompatActivity {
if (DEBUG) {
Log.d(TAG, "main page has changed, recreating main fragment...");
}
sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
sharedPrefEditor.putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply();
NavigationHelper.openMainActivity(this);
}
@@ -557,39 +606,27 @@ public class MainActivity extends AppCompatActivity {
// In case bottomSheet is not visible on the screen or collapsed we can assume that the user
// interacts with a fragment inside fragment_holder so all back presses should be
// handled by it
if (bottomSheetHiddenOrCollapsed()) {
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);
}
final var fragmentManager = getSupportFragmentManager();
} else {
final Fragment fragmentPlayer = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
if (bottomSheetHiddenOrCollapsed()) {
final var fragment = fragmentManager.findFragmentById(R.id.fragment_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (fragmentPlayer instanceof BackPressable) {
if (!((BackPressable) fragmentPlayer).onBackPressed()) {
BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
if (fragment instanceof BackPressable backPressable && backPressable.onBackPressed()) {
return;
}
} else {
final var player = fragmentManager.findFragmentById(R.id.fragment_player_holder);
// If current fragment implements BackPressable (i.e. can/wanna handle back press)
// delegate the back press to it
if (player instanceof BackPressable backPressable && !backPressable.onBackPressed()) {
BottomSheetBehavior.from(mainBinding.fragmentPlayerHolder)
.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
return;
}
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
if (fragmentManager.getBackStackEntryCount() == 1) {
finish();
} else {
super.onBackPressed();
@@ -648,15 +685,9 @@ public class MainActivity extends AppCompatActivity {
* </pre>
*/
private void onHomeButtonPressed() {
final FragmentManager fm = getSupportFragmentManager();
final Fragment fragment = fm.findFragmentById(R.id.fragment_holder);
final var fm = getSupportFragmentManager();
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 (!NavigationHelper.tryGotoSearchFragment(fm)) {
// If search fragment wasn't found in the backstack go to the main fragment
NavigationHelper.gotoMainFragment(fm);
}
@@ -830,7 +861,7 @@ public class MainActivity extends AppCompatActivity {
return;
}
if (PlayerHolder.getInstance().isPlayerOpen()) {
if (PlayerHolder.INSTANCE.isPlayerOpen()) {
// if the player is already open, no need for a broadcast receiver
openMiniPlayerIfMissing();
} else {
@@ -839,7 +870,8 @@ public class MainActivity extends AppCompatActivity {
@Override
public void onReceive(final Context context, final Intent intent) {
if (Objects.equals(intent.getAction(),
VideoDetailFragment.ACTION_PLAYER_STARTED)) {
VideoDetailFragment.ACTION_PLAYER_STARTED)
&& PlayerHolder.INSTANCE.isPlayerOpen()) {
openMiniPlayerIfMissing();
// At this point the player is added 100%, we can unregister. Other actions
// are useless since the fragment will not be removed after that.
@@ -850,72 +882,15 @@ public class MainActivity extends AppCompatActivity {
};
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
registerReceiver(broadcastReceiver, intentFilter);
ContextCompat.registerReceiver(this, broadcastReceiver, intentFilter,
ContextCompat.RECEIVER_EXPORTED);
// If the PlayerHolder is not bound yet, but the service is running, try to bind to it.
// Once the connection is established, the ACTION_PLAYER_STARTED will be sent.
PlayerHolder.INSTANCE.tryBindIfNeeded(this);
}
}
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);
@@ -924,4 +899,79 @@ public class MainActivity extends AppCompatActivity {
return sheetState == BottomSheetBehavior.STATE_HIDDEN
|| sheetState == BottomSheetBehavior.STATE_COLLAPSED;
}
private void showKeepAndroidDialog() {
final var prefs = PreferenceManager.getDefaultSharedPreferences(this);
final var lastCheckKey = getString(R.string.kao_last_checked_key);
final var lastCheck = Instant.ofEpochMilli(prefs.getLong(lastCheckKey, 0));
final var now = Instant.now();
if (lastCheck.plus(30, ChronoUnit.DAYS).isBefore(now)) {
final String detailsUrl = getKeepAndroidOpenDetailsUrl();
final var solutionUrl = "https://github.com/woheller69/FreeDroidWarn#solutions";
final var dialog = new AlertDialog.Builder(this)
.setTitle("Keep Android Open")
.setCancelable(false)
.setMessage(R.string.kao_dialog_warning)
.setPositiveButton(android.R.string.ok, (d, w) -> prefs.edit()
.putLong(lastCheckKey, now.toEpochMilli())
.apply())
.setNeutralButton(R.string.kao_solution, null)
.setNegativeButton(R.string.kao_dialog_more_info, null)
.show();
// If we use setNeutralButton/setNegativeButton, dialog will close after pressing the
// buttons, but we want it to close only when positive button is pressed
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
.setOnClickListener(v -> ShareUtils.openUrlInBrowser(this, detailsUrl));
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
.setOnClickListener(v -> ShareUtils.openUrlInBrowser(this, solutionUrl));
}
}
@NonNull
private static String getKeepAndroidOpenDetailsUrl() {
final var supportedLanguages = List.of("fr", "de", "ca", "es", "id", "it", "pl",
"pt", "cs", "sk", "fa", "ar", "tr", "el", "th", "ru", "uk", "ko", "zh", "ja");
final String kaoBaseUrl = "https://keepandroidopen.org/";
final var locale = Localization.getAppLocale();
if (supportedLanguages.contains(locale.getLanguage())) {
if ("zh".equals(locale.getLanguage())) {
return kaoBaseUrl + ("TW".equals(locale.getCountry()) ? "zh-TW" : "zh-CN");
} else {
return kaoBaseUrl + locale.getLanguage();
}
} else {
return kaoBaseUrl;
}
}
private void showApi23RequirementDialog() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return; // only show dialog on the devices that will stop being supported
}
final var prefs = PreferenceManager.getDefaultSharedPreferences(this);
final var shownKey = getString(R.string.api23_requirement_dialog_shown_key);
if (prefs.getBoolean(shownKey, false)) {
return; // dialog was already shown in the past, no need to show it again
}
final var dialog = new AlertDialog.Builder(this)
.setTitle(R.string.api23_requirement_dialog_title)
.setCancelable(false)
.setMessage(R.string.api23_requirement_dialog_message)
.setPositiveButton(android.R.string.ok, (d, w) -> prefs.edit()
.putBoolean(shownKey, true)
.apply())
.setNegativeButton(R.string.api23_requirement_dialog_blogpost, null)
.show();
// If we use setNegativeButton, dialog will close after pressing the button,
// but we want it to close only when positive button is pressed
final var blogpostUrl = "https://newpipe.net/blog/pinned/announcement/drop-android-5/";
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
.setOnClickListener(v -> ShareUtils.openUrlInBrowser(this, blogpostUrl));
}
}
@@ -1,72 +0,0 @@
package org.schabi.newpipe;
import static org.schabi.newpipe.database.AppDatabase.DATABASE_NAME;
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;
import androidx.annotation.NonNull;
import androidx.room.Room;
import org.schabi.newpipe.database.AppDatabase;
public final class NewPipeDatabase {
private static volatile AppDatabase databaseInstance;
private NewPipeDatabase() {
//no instance
}
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,
MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
.build();
}
@NonNull
public static AppDatabase getInstance(@NonNull final Context context) {
AppDatabase result = databaseInstance;
if (result == null) {
synchronized (NewPipeDatabase.class) {
result = databaseInstance;
if (result == null) {
databaseInstance = getDatabase(context);
result = databaseInstance;
}
}
}
return result;
}
public static void checkpoint() {
if (databaseInstance == null) {
throw new IllegalStateException("database is not initialized");
}
final Cursor c = databaseInstance.query("pragma wal_checkpoint(full)", null);
if (c.moveToFirst() && c.getInt(0) == 1) {
throw new RuntimeException("Checkpoint was blocked from completing");
}
}
public static void close() {
if (databaseInstance != null) {
synchronized (NewPipeDatabase.class) {
if (databaseInstance != null) {
databaseInstance.close();
databaseInstance = null;
}
}
}
}
}
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2017-2024 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe
import android.content.Context
import androidx.room.Room.databaseBuilder
import kotlin.concurrent.Volatile
import org.schabi.newpipe.database.AppDatabase
import org.schabi.newpipe.database.Migrations.MIGRATION_1_2
import org.schabi.newpipe.database.Migrations.MIGRATION_2_3
import org.schabi.newpipe.database.Migrations.MIGRATION_3_4
import org.schabi.newpipe.database.Migrations.MIGRATION_4_5
import org.schabi.newpipe.database.Migrations.MIGRATION_5_6
import org.schabi.newpipe.database.Migrations.MIGRATION_6_7
import org.schabi.newpipe.database.Migrations.MIGRATION_7_8
import org.schabi.newpipe.database.Migrations.MIGRATION_8_9
object NewPipeDatabase {
@Volatile
private var databaseInstance: AppDatabase? = null
private fun getDatabase(context: Context): AppDatabase {
return databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
AppDatabase.Companion.DATABASE_NAME
).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()
}
@JvmStatic
fun getInstance(context: Context): AppDatabase {
var result = databaseInstance
if (result == null) {
synchronized(NewPipeDatabase::class.java) {
result = databaseInstance
if (result == null) {
databaseInstance = getDatabase(context)
result = databaseInstance
}
}
}
return result!!
}
@JvmStatic
fun checkpoint() {
checkNotNull(databaseInstance) { "database is not initialized" }
val c = databaseInstance!!.query("pragma wal_checkpoint(full)", null)
if (c.moveToFirst() && c.getInt(0) == 1) {
throw RuntimeException("Checkpoint was blocked from completing")
}
}
@JvmStatic
fun close() {
if (databaseInstance != null) {
synchronized(NewPipeDatabase::class.java) {
if (databaseInstance != null) {
databaseInstance!!.close()
databaseInstance = null
}
}
}
}
}
@@ -18,10 +18,10 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.grack.nanojson.JsonParser
import com.grack.nanojson.JsonParserException
import java.io.IOException
import org.schabi.newpipe.extractor.downloader.Response
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.util.ReleaseVersionUtil
import java.io.IOException
class NewVersionWorker(
context: Context,
@@ -46,7 +46,8 @@ class NewVersionWorker(
// 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,
applicationContext,
R.string.app_update_unavailable_toast,
Toast.LENGTH_SHORT
).show()
}
@@ -58,7 +59,11 @@ class NewVersionWorker(
val intent = Intent(Intent.ACTION_VIEW, apkLocationUrl?.toUri())
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = PendingIntentCompat.getActivity(
applicationContext, 0, intent, 0, false
applicationContext,
0,
intent,
0,
false
)
val channelId = applicationContext.getString(R.string.app_update_notification_channel_id)
val notificationBuilder = NotificationCompat.Builder(applicationContext, channelId)
@@ -71,12 +76,15 @@ class NewVersionWorker(
)
.setContentText(
applicationContext.getString(
R.string.app_update_available_notification_text, versionName
R.string.app_update_available_notification_text,
versionName
)
)
val notificationManager = NotificationManagerCompat.from(applicationContext)
notificationManager.notify(2000, notificationBuilder.build())
if (notificationManager.areNotificationsEnabled()) {
notificationManager.notify(2000, notificationBuilder.build())
}
}
@Throws(IOException::class, ReCaptchaException::class)
@@ -41,50 +41,50 @@ public final class QueueItemMenuUtil {
}
popupMenu.setOnMenuItemClickListener(menuItem -> {
switch (menuItem.getItemId()) {
case R.id.menu_item_remove:
final int index = playQueue.indexOf(item);
playQueue.remove(index);
return true;
case R.id.menu_item_details:
// playQueue is null since we don't want any queue change
NavigationHelper.openVideoDetail(context, item.getServiceId(),
item.getUrl(), item.getTitle(), null,
false);
return true;
case R.id.menu_item_append_playlist:
PlaylistDialog.createCorrespondingDialog(
context,
List.of(new StreamEntity(item)),
dialog -> dialog.show(
fragmentManager,
"QueueItemMenuUtil@append_playlist"
)
);
final int itemId = menuItem.getItemId();
if (itemId == R.id.menu_item_remove) {
final int index = playQueue.indexOf(item);
playQueue.remove(index);
return true;
} else if (itemId == R.id.menu_item_details) {
// playQueue is null since we don't want any queue change
NavigationHelper.openVideoDetail(context, item.getServiceId(),
item.getUrl(), item.getTitle(), null,
false);
return true;
} else if (itemId == R.id.menu_item_append_playlist) {
PlaylistDialog.createCorrespondingDialog(
context,
List.of(new StreamEntity(item)),
dialog -> dialog.show(
fragmentManager,
"QueueItemMenuUtil@append_playlist"
)
);
return true;
case R.id.menu_item_channel_details:
SparseItemUtil.fetchUploaderUrlIfSparse(context, item.getServiceId(),
item.getUrl(), item.getUploaderUrl(),
// An intent must be used here.
// Opening with FragmentManager transactions is not working,
// as PlayQueueActivity doesn't use fragments.
uploaderUrl -> NavigationHelper.openChannelFragmentUsingIntent(
context, item.getServiceId(), uploaderUrl, item.getUploader()
));
return true;
case R.id.menu_item_share:
shareText(context, item.getTitle(), item.getUrl(),
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 true;
} else if (itemId == R.id.menu_item_channel_details) {
SparseItemUtil.fetchUploaderUrlIfSparse(context, item.getServiceId(),
item.getUrl(), item.getUploaderUrl(),
// An intent must be used here.
// Opening with FragmentManager transactions is not working,
// as PlayQueueActivity doesn't use fragments.
uploaderUrl -> NavigationHelper.openChannelFragmentUsingIntent(
context, item.getServiceId(), uploaderUrl, item.getUploader()
));
return true;
} else if (itemId == R.id.menu_item_share) {
shareText(context, item.getTitle(), item.getUrl(),
item.getThumbnails());
return true;
} else if (itemId == 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;
});
@@ -41,6 +41,9 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceManager;
import com.evernote.android.state.State;
import com.livefront.bridge.Bridge;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
@@ -55,20 +58,10 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.StreamingService.LinkType;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
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.exceptions.GeographicRestrictionException;
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
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;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.helper.PlayerHelper;
@@ -81,7 +74,6 @@ import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.Constants;
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.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -98,8 +90,6 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import icepick.Icepick;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
@@ -131,7 +121,6 @@ public class RouterActivity extends AppCompatActivity {
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
@@ -152,7 +141,7 @@ public class RouterActivity extends AppCompatActivity {
getWindow().setAttributes(params);
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
Bridge.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
@@ -197,7 +186,7 @@ public class RouterActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
Bridge.saveInstanceState(this, outState);
}
@Override
@@ -261,7 +250,8 @@ public class RouterActivity extends AppCompatActivity {
showUnsupportedUrlDialog(url);
}
}, throwable -> handleError(this, new ErrorInfo(throwable,
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url))));
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url,
null, url))));
}
/**
@@ -270,40 +260,19 @@ public class RouterActivity extends AppCompatActivity {
* @param errorInfo the error information
*/
private static void handleError(final Context context, final ErrorInfo errorInfo) {
if (errorInfo.getThrowable() != null) {
errorInfo.getThrowable().printStackTrace();
}
if (errorInfo.getThrowable() instanceof ReCaptchaException) {
if (errorInfo.getRecaptchaUrl() != null) {
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
final Intent intent = new Intent(context, ReCaptchaActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, errorInfo.getRecaptchaUrl());
context.startActivity(intent);
} else if (errorInfo.getThrowable() != null
&& ExceptionUtils.isNetworkRelated(errorInfo.getThrowable())) {
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof AgeRestrictedContentException) {
Toast.makeText(context, R.string.restricted_video_no_stream,
Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof GeographicRestrictionException) {
Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof PaidContentException) {
Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof PrivateContentException) {
Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof SoundCloudGoPlusContentException) {
Toast.makeText(context, R.string.soundcloud_go_plus_content,
Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof YoutubeMusicPremiumContentException) {
Toast.makeText(context, R.string.youtube_music_premium_content,
Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof ContentNotAvailableException) {
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
} else {
} else if (errorInfo.isReportable()) {
ErrorUtil.createNotification(context, errorInfo);
} else {
// this exception does not usually indicate a problem that should be reported,
// so just show a toast instead of the notification
Toast.makeText(context, errorInfo.getMessage(context), Toast.LENGTH_LONG).show();
}
if (context instanceof RouterActivity) {
@@ -347,7 +316,8 @@ public class RouterActivity extends AppCompatActivity {
if (choiceChecker.isAvailableAndSelected(
R.string.video_player_key,
R.string.background_player_key,
R.string.popup_player_key)) {
R.string.popup_player_key,
R.string.enqueue_key)) {
final String selectedChoice = choiceChecker.getSelectedChoiceKey();
@@ -360,6 +330,8 @@ public class RouterActivity extends AppCompatActivity {
|| selectedChoice.equals(getString(R.string.popup_player_key));
final boolean isAudioPlayerSelected =
selectedChoice.equals(getString(R.string.background_player_key));
final boolean isEnqueueSelected =
selectedChoice.equals(getString(R.string.enqueue_key));
if (currentLinkType != LinkType.STREAM
&& ((isExtAudioEnabled && isAudioPlayerSelected)
@@ -371,12 +343,13 @@ public class RouterActivity extends AppCompatActivity {
return;
}
final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
currentService.getServiceInfo().getMediaCapabilities();
final var capabilities = currentService.getServiceInfo().getMediaCapabilities();
// Check if the service supports the choice
if ((isVideoPlayerSelected && capabilities.contains(VIDEO))
|| (isAudioPlayerSelected && capabilities.contains(AUDIO))) {
|| (isAudioPlayerSelected && capabilities.contains(AUDIO))
|| (isEnqueueSelected && (capabilities.contains(VIDEO)
|| capabilities.contains(AUDIO)))) {
handleChoice(selectedChoice);
} else {
handleChoice(getString(R.string.show_info_key));
@@ -387,15 +360,9 @@ public class RouterActivity extends AppCompatActivity {
// Default / Ask always
final List<AdapterChoiceItem> availableChoices = choiceChecker.getAvailableChoices();
switch (availableChoices.size()) {
case 1:
handleChoice(availableChoices.get(0).key);
break;
case 0:
handleChoice(getString(R.string.show_info_key));
break;
default:
showDialog(availableChoices);
break;
case 1 -> handleChoice(availableChoices.get(0).key);
case 0 -> handleChoice(getString(R.string.show_info_key));
default -> showDialog(availableChoices);
}
}
@@ -554,10 +521,9 @@ public class RouterActivity extends AppCompatActivity {
final List<AdapterChoiceItem> returnedItems = new ArrayList<>();
returnedItems.add(showInfo); // Always present
final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
service.getServiceInfo().getMediaCapabilities();
final var capabilities = service.getServiceInfo().getMediaCapabilities();
if (linkType == LinkType.STREAM) {
if (linkType == LinkType.STREAM || linkType == LinkType.PLAYLIST) {
if (capabilities.contains(VIDEO)) {
returnedItems.add(videoPlayer);
returnedItems.add(popupPlayer);
@@ -565,17 +531,28 @@ public class RouterActivity extends AppCompatActivity {
if (capabilities.contains(AUDIO)) {
returnedItems.add(backgroundPlayer);
}
// download is redundant for linkType CHANNEL AND PLAYLIST (till playlist downloading is
// not supported )
returnedItems.add(new AdapterChoiceItem(getString(R.string.download_key),
getString(R.string.download),
R.drawable.ic_file_download));
// Add to playlist is not necessary for CHANNEL and PLAYLIST linkType since those can
// not be added to a playlist
returnedItems.add(new AdapterChoiceItem(getString(R.string.add_to_playlist_key),
getString(R.string.add_to_playlist),
R.drawable.ic_add));
// Enqueue is only shown if the current queue is not empty.
// However, if the playqueue or the player is cleared after this item was chosen and
// while the item is extracted, it will automatically fall back to background player.
if (PlayerHolder.INSTANCE.getQueueSize() > 0) {
returnedItems.add(new AdapterChoiceItem(getString(R.string.enqueue_key),
getString(R.string.enqueue_stream), R.drawable.ic_add));
}
if (linkType == LinkType.STREAM) {
// download is redundant for linkType CHANNEL AND PLAYLIST
// (till playlist downloading is not supported )
returnedItems.add(new AdapterChoiceItem(getString(R.string.download_key),
getString(R.string.download),
R.drawable.ic_file_download));
// Add to playlist is not necessary for CHANNEL and PLAYLIST linkType
// since those can not be added to a playlist
returnedItems.add(new AdapterChoiceItem(getString(R.string.add_to_playlist_key),
getString(R.string.add_to_playlist),
R.drawable.ic_playlist_add));
}
} else {
// LinkType.NONE is never present because it's filtered out before
// channels and playlist can be played as they contain a list of videos
@@ -666,7 +643,8 @@ public class RouterActivity extends AppCompatActivity {
startActivity(intent);
finish();
}, throwable -> handleError(this, new ErrorInfo(throwable,
UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl)))
UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl,
null, currentUrl)))
);
return;
}
@@ -700,7 +678,7 @@ public class RouterActivity extends AppCompatActivity {
}
// ...the player is not running or in normal Video-mode/type
final PlayerType playerType = PlayerHolder.getInstance().getType();
final PlayerType playerType = PlayerHolder.INSTANCE.getType();
return playerType == null || playerType == PlayerType.MAIN;
}
@@ -853,10 +831,10 @@ public class RouterActivity extends AppCompatActivity {
})
)),
throwable -> runOnVisible(ctx -> handleError(ctx, new ErrorInfo(
throwable,
UserAction.REQUESTED_STREAM,
throwable, UserAction.REQUESTED_STREAM,
"Tried to add " + currentUrl + " to a playlist",
((RouterActivity) ctx).currentService.getServiceId())
((RouterActivity) ctx).currentService.getServiceId(),
currentUrl)
))
)
);
@@ -996,7 +974,7 @@ public class RouterActivity extends AppCompatActivity {
}
}, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction,
choice.url + " opened with " + choice.playerChoice,
choice.serviceId)));
choice.serviceId, choice.url)));
}
}
@@ -1046,6 +1024,8 @@ public class RouterActivity extends AppCompatActivity {
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
} else if (choice.playerChoice.equals(popupPlayerKey)) {
NavigationHelper.playOnPopupPlayer(this, playQueue, true);
} else if (choice.playerChoice.equals(getString(R.string.enqueue_key))) {
NavigationHelper.enqueueOnPlayer(this, playQueue);
}
};
}
@@ -1,199 +0,0 @@
package org.schabi.newpipe.about
import android.os.Bundle
import android.view.LayoutInflater
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
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.ActivityAboutBinding
import org.schabi.newpipe.databinding.FragmentAboutBinding
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ThemeHelper
import org.schabi.newpipe.util.external_communication.ShareUtils
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Localization.assureCorrectAppLanguage(this)
super.onCreate(savedInstanceState)
ThemeHelper.setTheme(this)
title = getString(R.string.title_activity_about)
val aboutBinding = ActivityAboutBinding.inflate(layoutInflater)
setContentView(aboutBinding.root)
setSupportActionBar(aboutBinding.aboutToolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
val mAboutStateAdapter = AboutStateAdapter(this)
// Set up the ViewPager with the sections adapter.
aboutBinding.aboutViewPager2.adapter = mAboutStateAdapter
TabLayoutMediator(
aboutBinding.aboutTabLayout,
aboutBinding.aboutViewPager2
) { tab, position ->
tab.setText(mAboutStateAdapter.getPageTitle(position))
}.attach()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
/**
* A placeholder fragment containing a simple view.
*/
class AboutFragment : Fragment() {
private fun Button.openLink(@StringRes url: Int) {
setOnClickListener {
ShareUtils.openUrlInApp(context, requireContext().getString(url))
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
FragmentAboutBinding.inflate(inflater, container, false).apply {
aboutAppVersion.text = BuildConfig.VERSION_NAME
aboutGithubLink.openLink(R.string.github_url)
aboutDonationLink.openLink(R.string.donation_url)
aboutWebsiteLink.openLink(R.string.website_url)
aboutPrivacyPolicyLink.openLink(R.string.privacy_policy_url)
faqLink.openLink(R.string.faq_url)
return root
}
}
}
/**
* A [FragmentStateAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
private class AboutStateAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
private val posAbout = 0
private val posLicense = 1
private val totalCount = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
posAbout -> AboutFragment()
posLicense -> LicenseFragment.newInstance(SOFTWARE_COMPONENTS)
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
}
}
override fun getItemCount(): Int {
// Show 2 total pages.
return totalCount
}
fun getPageTitle(position: Int): Int {
return when (position) {
posAbout -> R.string.tab_about
posLicense -> R.string.tab_licenses
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
}
}
}
companion object {
/**
* List of all software components.
*/
private val SOFTWARE_COMPONENTS = arrayListOf(
SoftwareComponent(
"ACRA", "2013", "Kevin Gaudin",
"https://github.com/ACRA/acra", StandardLicenses.APACHE2
),
SoftwareComponent(
"AndroidX", "2005 - 2011", "The Android Open Source Project",
"https://developer.android.com/jetpack", StandardLicenses.APACHE2
),
SoftwareComponent(
"ExoPlayer", "2014 - 2020", "Google, Inc.",
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2
),
SoftwareComponent(
"GigaGet", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3
),
SoftwareComponent(
"Groupie", "2016", "Lisa Wray",
"https://github.com/lisawray/groupie", StandardLicenses.MIT
),
SoftwareComponent(
"Icepick", "2015", "Frankie Sardo",
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1
),
SoftwareComponent(
"Jsoup", "2009 - 2020", "Jonathan Hedley",
"https://github.com/jhy/jsoup", StandardLicenses.MIT
),
SoftwareComponent(
"Markwon", "2019", "Dimitry Ivanov",
"https://github.com/noties/Markwon", StandardLicenses.APACHE2
),
SoftwareComponent(
"Material Components for Android", "2016 - 2020", "Google, Inc.",
"https://github.com/material-components/material-components-android",
StandardLicenses.APACHE2
),
SoftwareComponent(
"NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3
),
SoftwareComponent(
"NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
"https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2
),
SoftwareComponent(
"OkHttp", "2019", "Square, Inc.",
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
),
SoftwareComponent(
"Picasso", "2013", "Square, Inc.",
"https://square.github.io/picasso/", StandardLicenses.APACHE2
),
SoftwareComponent(
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2
),
SoftwareComponent(
"ProcessPhoenix", "2015", "Jake Wharton",
"https://github.com/JakeWharton/ProcessPhoenix", StandardLicenses.APACHE2
),
SoftwareComponent(
"RxAndroid", "2015", "The RxAndroid authors",
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2
),
SoftwareComponent(
"RxBinding", "2015", "Jake Wharton",
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2
),
SoftwareComponent(
"RxJava", "2016 - 2020", "RxJava Contributors",
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2
),
SoftwareComponent(
"SearchPreference", "2018", "ByteHamster",
"https://github.com/ByteHamster/SearchPreference", StandardLicenses.MIT
),
)
}
}
@@ -1,11 +0,0 @@
package org.schabi.newpipe.about
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.io.Serializable
/**
* Class for storing information about a software license.
*/
@Parcelize
class License(val name: String, val abbreviation: String, val filename: String) : Parcelable, Serializable
@@ -1,140 +0,0 @@
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: List<SoftwareComponent>
private var activeSoftwareComponent: SoftwareComponent? = null
private val compositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
softwareComponents = arguments?.parcelableArrayList<SoftwareComponent>(ARG_COMPONENTS)!!
.sortedBy { it.name } // Sort components by name
activeSoftwareComponent = savedInstanceState?.getSerializable(SOFTWARE_COMPONENT_KEY) as? SoftwareComponent
}
override fun onDestroy() {
compositeDisposable.dispose()
super.onDestroy()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentLicensesBinding.inflate(inflater, container, false)
binding.licensesAppReadLicense.setOnClickListener {
compositeDisposable.add(
showLicense(NEWPIPE_SOFTWARE_COMPONENT)
)
}
for (component in softwareComponents) {
val componentBinding = ItemSoftwareComponentBinding
.inflate(inflater, container, false)
componentBinding.name.text = component.name
componentBinding.copyright.text = getString(
R.string.copyright,
component.years,
component.copyrightOwner,
component.license.abbreviation
)
val root: View = componentBinding.root
root.tag = component
root.setOnClickListener {
compositeDisposable.add(
showLicense(component)
)
}
binding.licensesSoftwareComponents.addView(root)
registerForContextMenu(root)
}
activeSoftwareComponent?.let { compositeDisposable.add(showLicense(it)) }
return binding.root
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
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 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
}
}
}
@@ -1,52 +0,0 @@
package org.schabi.newpipe.about
import android.content.Context
import org.schabi.newpipe.R
import org.schabi.newpipe.util.ThemeHelper
import java.io.IOException
/**
* @param context the context to use
* @param license the license
* @return String which contains a HTML formatted license page
* styled according to the context's theme
*/
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
.replace("</head>", "<style>${getLicenseStylesheet(context)}</style></head>")
} catch (e: IOException) {
throw IllegalArgumentException("Could not get license file: ${license.filename}", e)
}
}
/**
* @param context the Android context
* @return String which is a CSS stylesheet according to the context's theme
*/
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
)
val licenseTextColor = getHexRGBColor(
context, if (isLightTheme) R.color.light_license_text_color else R.color.dark_license_text_color
)
val youtubePrimaryColor = getHexRGBColor(
context, if (isLightTheme) R.color.light_youtube_primary_color else R.color.dark_youtube_primary_color
)
return "body{padding:12px 15px;margin:0;background:#$licenseBackgroundColor;color:#$licenseTextColor}" +
"a[href]{color:#$youtubePrimaryColor}pre{white-space:pre-wrap}"
}
/**
* Cast R.color to a hexadecimal color value.
*
* @param context the context to use
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
fun getHexRGBColor(context: Context, color: Int): String {
return context.getString(color).substring(3)
}
@@ -1,17 +0,0 @@
package org.schabi.newpipe.about
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.io.Serializable
@Parcelize
class SoftwareComponent
@JvmOverloads
constructor(
val name: String,
val years: String,
val copyrightOwner: String,
val link: String,
val license: License,
val version: String? = null
) : Parcelable, Serializable
@@ -1,21 +0,0 @@
package org.schabi.newpipe.about
/**
* Class containing information about standard software licenses.
*/
object StandardLicenses {
@JvmField
val GPL3 = License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html")
@JvmField
val APACHE2 = License("Apache License, Version 2.0", "ALv2", "apache2.html")
@JvmField
val MPL2 = License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html")
@JvmField
val MIT = License("MIT License", "MIT", "mit.html")
@JvmField
val EPL1 = License("Eclipse Public License, Version 1.0", "EPL 1.0", "epl1.html")
}
@@ -1,65 +0,0 @@
package org.schabi.newpipe.database;
import static org.schabi.newpipe.database.Migrations.DB_VER_9;
import androidx.room.Database;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import org.schabi.newpipe.database.feed.dao.FeedDAO;
import org.schabi.newpipe.database.feed.dao.FeedGroupDAO;
import org.schabi.newpipe.database.feed.model.FeedEntity;
import org.schabi.newpipe.database.feed.model.FeedGroupEntity;
import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity;
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity;
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO;
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO;
import org.schabi.newpipe.database.playlist.dao.PlaylistRemoteDAO;
import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO;
import org.schabi.newpipe.database.playlist.model.PlaylistEntity;
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity;
import org.schabi.newpipe.database.stream.dao.StreamDAO;
import org.schabi.newpipe.database.stream.dao.StreamStateDAO;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.database.subscription.SubscriptionDAO;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
@TypeConverters({Converters.class})
@Database(
entities = {
SubscriptionEntity.class, SearchHistoryEntry.class,
StreamEntity.class, StreamHistoryEntity.class, StreamStateEntity.class,
PlaylistEntity.class, PlaylistStreamEntity.class, PlaylistRemoteEntity.class,
FeedEntity.class, FeedGroupEntity.class, FeedGroupSubscriptionEntity.class,
FeedLastUpdatedEntity.class
},
version = DB_VER_9
)
public abstract class AppDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "newpipe.db";
public abstract SearchHistoryDAO searchHistoryDAO();
public abstract StreamDAO streamDAO();
public abstract StreamHistoryDAO streamHistoryDAO();
public abstract StreamStateDAO streamStateDAO();
public abstract PlaylistDAO playlistDAO();
public abstract PlaylistStreamDAO playlistStreamDAO();
public abstract PlaylistRemoteDAO playlistRemoteDAO();
public abstract FeedDAO feedDAO();
public abstract FeedGroupDAO feedGroupDAO();
public abstract SubscriptionDAO subscriptionDAO();
}
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2017-2024 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import org.schabi.newpipe.database.feed.dao.FeedDAO
import org.schabi.newpipe.database.feed.dao.FeedGroupDAO
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.feed.model.FeedGroupSubscriptionEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
import org.schabi.newpipe.database.history.dao.SearchHistoryDAO
import org.schabi.newpipe.database.history.dao.StreamHistoryDAO
import org.schabi.newpipe.database.history.model.SearchHistoryEntry
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.playlist.dao.PlaylistDAO
import org.schabi.newpipe.database.playlist.dao.PlaylistRemoteDAO
import org.schabi.newpipe.database.playlist.dao.PlaylistStreamDAO
import org.schabi.newpipe.database.playlist.model.PlaylistEntity
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
import org.schabi.newpipe.database.stream.dao.StreamDAO
import org.schabi.newpipe.database.stream.dao.StreamStateDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.database.subscription.SubscriptionDAO
import org.schabi.newpipe.database.subscription.SubscriptionEntity
@TypeConverters(Converters::class)
@Database(
version = Migrations.DB_VER_9,
entities = [
SubscriptionEntity::class,
SearchHistoryEntry::class,
StreamEntity::class,
StreamHistoryEntity::class,
StreamStateEntity::class,
PlaylistEntity::class,
PlaylistStreamEntity::class,
PlaylistRemoteEntity::class,
FeedEntity::class,
FeedGroupEntity::class,
FeedGroupSubscriptionEntity::class,
FeedLastUpdatedEntity::class
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun feedDAO(): FeedDAO
abstract fun feedGroupDAO(): FeedGroupDAO
abstract fun playlistDAO(): PlaylistDAO
abstract fun playlistRemoteDAO(): PlaylistRemoteDAO
abstract fun playlistStreamDAO(): PlaylistStreamDAO
abstract fun searchHistoryDAO(): SearchHistoryDAO
abstract fun streamDAO(): StreamDAO
abstract fun streamHistoryDAO(): StreamHistoryDAO
abstract fun streamStateDAO(): StreamStateDAO
abstract fun subscriptionDAO(): SubscriptionDAO
companion object {
const val DATABASE_NAME: String = "newpipe.db"
}
}
@@ -1,39 +0,0 @@
package org.schabi.newpipe.database;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Update;
import java.util.Collection;
import java.util.List;
import io.reactivex.rxjava3.core.Flowable;
@Dao
public interface BasicDAO<Entity> {
/* Inserts */
@Insert
long insert(Entity entity);
@Insert
List<Long> insertAll(Collection<Entity> entities);
/* Searches */
Flowable<List<Entity>> getAll();
Flowable<List<Entity>> listByService(int serviceId);
/* Deletes */
@Delete
void delete(Entity entity);
int deleteAll();
/* Updates */
@Update
int update(Entity entity);
@Update
void update(Collection<Entity> entities);
}
@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2017-2022 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Update
import io.reactivex.rxjava3.core.Flowable
@Dao
interface BasicDAO<Entity> {
/* Inserts */
@Insert
fun insert(entity: Entity): Long
@Insert
fun insertAll(entities: Collection<Entity>): List<Long>
/* Searches */
fun getAll(): Flowable<List<Entity>>
fun listByService(serviceId: Int): Flowable<List<Entity>>
/* Deletes */
@Delete
fun delete(entity: Entity)
fun deleteAll(): Int
/* Updates */
@Update
fun update(entity: Entity): Int
@Update
fun update(entities: Collection<Entity>)
}
@@ -1,11 +1,11 @@
package org.schabi.newpipe.database
import androidx.room.TypeConverter
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.local.subscription.FeedGroupIcon
class Converters {
/**
@@ -1,13 +0,0 @@
package org.schabi.newpipe.database;
public interface LocalItem {
LocalItemType getLocalItemType();
enum LocalItemType {
PLAYLIST_LOCAL_ITEM,
PLAYLIST_REMOTE_ITEM,
PLAYLIST_STREAM_ITEM,
STATISTIC_STREAM_ITEM,
}
}
@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2018-2020 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database
interface LocalItem {
val localItemType: LocalItemType
enum class LocalItemType {
PLAYLIST_LOCAL_ITEM,
PLAYLIST_REMOTE_ITEM,
PLAYLIST_STREAM_ITEM,
STATISTIC_STREAM_ITEM
}
}
@@ -1,307 +0,0 @@
package org.schabi.newpipe.database;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import org.schabi.newpipe.MainActivity;
public final class Migrations {
/////////////////////////////////////////////////////////////////////////////
// Test new migrations manually by importing a database from daily usage //
// and checking if the migration works (Use the Database Inspector //
// https://developer.android.com/studio/inspect/database). //
// If you add a migration point it out in the pull request, so that //
// others remember to test it themselves. //
/////////////////////////////////////////////////////////////////////////////
public static final int DB_VER_1 = 1;
public static final int DB_VER_2 = 2;
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;
public static final Migration MIGRATION_1_2 = new Migration(DB_VER_1, DB_VER_2) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
if (DEBUG) {
Log.d(TAG, "Start migrating database");
}
/*
* Unfortunately these queries must be hardcoded due to the possibility of
* schema and names changing at a later date, thus invalidating the older migration
* scripts if they are not hardcoded.
* */
// Not much we can do about this, since room doesn't create tables before migration.
// It's either this or blasting the entire database anew.
database.execSQL("CREATE INDEX `index_search_history_search` "
+ "ON `search_history` (`search`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `streams` "
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "`service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, "
+ "`stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, "
+ "`thumbnail_url` TEXT)");
database.execSQL("CREATE UNIQUE INDEX `index_streams_service_id_url` "
+ "ON `streams` (`service_id`, `url`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_history` "
+ "(`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 )");
database.execSQL("CREATE INDEX `index_stream_history_stream_id` "
+ "ON `stream_history` (`stream_id`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `stream_state` "
+ "(`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 )");
database.execSQL("CREATE TABLE IF NOT EXISTS `playlists` "
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "`name` TEXT, `thumbnail_url` TEXT)");
database.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `playlist_stream_join` "
+ "(`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)");
database.execSQL("CREATE UNIQUE INDEX "
+ "`index_playlist_stream_join_playlist_id_join_index` "
+ "ON `playlist_stream_join` (`playlist_id`, `join_index`)");
database.execSQL("CREATE INDEX `index_playlist_stream_join_stream_id` "
+ "ON `playlist_stream_join` (`stream_id`)");
database.execSQL("CREATE TABLE IF NOT EXISTS `remote_playlists` "
+ "(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ "`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, "
+ "`thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)");
database.execSQL("CREATE INDEX `index_remote_playlists_name` "
+ "ON `remote_playlists` (`name`)");
database.execSQL("CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` "
+ "ON `remote_playlists` (`service_id`, `url`)");
// Populate streams table with existing entries in watch history
// Latest data first, thus ignoring older entries with the same indices
database.execSQL("INSERT OR IGNORE INTO streams (service_id, url, title, "
+ "stream_type, duration, uploader, thumbnail_url) "
+ "SELECT service_id, url, title, 'VIDEO_STREAM', duration, "
+ "uploader, thumbnail_url "
+ "FROM watch_history "
+ "ORDER BY creation_date DESC");
// Once the streams have PKs, join them with the normalized history table
// and populate it with the remaining data from watch history
database.execSQL("INSERT INTO stream_history (stream_id, access_date, repeat_count)"
+ "SELECT uid, creation_date, 1 "
+ "FROM watch_history INNER JOIN streams "
+ "ON watch_history.service_id == streams.service_id "
+ "AND watch_history.url == streams.url "
+ "ORDER BY creation_date DESC");
database.execSQL("DROP TABLE IF EXISTS watch_history");
if (DEBUG) {
Log.d(TAG, "Stop migrating database");
}
}
};
public static final Migration MIGRATION_2_3 = new Migration(DB_VER_2, DB_VER_3) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
// Add NOT NULLs and new fields
database.execSQL("CREATE TABLE IF NOT EXISTS streams_new "
+ "(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, thumbnail_url TEXT, view_count INTEGER, "
+ "textual_upload_date TEXT, upload_date INTEGER, "
+ "is_upload_date_approximation INTEGER)");
database.execSQL("INSERT INTO streams_new (uid, service_id, url, title, stream_type, "
+ "duration, uploader, thumbnail_url, view_count, textual_upload_date, "
+ "upload_date, is_upload_date_approximation) "
+ "SELECT uid, service_id, url, ifnull(title, ''), "
+ "ifnull(stream_type, 'VIDEO_STREAM'), ifnull(duration, 0), "
+ "ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL, NULL, NULL, NULL "
+ "FROM streams WHERE url IS NOT NULL");
database.execSQL("DROP TABLE streams");
database.execSQL("ALTER TABLE streams_new RENAME TO streams");
database.execSQL("CREATE UNIQUE INDEX index_streams_service_id_url "
+ "ON streams (service_id, url)");
// Tables for feed feature
database.execSQL("CREATE TABLE IF NOT EXISTS feed "
+ "(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)");
database.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)");
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group "
+ "(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, "
+ "icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)");
database.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)");
database.execSQL("CREATE TABLE IF NOT EXISTS feed_group_subscription_join "
+ "(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)");
database.execSQL("CREATE INDEX index_feed_group_subscription_join_subscription_id "
+ "ON feed_group_subscription_join (subscription_id)");
database.execSQL("CREATE TABLE IF NOT EXISTS feed_last_updated "
+ "(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)");
}
};
public static final Migration MIGRATION_3_4 = new Migration(DB_VER_3, DB_VER_4) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL(
"ALTER TABLE streams ADD COLUMN uploader_url TEXT"
);
}
};
public static final Migration MIGRATION_4_5 = new Migration(DB_VER_4, DB_VER_5) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `subscriptions` ADD COLUMN `notification_mode` "
+ "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();
}
}
};
private Migrations() {
}
}
@@ -0,0 +1,351 @@
/*
* SPDX-FileCopyrightText: 2018-2024 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database
import android.util.Log
import androidx.room.migration.Migration
import org.schabi.newpipe.MainActivity
object Migrations {
// /////////////////////////////////////////////////////////////////////// //
// Test new migrations manually by importing a database from daily usage //
// and checking if the migration works (Use the Database Inspector //
// https://developer.android.com/studio/inspect/database). //
// If you add a migration point it out in the pull request, so that //
// others remember to test it themselves. //
// /////////////////////////////////////////////////////////////////////// //
const val DB_VER_1 = 1
const val DB_VER_2 = 2
const val DB_VER_3 = 3
const val DB_VER_4 = 4
const val DB_VER_5 = 5
const val DB_VER_6 = 6
const val DB_VER_7 = 7
const val DB_VER_8 = 8
const val DB_VER_9 = 9
private val TAG = Migrations::class.java.getName()
private val isDebug = MainActivity.DEBUG
val MIGRATION_1_2 = Migration(DB_VER_1, DB_VER_2) { db ->
if (isDebug) {
Log.d(TAG, "Start migrating database")
}
/*
* Unfortunately these queries must be hardcoded due to the possibility of
* schema and names changing at a later date, thus invalidating the older migration
* scripts if they are not hardcoded.
* */
// Not much we can do about this, since room doesn't create tables before migration.
// It's either this or blasting the entire database anew.
db.execSQL(
"CREATE INDEX `index_search_history_search` " +
"ON `search_history` (`search`)"
)
db.execSQL(
"CREATE TABLE IF NOT EXISTS `streams` " +
"(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`service_id` INTEGER NOT NULL, `url` TEXT, `title` TEXT, " +
"`stream_type` TEXT, `duration` INTEGER, `uploader` TEXT, " +
"`thumbnail_url` TEXT)"
)
db.execSQL(
"CREATE UNIQUE INDEX `index_streams_service_id_url` " +
"ON `streams` (`service_id`, `url`)"
)
db.execSQL(
"CREATE TABLE IF NOT EXISTS `stream_history` " +
"(`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 )"
)
db.execSQL(
"CREATE INDEX `index_stream_history_stream_id` " +
"ON `stream_history` (`stream_id`)"
)
db.execSQL(
"CREATE TABLE IF NOT EXISTS `stream_state` " +
"(`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 )"
)
db.execSQL(
"CREATE TABLE IF NOT EXISTS `playlists` " +
"(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`name` TEXT, `thumbnail_url` TEXT)"
)
db.execSQL("CREATE INDEX `index_playlists_name` ON `playlists` (`name`)")
db.execSQL(
"CREATE TABLE IF NOT EXISTS `playlist_stream_join` " +
"(`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)"
)
db.execSQL(
"CREATE UNIQUE INDEX " +
"`index_playlist_stream_join_playlist_id_join_index` " +
"ON `playlist_stream_join` (`playlist_id`, `join_index`)"
)
db.execSQL(
"CREATE INDEX `index_playlist_stream_join_stream_id` " +
"ON `playlist_stream_join` (`stream_id`)"
)
db.execSQL(
"CREATE TABLE IF NOT EXISTS `remote_playlists` " +
"(`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`service_id` INTEGER NOT NULL, `name` TEXT, `url` TEXT, " +
"`thumbnail_url` TEXT, `uploader` TEXT, `stream_count` INTEGER)"
)
db.execSQL(
"CREATE INDEX `index_remote_playlists_name` " +
"ON `remote_playlists` (`name`)"
)
db.execSQL(
"CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` " +
"ON `remote_playlists` (`service_id`, `url`)"
)
// Populate streams table with existing entries in watch history
// Latest data first, thus ignoring older entries with the same indices
db.execSQL(
"INSERT OR IGNORE INTO streams (service_id, url, title, " +
"stream_type, duration, uploader, thumbnail_url) " +
"SELECT service_id, url, title, 'VIDEO_STREAM', duration, " +
"uploader, thumbnail_url " +
"FROM watch_history " +
"ORDER BY creation_date DESC"
)
// Once the streams have PKs, join them with the normalized history table
// and populate it with the remaining data from watch history
db.execSQL(
"INSERT INTO stream_history (stream_id, access_date, repeat_count)" +
"SELECT uid, creation_date, 1 " +
"FROM watch_history INNER JOIN streams " +
"ON watch_history.service_id == streams.service_id " +
"AND watch_history.url == streams.url " +
"ORDER BY creation_date DESC"
)
db.execSQL("DROP TABLE IF EXISTS watch_history")
if (isDebug) {
Log.d(TAG, "Stop migrating database")
}
}
val MIGRATION_2_3 = Migration(DB_VER_2, DB_VER_3) { db ->
// Add NOT NULLs and new fields
db.execSQL(
"CREATE TABLE IF NOT EXISTS streams_new " +
"(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, thumbnail_url TEXT, view_count INTEGER, " +
"textual_upload_date TEXT, upload_date INTEGER, " +
"is_upload_date_approximation INTEGER)"
)
db.execSQL(
"INSERT INTO streams_new (uid, service_id, url, title, stream_type, " +
"duration, uploader, thumbnail_url, view_count, textual_upload_date, " +
"upload_date, is_upload_date_approximation) " +
"SELECT uid, service_id, url, ifnull(title, ''), " +
"ifnull(stream_type, 'VIDEO_STREAM'), ifnull(duration, 0), " +
"ifnull(uploader, ''), ifnull(thumbnail_url, ''), NULL, NULL, NULL, NULL " +
"FROM streams WHERE url IS NOT NULL"
)
db.execSQL("DROP TABLE streams")
db.execSQL("ALTER TABLE streams_new RENAME TO streams")
db.execSQL(
"CREATE UNIQUE INDEX index_streams_service_id_url " +
"ON streams (service_id, url)"
)
// Tables for feed feature
db.execSQL(
"CREATE TABLE IF NOT EXISTS feed " +
"(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)"
)
db.execSQL("CREATE INDEX index_feed_subscription_id ON feed (subscription_id)")
db.execSQL(
"CREATE TABLE IF NOT EXISTS feed_group " +
"(uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, " +
"icon_id INTEGER NOT NULL, sort_order INTEGER NOT NULL)"
)
db.execSQL("CREATE INDEX index_feed_group_sort_order ON feed_group (sort_order)")
db.execSQL(
"CREATE TABLE IF NOT EXISTS feed_group_subscription_join " +
"(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)"
)
db.execSQL(
"CREATE INDEX index_feed_group_subscription_join_subscription_id " +
"ON feed_group_subscription_join (subscription_id)"
)
db.execSQL(
"CREATE TABLE IF NOT EXISTS feed_last_updated " +
"(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)"
)
}
val MIGRATION_3_4 = Migration(DB_VER_3, DB_VER_4) { db ->
db.execSQL("ALTER TABLE streams ADD COLUMN uploader_url TEXT")
}
val MIGRATION_4_5 = Migration(DB_VER_4, DB_VER_5) { db ->
db.execSQL(
"ALTER TABLE `subscriptions` ADD COLUMN `notification_mode` " +
"INTEGER NOT NULL DEFAULT 0"
)
}
val MIGRATION_5_6 = Migration(DB_VER_5, DB_VER_6) { db ->
db.execSQL(
"ALTER TABLE `playlists` ADD COLUMN `is_thumbnail_permanent` " +
"INTEGER NOT NULL DEFAULT 0"
)
}
val MIGRATION_6_7 = Migration(DB_VER_6, DB_VER_7) { db ->
// Create a new column thumbnail_stream_id
db.execSQL(
"ALTER TABLE `playlists` ADD COLUMN `thumbnail_stream_id` " +
"INTEGER NOT NULL DEFAULT -1"
)
// Migrate the thumbnail_url to the thumbnail_stream_id
db.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
db.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)"
)
db.execSQL(
"INSERT INTO playlists_new" +
" SELECT uid, name, is_thumbnail_permanent, thumbnail_stream_id " +
" FROM playlists"
)
db.execSQL("DROP TABLE playlists")
db.execSQL("ALTER TABLE playlists_new RENAME TO playlists")
db.execSQL(
"CREATE INDEX IF NOT EXISTS " +
"`index_playlists_name` ON `playlists` (`name`)"
)
}
val MIGRATION_7_8 = Migration(DB_VER_7, DB_VER_8) { db ->
db.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)"
)
db.execSQL("UPDATE search_history SET search = trim(search)")
}
val MIGRATION_8_9 = Migration(DB_VER_8, DB_VER_9) { db ->
try {
db.beginTransaction()
// Update playlists.
// Create a temp table to initialize display_index.
db.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)"
)
db.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.
db.execSQL("DROP TABLE `playlists`")
db.execSQL("ALTER TABLE `playlists_tmp` RENAME TO `playlists`")
// Update remote_playlists.
// Create a temp table to initialize display_index.
db.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)"
)
db.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.
db.execSQL("DROP TABLE `remote_playlists`")
db.execSQL("ALTER TABLE `remote_playlists_tmp` RENAME TO `remote_playlists`")
// Create index on the new table.
db.execSQL(
"CREATE UNIQUE INDEX `index_remote_playlists_service_id_url` " +
"ON `remote_playlists` (`service_id`, `url`)"
)
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
}
@@ -8,6 +8,7 @@ import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import java.time.OffsetDateTime
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
@@ -15,7 +16,6 @@ import org.schabi.newpipe.database.stream.StreamWithState
import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.database.subscription.NotificationMode
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
@Dao
abstract class FeedDAO {
@@ -119,7 +119,7 @@ abstract class FeedDAO {
AND s.upload_date <> max_upload_date))
"""
)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
abstract suspend fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
@Query(
"""
@@ -168,10 +168,10 @@ abstract class FeedDAO {
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
"""
)
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime?>>
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime?>>
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
abstract fun notLoadedCount(): Flowable<Long>
@@ -17,15 +17,19 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
foreignKeys = [
ForeignKey(
entity = StreamEntity::class,
parentColumns = [StreamEntity.STREAM_ID],
parentColumns = ["uid"],
childColumns = [STREAM_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
deferred = true
),
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
deferred = true
)
]
)
@@ -18,14 +18,18 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
entity = FeedGroupEntity::class,
parentColumns = [FeedGroupEntity.ID],
childColumns = [GROUP_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
deferred = true
),
ForeignKey(
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
deferred = true
)
]
)
@@ -4,10 +4,10 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.time.OffsetDateTime
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
import org.schabi.newpipe.database.subscription.SubscriptionEntity
import java.time.OffsetDateTime
@Entity(
tableName = FEED_LAST_UPDATED_TABLE,
@@ -16,7 +16,9 @@ import java.time.OffsetDateTime
entity = SubscriptionEntity::class,
parentColumns = [SubscriptionEntity.SUBSCRIPTION_UID],
childColumns = [SUBSCRIPTION_ID],
onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE, deferred = true
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
deferred = true
)
]
)
@@ -1,7 +0,0 @@
package org.schabi.newpipe.database.history.dao;
import org.schabi.newpipe.database.BasicDAO;
public interface HistoryDAO<T> extends BasicDAO<T> {
T getLatestEntry();
}
@@ -1,52 +0,0 @@
package org.schabi.newpipe.database.history.dao;
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Query;
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
import java.util.List;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME;
@Dao
public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
String ORDER_BY_CREATION_DATE = " ORDER BY " + CREATION_DATE + " DESC";
String ORDER_BY_MAX_CREATION_DATE = " ORDER BY MAX(" + CREATION_DATE + ") DESC";
@Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + ID + " = (SELECT MAX(" + ID + ") FROM " + TABLE_NAME + ")")
@Nullable
SearchHistoryEntry getLatestEntry();
@Query("DELETE FROM " + TABLE_NAME)
@Override
int deleteAll();
@Query("DELETE FROM " + TABLE_NAME + " WHERE " + SEARCH + " = :query")
int deleteAllWhereQuery(String query);
@Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE)
@Override
Flowable<List<SearchHistoryEntry>> getAll();
@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " GROUP BY " + SEARCH
+ ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
Flowable<List<String>> getUniqueEntries(int limit);
@Query("SELECT * FROM " + TABLE_NAME
+ " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
@Override
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
+ " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
Flowable<List<String>> getSimilarEntries(String query, int limit);
}
@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2017-2021 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.history.dao
import androidx.room.Dao
import androidx.room.Query
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.history.model.SearchHistoryEntry
@Dao
interface SearchHistoryDAO : BasicDAO<SearchHistoryEntry> {
@get:Query("SELECT * FROM search_history WHERE id = (SELECT MAX(id) FROM search_history)")
val latestEntry: SearchHistoryEntry?
@Query("DELETE FROM search_history")
override fun deleteAll(): Int
@Query("DELETE FROM search_history WHERE search = :query")
fun deleteAllWhereQuery(query: String): Int
@Query("SELECT * FROM search_history ORDER BY creation_date DESC")
override fun getAll(): Flowable<List<SearchHistoryEntry>>
@Query("SELECT search FROM search_history GROUP BY search ORDER BY MAX(creation_date) DESC LIMIT :limit")
fun getUniqueEntries(limit: Int): Flowable<MutableList<String>>
@Query("SELECT * FROM search_history WHERE service_id = :serviceId ORDER BY creation_date DESC")
override fun listByService(serviceId: Int): Flowable<List<SearchHistoryEntry>>
@Query(
"""
SELECT search FROM search_history WHERE search LIKE :query ||
'%' GROUP BY search ORDER BY MAX(creation_date) DESC LIMIT :limit
"""
)
fun getSimilarEntries(query: String, limit: Int): Flowable<MutableList<String>>
}
@@ -1,89 +0,0 @@
package org.schabi.newpipe.database.history.dao;
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.RewriteQueriesToDropUnusedColumns;
import org.schabi.newpipe.database.history.model.StreamHistoryEntity;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import java.util.List;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_REPEAT_COUNT;
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_LATEST_DATE;
import static org.schabi.newpipe.database.stream.StreamStatisticsEntry.STREAM_WATCH_COUNT;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_ID;
import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID_ALIAS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS;
import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE;
@Dao
public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity> {
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE
+ " WHERE " + STREAM_ACCESS_DATE + " = "
+ "(SELECT MAX(" + STREAM_ACCESS_DATE + ") FROM " + STREAM_HISTORY_TABLE + ")")
@Override
@Nullable
public abstract StreamHistoryEntity getLatestEntry();
@Override
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE)
public abstract Flowable<List<StreamHistoryEntity>> getAll();
@Override
@Query("DELETE FROM " + STREAM_HISTORY_TABLE)
public abstract int deleteAll();
@Override
public Flowable<List<StreamHistoryEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException();
}
@Query("SELECT * FROM " + STREAM_TABLE
+ " INNER JOIN " + STREAM_HISTORY_TABLE
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " ORDER BY " + STREAM_ACCESS_DATE + " DESC")
public abstract Flowable<List<StreamHistoryEntry>> getHistory();
@Query("SELECT * FROM " + STREAM_TABLE
+ " INNER JOIN " + STREAM_HISTORY_TABLE
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " ORDER BY " + STREAM_ID + " ASC")
public abstract Flowable<List<StreamHistoryEntry>> getHistorySortedById();
@Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID
+ " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1")
@Nullable
public abstract StreamHistoryEntity getLatestEntry(long streamId);
@Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId")
public abstract int deleteStreamHistory(long streamId);
@RewriteQueriesToDropUnusedColumns
@Query("SELECT * FROM " + STREAM_TABLE
// Select the latest entry and watch count for each stream id on history table
+ " INNER JOIN "
+ "(SELECT " + JOIN_STREAM_ID + ", "
+ " MAX(" + STREAM_ACCESS_DATE + ") AS " + STREAM_LATEST_DATE + ", "
+ " SUM(" + STREAM_REPEAT_COUNT + ") AS " + STREAM_WATCH_COUNT
+ " FROM " + STREAM_HISTORY_TABLE + " GROUP BY " + JOIN_STREAM_ID + ")"
+ " 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)
public abstract Flowable<List<StreamStatisticsEntry>> getStatistics();
}
@@ -0,0 +1,66 @@
package org.schabi.newpipe.database.history.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RawQuery
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.history.model.StreamHistoryEntry
import org.schabi.newpipe.database.stream.StreamStatisticsEntry
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.local.history.SortKey
@Dao
interface StreamHistoryDAO {
@Insert
fun insert(entity: StreamHistoryEntity): Long
@Delete
fun delete(entity: StreamHistoryEntity)
@Query("DELETE FROM stream_history")
fun deleteAll(): Completable
@Query("SELECT * FROM streams INNER JOIN stream_history ON uid = stream_id ORDER BY uid ASC")
fun getHistorySortedById(): Flowable<List<StreamHistoryEntry>>
@Query("SELECT * FROM stream_history WHERE stream_id = :streamId ORDER BY access_date DESC LIMIT 1")
fun getLatestEntry(streamId: Long): StreamHistoryEntity?
@Query("DELETE FROM stream_history WHERE stream_id = :streamId")
fun deleteStreamHistory(streamId: Long): Completable
@Query("SELECT * FROM streams INNER JOIN stream_history ON uid = stream_id ORDER BY access_date DESC")
fun getHistory(): Flowable<List<StreamHistoryEntry>>
@RawQuery(
observedEntities = [StreamStatisticsEntry::class, StreamEntity::class, StreamHistoryEntity::class]
)
fun getOrderedHistoryByRaw(query: SupportSQLiteQuery): PagingSource<Int, StreamStatisticsEntry>
fun getOrderedHistory(key: SortKey): PagingSource<Int, StreamStatisticsEntry> {
val orderBy = when (key) {
SortKey.LAST_PLAYED -> "latestAccess"
SortKey.MOST_PLAYED -> "watchCount"
}
return getOrderedHistoryByRaw(
SimpleSQLiteQuery(
"""SELECT * FROM streams INNER JOIN
(SELECT stream_id, MAX(access_date) AS latestAccess,
SUM(repeat_count) AS watchCount FROM stream_history GROUP BY stream_id)
ON uid = stream_id LEFT JOIN
(SELECT stream_id AS stream_id_alias, progress_time FROM stream_state)
ON uid = stream_id_alias
ORDER BY $orderBy DESC
""".trimIndent()
)
)
}
}
@@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
@@ -11,23 +17,24 @@ import java.time.OffsetDateTime
tableName = SearchHistoryEntry.TABLE_NAME,
indices = [Index(value = [SearchHistoryEntry.SEARCH])]
)
data class SearchHistoryEntry(
@field:ColumnInfo(name = CREATION_DATE) var creationDate: OffsetDateTime?,
@field:ColumnInfo(
name = SERVICE_ID
) var serviceId: Int,
@field:ColumnInfo(name = SEARCH) var search: String?
) {
data class SearchHistoryEntry @JvmOverloads constructor(
@ColumnInfo(name = CREATION_DATE)
var creationDate: OffsetDateTime?,
@ColumnInfo(name = SERVICE_ID)
val serviceId: Int,
@ColumnInfo(name = SEARCH)
val search: String?,
@ColumnInfo(name = ID)
@PrimaryKey(autoGenerate = true)
var id: Long = 0
val id: Long = 0
) {
@Ignore
fun hasEqualValues(otherEntry: SearchHistoryEntry): Boolean {
return (
serviceId == otherEntry.serviceId &&
search == otherEntry.search
)
return serviceId == otherEntry.serviceId && search == otherEntry.search
}
companion object {
@@ -1,81 +0,0 @@
package org.schabi.newpipe.database.history.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import java.time.OffsetDateTime;
import static androidx.room.ForeignKey.CASCADE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE;
@Entity(tableName = STREAM_HISTORY_TABLE,
primaryKeys = {JOIN_STREAM_ID, STREAM_ACCESS_DATE},
// No need to index for timestamp as they will almost always be unique
indices = {@Index(value = {JOIN_STREAM_ID})},
foreignKeys = {
@ForeignKey(entity = StreamEntity.class,
parentColumns = StreamEntity.STREAM_ID,
childColumns = JOIN_STREAM_ID,
onDelete = CASCADE, onUpdate = CASCADE)
})
public class StreamHistoryEntity {
public static final String STREAM_HISTORY_TABLE = "stream_history";
public static final String JOIN_STREAM_ID = "stream_id";
public static final String STREAM_ACCESS_DATE = "access_date";
public static final String STREAM_REPEAT_COUNT = "repeat_count";
@ColumnInfo(name = JOIN_STREAM_ID)
private long streamUid;
@NonNull
@ColumnInfo(name = STREAM_ACCESS_DATE)
private OffsetDateTime accessDate;
@ColumnInfo(name = STREAM_REPEAT_COUNT)
private long repeatCount;
/**
* @param streamUid the stream id this history item will refer to
* @param accessDate the last time the stream was accessed
* @param repeatCount the total number of views this stream received
*/
public StreamHistoryEntity(final long streamUid,
@NonNull final OffsetDateTime accessDate,
final long repeatCount) {
this.streamUid = streamUid;
this.accessDate = accessDate;
this.repeatCount = repeatCount;
}
public long getStreamUid() {
return streamUid;
}
public void setStreamUid(final long streamUid) {
this.streamUid = streamUid;
}
@NonNull
public OffsetDateTime getAccessDate() {
return accessDate;
}
public void setAccessDate(@NonNull final OffsetDateTime accessDate) {
this.accessDate = accessDate;
}
public long getRepeatCount() {
return repeatCount;
}
public void setRepeatCount(final long repeatCount) {
this.repeatCount = repeatCount;
}
}
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2018-2022 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.ForeignKey.Companion.CASCADE
import androidx.room.Index
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity
/**
* @param streamUid the stream id this history item will refer to
* @param accessDate the last time the stream was accessed
* @param repeatCount the total number of views this stream received
*/
@Entity(
tableName = "stream_history",
primaryKeys = ["stream_id", "access_date"],
indices = [Index(value = ["stream_id"])],
foreignKeys = [
ForeignKey(
entity = StreamEntity::class,
parentColumns = arrayOf("uid"),
childColumns = arrayOf("stream_id"),
onDelete = CASCADE,
onUpdate = CASCADE
)
]
)
data class StreamHistoryEntity(
@ColumnInfo(name = "stream_id")
val streamUid: Long,
@ColumnInfo(name = "access_date")
var accessDate: OffsetDateTime,
@ColumnInfo(name = "repeat_count")
var repeatCount: Long
)
@@ -2,20 +2,22 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import org.schabi.newpipe.database.stream.model.StreamEntity
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
data class StreamHistoryEntry(
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
@ColumnInfo(name = "stream_id")
val streamId: Long,
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
@ColumnInfo(name = "access_date")
val accessDate: OffsetDateTime,
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
@ColumnInfo(name = "repeat_count")
val repeatCount: Long
) {
@@ -27,4 +29,16 @@ data class StreamHistoryEntry(
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
accessDate.isEqual(other.accessDate)
}
fun toStreamInfoItem(): StreamInfoItem = StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}
}
@@ -1,29 +0,0 @@
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;
}
}
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2023-2024 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist
import androidx.room.ColumnInfo
import org.schabi.newpipe.database.playlist.model.PlaylistEntity
/**
* This class adds a field to [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
*/
data class PlaylistDuplicatesEntry(
@ColumnInfo(name = PlaylistEntity.PLAYLIST_ID)
override val uid: Long,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_URL)
override val thumbnailUrl: String?,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_PERMANENT)
override val isThumbnailPermanent: Boolean?,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID)
override val thumbnailStreamId: Long?,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_DISPLAY_INDEX)
override var displayIndex: Long?,
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
override val streamCount: Long,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_NAME)
override val orderingName: String?,
@ColumnInfo(name = PLAYLIST_TIMES_STREAM_IS_CONTAINED)
val timesStreamIsContained: Long
) : PlaylistMetadataEntry(
uid = uid,
orderingName = orderingName,
thumbnailUrl = thumbnailUrl,
isThumbnailPermanent = isThumbnailPermanent,
thumbnailStreamId = thumbnailStreamId,
displayIndex = displayIndex,
streamCount = streamCount
) {
companion object {
const val PLAYLIST_TIMES_STREAM_IS_CONTAINED: String = "timesStreamIsContained"
}
}
@@ -1,13 +0,0 @@
package org.schabi.newpipe.database.playlist;
import org.schabi.newpipe.database.LocalItem;
public interface PlaylistLocalItem extends LocalItem {
String getOrderingName();
long getDisplayIndex();
long getUid();
void setDisplayIndex(long displayIndex);
}
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2018-2025 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist
import org.schabi.newpipe.database.LocalItem
interface PlaylistLocalItem : LocalItem {
val orderingName: String?
val displayIndex: Long?
val uid: Long
val thumbnailUrl: String?
}
@@ -1,74 +0,0 @@
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)
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 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;
}
@Override
public LocalItemType getLocalItemType() {
return LocalItemType.PLAYLIST_LOCAL_ITEM;
}
@Override
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;
}
}
@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2018-2025 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist
import androidx.room.ColumnInfo
import org.schabi.newpipe.database.LocalItem.LocalItemType
import org.schabi.newpipe.database.playlist.model.PlaylistEntity
open class PlaylistMetadataEntry(
@ColumnInfo(name = PlaylistEntity.PLAYLIST_ID)
override val uid: Long,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_NAME)
override val orderingName: String?,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_URL)
override val thumbnailUrl: String?,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_DISPLAY_INDEX)
override var displayIndex: Long?,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_PERMANENT)
open val isThumbnailPermanent: Boolean?,
@ColumnInfo(name = PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID)
open val thumbnailStreamId: Long?,
@ColumnInfo(name = PLAYLIST_STREAM_COUNT)
open val streamCount: Long
) : PlaylistLocalItem {
override val localItemType: LocalItemType
get() = LocalItemType.PLAYLIST_LOCAL_ITEM
companion object {
const val PLAYLIST_STREAM_COUNT: String = "streamCount"
}
}
@@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2020-2023 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist
import androidx.room.ColumnInfo
@@ -5,7 +11,6 @@ import androidx.room.Embedded
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
@@ -13,7 +18,7 @@ data class PlaylistStreamEntry(
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamStateEntity.STREAM_PROGRESS_MILLIS, defaultValue = "0")
@ColumnInfo(name = "progress_time", defaultValue = "0")
val progressMillis: Long,
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
@@ -23,18 +28,21 @@ data class PlaylistStreamEntry(
val joinIndex: Int
) : LocalItem {
override val localItemType: LocalItem.LocalItemType
get() = LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM
@Throws(IllegalArgumentException::class)
fun toStreamInfoItem(): StreamInfoItem {
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
return item
}
override fun getLocalItemType(): LocalItem.LocalItemType {
return LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM
return StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}
}
}
@@ -1,53 +0,0 @@
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;
import java.util.List;
import io.reactivex.rxjava3.core.Flowable;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
@Dao
public interface PlaylistDAO extends BasicDAO<PlaylistEntity> {
@Override
@Query("SELECT * FROM " + PLAYLIST_TABLE)
Flowable<List<PlaylistEntity>> getAll();
@Override
@Query("DELETE FROM " + PLAYLIST_TABLE)
int deleteAll();
@Override
default Flowable<List<PlaylistEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException();
}
@Query("SELECT * FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
Flowable<List<PlaylistEntity>> getPlaylist(long playlistId);
@Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
int deletePlaylist(long playlistId);
@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;
}
}
}
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2018-2022 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.playlist.model.PlaylistEntity
@Dao
interface PlaylistDAO : BasicDAO<PlaylistEntity> {
@Query("SELECT * FROM playlists")
override fun getAll(): Flowable<List<PlaylistEntity>>
@Query("DELETE FROM playlists")
override fun deleteAll(): Int
override fun listByService(serviceId: Int): Flowable<List<PlaylistEntity>> {
throw UnsupportedOperationException()
}
@Query("SELECT * FROM playlists WHERE uid = :playlistId")
fun getPlaylist(playlistId: Long): Flowable<MutableList<PlaylistEntity>>
@Query("DELETE FROM playlists WHERE uid = :playlistId")
fun deletePlaylist(playlistId: Long): Int
@get:Query("SELECT COUNT(*) FROM playlists")
val count: Flowable<Long>
@Transaction
fun upsertPlaylist(playlist: PlaylistEntity): Long {
if (playlist.uid == -1L) {
// This situation is probably impossible.
return insert(playlist)
} else {
update(playlist)
return playlist.uid
}
}
}
@@ -1,68 +0,0 @@
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.PlaylistRemoteEntity;
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;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
@Dao
public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
@Override
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE)
Flowable<List<PlaylistRemoteEntity>> getAll();
@Override
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE)
int deleteAll();
@Override
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE
+ " 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")
Long getPlaylistIdInternal(long serviceId, String url);
@Transaction
default long upsert(final PlaylistRemoteEntity playlist) {
final Long playlistId = getPlaylistIdInternal(playlist.getServiceId(), playlist.getUrl());
if (playlistId == null) {
return insert(playlist);
} else {
playlist.setUid(playlistId);
update(playlist);
return playlistId;
}
}
@Query("DELETE FROM " + REMOTE_PLAYLIST_TABLE
+ " WHERE " + REMOTE_PLAYLIST_ID + " = :playlistId")
int deletePlaylist(long playlistId);
}
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2018-2025 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.rxjava3.core.Flowable
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
@Dao
interface PlaylistRemoteDAO : BasicDAO<PlaylistRemoteEntity> {
@Query("SELECT * FROM remote_playlists")
override fun getAll(): Flowable<List<PlaylistRemoteEntity>>
@Query("DELETE FROM remote_playlists")
override fun deleteAll(): Int
@Query("SELECT * FROM remote_playlists WHERE service_id = :serviceId")
override fun listByService(serviceId: Int): Flowable<List<PlaylistRemoteEntity>>
@Query("SELECT * FROM remote_playlists WHERE uid = :playlistId")
fun getPlaylist(playlistId: Long): Flowable<PlaylistRemoteEntity>
@Query("SELECT * FROM remote_playlists WHERE url = :url AND service_id = :serviceId")
fun getPlaylist(serviceId: Long, url: String?): Flowable<MutableList<PlaylistRemoteEntity>>
@get:Query("SELECT * FROM remote_playlists ORDER BY display_index")
val playlists: Flowable<MutableList<PlaylistRemoteEntity>>
@Query("SELECT uid FROM remote_playlists WHERE url = :url AND service_id = :serviceId")
fun getPlaylistIdInternal(serviceId: Long, url: String?): Long?
@Transaction
fun upsert(playlist: PlaylistRemoteEntity): Long {
val playlistId = getPlaylistIdInternal(playlist.serviceId.toLong(), playlist.url)
if (playlistId == null) {
return insert(playlist)
} else {
playlist.uid = playlistId
update(playlist)
return playlistId
}
}
@Query("DELETE FROM remote_playlists WHERE uid = :playlistId")
fun deletePlaylist(playlistId: Long): Int
}
@@ -1,159 +0,0 @@
package org.schabi.newpipe.database.playlist.dao;
import androidx.room.Dao;
import androidx.room.Query;
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;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID;
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;
@Dao
public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
@Override
@Query("SELECT * FROM " + PLAYLIST_STREAM_JOIN_TABLE)
Flowable<List<PlaylistStreamEntity>> getAll();
@Override
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE)
int deleteAll();
@Override
default Flowable<List<PlaylistStreamEntity>> listByService(final int serviceId) {
throw new UnsupportedOperationException();
}
@Query("DELETE FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId")
void deleteBatch(long playlistId);
@Query("SELECT COALESCE(MAX(" + JOIN_INDEX + "), -1)"
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " 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 "
// get ids of streams of the given playlist
+ "(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)"
// then merge with the stream metadata
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_MILLIS
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS
+ " ORDER BY " + JOIN_INDEX + " ASC")
Flowable<List<PlaylistStreamEntry>> getOrderedStreamsOf(long playlistId);
@Transaction
@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_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_DISPLAY_INDEX)
Flowable<List<PlaylistDuplicatesEntry>> getPlaylistDuplicatesMetadata(String streamUrl);
}
@@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2018-2024 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist.dao
import androidx.room.Dao
import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import androidx.room.Transaction
import io.reactivex.rxjava3.core.Flowable
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.Companion.DEFAULT_THUMBNAIL_ID
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
@Dao
interface PlaylistStreamDAO : BasicDAO<PlaylistStreamEntity> {
@Query("SELECT * FROM playlist_stream_join")
override fun getAll(): Flowable<List<PlaylistStreamEntity>>
@Query("DELETE FROM playlist_stream_join")
override fun deleteAll(): Int
override fun listByService(serviceId: Int): Flowable<List<PlaylistStreamEntity>> {
throw UnsupportedOperationException()
}
@Query("DELETE FROM playlist_stream_join WHERE playlist_id = :playlistId")
fun deleteBatch(playlistId: Long)
@Query("SELECT COALESCE(MAX(join_index), -1) FROM playlist_stream_join WHERE playlist_id = :playlistId")
fun getMaximumIndexOf(playlistId: Long): Flowable<Int>
@Query(
"""
SELECT CASE WHEN COUNT(*) != 0 then stream_id ELSE $DEFAULT_THUMBNAIL_ID END
FROM streams
LEFT JOIN playlist_stream_join
ON uid = stream_id
WHERE playlist_id = :playlistId LIMIT 1
"""
)
fun getAutomaticThumbnailStreamId(playlistId: Long): Flowable<Long>
// get ids of streams of the given playlist then merge with the stream metadata
@RewriteQueriesToDropUnusedColumns
@Transaction
@Query(
"""
SELECT * FROM streams
INNER JOIN (SELECT stream_id, join_index FROM playlist_stream_join WHERE playlist_id = :playlistId)
ON uid = stream_id
LEFT JOIN (SELECT stream_id AS stream_id_alias, progress_time FROM stream_state )
ON uid = stream_id_alias
ORDER BY join_index ASC
"""
)
fun getOrderedStreamsOf(playlistId: Long): Flowable<MutableList<PlaylistStreamEntry>>
// If a playlist has no streams, there wont be any rows in the **playlist_stream_join** table
// that have a foreign key to that playlist. Thus, the **playlist_id** will not have a
// corresponding value in any rows of the join table. So, if you group by the **playlist_id**,
// only playlists that contain videos are grouped and displayed. Look at #9642 #13055
@Transaction
@Query(
"""
SELECT uid, name, is_thumbnail_permanent, thumbnail_stream_id, display_index,
(SELECT thumbnail_url FROM streams WHERE streams.uid = thumbnail_stream_id) AS thumbnail_url,
COALESCE(COUNT(playlist_id), 0) AS streamCount FROM playlists
LEFT JOIN playlist_stream_join
ON playlists.uid = playlist_id
GROUP BY uid
ORDER BY display_index
"""
)
fun getPlaylistMetadata(): Flowable<MutableList<PlaylistMetadataEntry>>
@RewriteQueriesToDropUnusedColumns
@Transaction
@Query(
"""
SELECT *, MIN(join_index) FROM streams
INNER JOIN (SELECT stream_id, join_index FROM playlist_stream_join WHERE playlist_id = :playlistId)
ON uid = stream_id
LEFT JOIN (SELECT stream_id AS stream_id_alias, progress_time FROM stream_state )
ON uid = stream_id_alias
GROUP BY uid
ORDER BY MIN(join_index) ASC
"""
)
fun getStreamsWithoutDuplicates(playlistId: Long): Flowable<MutableList<PlaylistStreamEntry>>
// If a playlist has no streams, there wont be any rows in the **playlist_stream_join** table
// that have a foreign key to that playlist. Thus, the **playlist_id** will not have a
// corresponding value in any rows of the join table. So, if you group by the **playlist_id**,
// only playlists that contain videos are grouped and displayed. Look at #9642 #13055
@Transaction
@Query(
"""
SELECT playlists.uid, name, is_thumbnail_permanent, thumbnail_stream_id, display_index,
(SELECT thumbnail_url FROM streams WHERE streams.uid = thumbnail_stream_id) AS thumbnail_url,
COALESCE(COUNT(playlist_id), 0) AS streamCount,
COALESCE(SUM(url = :streamUrl), 0) AS timesStreamIsContained FROM playlists
LEFT JOIN playlist_stream_join
ON playlists.uid = playlist_id
LEFT JOIN streams
ON streams.uid = stream_id AND :streamUrl = :streamUrl
GROUP BY playlists.uid
ORDER BY display_index, name
"""
)
fun getPlaylistDuplicatesMetadata(streamUrl: String): Flowable<MutableList<PlaylistDuplicatesEntry>>
}
@@ -1,100 +0,0 @@
package org.schabi.newpipe.database.playlist.model;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE;
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)
private long uid = 0;
@ColumnInfo(name = PLAYLIST_NAME)
private String name;
@ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT)
private boolean isThumbnailPermanent;
@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.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() {
return uid;
}
public void setUid(final long uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public long getThumbnailStreamId() {
return thumbnailStreamId;
}
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;
}
}
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2018-2024 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry
@Entity(tableName = PlaylistEntity.Companion.PLAYLIST_TABLE)
data class PlaylistEntity @JvmOverloads constructor(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = PLAYLIST_ID)
var uid: Long = 0,
@ColumnInfo(name = PLAYLIST_NAME)
var name: String?,
@ColumnInfo(name = PLAYLIST_THUMBNAIL_PERMANENT)
var isThumbnailPermanent: Boolean,
@ColumnInfo(name = PLAYLIST_THUMBNAIL_STREAM_ID)
var thumbnailStreamId: Long,
@ColumnInfo(name = PLAYLIST_DISPLAY_INDEX)
var displayIndex: Long
) {
@Ignore
constructor(item: PlaylistMetadataEntry) : this(
uid = item.uid,
name = item.orderingName,
isThumbnailPermanent = item.isThumbnailPermanent!!,
thumbnailStreamId = item.thumbnailStreamId!!,
displayIndex = item.displayIndex!!
)
companion object {
const val DEFAULT_THUMBNAIL_ID = -1L
const val PLAYLIST_TABLE = "playlists"
const val PLAYLIST_ID = "uid"
const val PLAYLIST_NAME = "name"
const val PLAYLIST_THUMBNAIL_URL = "thumbnail_url"
const val PLAYLIST_DISPLAY_INDEX = "display_index"
const val PLAYLIST_THUMBNAIL_PERMANENT = "is_thumbnail_permanent"
const val PLAYLIST_THUMBNAIL_STREAM_ID = "thumbnail_stream_id"
}
}
@@ -1,188 +0,0 @@
package org.schabi.newpipe.database.playlist.model;
import android.text.TextUtils;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
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;
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;
import static org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.REMOTE_PLAYLIST_URL;
@Entity(tableName = REMOTE_PLAYLIST_TABLE,
indices = {
@Index(value = {REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL}, unique = true)
})
public class PlaylistRemoteEntity implements PlaylistLocalItem {
public static final String REMOTE_PLAYLIST_TABLE = "remote_playlists";
public static final String REMOTE_PLAYLIST_ID = "uid";
public static final String REMOTE_PLAYLIST_SERVICE_ID = "service_id";
public static final String REMOTE_PLAYLIST_NAME = "name";
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)
@ColumnInfo(name = REMOTE_PLAYLIST_ID)
private long uid = 0;
@ColumnInfo(name = REMOTE_PLAYLIST_SERVICE_ID)
private int serviceId = Constants.NO_SERVICE_ID;
@ColumnInfo(name = REMOTE_PLAYLIST_NAME)
private String name;
@ColumnInfo(name = REMOTE_PLAYLIST_URL)
private String url;
@ColumnInfo(name = REMOTE_PLAYLIST_THUMBNAIL_URL)
private String thumbnailUrl;
@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;
public PlaylistRemoteEntity(final int serviceId, final String name, final String url,
final String thumbnailUrl, final String uploader,
final Long streamCount) {
this.serviceId = serviceId;
this.name = name;
this.url = url;
this.thumbnailUrl = thumbnailUrl;
this.uploader = uploader;
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(),
// use uploader avatar when no thumbnail is available
ImageStrategy.imageListToDbUrl(info.getThumbnails().isEmpty()
? info.getUploaderAvatars() : info.getThumbnails()),
info.getUploaderName(), info.getStreamCount());
}
@Ignore
public boolean isIdenticalTo(final PlaylistInfo info) {
/*
* Returns boolean comparing the online playlist and the local copy.
* (False if info changed such as playlist name or track count)
*/
return getServiceId() == info.getServiceId()
&& getStreamCount() == info.getStreamCount()
&& TextUtils.equals(getName(), info.getName())
&& TextUtils.equals(getUrl(), info.getUrl())
// 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;
}
public void setUid(final long uid) {
this.uid = uid;
}
public int getServiceId() {
return serviceId;
}
public void setServiceId(final int serviceId) {
this.serviceId = serviceId;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public void setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
public String getUrl() {
return url;
}
public void setUrl(final String url) {
this.url = url;
}
public String getUploader() {
return uploader;
}
public void setUploader(final String uploader) {
this.uploader = uploader;
}
@Override
public long getDisplayIndex() {
return displayIndex;
}
@Override
public void setDisplayIndex(final long displayIndex) {
this.displayIndex = displayIndex;
}
public Long getStreamCount() {
return streamCount;
}
public void setStreamCount(final Long streamCount) {
this.streamCount = streamCount;
}
@Override
public LocalItemType getLocalItemType() {
return PLAYLIST_REMOTE_ITEM;
}
@Override
public String getOrderingName() {
return name;
}
}
@@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: 2018-2025 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist.model
import android.text.TextUtils
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import org.schabi.newpipe.database.LocalItem.LocalItemType
import org.schabi.newpipe.database.playlist.PlaylistLocalItem
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.Companion.REMOTE_PLAYLIST_SERVICE_ID
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.Companion.REMOTE_PLAYLIST_TABLE
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity.Companion.REMOTE_PLAYLIST_URL
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
import org.schabi.newpipe.util.NO_SERVICE_ID
import org.schabi.newpipe.util.image.ImageStrategy
@Entity(
tableName = REMOTE_PLAYLIST_TABLE,
indices = [
Index(
value = [REMOTE_PLAYLIST_SERVICE_ID, REMOTE_PLAYLIST_URL],
unique = true
)
]
)
data class PlaylistRemoteEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = REMOTE_PLAYLIST_ID)
override var uid: Long = 0,
@ColumnInfo(name = REMOTE_PLAYLIST_SERVICE_ID)
val serviceId: Int = NO_SERVICE_ID,
@ColumnInfo(name = REMOTE_PLAYLIST_NAME)
override val orderingName: String?,
@ColumnInfo(name = REMOTE_PLAYLIST_URL)
val url: String?,
@ColumnInfo(name = REMOTE_PLAYLIST_THUMBNAIL_URL)
override val thumbnailUrl: String?,
@ColumnInfo(name = REMOTE_PLAYLIST_UPLOADER_NAME)
val uploader: String?,
@ColumnInfo(name = REMOTE_PLAYLIST_DISPLAY_INDEX)
override var displayIndex: Long = -1, // Make sure the new item is on the top
@ColumnInfo(name = REMOTE_PLAYLIST_STREAM_COUNT)
val streamCount: Long?
) : PlaylistLocalItem {
constructor(playlistInfo: PlaylistInfo) : this(
serviceId = playlistInfo.serviceId,
orderingName = playlistInfo.name,
url = playlistInfo.url,
thumbnailUrl = ImageStrategy.imageListToDbUrl(
playlistInfo.thumbnails.ifEmpty { playlistInfo.uploaderAvatars }
),
uploader = playlistInfo.uploaderName,
streamCount = playlistInfo.streamCount
)
override val localItemType: LocalItemType
get() = LocalItemType.PLAYLIST_REMOTE_ITEM
/**
* Returns boolean comparing the online playlist and the local copy.
* (False if info changed such as playlist name or track count)
*/
@Ignore
fun isIdenticalTo(info: PlaylistInfo): Boolean {
return this.serviceId == info.serviceId && this.streamCount == info.streamCount &&
TextUtils.equals(this.orderingName, info.name) &&
TextUtils.equals(this.url, info.url) &&
// 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(thumbnailUrl, ImageStrategy.imageListToDbUrl(info.thumbnails)) &&
TextUtils.equals(this.uploader, info.uploaderName)
}
companion object {
const val REMOTE_PLAYLIST_TABLE = "remote_playlists"
const val REMOTE_PLAYLIST_ID = "uid"
const val REMOTE_PLAYLIST_SERVICE_ID = "service_id"
const val REMOTE_PLAYLIST_NAME = "name"
const val REMOTE_PLAYLIST_URL = "url"
const val REMOTE_PLAYLIST_THUMBNAIL_URL = "thumbnail_url"
const val REMOTE_PLAYLIST_UPLOADER_NAME = "uploader"
const val REMOTE_PLAYLIST_DISPLAY_INDEX = "display_index"
const val REMOTE_PLAYLIST_STREAM_COUNT = "stream_count"
}
}
@@ -1,76 +0,0 @@
package org.schabi.newpipe.database.playlist.model;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import static androidx.room.ForeignKey.CASCADE;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.PLAYLIST_STREAM_JOIN_TABLE;
@Entity(tableName = PLAYLIST_STREAM_JOIN_TABLE,
primaryKeys = {JOIN_PLAYLIST_ID, JOIN_INDEX},
indices = {
@Index(value = {JOIN_PLAYLIST_ID, JOIN_INDEX}, unique = true),
@Index(value = {JOIN_STREAM_ID})
},
foreignKeys = {
@ForeignKey(entity = PlaylistEntity.class,
parentColumns = PlaylistEntity.PLAYLIST_ID,
childColumns = JOIN_PLAYLIST_ID,
onDelete = CASCADE, onUpdate = CASCADE, deferred = true),
@ForeignKey(entity = StreamEntity.class,
parentColumns = StreamEntity.STREAM_ID,
childColumns = JOIN_STREAM_ID,
onDelete = CASCADE, onUpdate = CASCADE, deferred = true)
})
public class PlaylistStreamEntity {
public static final String PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join";
public static final String JOIN_PLAYLIST_ID = "playlist_id";
public static final String JOIN_STREAM_ID = "stream_id";
public static final String JOIN_INDEX = "join_index";
@ColumnInfo(name = JOIN_PLAYLIST_ID)
private long playlistUid;
@ColumnInfo(name = JOIN_STREAM_ID)
private long streamUid;
@ColumnInfo(name = JOIN_INDEX)
private int index;
public PlaylistStreamEntity(final long playlistUid, final long streamUid, final int index) {
this.playlistUid = playlistUid;
this.streamUid = streamUid;
this.index = index;
}
public long getPlaylistUid() {
return playlistUid;
}
public void setPlaylistUid(final long playlistUid) {
this.playlistUid = playlistUid;
}
public long getStreamUid() {
return streamUid;
}
public void setStreamUid(final long streamUid) {
this.streamUid = streamUid;
}
public int getIndex() {
return index;
}
public void setIndex(final int index) {
this.index = index;
}
}
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2018-2020 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.playlist.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.ForeignKey.Companion.CASCADE
import androidx.room.Index
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.playlist.model.PlaylistEntity.Companion.PLAYLIST_ID
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.Companion.JOIN_INDEX
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.Companion.JOIN_PLAYLIST_ID
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.Companion.JOIN_STREAM_ID
import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.Companion.PLAYLIST_STREAM_JOIN_TABLE
import org.schabi.newpipe.database.stream.model.StreamEntity
@Entity(
tableName = PLAYLIST_STREAM_JOIN_TABLE,
primaryKeys = [JOIN_PLAYLIST_ID, JOIN_INDEX],
indices = [
Index(value = [JOIN_PLAYLIST_ID, JOIN_INDEX], unique = true),
Index(value = [JOIN_STREAM_ID])
],
foreignKeys = [
ForeignKey(
entity = PlaylistEntity::class,
parentColumns = arrayOf(PLAYLIST_ID),
childColumns = arrayOf(JOIN_PLAYLIST_ID),
onDelete = CASCADE,
onUpdate = CASCADE,
deferred = true
),
ForeignKey(
entity = StreamEntity::class,
parentColumns = arrayOf("uid"),
childColumns = arrayOf(JOIN_STREAM_ID),
onDelete = CASCADE,
onUpdate = CASCADE,
deferred = true
)
]
)
data class PlaylistStreamEntity(
@ColumnInfo(name = JOIN_PLAYLIST_ID)
val playlistUid: Long,
@ColumnInfo(name = JOIN_STREAM_ID)
val streamUid: Long,
@ColumnInfo(name = JOIN_INDEX)
val index: Int
) : LocalItem {
override val localItemType: LocalItem.LocalItemType
get() = LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM
companion object {
const val PLAYLIST_STREAM_JOIN_TABLE = "playlist_stream_join"
const val JOIN_PLAYLIST_ID = "playlist_id"
const val JOIN_STREAM_ID = "stream_id"
const val JOIN_INDEX = "join_index"
}
}
@@ -1,47 +1,52 @@
/*
* SPDX-FileCopyrightText: 2020-2023 NewPipe contributors <https://newpipe.net>
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package org.schabi.newpipe.database.stream
import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.Ignore
import java.time.OffsetDateTime
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_PROGRESS_MILLIS
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
import java.time.OffsetDateTime
class StreamStatisticsEntry(
data class StreamStatisticsEntry(
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = STREAM_PROGRESS_MILLIS, defaultValue = "0")
@ColumnInfo(name = "progress_time", defaultValue = "0")
val progressMillis: Long,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
@ColumnInfo(name = "stream_id")
val streamId: Long,
@ColumnInfo(name = STREAM_LATEST_DATE)
@ColumnInfo(name = "latestAccess")
val latestAccessDate: OffsetDateTime,
@ColumnInfo(name = STREAM_WATCH_COUNT)
@ColumnInfo(name = "watchCount")
val watchCount: Long
) : LocalItem {
override val localItemType: LocalItem.LocalItemType
get() = LocalItem.LocalItemType.STATISTIC_STREAM_ITEM
@Ignore
fun toStreamInfoItem(): StreamInfoItem {
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
return item
}
override fun getLocalItemType(): LocalItem.LocalItemType {
return LocalItem.LocalItemType.STATISTIC_STREAM_ITEM
}
companion object {
const val STREAM_LATEST_DATE = "latestAccess"
const val STREAM_WATCH_COUNT = "watchCount"
return StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}
}
}

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