mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-21 10:40:51 +02:00
Compare commits
149 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d403a83a24 | ||
![]() |
35fc27cfb0 | ||
![]() |
81742565a4 | ||
![]() |
923d0b7c80 | ||
![]() |
d416465371 | ||
![]() |
2586c543d3 | ||
![]() |
c32bc26328 | ||
![]() |
4074c71b6a | ||
![]() |
d7cab6a8d8 | ||
![]() |
eddc12693a | ||
![]() |
ced3898499 | ||
![]() |
318a5df109 | ||
![]() |
b2e9981313 | ||
![]() |
74f43639ad | ||
![]() |
fc342bd458 | ||
![]() |
adfbf5b49f | ||
![]() |
2cb7bb84f7 | ||
![]() |
84d1792e7f | ||
![]() |
531859ac60 | ||
![]() |
bf071d65d7 | ||
![]() |
e4aa7a90c7 | ||
![]() |
6d4e3c5633 | ||
![]() |
19f9b4f502 | ||
![]() |
f3dbb19364 | ||
![]() |
0a831ec84e | ||
![]() |
0c656abb8e | ||
![]() |
7a7a90bf79 | ||
![]() |
908dff3931 | ||
![]() |
a786cff036 | ||
![]() |
28802805f8 | ||
![]() |
f59099395f | ||
![]() |
0fe3fe7594 | ||
![]() |
467dacd35a | ||
![]() |
173150591d | ||
![]() |
e4d94b1a4e | ||
![]() |
75e34a5a8e | ||
![]() |
d6121c8e21 | ||
![]() |
b4d77df1be | ||
![]() |
e6021465f6 | ||
![]() |
22ec70e94d | ||
![]() |
a1a70a94a8 | ||
![]() |
a65ed7e914 | ||
![]() |
4545b8e92d | ||
![]() |
ba0c0fb109 | ||
![]() |
18d530021c | ||
![]() |
31bb70e333 | ||
![]() |
a919a039e5 | ||
![]() |
aacb1f46a8 | ||
![]() |
96862cbcb3 | ||
![]() |
10f79e1307 | ||
![]() |
e0ee3dce40 | ||
![]() |
13e7d2e7ac | ||
![]() |
a7723373a0 | ||
![]() |
7e469ead45 | ||
![]() |
5397a4e410 | ||
![]() |
99b59f0126 | ||
![]() |
d46c7eb8fe | ||
![]() |
e4a1fc9d95 | ||
![]() |
276f50a944 | ||
![]() |
40fcd93312 | ||
![]() |
807e4d4af9 | ||
![]() |
480348f11a | ||
![]() |
30613b7064 | ||
![]() |
79189dcc83 | ||
![]() |
c2210330b6 | ||
![]() |
917f459569 | ||
![]() |
0ced9ba799 | ||
![]() |
5e95277d7c | ||
![]() |
efb417dba7 | ||
![]() |
5c2d4c4d9d | ||
![]() |
e8bd9920fd | ||
![]() |
69ed531a5c | ||
![]() |
b967d7c148 | ||
![]() |
90150c42ed | ||
![]() |
8e2fd9ccce | ||
![]() |
567ffad41d | ||
![]() |
3c306a0971 | ||
![]() |
c0d6c8aeb3 | ||
![]() |
b27b49e4f3 | ||
![]() |
7ed0dbcf1a | ||
![]() |
8a23de6b20 | ||
![]() |
6cc3089204 | ||
![]() |
093e95c078 | ||
![]() |
7c8ac04e35 | ||
![]() |
dc88f8b172 | ||
![]() |
a00ac6b9ca | ||
![]() |
c94f0ded27 | ||
![]() |
b553aa2159 | ||
![]() |
a7bd2666f0 | ||
![]() |
fe2fc60581 | ||
![]() |
ce59c05d5b | ||
![]() |
a4858bc702 | ||
![]() |
a2bb58a991 | ||
![]() |
f7b41227d2 | ||
![]() |
5b1a6831d5 | ||
![]() |
42b1bbe414 | ||
![]() |
ac86fe80c8 | ||
![]() |
db9f20a22f | ||
![]() |
b30e025bda | ||
![]() |
5f3eb4871a | ||
![]() |
9a223532c5 | ||
![]() |
cf67b592da | ||
![]() |
e867bfbc82 | ||
![]() |
9a671851df | ||
![]() |
4b92f78cc8 | ||
![]() |
c585982557 | ||
![]() |
6bf22e7ad0 | ||
![]() |
2f8dccf7f6 | ||
![]() |
027768d97d | ||
![]() |
085f63b8c5 | ||
![]() |
6f7c337e00 | ||
![]() |
16a968f3bb | ||
![]() |
d7e0167fed | ||
![]() |
41c4f515cf | ||
![]() |
d9a8218372 | ||
![]() |
dd9bd4da8b | ||
![]() |
cf98500b7f | ||
![]() |
2ce8facc05 | ||
![]() |
d1b117d07c | ||
![]() |
c0377c7ebf | ||
![]() |
a2490a5730 | ||
![]() |
177334ba62 | ||
![]() |
f7f00293cc | ||
![]() |
7bce588767 | ||
![]() |
4bb67c634f | ||
![]() |
3653afbcc4 | ||
![]() |
1f4a4ea09f | ||
![]() |
3d38add4b4 | ||
![]() |
124b7eefb5 | ||
![]() |
b52924048c | ||
![]() |
93393f5dff | ||
![]() |
275a75ebaa | ||
![]() |
3e4a7a19cc | ||
![]() |
734af457f3 | ||
![]() |
55bdb1f47a | ||
![]() |
adff0d199d | ||
![]() |
f95b3262a0 | ||
![]() |
794a14e76c | ||
![]() |
ba857b5ef7 | ||
![]() |
2aed04a8c2 | ||
![]() |
5f9e6b51da | ||
![]() |
e7b5c99ed6 | ||
![]() |
9c0b3d35be | ||
![]() |
4277b6e262 | ||
![]() |
506c4ce701 | ||
![]() |
d251e58984 | ||
![]() |
7a8dab2d58 | ||
![]() |
6f3dfad550 | ||
![]() |
b822c5a039 |
30
.github/workflows/ci.yml
vendored
Normal file
30
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1.4.3
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
restore-keys: ${{ runner.os }}-gradle
|
||||
|
||||
- name: Build debug APK and run Tests
|
||||
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app
|
||||
path: app/build/outputs/apk/debug/*.apk
|
18
.travis.yml
18
.travis.yml
@@ -1,18 +0,0 @@
|
||||
language: android
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
android:
|
||||
components:
|
||||
# The BuildTools version used by NewPipe
|
||||
- tools
|
||||
- build-tools-29.0.3
|
||||
|
||||
# The SDK version used to compile NewPipe
|
||||
- android-29
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-29"
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||
|
||||
licenses:
|
||||
- '.+'
|
10
README.ko.md
10
README.ko.md
@@ -1,4 +1,4 @@
|
||||
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||
<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 frontend for Android.</h4>
|
||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
||||
@@ -13,7 +13,7 @@
|
||||
</p>
|
||||
<hr>
|
||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#updates">Updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||
<p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/FAQ/">FAQ</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p>
|
||||
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
||||
<hr>
|
||||
|
||||
*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
|
||||
@@ -86,7 +86,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
|
||||
1. 직접 디버그 APK를 생성할 수 있습니다. 이 방법은 당신의 기기에서 새로운 기능을 얻을 수 있는 가장 빠른 방법이지만, 꽤 많이 복잡합니다.
|
||||
따라서 우리는 다른 방법들 중 하나를 사용하는 것을 추천합니다.
|
||||
2. 우리의 커스텀 저장소를 F-Droid에 추가하고 우리가 릴리즈를 게시하는 대로 저곳에서 릴리즈를 설치할 수 있습니다.
|
||||
이에 대한 설명서는 이곳에서 확인할 수 있습니다: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
|
||||
이에 대한 설명서는 이곳에서 확인할 수 있습니다: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
|
||||
3. 우리가 릴리즈를 게시하는 대로 [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases)에서 APK를 다운받고 이것을 설치할 수 있습니다.
|
||||
4. F-Droid를 통해 업데이트 할 수 있습니다. F-Droid는 변화를 인식하고, 스스로 APK를 생성하고, 이것에 서명하고, 사용자들에서 업데이트를 전달해야만 하기 때문에,
|
||||
이것은 업데이트를 받는 가장 느린 방법입니다.
|
||||
@@ -111,7 +111,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
|
||||
</a>
|
||||
|
||||
## Donate
|
||||
만약 NewPipe가 마음에 들었다면, 우리는 기부에 대해 기꺼이 환영합니다. bitcoin을 보내거나, Bountysource 또는 Liberapay를 통해 기부할 수 있습니다. NewPipe에 기부하는 것에 대한 자세한 정보를 원한다면, 우리의 [웹사이트](https://newpipe.schabi.org/donate)를 방문하여 주십시오.
|
||||
만약 NewPipe가 마음에 들었다면, 우리는 기부에 대해 기꺼이 환영합니다. bitcoin을 보내거나, Bountysource 또는 Liberapay를 통해 기부할 수 있습니다. NewPipe에 기부하는 것에 대한 자세한 정보를 원한다면, 우리의 [웹사이트](https://newpipe.net/donate)를 방문하여 주십시오.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -134,7 +134,7 @@ NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로
|
||||
## Privacy Policy
|
||||
|
||||
NewPipe 프로젝트는 미디어 웹 서비스를 사용하는 것에 대한 사적의, 익명의 경험을 제공하는 것을 목표로 하고 있습니다.
|
||||
그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.schabi.org/legal/privacy/)에서 확인할 수 있습니다.
|
||||
그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.net/legal/privacy/)에서 확인할 수 있습니다.
|
||||
|
||||
## License
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
10
README.md
10
README.md
@@ -1,4 +1,4 @@
|
||||
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||
<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 frontend for Android.</h4>
|
||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
||||
@@ -13,7 +13,7 @@
|
||||
</p>
|
||||
<hr>
|
||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#updates">Updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||
<p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/FAQ/">FAQ</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p>
|
||||
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
||||
<hr>
|
||||
|
||||
*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
|
||||
@@ -83,7 +83,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
|
||||
## Updates
|
||||
When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
|
||||
1. 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.
|
||||
2. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
|
||||
2. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
|
||||
3. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it as soon as we publish a release.
|
||||
4. Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
|
||||
|
||||
@@ -106,7 +106,7 @@ If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTI
|
||||
</a>
|
||||
|
||||
## Donate
|
||||
If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).
|
||||
If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.net/donate).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -129,7 +129,7 @@ If you like NewPipe we'd be happy about a donation. You can either send bitcoin
|
||||
## Privacy Policy
|
||||
|
||||
The NewPipe project aims to provide a private, anonymous experience for using media web services.
|
||||
Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.schabi.org/legal/privacy/).
|
||||
Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or comment in our blog. You can find the document [here](https://newpipe.net/legal/privacy/).
|
||||
|
||||
## License
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
@@ -13,8 +13,8 @@ android {
|
||||
resValue "string", "app_name", "NewPipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 959
|
||||
versionName "0.20.5"
|
||||
versionCode 961
|
||||
versionName "0.20.7"
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
@@ -85,11 +85,15 @@ android {
|
||||
sourceSets {
|
||||
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
icepickVersion = '3.2.0'
|
||||
checkstyleVersion = '8.37'
|
||||
checkstyleVersion = '8.38'
|
||||
stethoVersion = '1.5.1'
|
||||
leakCanaryVersion = '2.5'
|
||||
exoPlayerVersion = '2.11.8'
|
||||
@@ -162,7 +166,7 @@ dependencies {
|
||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
ktlint "com.pinterest:ktlint:0.39.0"
|
||||
ktlint "com.pinterest:ktlint:0.40.0"
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
||||
@@ -175,7 +179,7 @@ dependencies {
|
||||
|
||||
// NewPipe dependencies
|
||||
// You can use a local version by uncommenting a few lines in settings.gradle
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:175df679e05b24b6094570d719cc11f8dfc17c68'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:b2837698f55296e00aeca5cb1847755dd1174af4'
|
||||
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||
|
||||
implementation "org.jsoup:jsoup:1.13.1"
|
||||
@@ -199,6 +203,7 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.webkit:webkit:1.4.0'
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||
|
@@ -227,20 +227,18 @@
|
||||
<data android:host="invidio.us" />
|
||||
<data android:host="dev.invidio.us" />
|
||||
<data android:host="www.invidio.us" />
|
||||
<data android:host="vid.encryptionin.space" />
|
||||
<data android:host="invidious.snopyta.org" />
|
||||
<data android:host="fi.invidious.snopyta.org" />
|
||||
<data android:host="yewtu.be" />
|
||||
<data android:host="invidious.ggc-project.de" />
|
||||
<data android:host="yt.maisputain.ovh" />
|
||||
<data android:host="invidious.13ad.de" />
|
||||
<data android:host="invidious.toot.koeln" />
|
||||
<data android:host="tube.connect.cafe" />
|
||||
<data android:host="invidious.zapashcanon.fr" />
|
||||
<data android:host="invidious.kavin.rocks" />
|
||||
<data android:host="invidious.tube" />
|
||||
<data android:host="invidious.site" />
|
||||
<data android:host="invidious.xyz" />
|
||||
<data android:host="vid.mint.lgbt" />
|
||||
<data android:host="invidiou.site" />
|
||||
<data android:host="invidious.fdn.fr" />
|
||||
<data android:host="watch.nettohikari.com" />
|
||||
<data android:host="invidious.snwmds.net" />
|
||||
<data android:host="invidious.snwmds.org" />
|
||||
<data android:host="invidious.snwmds.com" />
|
||||
<data android:host="invidious.sunsetravens.com" />
|
||||
<data android:host="invidious.gachirangers.com" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -268,7 +266,7 @@
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- MediaCCC filter -->
|
||||
<!-- media.ccc.de filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
|
@@ -242,8 +242,9 @@ public class App extends MultiDexApplication {
|
||||
String name = getString(R.string.notification_channel_name);
|
||||
String description = getString(R.string.notification_channel_description);
|
||||
|
||||
// Keep this below DEFAULT to avoid making noise on every notification update
|
||||
final int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
// Keep this below DEFAULT to avoid making noise on every notification update for the main
|
||||
// and update channels
|
||||
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
|
||||
final NotificationChannel mainChannel = new NotificationChannel(id, name, importance);
|
||||
mainChannel.setDescription(description);
|
||||
@@ -255,9 +256,17 @@ public class App extends MultiDexApplication {
|
||||
final NotificationChannel appUpdateChannel = new NotificationChannel(id, name, importance);
|
||||
appUpdateChannel.setDescription(description);
|
||||
|
||||
id = getString(R.string.hash_channel_id);
|
||||
name = getString(R.string.hash_channel_name);
|
||||
description = getString(R.string.hash_channel_description);
|
||||
importance = NotificationManager.IMPORTANCE_HIGH;
|
||||
|
||||
final NotificationChannel hashChannel = new NotificationChannel(id, name, importance);
|
||||
hashChannel.setDescription(description);
|
||||
|
||||
final NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||
notificationManager.createNotificationChannels(Arrays.asList(mainChannel,
|
||||
appUpdateChannel));
|
||||
appUpdateChannel, hashChannel));
|
||||
}
|
||||
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
|
@@ -48,13 +48,13 @@ public final class CheckForNewAppVersion {
|
||||
|
||||
private static final String GITHUB_APK_SHA1
|
||||
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||
private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
|
||||
private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
|
||||
|
||||
/**
|
||||
* Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
|
||||
* Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
|
||||
*
|
||||
* @param application The application
|
||||
* @return String with the apk's SHA1 fingeprint in hexadecimal
|
||||
* @return String with the APK's SHA1 fingerprint in hexadecimal
|
||||
*/
|
||||
@NonNull
|
||||
private static String getCertificateSHA1Fingerprint(@NonNull final Application application) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -8,20 +8,18 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.webkit.WebViewClientCompat;
|
||||
|
||||
import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -53,46 +51,37 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
public static final String YT_URL = "https://www.youtube.com";
|
||||
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
|
||||
|
||||
private WebView webView;
|
||||
public static String sanitizeRecaptchaUrl(@Nullable final String url) {
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
return YT_URL; // YouTube is the most likely service to have thrown a recaptcha
|
||||
} else {
|
||||
// remove "pbj=1" parameter from YouYube urls, as it makes the page JSON and not HTML
|
||||
return url.replace("&pbj=1", "").replace("pbj=1&", "").replace("?pbj=1", "");
|
||||
}
|
||||
}
|
||||
|
||||
private ActivityRecaptchaBinding recaptchaBinding;
|
||||
private String foundCookies = "";
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
ThemeHelper.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_recaptcha);
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
String url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA);
|
||||
if (url == null || url.isEmpty()) {
|
||||
url = YT_URL;
|
||||
}
|
||||
recaptchaBinding = ActivityRecaptchaBinding.inflate(getLayoutInflater());
|
||||
setContentView(recaptchaBinding.getRoot());
|
||||
setSupportActionBar(recaptchaBinding.toolbar);
|
||||
|
||||
final String url = sanitizeRecaptchaUrl(getIntent().getStringExtra(RECAPTCHA_URL_EXTRA));
|
||||
// set return to Cancel by default
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
|
||||
webView = findViewById(R.id.reCaptchaWebView);
|
||||
|
||||
// enable Javascript
|
||||
final WebSettings webSettings = webView.getSettings();
|
||||
final WebSettings webSettings = recaptchaBinding.reCaptchaWebView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
webSettings.setUserAgentString(DownloaderImpl.USER_AGENT);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(final WebView view,
|
||||
final WebResourceRequest request) {
|
||||
final String url = request.getUrl().toString();
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "shouldOverrideUrlLoading: request.url=" + url);
|
||||
}
|
||||
|
||||
handleCookiesFromUrl(url);
|
||||
return false;
|
||||
}
|
||||
|
||||
recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClientCompat() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
|
||||
if (MainActivity.DEBUG) {
|
||||
@@ -111,17 +100,16 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
});
|
||||
|
||||
// cleaning cache, history and cookies from webView
|
||||
webView.clearCache(true);
|
||||
webView.clearHistory();
|
||||
final android.webkit.CookieManager cookieManager = CookieManager.getInstance();
|
||||
recaptchaBinding.reCaptchaWebView.clearCache(true);
|
||||
recaptchaBinding.reCaptchaWebView.clearHistory();
|
||||
final CookieManager cookieManager = CookieManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cookieManager.removeAllCookies(aBoolean -> {
|
||||
});
|
||||
cookieManager.removeAllCookies(value -> { });
|
||||
} else {
|
||||
cookieManager.removeAllCookie();
|
||||
}
|
||||
|
||||
webView.loadUrl(url);
|
||||
recaptchaBinding.reCaptchaWebView.loadUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,18 +133,16 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
final int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.menu_item_done:
|
||||
saveCookiesAndFinish();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
if (item.getItemId() == R.id.menu_item_done) {
|
||||
saveCookiesAndFinish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveCookiesAndFinish() {
|
||||
handleCookiesFromUrl(webView.getUrl()); // try to get cookies of unclosed page
|
||||
// try to get cookies of unclosed page
|
||||
handleCookiesFromUrl(recaptchaBinding.reCaptchaWebView.getUrl());
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "saveCookiesAndFinish: foundCookies=" + foundCookies);
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Toast;
|
||||
@@ -26,10 +25,13 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.widget.TextViewCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
@@ -267,9 +269,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||
final Context themeWrapperContext = getThemeWrapperContext();
|
||||
|
||||
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
|
||||
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
|
||||
R.layout.single_choice_dialog_view, null, false);
|
||||
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
|
||||
final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(getLayoutInflater())
|
||||
.list;
|
||||
|
||||
final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> {
|
||||
final int indexOfChild = radioGroup.indexOfChild(
|
||||
@@ -322,8 +323,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
int id = 12345;
|
||||
for (final AdapterChoiceItem item : choices) {
|
||||
final RadioButton radioButton
|
||||
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
||||
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot();
|
||||
radioButton.setText(item.description);
|
||||
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton,
|
||||
AppCompatResources.getDrawable(getApplicationContext(), item.icon),
|
||||
@@ -696,7 +696,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopForeground(true);
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
|
||||
if (fetcher != null) {
|
||||
fetcher.dispose();
|
||||
}
|
||||
|
@@ -6,22 +6,19 @@ import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
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.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
@@ -68,40 +65,27 @@ public class AboutActivity extends AppCompatActivity {
|
||||
private static final int POS_ABOUT = 0;
|
||||
private static final int POS_LICENSE = 1;
|
||||
private static final int TOTAL_COUNT = 2;
|
||||
/**
|
||||
* The {@link RecyclerView.Adapter} that will provide
|
||||
* fragments for each of the sections. We use a
|
||||
* {@link FragmentStateAdapter} derivative, which will keep every
|
||||
* loaded fragment in memory.
|
||||
*/
|
||||
private SectionsPagerAdapter mSectionsPagerAdapter;
|
||||
/**
|
||||
* The {@link ViewPager2} that will host the section contents.
|
||||
*/
|
||||
private ViewPager2 mViewPager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeHelper.setTheme(this);
|
||||
this.setTitle(getString(R.string.title_activity_about));
|
||||
setTitle(getString(R.string.title_activity_about));
|
||||
|
||||
setContentView(R.layout.activity_about);
|
||||
final ActivityAboutBinding aboutBinding = ActivityAboutBinding.inflate(getLayoutInflater());
|
||||
setContentView(aboutBinding.getRoot());
|
||||
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
setSupportActionBar(aboutBinding.toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
// Create the adapter that will return a fragment for each of the three
|
||||
// primary sections of the activity.
|
||||
mSectionsPagerAdapter = new SectionsPagerAdapter(this);
|
||||
final SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(this);
|
||||
|
||||
// Set up the ViewPager with the sections adapter.
|
||||
mViewPager = findViewById(R.id.container);
|
||||
mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||
aboutBinding.container.setAdapter(mSectionsPagerAdapter);
|
||||
|
||||
final TabLayout tabLayout = findViewById(R.id.tabs);
|
||||
new TabLayoutMediator(tabLayout, mViewPager, (tab, position) -> {
|
||||
new TabLayoutMediator(aboutBinding.tabs, aboutBinding.container, (tab, position) -> {
|
||||
switch (position) {
|
||||
default:
|
||||
case POS_ABOUT:
|
||||
@@ -143,33 +127,28 @@ public class AboutActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final View rootView = inflater.inflate(R.layout.fragment_about, container, false);
|
||||
final Context context = this.getContext();
|
||||
final FragmentAboutBinding aboutBinding =
|
||||
FragmentAboutBinding.inflate(inflater, container, false);
|
||||
final Context context = getContext();
|
||||
|
||||
final TextView version = rootView.findViewById(R.id.app_version);
|
||||
version.setText(BuildConfig.VERSION_NAME);
|
||||
aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME);
|
||||
|
||||
final View githubLink = rootView.findViewById(R.id.github_link);
|
||||
githubLink.setOnClickListener(nv ->
|
||||
aboutBinding.githubLink.setOnClickListener(nv ->
|
||||
openUrlInBrowser(context, context.getString(R.string.github_url)));
|
||||
|
||||
final View donationLink = rootView.findViewById(R.id.donation_link);
|
||||
donationLink.setOnClickListener(v ->
|
||||
aboutBinding.donationLink.setOnClickListener(v ->
|
||||
openUrlInBrowser(context, context.getString(R.string.donation_url)));
|
||||
|
||||
final View websiteLink = rootView.findViewById(R.id.website_link);
|
||||
websiteLink.setOnClickListener(nv ->
|
||||
aboutBinding.websiteLink.setOnClickListener(nv ->
|
||||
openUrlInBrowser(context, context.getString(R.string.website_url)));
|
||||
|
||||
final View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||
privacyPolicyLink.setOnClickListener(v ->
|
||||
aboutBinding.privacyPolicyLink.setOnClickListener(v ->
|
||||
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
|
||||
|
||||
return rootView;
|
||||
return aboutBinding.getRoot();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -9,7 +9,7 @@ import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
class PlaylistStreamEntry(
|
||||
data class PlaylistStreamEntry(
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
|
||||
|
@@ -9,10 +9,10 @@ import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.ActivityDownloaderBinding;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
@@ -35,11 +35,14 @@ public class DownloadActivity extends AppCompatActivity {
|
||||
|
||||
assureCorrectAppLanguage(this);
|
||||
ThemeHelper.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_downloader);
|
||||
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final ActivityDownloaderBinding downloaderBinding =
|
||||
ActivityDownloaderBinding.inflate(getLayoutInflater());
|
||||
setContentView(downloaderBinding.getRoot());
|
||||
|
||||
setSupportActionBar(downloaderBinding.toolbarLayout.toolbar);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
|
@@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -19,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
@@ -16,7 +16,6 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
@@ -122,12 +121,14 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
|
||||
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired;
|
||||
import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
|
||||
|
||||
public final class VideoDetailFragment
|
||||
extends BaseStateFragment<StreamInfo>
|
||||
@@ -218,6 +219,9 @@ public final class VideoDetailFragment
|
||||
private TextView detailDurationView;
|
||||
private TextView detailPositionView;
|
||||
|
||||
private View detailMetaInfoSeparator;
|
||||
private TextView detailMetaInfoTextView;
|
||||
|
||||
private LinearLayout videoDescriptionRootLayout;
|
||||
private TextView videoUploadDateView;
|
||||
private TextView videoDescriptionView;
|
||||
@@ -508,8 +512,8 @@ public final class VideoDetailFragment
|
||||
}
|
||||
break;
|
||||
case R.id.detail_uploader_root_layout:
|
||||
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
|
||||
if (!TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
|
||||
if (isEmpty(currentInfo.getSubChannelUrl())) {
|
||||
if (!isEmpty(currentInfo.getUploaderUrl())) {
|
||||
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
|
||||
}
|
||||
|
||||
@@ -583,7 +587,7 @@ public final class VideoDetailFragment
|
||||
}
|
||||
break;
|
||||
case R.id.detail_uploader_root_layout:
|
||||
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
|
||||
if (isEmpty(currentInfo.getSubChannelUrl())) {
|
||||
Log.w(TAG,
|
||||
"Can't open parent channel because we got no parent channel URL");
|
||||
} else {
|
||||
@@ -644,6 +648,9 @@ public final class VideoDetailFragment
|
||||
detailDurationView = rootView.findViewById(R.id.detail_duration_view);
|
||||
detailPositionView = rootView.findViewById(R.id.detail_position_view);
|
||||
|
||||
detailMetaInfoSeparator = rootView.findViewById(R.id.detail_meta_info_separator);
|
||||
detailMetaInfoTextView = rootView.findViewById(R.id.detail_meta_info_text_view);
|
||||
|
||||
videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout);
|
||||
videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view);
|
||||
videoDescriptionView = rootView.findViewById(R.id.detail_description_view);
|
||||
@@ -748,7 +755,7 @@ public final class VideoDetailFragment
|
||||
private void initThumbnailViews(@NonNull final StreamInfo info) {
|
||||
thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
||||
|
||||
if (!TextUtils.isEmpty(info.getThumbnailUrl())) {
|
||||
if (!isEmpty(info.getThumbnailUrl())) {
|
||||
final String infoServiceName = NewPipe.getNameOfService(info.getServiceId());
|
||||
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
|
||||
@Override
|
||||
@@ -763,12 +770,12 @@ public final class VideoDetailFragment
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(info.getSubChannelAvatarUrl())) {
|
||||
if (!isEmpty(info.getSubChannelAvatarUrl())) {
|
||||
IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
|
||||
if (!isEmpty(info.getUploaderAvatarUrl())) {
|
||||
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
}
|
||||
@@ -1217,7 +1224,7 @@ public final class VideoDetailFragment
|
||||
}
|
||||
|
||||
private void prepareDescription(final Description description) {
|
||||
if (description == null || TextUtils.isEmpty(description.getContent())
|
||||
if (description == null || isEmpty(description.getContent())
|
||||
|| description == Description.emptyDescription) {
|
||||
return;
|
||||
}
|
||||
@@ -1462,9 +1469,9 @@ public final class VideoDetailFragment
|
||||
animateView(thumbnailPlayButton, true, 200);
|
||||
videoTitleTextView.setText(title);
|
||||
|
||||
if (!TextUtils.isEmpty(info.getSubChannelName())) {
|
||||
if (!isEmpty(info.getSubChannelName())) {
|
||||
displayBothUploaderAndSubChannel(info);
|
||||
} else if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||
} else if (!isEmpty(info.getUploaderName())) {
|
||||
displayUploaderAsSubChannel(info);
|
||||
} else {
|
||||
uploaderTextView.setVisibility(View.GONE);
|
||||
@@ -1559,6 +1566,8 @@ public final class VideoDetailFragment
|
||||
prepareDescription(info.getDescription());
|
||||
updateProgressInfo(info);
|
||||
initThumbnailViews(info);
|
||||
showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator);
|
||||
|
||||
|
||||
if (player == null || player.isPlayerStopped()) {
|
||||
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());
|
||||
@@ -1610,7 +1619,7 @@ public final class VideoDetailFragment
|
||||
|
||||
subChannelThumb.setVisibility(View.VISIBLE);
|
||||
|
||||
if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||
if (!isEmpty(info.getUploaderName())) {
|
||||
uploaderTextView.setText(
|
||||
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
|
||||
uploaderTextView.setVisibility(View.VISIBLE);
|
||||
@@ -2305,10 +2314,10 @@ public final class VideoDetailFragment
|
||||
private void updateOverlayData(@Nullable final String overlayTitle,
|
||||
@Nullable final String uploader,
|
||||
@Nullable final String thumbnailUrl) {
|
||||
overlayTitleTextView.setText(TextUtils.isEmpty(overlayTitle) ? "" : overlayTitle);
|
||||
overlayChannelTextView.setText(TextUtils.isEmpty(uploader) ? "" : uploader);
|
||||
overlayTitleTextView.setText(isEmpty(title) ? "" : title);
|
||||
overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
|
||||
overlayThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
||||
if (!TextUtils.isEmpty(thumbnailUrl)) {
|
||||
if (!isEmpty(thumbnailUrl)) {
|
||||
IMAGE_LOADER.displayImage(thumbnailUrl, overlayThumbnailImageView,
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, null);
|
||||
}
|
||||
|
@@ -11,12 +11,12 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
@@ -26,8 +26,10 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
@@ -44,13 +46,13 @@ import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.StreamDialogEntry;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
@@ -58,6 +60,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||
|
||||
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
private CompositeDisposable disposables;
|
||||
@@ -74,7 +77,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
private TextView headerTitleView;
|
||||
private View headerUploaderLayout;
|
||||
private TextView headerUploaderName;
|
||||
private ImageView headerUploaderAvatar;
|
||||
private CircleImageView headerUploaderAvatar;
|
||||
private TextView headerStreamCount;
|
||||
private View playlistCtrl;
|
||||
|
||||
@@ -301,8 +304,22 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
|
||||
playlistCtrl.setVisibility(View.VISIBLE);
|
||||
|
||||
IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
final String avatarUrl = result.getUploaderAvatarUrl();
|
||||
if (result.getServiceId() == ServiceList.YouTube.getServiceId()
|
||||
&& (YoutubeParsingHelper.isYoutubeMixId(result.getId())
|
||||
|| YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) {
|
||||
// this is an auto-generated playlist (e.g. Youtube mix), so a radio is shown
|
||||
headerUploaderAvatar.setDisableCircularTransformation(true);
|
||||
headerUploaderAvatar.setBorderColor(
|
||||
getResources().getColor(R.color.transparent_background_color));
|
||||
headerUploaderAvatar.setImageDrawable(AppCompatResources.getDrawable(requireContext(),
|
||||
resolveResourceIdFromAttr(requireContext(), R.attr.ic_radio)));
|
||||
|
||||
} else {
|
||||
IMAGE_LOADER.displayImage(avatarUrl, headerUploaderAvatar,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
}
|
||||
|
||||
headerStreamCount.setText(Localization
|
||||
.localizeStreamCount(getContext(), result.getStreamCount()));
|
||||
|
||||
@@ -476,7 +493,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
final int titleRes = playlistEntity == null
|
||||
? R.string.bookmark_playlist : R.string.unbookmark_playlist;
|
||||
|
||||
playlistBookmarkButton.setIcon(ThemeHelper.resolveResourceIdFromAttr(activity, iconAttr));
|
||||
playlistBookmarkButton.setIcon(resolveResourceIdFromAttr(activity, iconAttr));
|
||||
playlistBookmarkButton.setTitle(titleRes);
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,7 @@ import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.MetaInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
@@ -79,6 +80,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
|
||||
|
||||
public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage<?>>
|
||||
implements BackPressable {
|
||||
@@ -129,6 +131,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@State
|
||||
boolean isCorrectedSearch;
|
||||
|
||||
@State
|
||||
MetaInfo[] metaInfo;
|
||||
|
||||
@State
|
||||
boolean wasSearchFocused = false;
|
||||
|
||||
@@ -153,6 +158,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
private View searchClear;
|
||||
|
||||
private TextView correctSuggestion;
|
||||
private TextView metaInfoTextView;
|
||||
private View metaInfoSeparator;
|
||||
|
||||
private View suggestionsPanel;
|
||||
private boolean suggestionsPanelVisible = false;
|
||||
@@ -269,6 +276,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
||||
metaInfoTextView, metaInfoSeparator);
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||
initSuggestionObserver();
|
||||
}
|
||||
@@ -353,6 +363,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||
|
||||
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
|
||||
metaInfoTextView = rootView.findViewById(R.id.search_meta_info_text_view);
|
||||
metaInfoSeparator = rootView.findViewById(R.id.search_meta_info_separator);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -973,8 +985,14 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
searchSuggestion = result.getSearchSuggestion();
|
||||
isCorrectedSearch = result.isCorrectedSearch();
|
||||
|
||||
// List<MetaInfo> cannot be bundled without creating some containers
|
||||
metaInfo = new MetaInfo[result.getMetaInfo().size()];
|
||||
metaInfo = result.getMetaInfo().toArray(metaInfo);
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator);
|
||||
|
||||
lastSearchedString = searchString;
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
|
@@ -1,10 +1,29 @@
|
||||
package org.schabi.newpipe.ktx
|
||||
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.time.temporal.ChronoField
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.TimeZone
|
||||
|
||||
fun OffsetDateTime.toCalendar(zoneId: ZoneId = ZoneId.systemDefault()): Calendar {
|
||||
return GregorianCalendar.from(if (zoneId != offset) atZoneSameInstant(zoneId) else toZonedDateTime())
|
||||
// This method is a modified version of GregorianCalendar.from(ZonedDateTime).
|
||||
// Math.addExact() and Math.multiplyExact() are desugared even though lint displays a warning.
|
||||
@SuppressWarnings("NewApi")
|
||||
fun OffsetDateTime.toCalendar(): Calendar {
|
||||
val cal = GregorianCalendar(TimeZone.getTimeZone("UTC"))
|
||||
val offsetDateTimeUTC = withOffsetSameInstant(ZoneOffset.UTC)
|
||||
cal.gregorianChange = Date(Long.MIN_VALUE)
|
||||
cal.firstDayOfWeek = Calendar.MONDAY
|
||||
cal.minimalDaysInFirstWeek = 4
|
||||
try {
|
||||
cal.timeInMillis = Math.addExact(
|
||||
Math.multiplyExact(offsetDateTimeUTC.toEpochSecond(), 1000),
|
||||
offsetDateTimeUTC[ChronoField.MILLI_OF_SECOND].toLong()
|
||||
)
|
||||
} catch (ex: ArithmeticException) {
|
||||
throw IllegalArgumentException(ex)
|
||||
}
|
||||
return cal
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
@@ -147,7 +148,7 @@ class FeedLoadService : Service() {
|
||||
|
||||
private fun stopService() {
|
||||
disposeAll()
|
||||
stopForeground(true)
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
notificationManager.cancel(NOTIFICATION_ID)
|
||||
stopSelf()
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -162,7 +163,7 @@ public abstract class BaseImportExportService extends Service {
|
||||
|
||||
protected void postErrorResult(final String title, final String text) {
|
||||
disposeAll();
|
||||
stopForeground(true);
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
|
||||
stopSelf();
|
||||
|
||||
if (title == null) {
|
||||
|
@@ -9,7 +9,7 @@ public interface ImportExportEventListener {
|
||||
void onSizeReceived(int size);
|
||||
|
||||
/**
|
||||
* Called everytime an item has been parsed/resolved.
|
||||
* Called every time an item has been parsed/resolved.
|
||||
*
|
||||
* @param itemName the name of the subscription item
|
||||
*/
|
||||
|
@@ -684,7 +684,7 @@ public abstract class BasePlayer implements
|
||||
|
||||
public void onMuteUnmuteButtonClicked() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onMuteUnmuteButtonClicled() called");
|
||||
Log.d(TAG, "onMuteUnmuteButtonClicked() called");
|
||||
}
|
||||
simpleExoPlayer.setVolume(isMuted() ? 1 : 0);
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.app.ServiceCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
@@ -188,7 +189,7 @@ public final class NotificationUtil {
|
||||
}
|
||||
|
||||
void cancelNotificationAndStopForeground(final Service service) {
|
||||
service.stopForeground(true);
|
||||
ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE);
|
||||
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -103,6 +103,7 @@ import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
||||
import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE;
|
||||
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD;
|
||||
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND;
|
||||
@@ -889,10 +890,17 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||
private void onShareClicked() {
|
||||
// share video at the current time (youtube.com/watch?v=ID&t=SECONDS)
|
||||
// Timestamp doesn't make sense in a live stream so drop it
|
||||
final String ts = isLive() ? "" : ("&t=" + (getPlaybackSeekBar().getProgress() / 1000));
|
||||
|
||||
final int ts = getPlaybackSeekBar().getProgress() / 1000;
|
||||
final MediaSourceTag metadata = getCurrentMetadata();
|
||||
String videoUrl = getVideoUrl();
|
||||
if (!isLive() && ts >= 0 && metadata != null
|
||||
&& metadata.getMetadata().getServiceId() == YouTube.getServiceId()) {
|
||||
videoUrl += ("&t=" + ts);
|
||||
}
|
||||
ShareUtils.shareUrl(service,
|
||||
getVideoTitle(),
|
||||
getVideoUrl() + ts);
|
||||
videoUrl);
|
||||
}
|
||||
|
||||
private void onPlayWithKodiClicked() {
|
||||
|
@@ -26,12 +26,12 @@ public class LoadController implements LoadControl {
|
||||
}
|
||||
|
||||
private LoadController(final int initialPlaybackBufferMs,
|
||||
final int minimumPlaybackbufferMs,
|
||||
final int minimumPlaybackBufferMs,
|
||||
final int optimalPlaybackBufferMs) {
|
||||
this.initialPlaybackBufferUs = initialPlaybackBufferMs * 1000;
|
||||
|
||||
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||
builder.setBufferDurationsMs(minimumPlaybackbufferMs, optimalPlaybackBufferMs,
|
||||
builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs,
|
||||
initialPlaybackBufferMs, initialPlaybackBufferMs);
|
||||
internalLoadControl = builder.createDefaultLoadControl();
|
||||
}
|
||||
|
@@ -128,9 +128,9 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
fetchReactor = null;
|
||||
}
|
||||
|
||||
private static List<PlayQueueItem> extractListItems(final List<StreamInfoItem> infos) {
|
||||
private static List<PlayQueueItem> extractListItems(final List<StreamInfoItem> infoItems) {
|
||||
final List<PlayQueueItem> result = new ArrayList<>();
|
||||
for (final InfoItem stream : infos) {
|
||||
for (final InfoItem stream : infoItems) {
|
||||
if (stream instanceof StreamInfoItem) {
|
||||
result.add(new PlayQueueItem((StreamInfoItem) stream));
|
||||
}
|
||||
|
@@ -14,15 +14,11 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.NavUtils;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
@@ -34,6 +30,7 @@ import org.schabi.newpipe.ActivityCommunicator;
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.ActivityErrorBinding;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
@@ -87,7 +84,8 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
private ErrorInfo errorInfo;
|
||||
private Class returnActivity;
|
||||
private String currentTimeStamp;
|
||||
private EditText userCommentBox;
|
||||
|
||||
private ActivityErrorBinding activityErrorBinding;
|
||||
|
||||
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
|
||||
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR,
|
||||
@@ -181,12 +179,13 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
assureCorrectAppLanguage(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeHelper.setTheme(this);
|
||||
setContentView(R.layout.activity_error);
|
||||
|
||||
activityErrorBinding = ActivityErrorBinding.inflate(getLayoutInflater());
|
||||
setContentView(activityErrorBinding.getRoot());
|
||||
|
||||
final Intent intent = getIntent();
|
||||
|
||||
final Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
setSupportActionBar(activityErrorBinding.toolbarLayout.toolbar);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
@@ -195,15 +194,6 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
|
||||
final Button reportEmailButton = findViewById(R.id.errorReportEmailButton);
|
||||
final Button copyButton = findViewById(R.id.errorReportCopyButton);
|
||||
final Button reportGithubButton = findViewById(R.id.errorReportGitHubButton);
|
||||
|
||||
userCommentBox = findViewById(R.id.errorCommentBox);
|
||||
final TextView errorView = findViewById(R.id.errorView);
|
||||
final TextView infoView = findViewById(R.id.errorInfosView);
|
||||
final TextView errorMessageView = findViewById(R.id.errorMessageView);
|
||||
|
||||
final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
||||
returnActivity = ac.getReturnActivity();
|
||||
errorInfo = intent.getParcelableExtra(ERROR_INFO);
|
||||
@@ -213,28 +203,27 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
addGuruMeditation();
|
||||
currentTimeStamp = getCurrentTimeStamp();
|
||||
|
||||
reportEmailButton.setOnClickListener(v ->
|
||||
activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
|
||||
openPrivacyPolicyDialog(this, "EMAIL"));
|
||||
|
||||
copyButton.setOnClickListener(v -> {
|
||||
activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> {
|
||||
ShareUtils.copyToClipboard(this, buildMarkdown());
|
||||
Toast.makeText(this, R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
reportGithubButton.setOnClickListener(v ->
|
||||
activityErrorBinding.errorReportGitHubButton.setOnClickListener(v ->
|
||||
openPrivacyPolicyDialog(this, "GITHUB"));
|
||||
|
||||
|
||||
// normal bugreport
|
||||
buildInfo(errorInfo);
|
||||
if (errorInfo.getMessage() != 0) {
|
||||
errorMessageView.setText(errorInfo.getMessage());
|
||||
activityErrorBinding.errorMessageView.setText(errorInfo.getMessage());
|
||||
} else {
|
||||
errorMessageView.setVisibility(View.GONE);
|
||||
findViewById(R.id.messageWhatHappenedView).setVisibility(View.GONE);
|
||||
activityErrorBinding.errorMessageView.setVisibility(View.GONE);
|
||||
activityErrorBinding.messageWhatHappenedView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
errorView.setText(formErrorText(errorList));
|
||||
activityErrorBinding.errorView.setText(formErrorText(errorList));
|
||||
|
||||
// print stack trace once again for debugging:
|
||||
for (final String e : errorList) {
|
||||
@@ -339,11 +328,10 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void buildInfo(final ErrorInfo info) {
|
||||
final TextView infoLabelView = findViewById(R.id.errorInfoLabelsView);
|
||||
final TextView infoView = findViewById(R.id.errorInfosView);
|
||||
String text = "";
|
||||
|
||||
infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n"));
|
||||
activityErrorBinding.errorInfoLabelsView.setText(getString(R.string.info_labels)
|
||||
.replace("\\n", "\n"));
|
||||
|
||||
text += getUserActionString(info.getUserAction()) + "\n"
|
||||
+ info.getRequest() + "\n"
|
||||
@@ -356,7 +344,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
+ BuildConfig.VERSION_NAME + "\n"
|
||||
+ getOsString();
|
||||
|
||||
infoView.setText(text);
|
||||
activityErrorBinding.errorInfosView.setText(text);
|
||||
}
|
||||
|
||||
private String buildJson() {
|
||||
@@ -374,7 +362,8 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
.value("os", getOsString())
|
||||
.value("time", currentTimeStamp)
|
||||
.array("exceptions", Arrays.asList(errorList))
|
||||
.value("user_comment", userCommentBox.getText().toString())
|
||||
.value("user_comment", activityErrorBinding.errorCommentBox.getText()
|
||||
.toString())
|
||||
.end()
|
||||
.done();
|
||||
} catch (final Throwable e) {
|
||||
@@ -389,7 +378,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
try {
|
||||
final StringBuilder htmlErrorReport = new StringBuilder();
|
||||
|
||||
final String userComment = userCommentBox.getText().toString();
|
||||
final String userComment = activityErrorBinding.errorCommentBox.getText().toString();
|
||||
if (!userComment.isEmpty()) {
|
||||
htmlErrorReport.append(userComment).append("\n");
|
||||
}
|
||||
@@ -473,10 +462,9 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
|
||||
private void addGuruMeditation() {
|
||||
//just an easter egg
|
||||
final TextView sorryView = findViewById(R.id.errorSorryView);
|
||||
String text = sorryView.getText().toString();
|
||||
String text = activityErrorBinding.errorSorryView.getText().toString();
|
||||
text += "\n" + getString(R.string.guru_meditation);
|
||||
sorryView.setText(text);
|
||||
activityErrorBinding.errorSorryView.setText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -8,6 +8,7 @@ import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -31,7 +32,7 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
if (!newValue.equals(startThemeKey) && getActivity() != null) {
|
||||
// If it's not the current theme
|
||||
getActivity().recreate();
|
||||
ActivityCompat.recreate(requireActivity());
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -11,6 +11,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@@ -30,19 +31,15 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
import org.schabi.newpipe.util.ZipHelper;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
@@ -50,6 +47,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
private static final int REQUEST_IMPORT_PATH = 8945;
|
||||
private static final int REQUEST_EXPORT_PATH = 30945;
|
||||
|
||||
private ContentSettingsManager manager;
|
||||
|
||||
private File databasesDir;
|
||||
private File newpipeDb;
|
||||
private File newpipeDbJournal;
|
||||
@@ -120,17 +119,18 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
final File homeDir = ContextCompat.getDataDir(requireContext());
|
||||
databasesDir = new File(homeDir, "/databases");
|
||||
newpipeDb = new File(homeDir, "/databases/newpipe.db");
|
||||
newpipeDbJournal = new File(homeDir, "/databases/newpipe.db-journal");
|
||||
newpipeDbShm = new File(homeDir, "/databases/newpipe.db-shm");
|
||||
newpipeDbWal = new File(homeDir, "/databases/newpipe.db-wal");
|
||||
|
||||
final String homeDir = getActivity().getApplicationInfo().dataDir;
|
||||
databasesDir = new File(homeDir + "/databases");
|
||||
newpipeDb = new File(homeDir + "/databases/newpipe.db");
|
||||
newpipeDbJournal = new File(homeDir + "/databases/newpipe.db-journal");
|
||||
newpipeDbShm = new File(homeDir + "/databases/newpipe.db-shm");
|
||||
newpipeDbWal = new File(homeDir + "/databases/newpipe.db-wal");
|
||||
|
||||
newpipeSettings = new File(homeDir + "/databases/newpipe.settings");
|
||||
newpipeSettings = new File(homeDir, "/databases/newpipe.settings");
|
||||
newpipeSettings.delete();
|
||||
|
||||
manager = new ContentSettingsManager(homeDir);
|
||||
|
||||
addPreferencesFromResource(R.xml.content_settings);
|
||||
|
||||
final Preference importDataPreference = findPreference(getString(R.string.import_data));
|
||||
@@ -212,33 +212,16 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
//checkpoint before export
|
||||
NewPipeDatabase.checkpoint();
|
||||
|
||||
try (ZipOutputStream outZip = new ZipOutputStream(new BufferedOutputStream(
|
||||
new FileOutputStream(path)))) {
|
||||
ZipHelper.addFileToZip(outZip, newpipeDb.getPath(), "newpipe.db");
|
||||
final SharedPreferences preferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(requireContext());
|
||||
manager.exportDatabase(preferences, path);
|
||||
|
||||
saveSharedPreferencesToFile(newpipeSettings);
|
||||
ZipHelper.addFileToZip(outZip, newpipeSettings.getPath(),
|
||||
"newpipe.settings");
|
||||
}
|
||||
|
||||
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
|
||||
} catch (final Exception e) {
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveSharedPreferencesToFile(final File dst) {
|
||||
try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(dst))) {
|
||||
final SharedPreferences pref
|
||||
= PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||
output.writeObject(pref.getAll());
|
||||
output.flush();
|
||||
} catch (final IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void importDatabase(final String filePath) {
|
||||
// check if file is supported
|
||||
try (ZipFile zipFile = new ZipFile(filePath)) {
|
||||
|
@@ -0,0 +1,45 @@
|
||||
package org.schabi.newpipe.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import org.schabi.newpipe.util.ZipHelper
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.ObjectOutputStream
|
||||
import java.lang.Exception
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ContentSettingsManager(
|
||||
private val newpipeDb: File,
|
||||
private val newpipeSettings: File
|
||||
) {
|
||||
|
||||
constructor(homeDir: File) : this(
|
||||
File(homeDir, "databases/newpipe.db"),
|
||||
File(homeDir, "databases/newpipe.settings")
|
||||
)
|
||||
|
||||
/**
|
||||
* Exports given [SharedPreferences] to the file in given outputPath.
|
||||
* It also creates the file.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun exportDatabase(preferences: SharedPreferences, outputPath: String) {
|
||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputPath)))
|
||||
.use { outZip ->
|
||||
ZipHelper.addFileToZip(outZip, newpipeDb.path, "newpipe.db")
|
||||
|
||||
try {
|
||||
ObjectOutputStream(FileOutputStream(newpipeSettings)).use { output ->
|
||||
output.writeObject(preferences.all)
|
||||
output.flush()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
ZipHelper.addFileToZip(outZip, newpipeSettings.path, "newpipe.settings")
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user