mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-01-14 21:48:00 +00:00
Compare commits
280 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a98c0bdec7 | ||
|
|
cdfe686322 | ||
|
|
553943ab93 | ||
|
|
1281ea858c | ||
|
|
30a303f873 | ||
|
|
70b9330b61 | ||
|
|
f1e8667945 | ||
|
|
509f501696 | ||
|
|
3fe0368486 | ||
|
|
8f027e274e | ||
|
|
f6bca68da2 | ||
|
|
d921e2e61b | ||
|
|
0f7ed0ec70 | ||
|
|
49b12ea4f8 | ||
|
|
69fc466323 | ||
|
|
81d00f2e97 | ||
|
|
ded6540422 | ||
|
|
583a028529 | ||
|
|
f1bb56e2fb | ||
|
|
f583dd47ac | ||
|
|
7e3b3453c0 | ||
|
|
abc354f516 | ||
|
|
79efffe12f | ||
|
|
25130db371 | ||
|
|
932eb94f9d | ||
|
|
9bf4eff173 | ||
|
|
9fc3ddeab7 | ||
|
|
98fdbec442 | ||
|
|
332b90d6c1 | ||
|
|
db2e03eb14 | ||
|
|
8ed8b94ec7 | ||
|
|
63c9308f59 | ||
|
|
1306a777fc | ||
|
|
f739ed7581 | ||
|
|
b4d6015464 | ||
|
|
b9aaafdb30 | ||
|
|
71aa6c6e92 | ||
|
|
f98d2631e5 | ||
|
|
9e94c81ef2 | ||
|
|
d025ef11f8 | ||
|
|
fe7536e374 | ||
|
|
14256137e8 | ||
|
|
bc3e43ac58 | ||
|
|
d0d5373be9 | ||
|
|
997267bad1 | ||
|
|
ef6d0cc4b1 | ||
|
|
ffad244e1e | ||
|
|
fdee7c3d06 | ||
|
|
142cde975f | ||
|
|
004907d306 | ||
|
|
05eb0d0fbe | ||
|
|
f13a1b04e6 | ||
|
|
fd4408e572 | ||
|
|
a84ab7413c | ||
|
|
62b593da08 | ||
|
|
0eb69b6659 | ||
|
|
67b83388b1 | ||
|
|
ecc998aea8 | ||
|
|
6956d16f0e | ||
|
|
f1bc4f5c20 | ||
|
|
f134e2d02a | ||
|
|
6ec72ef945 | ||
|
|
e8d518cd6c | ||
|
|
b564433ff6 | ||
|
|
79f7dcd1a3 | ||
|
|
23ee9b7867 | ||
|
|
afbf36900f | ||
|
|
2829851e49 | ||
|
|
2b8fda3511 | ||
|
|
d31959990e | ||
|
|
26c535db84 | ||
|
|
ea1b910d7e | ||
|
|
8f4c6fb6ac | ||
|
|
9b1861417c | ||
|
|
8080c32b1f | ||
|
|
4b27aec196 | ||
|
|
38fb510375 | ||
|
|
6422e31b10 | ||
|
|
c0f47195a2 | ||
|
|
40f66977c7 | ||
|
|
e518c0dc14 | ||
|
|
2e161a1f45 | ||
|
|
5ab6e84044 | ||
|
|
e1a6347c4e | ||
|
|
bf8e8798d9 | ||
|
|
08949ee347 | ||
|
|
92a67bb8cb | ||
|
|
363bbf5fd3 | ||
|
|
77f6940336 | ||
|
|
e8eeac6735 | ||
|
|
775fbc9a75 | ||
|
|
8d0f2d371d | ||
|
|
8efe2859b8 | ||
|
|
441c68ead2 | ||
|
|
882b235a78 | ||
|
|
4cd1f201f5 | ||
|
|
013c59f904 | ||
|
|
57474e2dab | ||
|
|
139ced885d | ||
|
|
10b1da135e | ||
|
|
f0bb2e8687 | ||
|
|
4643ccef6f | ||
|
|
753ca7cb53 | ||
|
|
87d2f33e55 | ||
|
|
fc7944d287 | ||
|
|
376e5c1546 | ||
|
|
e8ad947d37 | ||
|
|
067528211f | ||
|
|
92ab9cae27 | ||
|
|
fa2b11b768 | ||
|
|
82f43ac6a6 | ||
|
|
c7660b8c2d | ||
|
|
847831c195 | ||
|
|
e0b246431f | ||
|
|
c808beec30 | ||
|
|
2d4a3c2554 | ||
|
|
b2b9938484 | ||
|
|
eb1cefe2fa | ||
|
|
5eb5dbddde | ||
|
|
bfe3eff5ff | ||
|
|
e7936e6c9a | ||
|
|
514f92e6f2 | ||
|
|
68fd7a031f | ||
|
|
95f61542b5 | ||
|
|
9fc6f19702 | ||
|
|
4038617d59 | ||
|
|
98ccd577d6 | ||
|
|
1d43a2362c | ||
|
|
0ff675171b | ||
|
|
59594c6637 | ||
|
|
9595733563 | ||
|
|
5eb1d49857 | ||
|
|
fa1fdbf73e | ||
|
|
52e52b3ca1 | ||
|
|
5b4fbe32b1 | ||
|
|
31ea44ccf1 | ||
|
|
d26ca194b3 | ||
|
|
a012e26d63 | ||
|
|
f80b1fb2fe | ||
|
|
38ed07caa7 | ||
|
|
72ee4be495 | ||
|
|
c85b97a484 | ||
|
|
c7510c628f | ||
|
|
3ca1e550fe | ||
|
|
01e8944077 | ||
|
|
d6ab3298a3 | ||
|
|
97b28bba4d | ||
|
|
7f6674a0e6 | ||
|
|
2c1df5f875 | ||
|
|
e7ae215ab0 | ||
|
|
4a9c790652 | ||
|
|
91ca680911 | ||
|
|
01376aba86 | ||
|
|
d56ffa3531 | ||
|
|
5a5a24bf1a | ||
|
|
3d2c65b398 | ||
|
|
bacb35fb1c | ||
|
|
0a2ed805a2 | ||
|
|
e70c153cd3 | ||
|
|
b54c2b7f57 | ||
|
|
3fe80ec5ac | ||
|
|
e52048c69e | ||
|
|
ceb930aed6 | ||
|
|
e775037366 | ||
|
|
4357e02c58 | ||
|
|
67c0ceedc9 | ||
|
|
0039312a64 | ||
|
|
57f1152751 | ||
|
|
bfb9be1225 | ||
|
|
8837b54aab | ||
|
|
8ab5a4d394 | ||
|
|
29bcf94d50 | ||
|
|
dd68bf8eeb | ||
|
|
fc4dd4524a | ||
|
|
c12ac64678 | ||
|
|
264044272a | ||
|
|
c74162c586 | ||
|
|
fa6ff4e5eb | ||
|
|
01bbc50c68 | ||
|
|
e5457e5029 | ||
|
|
b025bdf0c7 | ||
|
|
13ba708adc | ||
|
|
edf8bf2c9d | ||
|
|
c9e0bf4f02 | ||
|
|
8f9eaa22e6 | ||
|
|
3fcd580491 | ||
|
|
cf3cc2e984 | ||
|
|
76322d8089 | ||
|
|
9e29d8d692 | ||
|
|
5d5f8b4d51 | ||
|
|
4d74be881d | ||
|
|
425a312151 | ||
|
|
ea294e8e5d | ||
|
|
e75d0de135 | ||
|
|
81cacbd917 | ||
|
|
c0c78ae9bb | ||
|
|
eb572e8d8f | ||
|
|
781c499806 | ||
|
|
a3d74ea444 | ||
|
|
86a19aa037 | ||
|
|
e484339cca | ||
|
|
6b5a1d0202 | ||
|
|
24247fd6a6 | ||
|
|
2af20d5c40 | ||
|
|
dfb983c3cf | ||
|
|
e1a6b69f9a | ||
|
|
8df935f5fe | ||
|
|
04c5acd1d7 | ||
|
|
c9766d25ef | ||
|
|
aaea661b70 | ||
|
|
7061859112 | ||
|
|
66c24af3d2 | ||
|
|
083c315fd6 | ||
|
|
29b44a181b | ||
|
|
4fdc5ea646 | ||
|
|
e17bfa029c | ||
|
|
279e4c2fa8 | ||
|
|
856a39855e | ||
|
|
4a9d21062a | ||
|
|
ad8f3aa6c9 | ||
|
|
43f85408be | ||
|
|
5739caaa5a | ||
|
|
73cfa5499d | ||
|
|
8f0323fb8d | ||
|
|
83d16932a4 | ||
|
|
84e3f6ca18 | ||
|
|
5d6a568308 | ||
|
|
3e8cba745a | ||
|
|
be4d12789d | ||
|
|
00fbfb5a56 | ||
|
|
27d0f7f277 | ||
|
|
fb1aab2a49 | ||
|
|
cc72fa4793 | ||
|
|
e9c60eff85 | ||
|
|
5b7c87ee79 | ||
|
|
c8b4685fc9 | ||
|
|
561d5675f7 | ||
|
|
c906cb57ee | ||
|
|
84de865daf | ||
|
|
24a264d78c | ||
|
|
62c3c7ac21 | ||
|
|
a7d6ad5162 | ||
|
|
0dbb212d13 | ||
|
|
8002cc2771 | ||
|
|
8d64eac853 | ||
|
|
80d1c5b9f5 | ||
|
|
7175f27da8 | ||
|
|
d6f9aace8c | ||
|
|
aeccb5b472 | ||
|
|
09a7b7718a | ||
|
|
ef2c76efaf | ||
|
|
69793049c3 | ||
|
|
67942a906a | ||
|
|
6a9cae3de8 | ||
|
|
0afdac5683 | ||
|
|
609d09a8e2 | ||
|
|
01e8654fbd | ||
|
|
f477ab84d5 | ||
|
|
9f59d4baa3 | ||
|
|
2e9a1d958c | ||
|
|
e4f2c58933 | ||
|
|
a9e8b3e06b | ||
|
|
daa5b7827a | ||
|
|
dd00152485 | ||
|
|
df52a6ea6b | ||
|
|
7d5197e6fd | ||
|
|
81e08d0cc4 | ||
|
|
32a159d48f | ||
|
|
44f3a7484d | ||
|
|
38eb8e40ea | ||
|
|
fd14c8cdce | ||
|
|
216da63276 | ||
|
|
08d8f2564a | ||
|
|
1d51002173 | ||
|
|
610d0b272e | ||
|
|
b3e2418b93 | ||
|
|
6a9d5fd4cc | ||
|
|
a83106f717 | ||
|
|
4c2a6e346d | ||
|
|
cae63a7ada |
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@@ -41,6 +41,6 @@ You'll see exactly what is sent, be able to add your comments, and then send it.
|
||||
|
||||
## Communication
|
||||
|
||||
* The [#newpipe](irc:irc.freenode.net/newpipe) channel on freenode has the core team and other developers in it. [Click here for webchat](https://webchat.freenode.net/?channels=newpipe)!
|
||||
* You can also use a Matrix account to join the Newpipe channel at [#freenode_#newpipe:matrix.org](https://matrix.to/#/#freenode_#newpipe:matrix.org).
|
||||
* 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).
|
||||
* Post suggestions, changes, ideas etc. on GitHub or IRC.
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 IRC
|
||||
url: https://webchat.freenode.net/#newpipe
|
||||
url: https://web.libera.chat/#newpipe
|
||||
about: Chat with us via IRC for quick Q/A
|
||||
- name: 💬 Matrix
|
||||
url: https://matrix.to/#/#freenode_#newpipe:matrix.org
|
||||
url: https://matrix.to/#/#newpipe:libera.chat
|
||||
about: Chat with us via Matrix for quick Q/A
|
||||
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -13,8 +13,8 @@
|
||||
- take over the world
|
||||
|
||||
#### Fixes the following issue(s)
|
||||
<!-- Also add any other links relevant to your change. -->
|
||||
-
|
||||
<!-- Prefix issues with "Fixes" so that GitHub closes them when the PR is merged (note that each "Fixes #" should be in its own item). Also add any other relevant links. -->
|
||||
- Fixes #
|
||||
|
||||
#### Relies on the following changes
|
||||
<!-- Delete this if it doesn't apply to you. -->
|
||||
|
||||
44
.github/workflows/ci.yml
vendored
44
.github/workflows/ci.yml
vendored
@@ -4,26 +4,29 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
build-and-test-jvm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: create and checkout branch
|
||||
# push events already checked out the branch
|
||||
if: github.event_name == 'pull_request'
|
||||
run: git checkout -B ${{ github.head_ref }}
|
||||
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1.4.3
|
||||
- name: set up JDK 8
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 1.8
|
||||
java-version: 8
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
@@ -32,7 +35,7 @@ jobs:
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
restore-keys: ${{ runner.os }}-gradle
|
||||
|
||||
- name: Build debug APK and run Tests
|
||||
- name: Build debug APK and run jvm tests
|
||||
run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace
|
||||
|
||||
- name: Upload APK
|
||||
@@ -40,6 +43,32 @@ jobs:
|
||||
with:
|
||||
name: app
|
||||
path: app/build/outputs/apk/debug/*.apk
|
||||
test-android:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [21, 29]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: set up JDK 8
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 8
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
restore-keys: ${{ runner.os }}-gradle
|
||||
|
||||
- name: Run android tests
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
script: ./gradlew connectedCheck
|
||||
# sonar:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
@@ -48,9 +77,10 @@ jobs:
|
||||
# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
# - name: Set up JDK 11
|
||||
# uses: actions/setup-java@v1.4.3
|
||||
# uses: actions/setup-java@v2
|
||||
# with:
|
||||
# java-version: 11 # Sonar requires JDK 11
|
||||
# distribution: "adopt"
|
||||
|
||||
# - name: Cache SonarCloud packages
|
||||
# uses: actions/cache@v2
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/Licencia-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://hosted.weblate.org/engage/newpipe/es/" alt="Estado de la traducción"><img src="https://hosted.weblate.org/widgets/newpipe/es/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/Canal%20de%20IRC%20-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/Canal%20de%20IRC%20-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
@@ -18,7 +18,7 @@
|
||||
<p align="center"><a href="https://newpipe.net">Sitio web</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">Preguntas Frecuentes</a> • <a href="https://newpipe.net/press/">Prensa</a></p>
|
||||
<hr>
|
||||
|
||||
*Lea esto en otros idiomas: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md) .*
|
||||
*Lea esto en otros idiomas: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
|
||||
|
||||
<b>AVISO: ESTA ES UNA VERSIÓN BETA, POR LO TANTO, PUEDE ENCONTRAR BUGS (ERRORES). SI ENCUENTRA UNO, ABRA UN ISSUE A TRAVÉS DE NUESTRO REPOSITORIO GITHUB.</b>
|
||||
|
||||
@@ -85,7 +85,7 @@ NewPipe apoya varios servicios. Nuestras [documentaciones](https://teamnewpipe.g
|
||||
|
||||
## Installación y actualizaciones
|
||||
Se puede instalar NewPipe usando uno de los métodos siguientes:
|
||||
1. Agregar nuestro repositorio personalizado a F-Droid e instalarlo desde allí. Las instrucciones están aquí: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
|
||||
1. Agregar nuestro repositorio personalizado a F-Droid e instalarlo desde allí. Las instrucciones están aquí: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
|
||||
2. Descargar el archivo APK del enlace [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) e instalarlo.
|
||||
3. Actualizar a través de F-Droid. Este es el método más lento para obtener la actualización, como F-Droid debe reconocer cambios, construir el APK aparte, firmarlo con una clave, y finalmente empujar la actualización a los usuarios.
|
||||
4. Construir un APK de depuración por si mismo. Este es el modo más rápido para realizar nuevas características en su dispositivo, pero es mucho más complicado, asi que recomendamos uno de los otros métodos.
|
||||
@@ -135,6 +135,6 @@ El proyecto NewPipe tiene como objetivo proveer una experience privada y anónim
|
||||
Por lo tanto, la app no colecciona ningunos datos sin su consentimiento. La politica de privacidad de NewPipe explica en detalle los datos enviados y almacenados cuando envia un informe de error, o comentario en nuestro blog. Puede encontrar el documento [aqui](https://newpipe.net/legal/privacy/).
|
||||
|
||||
## Licencia
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe es Software Libre: Puede usar, estudiar, compartir, y mejorarlo a su voluntad. Especificamente puede redistribuir y/o modificarlo bajo los términos de la [GNU General Public License](https://www.gnu.org/licenses/gpl.html) como publicado por la Free Software Foundation, o versión 3 de la licencia, o (en su opción) cualquier versión posterior.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="ライセンス: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="ビルド状態"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
|
||||
<a href="https://hosted.weblate.org/engage/newpipe/" alt="翻訳状態"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC チャンネル: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="IRC チャンネル: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource 寄付"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
@@ -17,7 +17,7 @@
|
||||
<p align="center"><a href="https://newpipe.net">ウェブサイト</a> • <a href="https://newpipe.net/blog/">ブログ</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">ニュース</a></p>
|
||||
<hr>
|
||||
|
||||
*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md), [日本語](README.ja.md), [Română](README.ro.md) 。*
|
||||
*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md)。*
|
||||
|
||||
<b>注意: これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。</b>
|
||||
|
||||
@@ -143,7 +143,7 @@ NewPipe プロジェクトはメディアウェブサービスを使用する上
|
||||
|
||||
<span id="license"></span>
|
||||
## ライセンス
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe はフリーソフトウェアなので、あなたはあなたの望むように使用、習得、共有、改善を行えます。
|
||||
具体的には、フリーソフトウェア財団により公開された [GNU General Public License](https://www.gnu.org/licenses/gpl.html) のバージョン3のライセンスもしくは、(あなたの選択で) いずれかの後継バージョンの規約の元で配布または改変を行うことができます。
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<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://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
@@ -17,7 +17,7 @@
|
||||
<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), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
||||
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
|
||||
|
||||
<b>경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.</b>
|
||||
|
||||
@@ -139,7 +139,7 @@ NewPipe 프로젝트는 미디어 웹 서비스를 사용하는 것에 대한
|
||||
그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.net/legal/privacy/)에서 확인할 수 있습니다.
|
||||
|
||||
## License
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe는 자유 소프트웨어입니다: 당신의 마음대로 이것을 사용하고, 연구하고, 공유하고, 개선할 수 있습니다.
|
||||
구체적으로 당신은 자유 소프트웨어 재단에서 발행되는, 버전 3 또는 (당신의 선택에 따라)이후 버전의,
|
||||
|
||||
11
README.md
11
README.md
@@ -9,7 +9,7 @@
|
||||
<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://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
@@ -17,7 +17,7 @@
|
||||
<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), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md) .*
|
||||
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
|
||||
|
||||
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
|
||||
|
||||
@@ -45,6 +45,7 @@ NewPipe does not use any Google framework libraries, nor the YouTube API. Websit
|
||||
### Features
|
||||
|
||||
* Search videos
|
||||
* No Login Required
|
||||
* Display general info about videos
|
||||
* Watch YouTube videos
|
||||
* Listen to YouTube videos
|
||||
@@ -87,14 +88,14 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
|
||||
|
||||
## 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.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
|
||||
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.
|
||||
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, 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.
|
||||
|
||||
We recommend method 1 for most users. APKs installed using method 1 or 2 are compatible with each 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.
|
||||
|
||||
In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the update yet), we recommend following this procedure:
|
||||
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
|
||||
2. Uninstall NewPipe
|
||||
3. Download the APK from the new source and install it
|
||||
@@ -137,7 +138,7 @@ The NewPipe project aims to provide a private, anonymous experience for using me
|
||||
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)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe is Free Software: You can use, study share and improve it at your
|
||||
will. Specifically you can redistribute and/or modify it under the terms of the
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<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://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
@@ -18,7 +18,7 @@
|
||||
<p align="center"><a href="https://newpipe.net">Site</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), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
||||
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
|
||||
|
||||
<b>AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.</b>
|
||||
|
||||
@@ -93,7 +93,7 @@ Quando uma alteração no código NewPipe (devido à adição de recursos ou fix
|
||||
Recomendamos o método 2 para a maioria dos usuários. Os APKs instalados usando o método 2 ou 3 são compatíveis entre si, mas não com aqueles instalados usando o método 4. Isso se deve à mesma chave de assinatura (nossa) sendo usada para 2 e 3, mas uma chave de assinatura diferente (F-Droid's) está sendo usada para 4. Construir um APK depuração usando o método 1 exclui totalmente uma chave. Assinar chaves ajudam a garantir que um usuário não seja enganado para instalar uma atualização maliciosa em um aplicativo.
|
||||
|
||||
Enquanto isso, se você quiser trocar de fontes por algum motivo (por exemplo, a funcionalidade principal do NewPipe foi quebrada e o F-Droid ainda não tem a atualização), recomendamos seguir este procedimento:
|
||||
1. Back up your data via Settings > Content > Export Database so you keep your history, subscriptions, and playlistsFaça backup de seus dados através de Configurações > Conteúdo > Exportar Base de Dados para que você mantenha seu histórico, inscrições e playlists
|
||||
1. Faça backup de seus dados através de Configurações > Conteúdo > Exportar Base de Dados para que você mantenha seu histórico, inscrições e playlists
|
||||
2. Desinstale o NewPipe
|
||||
3. Baixe o APK da nova fonte e instale-o
|
||||
4. Importe os dados da etapa 1 via Configurações > Conteúdo > Inportar Banco de Dados
|
||||
@@ -135,7 +135,7 @@ O projeto NewPipe tem como objetivo proporcionar uma experiência privada e anô
|
||||
Portanto, o aplicativo não coleta nenhum dado sem o seu consentimento. A política de privacidade da NewPipe explica em detalhes quais dados são enviados e armazenados quando você envia um relatório de erro ou comenta em nosso blog. Você pode encontrar o documento [aqui](https://newpipe.net/legal/privacy/).
|
||||
|
||||
## Licença
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe é Software Livre: Você pode usar, estudar compartilhamento e melhorá-lo à sua vontade.
|
||||
Especificamente, você pode redistribuir e/ou modificá-lo sob os termos do
|
||||
|
||||
11
README.ro.md
11
README.ro.md
@@ -9,7 +9,7 @@
|
||||
<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://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
@@ -17,7 +17,7 @@
|
||||
<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/">Presă</a></p>
|
||||
<hr>
|
||||
|
||||
*Citiţi în alte limbi: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md)*
|
||||
*Citiţi în alte limbi: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
|
||||
|
||||
<b>Atenţionare: ACEASTA ESTE O VERSIUNE BETA, AŞA CĂ S-AR PUTE SĂ ÎNTÂLNIŢI ERORI. DACĂ SE ÎNTÂMPLĂ ACEST LUCRU, DESCHIDEŢI UN ISSUE PRIN REPSITORY-UL NOSTRU GITHUB.</b>
|
||||
|
||||
@@ -45,6 +45,7 @@ NewPipe nu foloseşte nici-o bibliotecă Google framework sau API-ul Youtube. We
|
||||
### Funcţii
|
||||
|
||||
* Căutarea videoclipurilor
|
||||
* Nu este necesară logarea
|
||||
* Afişarea informaţiilor generale despre videoclipuri
|
||||
* Urmărirea videoclipurilor Youtube
|
||||
* Ascultarea videoclipurilor Youtube
|
||||
@@ -87,9 +88,9 @@ NewPipe suportă servicii multiple. [Documentele](https://teamnewpipe.github.io/
|
||||
|
||||
## Instalare şi actualizări
|
||||
Puteţi instala NewPipe folosind una dintre următoarele metode:
|
||||
1. Adăugaţi depozitul nostru F-droid personalizat. Instrucţiunile sunt aici: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
|
||||
1. Adăugaţi depozitul nostru F-droid personalizat. Instrucţiunile sunt aici: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
|
||||
2. Descărcaţi APK-ul din [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) şi instalaţi-l.
|
||||
3. Actualizaţi via F-Droid. Aceasta este cea mai lentă metodă de a obţine actualizări, deoarece F-Droid trebuie să recunoască schimbările, să constriască APK-ul, să îl semneze, iar apoi să îl trimită utilizatorilor. (**IMPORTANT**: în momentul scrierii, o problemă împiedică versiunile mai noi de 0.20.1 să fie publicate. Aşa că, dacă doriţi să folosiţi F-droid, până această problemă este rezolvată, vă recomandăm metoda 1.)
|
||||
3. Actualizaţi via F-Droid. Aceasta este cea mai lentă metodă de a obţine actualizări, deoarece F-Droid trebuie să recunoască schimbările, să constriască APK-ul, să îl semneze, iar apoi să îl trimită utilizatorilor.
|
||||
4. Construiţi un APK de depanare. Aceasta este cea mai rapidă metodă de a primi funcţii noi, dar este mult mai complicată, aşa că vă recomandăm să folosiţi una dintre celelalte metode.
|
||||
|
||||
Recomandăm metoda 1 pentru majoritatea utilizatorilor. APK-urile din metodele 1 şi 2 suntcompatibile una cu cealaltă, dar nu cu cele din metoda 3. Acest lucru se datorează faptului că aceeași cheie de semnare (a noastră) este utilizată pentru 1 și 2, dar o altă cheie de semnare (F-Droid) este utilizată pentru 3. Construirea unui APK de depanare folosind metoda 4 exclude o cheie în întregime. Cheile de semnare vă asigură că un utilizator nu este păcălit să instaleze o actualizare rău intenționată a unei aplicații.
|
||||
@@ -137,7 +138,7 @@ Proiectul NewPipe îşi propune să furnizeze o experienţă privată şi anonim
|
||||
Prin urmare, aplicaţia nu colectează niciun fel de informaţii fără acordul dumneavoastră. Politica de confidențialitate a NewPipe explică în detaliu ce date sunt trimise și stocate atunci când trimiteți un raport de blocare sau comentați pe blogul nostru. Puteți găsi documentul [aici](https://newpipe.net/legal/privacy/).
|
||||
|
||||
## Licenţă
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe este Software Gratuit: Îl puteţi folosi şi împărtăşi cum doriţi. Mai exact, îl puteți redistribui și / sau modifica în conformitate cu termenii
|
||||
[GNU General Public License](https://www.gnu.org/licenses/gpl.html) aşa cum a fost publicat de Free Software Foundation, fie versiunea 3 a Licenței, fie
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="Laysinka: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Darajada Dhismaha"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
|
||||
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Heerka Turjimaada"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="Kanaalka IRC: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="Kanaalka IRC: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Kuwa Bountysource "><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
@@ -17,7 +17,7 @@
|
||||
<p align="center"><a href="https://newpipe.net">Website-ka</a> • <a href="https://newpipe.net/blog/">Maqaalada</a> • <a href="https://newpipe.net/FAQ/">Su'aalaha Aalaa La-iswaydiiyo</a> • <a href="https://newpipe.net/press/">Warbaahinta</a></p>
|
||||
<hr>
|
||||
|
||||
*Ku akhri luuqad kale: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
||||
*Ku akhri luuqad kale: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
|
||||
|
||||
<b>DIGNIIN: MIDKAN, NOOCA APP-KA EE HADDA WALI TIJAABO AYUU KU JIRAA, SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO, KA FUR ARIN SHARAXAYA QAYBTANADA ARRIMAHA EE GITHUB-KA.</b>
|
||||
|
||||
@@ -133,6 +133,6 @@ Mashruuca NewPipe waxay ujeedadiisu tahay inuu bixiyo wax kuu gaar ah, oo adoon
|
||||
Sidaa darteed, app-ku wax xog ah ma uruuriyo fasaxaaga la'aantii. Siyaasada Sirdhawrka NewPipe ayaa si faahfaahsan u sharaxda waxii xog ah ee la diro markaad cillad wariso, ama aad bogganaga faallo ka dhiibato. Warqada waxaad ka heli kartaa [halkan](https://newpipe.net/legal/privacy/).
|
||||
|
||||
## Laysinka
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe waa barnaamij bilaash ah oon lahayn xuquuqda daabacaada: Waad isticmaali kartaa, waad wadaagi kartaa waadna hormarin kartaa hadaad rabto. Gaar ahaan waad sii daabici kartaa ama wax baad ka badali kartaa ayadoo la raacayo shuruudaha sharciga guud ee [GNU](https://www.gnu.org/licenses/gpl.html) sida ay soosaareen Ururka Barnaamijyada Bilaashka ah, soosaarista 3aad ee laysinka, ama (hadaad doonto) nooc walba oo kasii dambeeyay laysinkii 3aad.
|
||||
|
||||
145
README.tr.md
Normal file
145
README.tr.md
Normal file
@@ -0,0 +1,145 @@
|
||||
<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">Android için hafif ve özgür bir akış arayüzü.</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-tr.svg"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub sürümleri"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="Lisans: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Derleme Durumu"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
|
||||
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Çeviri Durumu"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
|
||||
<a href="https://web.libera.chat/#newpipe" alt="IRC kanalı: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
|
||||
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource ödülleri"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||
</p>
|
||||
<hr>
|
||||
<p align="center"><a href="#ekran-fotoğrafları">Ekran fotoğrafları</a> • <a href="#açıklama">Açıklama</a> • <a href="#özellikler">Özellikler</a> • <a href="#kurulum-ve-güncellemeler">Kurulum ve güncellemeler</a> • <a href="#katkıda-bulunma">Katkıda bulunma</a> • <a href="#bağış">Bağış</a> • <a href="#lisans">Lisans</a></p>
|
||||
<p align="center"><a href="https://newpipe.net">Web sitesi</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">SSS</a> • <a href="https://newpipe.net/press/">Basın</a></p>
|
||||
<hr>
|
||||
|
||||
*Bu sayfayı diğer dillerde okuyun: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
|
||||
|
||||
<b>UYARI: BU SÜRÜM BETA SÜRÜMÜDÜR, BU NEDENLE HATALARLA KARŞILAŞABİLİRSİNİZ. HATA BULURSANIZ BU GITHUB DEPOSUNDA BUNU BİLDİRİN.</b>
|
||||
|
||||
<b>GOOGLE PLAY STORE'A NEWPIPE VEYA BAŞKA BİR KOPYASINI KOYMAK, PLAY STORE ŞARTLARINI VE KOŞULLARINI İHLAL EDER.</b>
|
||||
|
||||
## Ekran fotoğrafları
|
||||
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
|
||||
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
|
||||
|
||||
## Açıklama
|
||||
|
||||
NewPipe herhangi bir Google çerçeve kütüphanesini, ya da YouTube API hizmetlerini kullanmaz. Gerekli web hizmetleri yalnızca gerekli bilgileri almak için kaynak olarak kullanılır, bu nedenle bu uygulama Google hizmetleri yüklü olmayan cihazlarda da kullanılabilir. Ayrıca, copyleft özgür yazılımı olan NewPipe'ı kullanmak için bir YouTube hesabına ihtiyacınız yoktur.
|
||||
|
||||
### Özellikler
|
||||
|
||||
* Video arama
|
||||
* Videolar hakkında genel bilgileri görüntüleme
|
||||
* YouTube videoları izleme
|
||||
* YouTube videolarını dinleme
|
||||
* Pop-up modu (hareketli oynatıcı)
|
||||
* Video izlemek için akış oynatıcısını seçme
|
||||
* Video indirme
|
||||
* Sadece ses indirme
|
||||
* Videoyu Kodi'de açma
|
||||
* Sonraki video/ilgili videolar
|
||||
* YouTube'u belirli bir dilde arayın
|
||||
* Yaş sınırlı içeriği izleme/engelleme
|
||||
* Kanallar hakkındaki genel bilgileri görüntüleme
|
||||
* Kanal arama
|
||||
* Bir kanaldaki videoları izleme
|
||||
* Orbot/Tor desteği (henüz direkt olarak değil)
|
||||
* 1080p/2K/4K desteği
|
||||
* Geçmişi görme
|
||||
* Kanallara abone olma
|
||||
* Geçmişte arama
|
||||
* Oynatma listesi arama/oynatma
|
||||
* Çalma listelerini sıralayıp oynatın
|
||||
* Videoları sırayla oynatın
|
||||
* Yerel oynatma listeleri
|
||||
* Altyazılar
|
||||
* Canlı yayın desteği
|
||||
* Yorumları görme
|
||||
|
||||
### Desteklenen servisler
|
||||
|
||||
NewPipe birden fazla hizmeti destekler. Uygulamaya ve ayıklayıcıya yeni bir hizmet ekleme konusunda daha fazla bilgiye [kılavuzlarımızdan](https://teamnewpipe.github.io/documentation/) ulaşabilirsiniz. Yeni bir hizmet eklemek istiyorsanız lütfen bizimle iletişime geçin. Şu anda desteklenen hizmetler şunlardır:
|
||||
|
||||
* YouTube
|
||||
* SoundCloud \[beta\]
|
||||
* media.ccc.de \[beta\]
|
||||
* PeerTube \[beta\]
|
||||
* Bandcamp \[beta\]
|
||||
|
||||
<!-- Eski bağlantıları uyumlu tutmak için gizli span. -->
|
||||
<span id="updates"></span>
|
||||
|
||||
## Kurulum ve güncellemeler
|
||||
Aşağıdaki yöntemlerden birini kullanarak NewPipe'ı kurabilirsiniz:
|
||||
1. Özel depomuzu F-Droid'e ekleyin ve oradan yükleyin. Kılavuzu şurada bulabilirsiniz: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
|
||||
2. APK'yı [Github sürümlerinden](https://github.com/TeamNewPipe/NewPipe/releases) indirin ve kurun.
|
||||
3. F-Droid ile güncelleyin. Bu, güncellemeleri almanın en yavaş yöntemidir, çünkü F-Droid değişiklikleri tanımalı, APK'yı kendisi oluşturmalı, imzalamalı ve ardından güncellemeyi kullanıcılara dağıtmalıdır.
|
||||
4. Kendiniz bir APK derleyin. Bu yöntem, cihazınızda yeni özellikler edinmenin en hızlı yoludur, ancak çok daha karmaşıktır, bu nedenle diğer yöntemlerden birini kullanmanızı öneririz.
|
||||
|
||||
Çoğu kullanıcı için yöntem 1'i öneririz. Yöntem 1 veya 2 kullanılarak yüklenen APK'lar birbiriyle uyumludur, ancak yöntem 3 kullanılarak yüklenenlerle uyumlu değildir. Bu durum, 1 ve 2 için kullanılan aynı imzalama anahtarıın (bizim anahtarımız) 3 için kullanılan imzalama anahtarından (F-Droid'in anahtarı) farklı olmasından kaynaklanmaktadır. Yöntem 4 kullanılarak oluşturulan deneysel APK'larda anahtar yoktur. İmzalama anahtarları, bir kullanıcının bir uygulamaya kötü amaçlı bir güncelleme yüklemek için kandırılmadığından emin olmanıza yardımcı olur.
|
||||
|
||||
Bu arada, herhangi bir nedenle kaynakları değiştirmek istiyorsanız (örneğin, NewPipe'ın temel bir işlevi bozuldu ve F-Droid tarafında henüz bir güncelleme yayınlanmadı), bu prosedürü izlemenizi öneririz:
|
||||
1. Verilerinizi yedekleyin. `NewPipe Ayarları > İçerik > Veritabanını dışa aktar` seçeneklerini izleyerek aboneliklerinizi, oynatma listelerinizi ve geçmişinizi yedekleyin.
|
||||
2. NewPipe'ı kaldırın
|
||||
3. APK dosyasını yeni bir kaynaktan indirin ve yükleyin
|
||||
4. `Ayarlar > İçerik > Veritabanını içe aktar` seçeneklerini izleyerek 1. adımdaki verileri içe aktarın
|
||||
|
||||
## Katkıda bulunma
|
||||
Fikirleriniz, çevirileriniz, tasarım değişiklikleriniz, kod temizlemeniz veya ağır kod değişiklikleriniz olsun, yardımınıza her zaman açığız.
|
||||
Yapılan her değişiklikle NewPipe daha da iyi bir konuma geliyor!
|
||||
|
||||
Eğer yer almak istiyorsanız, [katkı sağlayanlar için hazırladığımız notları](.github/CONTRIBUTING.md) kontrol edin.
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/newpipe/">
|
||||
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Çeviri istatistikleri" />
|
||||
</a>
|
||||
|
||||
## Bağış
|
||||
NewPipe'ı beğendiyseniz, yapacağınız bağışlar bizi motive eder. Bitcoin gönderebilir veya Bountysource veya Liberapay aracılığıyla bağış yapabilirsiniz. NewPipe'a bağış yapma hakkında daha fazla bilgi için lütfen [web sitemizi](https://newpipe.net/donate) ziyaret edin.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin"></td>
|
||||
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR kodu" width="100px"></td>
|
||||
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Liberapay_logo_v2_white-on-yellow.svg" alt="Liberapay" width="80px" ></a></td>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="liberapay.com üzerinde NewPipe'ı ziyaret edin" width="100px"></a></td>
|
||||
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Liberapay aracılığıyla bağış yapın" height="35px"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px"></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="bountysource.com üzerinde NewPipe'ı ziyaret edin" width="100px"></a></td>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Ne kadar ödül kazanabileceğinizi kontrol edin."></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Gizlilik politikası
|
||||
|
||||
NewPipe projesi, çevrimiçi akış hizmetlerini kullanmak için özel, özgür ve anonim bir deneyim sunmayı amaçlamaktadır.
|
||||
Bu doğrultuda, uygulama sizin izniniz olmadan herhangi bir veri toplamaz. NewPipe'ın Gizlilik Politikası, bir çökme raporu gönderdiğinizde veya blogumuzda yorum yaptığınızda hangi verilerin gönderildiğini ve saklandığını ayrıntılı olarak açıklar. İlgili belgeyi [burada](https://newpipe.net/legal/privacy/) bulabilirsiniz.
|
||||
|
||||
## Lisans
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe özgür bir yazılımdır. Kendi başınıza kullanabilir, öğrenebilir, paylaşabilir
|
||||
ve geliştirebilirsiniz. Free Software Foundation tarafından yayınlanan GNU Genel Kamu Lisansı,
|
||||
Lisansın 3. sürümü veya (isteğe bağlı olarak) daha sonraki bir sürümü şartları ve
|
||||
koşulları altında yeniden dağıtabilir ve/veya değiştirebilirsiniz.
|
||||
131
app/build.gradle
131
app/build.gradle
@@ -17,8 +17,8 @@ android {
|
||||
resValue "string", "app_name", "NewPipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 967
|
||||
versionName "0.21.1"
|
||||
versionCode 971
|
||||
versionName "0.21.5"
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
@@ -66,6 +66,9 @@ android {
|
||||
// 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 {
|
||||
@@ -96,16 +99,19 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
icepickVersion = '3.2.0'
|
||||
checkstyleVersion = '8.38'
|
||||
stethoVersion = '1.5.1'
|
||||
leakCanaryVersion = '2.5'
|
||||
exoPlayerVersion = '2.12.3'
|
||||
|
||||
androidxLifecycleVersion = '2.2.0'
|
||||
androidxRoomVersion = '2.3.0-alpha03'
|
||||
|
||||
icepickVersion = '3.2.0'
|
||||
exoPlayerVersion = '2.12.3'
|
||||
googleAutoServiceVersion = '1.0-rc7'
|
||||
groupieVersion = '2.8.1'
|
||||
markwonVersion = '4.6.0'
|
||||
googleAutoServiceVersion = '1.0-rc7'
|
||||
|
||||
leakCanaryVersion = '2.5'
|
||||
stethoVersion = '1.5.1'
|
||||
mockitoVersion = '3.6.0'
|
||||
}
|
||||
|
||||
@@ -171,83 +177,100 @@ sonarqube {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
/** Desugaring **/
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
/** NewPipe libraries **/
|
||||
// You can use a local version by uncommenting a few lines in settings.gradle
|
||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.4'
|
||||
|
||||
/** Checkstyle **/
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
ktlint 'com.pinterest:ktlint:0.40.0'
|
||||
|
||||
/** Kotlin **/
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
|
||||
|
||||
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
ktlint "com.pinterest:ktlint:0.40.0"
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
||||
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
|
||||
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
||||
implementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}"
|
||||
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
|
||||
// NewPipe dependencies
|
||||
// You can use a local version by uncommenting a few lines in settings.gradle
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.1'
|
||||
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||
|
||||
implementation "org.jsoup:jsoup:1.13.1"
|
||||
|
||||
//noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users
|
||||
implementation "com.squareup.okhttp3:okhttp:3.12.13"
|
||||
|
||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
|
||||
|
||||
implementation "com.google.android.material:material:1.2.1"
|
||||
|
||||
compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}"
|
||||
kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
/** AndroidX **/
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.media:media:1.2.1'
|
||||
implementation 'androidx.webkit:webkit:1.4.0'
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.media:media:1.2.1'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
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'
|
||||
implementation 'androidx.webkit:webkit:1.4.0'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
/** Third-party libraries **/
|
||||
// Instance state boilerplate elimination
|
||||
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||
|
||||
// HTML parser
|
||||
implementation "org.jsoup:jsoup:1.13.1"
|
||||
|
||||
// HTTP client
|
||||
//noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users
|
||||
implementation "com.squareup.okhttp3:okhttp:3.12.13"
|
||||
|
||||
// Media player
|
||||
implementation "com.google.android.exoplayer:exoplayer:${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.xwray:groupie:${groupieVersion}"
|
||||
implementation "com.xwray:groupie-viewbinding:${groupieVersion}"
|
||||
|
||||
// Circular ImageView
|
||||
implementation "de.hdodenhof:circleimageview:3.1.0"
|
||||
// Image loading
|
||||
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
|
||||
|
||||
// Markdown library for Android
|
||||
implementation "io.noties.markwon:core:${markwonVersion}"
|
||||
implementation "io.noties.markwon:linkify:${markwonVersion}"
|
||||
|
||||
// File picker
|
||||
implementation "com.nononsenseapps:filepicker:4.2.1"
|
||||
|
||||
// Crash reporting
|
||||
implementation "ch.acra:acra-core:5.7.0"
|
||||
|
||||
// Reactive extensions for Java VM
|
||||
implementation "io.reactivex.rxjava3:rxjava:3.0.7"
|
||||
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
|
||||
// RxJava binding APIs for Android UI widgets
|
||||
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
|
||||
|
||||
implementation "org.ocpsoft.prettytime:prettytime:5.0.0.Final"
|
||||
// Date and time formatting
|
||||
implementation "org.ocpsoft.prettytime:prettytime:5.0.1.Final"
|
||||
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
/** Debugging **/
|
||||
// Memory leak detection
|
||||
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
||||
implementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}"
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
|
||||
// Debug bridge for Android
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
||||
|
||||
/** Testing **/
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
|
||||
testImplementation "org.mockito:mockito-inline:${mockitoVersion}"
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.schabi.newpipe.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -11,8 +10,8 @@ import leakcanary.LeakCanary;
|
||||
|
||||
public class DebugSettingsFragment extends BasePreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.debug_settings);
|
||||
|
||||
final Preference showMemoryLeaksPreference
|
||||
= findPreference(getString(R.string.show_memory_leaks_key));
|
||||
@@ -31,9 +30,4 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
|
||||
throw new RuntimeException();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.debug_settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,50 +6,50 @@
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.VideoAudioSettingsFragment"
|
||||
android:icon="?attr/ic_headset"
|
||||
android:icon="@drawable/ic_headset"
|
||||
android:title="@string/settings_category_video_audio_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.DownloadSettingsFragment"
|
||||
android:icon="?attr/ic_file_download"
|
||||
android:icon="@drawable/ic_file_download"
|
||||
android:title="@string/settings_category_downloads_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.AppearanceSettingsFragment"
|
||||
android:icon="?attr/ic_palette"
|
||||
android:icon="@drawable/ic_palette"
|
||||
android:title="@string/settings_category_appearance_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.HistorySettingsFragment"
|
||||
android:icon="?attr/ic_history"
|
||||
android:icon="@drawable/ic_history"
|
||||
android:title="@string/settings_category_history_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment"
|
||||
android:icon="?attr/ic_language"
|
||||
android:icon="@drawable/ic_language"
|
||||
android:title="@string/content"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.NotificationSettingsFragment"
|
||||
android:icon="?attr/ic_play_arrow"
|
||||
android:icon="@drawable/ic_play_arrow"
|
||||
android:title="@string/settings_category_notification_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.UpdateSettingsFragment"
|
||||
android:icon="?attr/ic_settings_update"
|
||||
android:icon="@drawable/ic_cloud_download"
|
||||
android:key="update_pref_screen_key"
|
||||
android:title="@string/settings_category_updates_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment"
|
||||
android:icon="?attr/ic_bug_report"
|
||||
android:icon="@drawable/ic_bug_report"
|
||||
android:key="@string/debug_pref_screen_key"
|
||||
android:title="@string/settings_category_debug_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.schabi.newpipe">
|
||||
package="org.schabi.newpipe"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
@@ -318,7 +319,7 @@
|
||||
<data android:pathPrefix="/video-channels/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Bandcamp filter -->
|
||||
<!-- Bandcamp filter for tracks, albums and playlists -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
@@ -329,10 +330,23 @@
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="bandcamp.com"/>
|
||||
<data android:host="*.bandcamp.com"/>
|
||||
<data android:pathPrefix="/"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Bandcamp filter for radio -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:sspPattern="bandcamp.com/?show=*"/>
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<service
|
||||
android:name=".RouterActivity$FetcherService"
|
||||
|
||||
@@ -130,7 +130,7 @@ public class App extends MultiDexApplication {
|
||||
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, ""));
|
||||
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, null));
|
||||
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
|
||||
private ActivityMainBinding mainBinding;
|
||||
@@ -133,6 +134,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
|
||||
TLSSocketFactoryCompat.setAsDefault();
|
||||
}
|
||||
|
||||
ThemeHelper.setDayNightMode(this);
|
||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||
|
||||
assureCorrectAppLanguage(this);
|
||||
@@ -180,27 +183,27 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
|
||||
R.string.tab_subscriptions)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
.setIcon(R.drawable.ic_tv);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||
.setIcon(R.drawable.ic_rss_feed);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
.setIcon(R.drawable.ic_bookmark);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||
.setIcon(R.drawable.ic_file_download);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||
.setIcon(R.drawable.ic_history);
|
||||
|
||||
//Settings and About
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||
.setIcon(R.drawable.ic_settings);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||
.setIcon(R.drawable.ic_info_outline);
|
||||
|
||||
toggle = new ActionBarDrawerToggle(this, mainBinding.getRoot(),
|
||||
toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close);
|
||||
@@ -346,7 +349,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void showServices() {
|
||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
|
||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_up);
|
||||
|
||||
for (final StreamingService s : NewPipe.getServices()) {
|
||||
final String title = s.getServiceInfo().getName()
|
||||
@@ -412,7 +415,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void showTabs() throws ExtractionException {
|
||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp);
|
||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_down);
|
||||
|
||||
//Tabs
|
||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||
@@ -430,27 +433,27 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
.setIcon(R.drawable.ic_tv);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||
.setIcon(R.drawable.ic_rss_feed);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
.setIcon(R.drawable.ic_bookmark);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||
.setIcon(R.drawable.ic_file_download);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||
.setIcon(R.drawable.ic_history);
|
||||
|
||||
//Settings and About
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||
.setIcon(R.drawable.ic_settings);
|
||||
drawerLayoutBinding.navigation.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||
.setIcon(R.drawable.ic_info_outline);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -91,7 +91,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
|
||||
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||
|
||||
/**
|
||||
* Get the url from the intent and open it in the chosen preferred player.
|
||||
@@ -231,7 +230,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.unsupported_url)
|
||||
.setMessage(R.string.unsupported_url_dialog_message)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_share))
|
||||
.setIcon(R.drawable.ic_share)
|
||||
.setPositiveButton(R.string.open_in_browser,
|
||||
(dialog, which) -> ShareUtils.openUrlInBrowser(this, url))
|
||||
.setNegativeButton(R.string.share,
|
||||
@@ -373,7 +372,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot();
|
||||
radioButton.setText(item.description);
|
||||
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton,
|
||||
AppCompatResources.getDrawable(getApplicationContext(), item.icon),
|
||||
AppCompatResources.getDrawable(themeWrapperContext, item.icon),
|
||||
null, null, null);
|
||||
radioButton.setChecked(false);
|
||||
radioButton.setId(id++);
|
||||
@@ -427,16 +426,16 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
final AdapterChoiceItem videoPlayer = new AdapterChoiceItem(
|
||||
getString(R.string.video_player_key), getString(R.string.video_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_play_arrow));
|
||||
R.drawable.ic_play_arrow);
|
||||
final AdapterChoiceItem showInfo = new AdapterChoiceItem(
|
||||
getString(R.string.show_info_key), getString(R.string.show_info),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_info_outline));
|
||||
R.drawable.ic_info_outline);
|
||||
final AdapterChoiceItem popupPlayer = new AdapterChoiceItem(
|
||||
getString(R.string.popup_player_key), getString(R.string.popup_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_popup));
|
||||
R.drawable.ic_picture_in_picture);
|
||||
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
|
||||
getString(R.string.background_player_key), getString(R.string.background_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_headset));
|
||||
R.drawable.ic_headset);
|
||||
|
||||
if (linkType == LinkType.STREAM) {
|
||||
if (isExtVideoEnabled) {
|
||||
@@ -467,6 +466,11 @@ public class RouterActivity extends AppCompatActivity {
|
||||
if (capabilities.contains(AUDIO)) {
|
||||
returnList.add(backgroundPlayer);
|
||||
}
|
||||
// download is redundant for linkType CHANNEL AND PLAYLIST (till playlist downloading is
|
||||
// not supported )
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
|
||||
getString(R.string.download),
|
||||
R.drawable.ic_file_download));
|
||||
|
||||
} else {
|
||||
returnList.add(showInfo);
|
||||
@@ -479,10 +483,6 @@ public class RouterActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
|
||||
getString(R.string.download),
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_file_download)));
|
||||
|
||||
return returnList;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
import static org.schabi.newpipe.util.ShareUtils.openUrlInBrowser;
|
||||
|
||||
public class AboutActivity extends AppCompatActivity {
|
||||
/**
|
||||
* List of all software components.
|
||||
*/
|
||||
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = {
|
||||
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
|
||||
"https://github.com/ACRA/acra", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("AndroidX", "2005 - 2011", "The Android Open Source Project",
|
||||
"https://developer.android.com/jetpack", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
|
||||
"https://github.com/hdodenhof/CircleImageView",
|
||||
StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google, Inc.",
|
||||
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("GigaGet", "2014 - 2015", "Peter Cai",
|
||||
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3),
|
||||
new SoftwareComponent("Groupie", "2016", "Lisa Wray",
|
||||
"https://github.com/lisawray/groupie", StandardLicenses.MIT),
|
||||
new SoftwareComponent("Icepick", "2015", "Frankie Sardo",
|
||||
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1),
|
||||
new SoftwareComponent("Jsoup", "2009 - 2020", "Jonathan Hedley",
|
||||
"https://github.com/jhy/jsoup", StandardLicenses.MIT),
|
||||
new SoftwareComponent("Markwon", "2019", "Dimitry Ivanov",
|
||||
"https://github.com/noties/Markwon", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("Material Components for Android", "2016 - 2020", "Google, Inc.",
|
||||
"https://github.com/material-components/material-components-android",
|
||||
StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
|
||||
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
|
||||
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
|
||||
"https://github.com/spacecowboy/NoNonsense-FilePicker",
|
||||
StandardLicenses.MPL2),
|
||||
new SoftwareComponent("OkHttp", "2019", "Square, Inc.",
|
||||
"https://square.github.io/okhttp/", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
|
||||
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors",
|
||||
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxBinding", "2015", "Jake Wharton",
|
||||
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
|
||||
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
|
||||
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
|
||||
"https://github.com/nostra13/Android-Universal-Image-Loader",
|
||||
StandardLicenses.APACHE2),
|
||||
};
|
||||
|
||||
private static final int POS_ABOUT = 0;
|
||||
private static final int POS_LICENSE = 1;
|
||||
private static final int TOTAL_COUNT = 2;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeHelper.setTheme(this);
|
||||
setTitle(getString(R.string.title_activity_about));
|
||||
|
||||
final ActivityAboutBinding aboutBinding = ActivityAboutBinding.inflate(getLayoutInflater());
|
||||
setContentView(aboutBinding.getRoot());
|
||||
|
||||
setSupportActionBar(aboutBinding.toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
// Create the adapter that will return a fragment for each of the three
|
||||
// primary sections of the activity.
|
||||
final SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(this);
|
||||
|
||||
// Set up the ViewPager with the sections adapter.
|
||||
aboutBinding.container.setAdapter(mSectionsPagerAdapter);
|
||||
|
||||
new TabLayoutMediator(aboutBinding.tabs, aboutBinding.container, (tab, position) -> {
|
||||
switch (position) {
|
||||
default:
|
||||
case POS_ABOUT:
|
||||
tab.setText(R.string.tab_about);
|
||||
break;
|
||||
case POS_LICENSE:
|
||||
tab.setText(R.string.tab_licenses);
|
||||
break;
|
||||
}
|
||||
}).attach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
final int id = item.getItemId();
|
||||
|
||||
switch (id) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* A placeholder fragment containing a simple view.
|
||||
*/
|
||||
public static class AboutFragment extends Fragment {
|
||||
public AboutFragment() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Created a new instance of this fragment for the given section number.
|
||||
*
|
||||
* @return New instance of {@link AboutFragment}
|
||||
*/
|
||||
public static AboutFragment newInstance() {
|
||||
return new AboutFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final FragmentAboutBinding aboutBinding =
|
||||
FragmentAboutBinding.inflate(inflater, container, false);
|
||||
final Context context = getContext();
|
||||
|
||||
aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME);
|
||||
|
||||
aboutBinding.githubLink.setOnClickListener(nv ->
|
||||
openUrlInBrowser(context, context.getString(R.string.github_url), false));
|
||||
|
||||
aboutBinding.donationLink.setOnClickListener(v ->
|
||||
openUrlInBrowser(context, context.getString(R.string.donation_url), false));
|
||||
|
||||
aboutBinding.websiteLink.setOnClickListener(nv ->
|
||||
openUrlInBrowser(context, context.getString(R.string.website_url), false));
|
||||
|
||||
aboutBinding.privacyPolicyLink.setOnClickListener(v ->
|
||||
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url),
|
||||
false));
|
||||
|
||||
return aboutBinding.getRoot();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link FragmentStateAdapter} that returns a fragment corresponding to
|
||||
* one of the sections/tabs/pages.
|
||||
*/
|
||||
public static class SectionsPagerAdapter extends FragmentStateAdapter {
|
||||
public SectionsPagerAdapter(final FragmentActivity fa) {
|
||||
super(fa);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(final int position) {
|
||||
switch (position) {
|
||||
default:
|
||||
case POS_ABOUT:
|
||||
return AboutFragment.newInstance();
|
||||
case POS_LICENSE:
|
||||
return LicenseFragment.newInstance(SOFTWARE_COMPONENTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
// Show 2 total pages.
|
||||
return TOTAL_COUNT;
|
||||
}
|
||||
}
|
||||
}
|
||||
191
app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
Normal file
191
app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
Normal file
@@ -0,0 +1,191 @@
|
||||
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.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
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.Localization
|
||||
import org.schabi.newpipe.util.ShareUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
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: TabLayout.Tab, position: Int ->
|
||||
when (position) {
|
||||
POS_ABOUT -> tab.setText(R.string.tab_about)
|
||||
POS_LICENSE -> tab.setText(R.string.tab_licenses)
|
||||
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
|
||||
}
|
||||
}.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(url: Int) {
|
||||
setOnClickListener {
|
||||
ShareUtils.openUrlInBrowser(
|
||||
context,
|
||||
requireContext().getString(url),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val aboutBinding = FragmentAboutBinding.inflate(inflater, container, false)
|
||||
aboutBinding.aboutAppVersion.text = BuildConfig.VERSION_NAME
|
||||
aboutBinding.aboutGithubLink.openLink(R.string.github_url)
|
||||
aboutBinding.aboutDonationLink.openLink(R.string.donation_url)
|
||||
aboutBinding.aboutWebsiteLink.openLink(R.string.website_url)
|
||||
aboutBinding.aboutPrivacyPolicyLink.openLink(R.string.privacy_policy_url)
|
||||
return aboutBinding.root
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [FragmentStateAdapter] that returns a fragment corresponding to
|
||||
* one of the sections/tabs/pages.
|
||||
*/
|
||||
private class AboutStateAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
POS_ABOUT -> AboutFragment()
|
||||
POS_LICENSE -> LicenseFragment.newInstance(SOFTWARE_COMPONENTS)
|
||||
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
// Show 2 total pages.
|
||||
return TOTAL_COUNT
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* List of all software components.
|
||||
*/
|
||||
private val SOFTWARE_COMPONENTS = arrayOf(
|
||||
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(
|
||||
"CircleImageView", "2014 - 2020", "Henning Dodenhof",
|
||||
"https://github.com/hdodenhof/CircleImageView", 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(
|
||||
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
|
||||
"https://github.com/ocpsoft/prettytime", 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(
|
||||
"Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
|
||||
"https://github.com/nostra13/Android-Universal-Image-Loader",
|
||||
StandardLicenses.APACHE2
|
||||
)
|
||||
)
|
||||
private const val POS_ABOUT = 0
|
||||
private const val POS_LICENSE = 1
|
||||
private const val TOTAL_COUNT = 2
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.FragmentLicensesBinding;
|
||||
import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
|
||||
/**
|
||||
* Fragment containing the software licenses.
|
||||
*/
|
||||
public class LicenseFragment extends Fragment {
|
||||
private static final String ARG_COMPONENTS = "components";
|
||||
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
|
||||
|
||||
private SoftwareComponent[] softwareComponents;
|
||||
private SoftwareComponent componentForContextMenu;
|
||||
private License activeLicense;
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArray(ARG_COMPONENTS, Objects.requireNonNull(softwareComponents));
|
||||
final LicenseFragment fragment = new LicenseFragment();
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
softwareComponents = (SoftwareComponent[]) getArguments()
|
||||
.getParcelableArray(ARG_COMPONENTS);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
final Serializable license = savedInstanceState.getSerializable(LICENSE_KEY);
|
||||
if (license != null) {
|
||||
activeLicense = (License) license;
|
||||
}
|
||||
}
|
||||
// Sort components by name
|
||||
Arrays.sort(softwareComponents, Comparator.comparing(SoftwareComponent::getName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
compositeDisposable.dispose();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
final FragmentLicensesBinding binding = FragmentLicensesBinding
|
||||
.inflate(inflater, container, false);
|
||||
|
||||
binding.appReadLicense.setOnClickListener(v -> {
|
||||
activeLicense = StandardLicenses.GPL3;
|
||||
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
|
||||
StandardLicenses.GPL3));
|
||||
});
|
||||
|
||||
for (final SoftwareComponent component : softwareComponents) {
|
||||
final ItemSoftwareComponentBinding componentBinding = ItemSoftwareComponentBinding
|
||||
.inflate(inflater, container, false);
|
||||
componentBinding.name.setText(component.getName());
|
||||
componentBinding.copyright.setText(getString(R.string.copyright,
|
||||
component.getYears(),
|
||||
component.getCopyrightOwner(),
|
||||
component.getLicense().getAbbreviation()));
|
||||
|
||||
final View root = componentBinding.getRoot();
|
||||
root.setTag(component);
|
||||
root.setOnClickListener(v -> {
|
||||
activeLicense = component.getLicense();
|
||||
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
|
||||
component.getLicense()));
|
||||
});
|
||||
binding.softwareComponents.addView(root);
|
||||
registerForContextMenu(root);
|
||||
}
|
||||
if (activeLicense != null) {
|
||||
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
|
||||
activeLicense));
|
||||
}
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(final ContextMenu menu, final View v,
|
||||
final ContextMenu.ContextMenuInfo menuInfo) {
|
||||
final MenuInflater inflater = getActivity().getMenuInflater();
|
||||
final SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||
menu.setHeaderTitle(component.getName());
|
||||
inflater.inflate(R.menu.software_component, menu);
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
componentForContextMenu = (SoftwareComponent) v.getTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(@NonNull final MenuItem item) {
|
||||
// item.getMenuInfo() is null so we use the tag of the view
|
||||
final SoftwareComponent component = componentForContextMenu;
|
||||
if (component == null) {
|
||||
return false;
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_website:
|
||||
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
|
||||
return true;
|
||||
case R.id.action_show_license:
|
||||
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
|
||||
component.getLicense()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
if (activeLicense != null) {
|
||||
savedInstanceState.putSerializable(LICENSE_KEY, activeLicense);
|
||||
}
|
||||
}
|
||||
}
|
||||
131
app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
Normal file
131
app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
Normal file
@@ -0,0 +1,131 @@
|
||||
package org.schabi.newpipe.about
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.ContextMenu
|
||||
import android.view.ContextMenu.ContextMenuInfo
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.about.LicenseFragmentHelper.showLicense
|
||||
import org.schabi.newpipe.databinding.FragmentLicensesBinding
|
||||
import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding
|
||||
import org.schabi.newpipe.util.ShareUtils
|
||||
import java.util.Arrays
|
||||
import java.util.Objects
|
||||
|
||||
/**
|
||||
* Fragment containing the software licenses.
|
||||
*/
|
||||
class LicenseFragment : Fragment() {
|
||||
private lateinit var softwareComponents: Array<SoftwareComponent>
|
||||
private var componentForContextMenu: SoftwareComponent? = null
|
||||
private var activeLicense: License? = null
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
softwareComponents =
|
||||
arguments?.getParcelableArray(ARG_COMPONENTS) as Array<SoftwareComponent>
|
||||
if (savedInstanceState != null) {
|
||||
val license = savedInstanceState.getSerializable(LICENSE_KEY)
|
||||
if (license != null) {
|
||||
activeLicense = license as License?
|
||||
}
|
||||
}
|
||||
// Sort components by name
|
||||
Arrays.sort(softwareComponents, Comparator.comparing(SoftwareComponent::name))
|
||||
}
|
||||
|
||||
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 {
|
||||
activeLicense = StandardLicenses.GPL3
|
||||
compositeDisposable.add(
|
||||
showLicense(activity, StandardLicenses.GPL3)
|
||||
)
|
||||
}
|
||||
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 {
|
||||
activeLicense = component.license
|
||||
compositeDisposable.add(
|
||||
showLicense(activity, component.license)
|
||||
)
|
||||
}
|
||||
binding.licensesSoftwareComponents.addView(root)
|
||||
registerForContextMenu(root)
|
||||
}
|
||||
if (activeLicense != null) {
|
||||
compositeDisposable.add(
|
||||
showLicense(activity, activeLicense!!)
|
||||
)
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?) {
|
||||
val inflater = requireActivity().menuInflater
|
||||
val component = v.tag as SoftwareComponent
|
||||
menu.setHeaderTitle(component.name)
|
||||
inflater.inflate(R.menu.software_component, menu)
|
||||
super.onCreateContextMenu(menu, v, menuInfo)
|
||||
componentForContextMenu = component
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
// item.getMenuInfo() is null so we use the tag of the view
|
||||
val component = componentForContextMenu ?: return false
|
||||
when (item.itemId) {
|
||||
R.id.menu_software_website -> {
|
||||
ShareUtils.openUrlInBrowser(activity, component.link)
|
||||
return true
|
||||
}
|
||||
R.id.menu_software_show_license -> compositeDisposable.add(
|
||||
showLicense(activity, component.license)
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
||||
super.onSaveInstanceState(savedInstanceState)
|
||||
if (activeLicense != null) {
|
||||
savedInstanceState.putSerializable(LICENSE_KEY, activeLicense)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ARG_COMPONENTS = "components"
|
||||
private const val LICENSE_KEY = "ACTIVE_LICENSE"
|
||||
fun newInstance(softwareComponents: Array<SoftwareComponent>): LicenseFragment {
|
||||
val fragment = LicenseFragment()
|
||||
fragment.arguments =
|
||||
bundleOf(ARG_COMPONENTS to Objects.requireNonNull(softwareComponents))
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Base64;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
public final class LicenseFragmentHelper {
|
||||
private LicenseFragmentHelper() { }
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
private static String getFormattedLicense(@NonNull final Context context,
|
||||
@NonNull final License license) {
|
||||
final StringBuilder licenseContent = new StringBuilder();
|
||||
final String webViewData;
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8))) {
|
||||
String str;
|
||||
while ((str = in.readLine()) != null) {
|
||||
licenseContent.append(str);
|
||||
}
|
||||
|
||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||
webViewData = licenseContent.toString().replace("</head>",
|
||||
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not get license file: " + license.getFilename(), e);
|
||||
}
|
||||
return webViewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context the Android context
|
||||
* @return String which is a CSS stylesheet according to the context's theme
|
||||
*/
|
||||
private static String getLicenseStylesheet(@NonNull final Context context) {
|
||||
final boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
||||
return "body{padding:12px 15px;margin:0;"
|
||||
+ "background:#" + getHexRGBColor(context, isLightTheme
|
||||
? R.color.light_license_background_color
|
||||
: R.color.dark_license_background_color) + ";"
|
||||
+ "color:#" + getHexRGBColor(context, isLightTheme
|
||||
? R.color.light_license_text_color
|
||||
: R.color.dark_license_text_color) + "}"
|
||||
+ "a[href]{color:#" + getHexRGBColor(context, isLightTheme
|
||||
? R.color.light_youtube_primary_color
|
||||
: R.color.dark_youtube_primary_color) + "}"
|
||||
+ "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
|
||||
*/
|
||||
private static String getHexRGBColor(@NonNull final Context context, final int color) {
|
||||
return context.getResources().getString(color).substring(3);
|
||||
}
|
||||
|
||||
static Disposable showLicense(@Nullable final Context context, @NonNull final License license) {
|
||||
if (context == null) {
|
||||
return Disposable.empty();
|
||||
}
|
||||
|
||||
return Observable.fromCallable(() -> getFormattedLicense(context, license))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(formattedLicense -> {
|
||||
final String webViewData = Base64.encodeToString(formattedLicense
|
||||
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
|
||||
final WebView webView = new WebView(context);
|
||||
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
|
||||
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(context);
|
||||
alert.setTitle(license.getName());
|
||||
alert.setView(webView);
|
||||
assureCorrectAppLanguage(context);
|
||||
alert.setNegativeButton(context.getString(R.string.finish),
|
||||
(dialog, which) -> dialog.dismiss());
|
||||
alert.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.schabi.newpipe.about
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import android.webkit.WebView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
object LicenseFragmentHelper {
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
private fun getFormattedLicense(context: Context, license: License): String {
|
||||
val licenseContent = StringBuilder()
|
||||
val webViewData: String
|
||||
try {
|
||||
BufferedReader(
|
||||
InputStreamReader(
|
||||
context.assets.open(license.filename),
|
||||
StandardCharsets.UTF_8
|
||||
)
|
||||
).use { `in` ->
|
||||
var str: String?
|
||||
while (`in`.readLine().also { str = it } != null) {
|
||||
licenseContent.append(str)
|
||||
}
|
||||
|
||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||
webViewData = "$licenseContent".replace(
|
||||
"</head>",
|
||||
"<style>" + getLicenseStylesheet(context) + "</style></head>"
|
||||
)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw IllegalArgumentException(
|
||||
"Could not get license file: " + license.filename, e
|
||||
)
|
||||
}
|
||||
return webViewData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context the Android context
|
||||
* @return String which is a CSS stylesheet according to the context's theme
|
||||
*/
|
||||
private fun getLicenseStylesheet(context: Context): String {
|
||||
val isLightTheme = ThemeHelper.isLightThemeSelected(context)
|
||||
return (
|
||||
"body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor(
|
||||
context,
|
||||
if (isLightTheme) R.color.light_license_background_color
|
||||
else R.color.dark_license_background_color
|
||||
) + ";" + "color:#" + getHexRGBColor(
|
||||
context,
|
||||
if (isLightTheme) R.color.light_license_text_color
|
||||
else R.color.dark_license_text_color
|
||||
) + "}" + "a[href]{color:#" + getHexRGBColor(
|
||||
context,
|
||||
if (isLightTheme) R.color.light_youtube_primary_color
|
||||
else R.color.dark_youtube_primary_color
|
||||
) + "}" + "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
|
||||
*/
|
||||
private fun getHexRGBColor(context: Context, color: Int): String {
|
||||
return context.getString(color).substring(3)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showLicense(context: Context?, license: License): Disposable {
|
||||
return if (context == null) {
|
||||
Disposable.empty()
|
||||
} else {
|
||||
Observable.fromCallable { getFormattedLicense(context, license) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { formattedLicense: String ->
|
||||
val webViewData = Base64.encodeToString(
|
||||
formattedLicense
|
||||
.toByteArray(StandardCharsets.UTF_8),
|
||||
Base64.NO_PADDING
|
||||
)
|
||||
val webView = WebView(context)
|
||||
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64")
|
||||
val alert = AlertDialog.Builder(context)
|
||||
alert.setTitle(license.name)
|
||||
alert.setView(webView)
|
||||
Localization.assureCorrectAppLanguage(context)
|
||||
alert.setNegativeButton(
|
||||
context.getString(R.string.finish)
|
||||
) { dialog, _ -> dialog.dismiss() }
|
||||
alert.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
/**
|
||||
* Class containing information about standard software licenses.
|
||||
*/
|
||||
public final class StandardLicenses {
|
||||
public static final License GPL3
|
||||
= new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html");
|
||||
public static final License APACHE2
|
||||
= new License("Apache License, Version 2.0", "ALv2", "apache2.html");
|
||||
public static final License MPL2
|
||||
= new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html");
|
||||
public static final License MIT
|
||||
= new License("MIT License", "MIT", "mit.html");
|
||||
public static final License EPL1
|
||||
= new License("Eclipse Public License, Version 1.0", "EPL 1.0", "epl1.html");
|
||||
|
||||
private StandardLicenses() { }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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")
|
||||
}
|
||||
@@ -312,25 +312,36 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
private void fetchStreamsSize() {
|
||||
disposables.clear();
|
||||
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
|
||||
.subscribe(result -> {
|
||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.video_button) {
|
||||
setupVideoSpinner();
|
||||
}
|
||||
}));
|
||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
||||
== R.id.video_button) {
|
||||
setupVideoSpinner();
|
||||
}
|
||||
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Downloading video stream size",
|
||||
currentInfo.getServiceId()))));
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
|
||||
.subscribe(result -> {
|
||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
|
||||
setupAudioSpinner();
|
||||
}
|
||||
}));
|
||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
||||
== R.id.audio_button) {
|
||||
setupAudioSpinner();
|
||||
}
|
||||
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Downloading audio stream size",
|
||||
currentInfo.getServiceId()))));
|
||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams)
|
||||
.subscribe(result -> {
|
||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) {
|
||||
setupSubtitleSpinner();
|
||||
}
|
||||
}));
|
||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
||||
== R.id.subtitle_button) {
|
||||
setupSubtitleSpinner();
|
||||
}
|
||||
}, throwable -> ErrorActivity.reportErrorInSnackbar(context,
|
||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||
"Downloading subtitle stream size",
|
||||
currentInfo.getServiceId()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -394,8 +405,7 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
|
||||
toolbar.setTitle(R.string.download_dialog_title);
|
||||
toolbar.setNavigationIcon(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_arrow_back));
|
||||
toolbar.setNavigationIcon(R.drawable.ic_arrow_back);
|
||||
toolbar.inflateMenu(R.menu.dialog_url);
|
||||
toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss());
|
||||
toolbar.setNavigationContentDescription(R.string.cancel);
|
||||
@@ -672,9 +682,6 @@ public class DownloadDialog extends DialogFragment
|
||||
prefs.edit()
|
||||
.putString(getString(R.string.last_used_download_type), selectedMediaType)
|
||||
.apply();
|
||||
|
||||
Toast.makeText(context, getString(R.string.download_has_started),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
|
||||
@@ -930,6 +937,9 @@ public class DownloadDialog extends DialogFragment
|
||||
DownloadManagerService.startMission(context, urls, storage, kind, threads,
|
||||
currentInfo.getUrl(), psName, psArgs, nearLength, recoveryInfo);
|
||||
|
||||
Toast.makeText(context, getString(R.string.download_has_started),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.schabi.newpipe.error;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
@@ -16,6 +15,7 @@ import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
@@ -137,6 +137,8 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ThemeHelper.setDayNightMode(this);
|
||||
ThemeHelper.setTheme(this);
|
||||
|
||||
activityErrorBinding = ActivityErrorBinding.inflate(getLayoutInflater());
|
||||
@@ -188,15 +190,16 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
final int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
onBackPressed();
|
||||
} else if (id == R.id.menu_item_share_error) {
|
||||
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
||||
} else {
|
||||
return false;
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
case R.id.menu_item_share_error:
|
||||
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
||||
|
||||
@@ -11,15 +11,20 @@ import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class EmptyFragment extends BaseFragment {
|
||||
final boolean showMessage;
|
||||
private static final String SHOW_MESSAGE = "SHOW_MESSAGE";
|
||||
|
||||
public EmptyFragment(final boolean showMessage) {
|
||||
this.showMessage = showMessage;
|
||||
public static final EmptyFragment newInstance(final boolean showMessage) {
|
||||
final EmptyFragment emptyFragment = new EmptyFragment();
|
||||
final Bundle bundle = new Bundle(1);
|
||||
bundle.putBoolean(SHOW_MESSAGE, showMessage);
|
||||
emptyFragment.setArguments(bundle);
|
||||
return emptyFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final boolean showMessage = getArguments().getBoolean(SHOW_MESSAGE);
|
||||
final View view = inflater.inflate(R.layout.fragment_empty, container, false);
|
||||
view.findViewById(R.id.empty_state_view).setVisibility(
|
||||
showMessage ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -30,7 +29,6 @@ import org.schabi.newpipe.settings.tabs.Tab;
|
||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -87,10 +85,10 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
|
||||
binding = FragmentMainBinding.bind(rootView);
|
||||
|
||||
binding.mainTabLayout.setTabIconTint(ColorStateList.valueOf(
|
||||
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
|
||||
binding.mainTabLayout.setupWithViewPager(binding.pager);
|
||||
binding.mainTabLayout.addOnTabSelectedListener(this);
|
||||
binding.mainTabLayout.setTabRippleColor(binding.mainTabLayout.getTabRippleColor()
|
||||
.withAlpha(32));
|
||||
|
||||
setupTabs();
|
||||
}
|
||||
|
||||
@@ -4,23 +4,38 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
|
||||
import com.google.android.material.chip.Chip;
|
||||
|
||||
import org.schabi.newpipe.BaseFragment;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.FragmentDescriptionBinding;
|
||||
import org.schabi.newpipe.databinding.ItemMetadataBinding;
|
||||
import org.schabi.newpipe.databinding.ItemMetadataTagsBinding;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.TextLinkifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
|
||||
|
||||
public class DescriptionFragment extends BaseFragment {
|
||||
|
||||
@@ -28,6 +43,7 @@ public class DescriptionFragment extends BaseFragment {
|
||||
StreamInfo streamInfo = null;
|
||||
@Nullable
|
||||
Disposable descriptionDisposable = null;
|
||||
FragmentDescriptionBinding binding;
|
||||
|
||||
public DescriptionFragment() {
|
||||
}
|
||||
@@ -40,11 +56,11 @@ public class DescriptionFragment extends BaseFragment {
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
final FragmentDescriptionBinding binding =
|
||||
FragmentDescriptionBinding.inflate(inflater, container, false);
|
||||
binding = FragmentDescriptionBinding.inflate(inflater, container, false);
|
||||
if (streamInfo != null) {
|
||||
setupUploadDate(binding.detailUploadDateView);
|
||||
setupDescription(binding.detailDescriptionView);
|
||||
setupUploadDate();
|
||||
setupDescription();
|
||||
setupMetadata(inflater, binding.detailMetadataLayout);
|
||||
}
|
||||
return binding.getRoot();
|
||||
}
|
||||
@@ -57,37 +73,197 @@ public class DescriptionFragment extends BaseFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupUploadDate(final TextView uploadDateTextView) {
|
||||
|
||||
private void setupUploadDate() {
|
||||
if (streamInfo.getUploadDate() != null) {
|
||||
uploadDateTextView.setText(Localization
|
||||
binding.detailUploadDateView.setText(Localization
|
||||
.localizeUploadDate(activity, streamInfo.getUploadDate().offsetDateTime()));
|
||||
} else {
|
||||
uploadDateTextView.setVisibility(View.GONE);
|
||||
binding.detailUploadDateView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDescription(final TextView descriptionTextView) {
|
||||
|
||||
private void setupDescription() {
|
||||
final Description description = streamInfo.getDescription();
|
||||
if (description == null || isEmpty(description.getContent())
|
||||
|| description == Description.emptyDescription) {
|
||||
descriptionTextView.setText("");
|
||||
binding.detailDescriptionView.setVisibility(View.GONE);
|
||||
binding.detailSelectDescriptionButton.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
// start with disabled state. This also loads description content (!)
|
||||
disableDescriptionSelection();
|
||||
|
||||
binding.detailSelectDescriptionButton.setOnClickListener(v -> {
|
||||
if (binding.detailDescriptionNoteView.getVisibility() == View.VISIBLE) {
|
||||
disableDescriptionSelection();
|
||||
} else {
|
||||
// enable selection only when button is clicked to prevent flickering
|
||||
enableDescriptionSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void enableDescriptionSelection() {
|
||||
binding.detailDescriptionNoteView.setVisibility(View.VISIBLE);
|
||||
binding.detailDescriptionView.setTextIsSelectable(true);
|
||||
|
||||
final String buttonLabel = getString(R.string.description_select_disable);
|
||||
binding.detailSelectDescriptionButton.setContentDescription(buttonLabel);
|
||||
TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel);
|
||||
binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_close);
|
||||
}
|
||||
|
||||
private void disableDescriptionSelection() {
|
||||
// show description content again, otherwise some links are not clickable
|
||||
loadDescriptionContent();
|
||||
|
||||
binding.detailDescriptionNoteView.setVisibility(View.GONE);
|
||||
binding.detailDescriptionView.setTextIsSelectable(false);
|
||||
|
||||
final String buttonLabel = getString(R.string.description_select_enable);
|
||||
binding.detailSelectDescriptionButton.setContentDescription(buttonLabel);
|
||||
TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel);
|
||||
binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_select_all);
|
||||
}
|
||||
|
||||
private void loadDescriptionContent() {
|
||||
final Description description = streamInfo.getDescription();
|
||||
switch (description.getType()) {
|
||||
case Description.HTML:
|
||||
descriptionDisposable = TextLinkifier.createLinksFromHtmlBlock(requireContext(),
|
||||
description.getContent(), descriptionTextView,
|
||||
description.getContent(), binding.detailDescriptionView,
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY);
|
||||
break;
|
||||
case Description.MARKDOWN:
|
||||
descriptionDisposable = TextLinkifier.createLinksFromMarkdownText(requireContext(),
|
||||
description.getContent(), descriptionTextView);
|
||||
description.getContent(), binding.detailDescriptionView);
|
||||
break;
|
||||
case Description.PLAIN_TEXT: default:
|
||||
descriptionDisposable = TextLinkifier.createLinksFromPlainText(requireContext(),
|
||||
description.getContent(), descriptionTextView);
|
||||
description.getContent(), binding.detailDescriptionView);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setupMetadata(final LayoutInflater inflater,
|
||||
final LinearLayout layout) {
|
||||
addMetadataItem(inflater, layout, false,
|
||||
R.string.metadata_category, streamInfo.getCategory());
|
||||
|
||||
addTagsMetadataItem(inflater, layout);
|
||||
|
||||
addMetadataItem(inflater, layout, false,
|
||||
R.string.metadata_licence, streamInfo.getLicence());
|
||||
|
||||
addPrivacyMetadataItem(inflater, layout);
|
||||
|
||||
if (streamInfo.getAgeLimit() != NO_AGE_LIMIT) {
|
||||
addMetadataItem(inflater, layout, false,
|
||||
R.string.metadata_age_limit, String.valueOf(streamInfo.getAgeLimit()));
|
||||
}
|
||||
|
||||
if (streamInfo.getLanguageInfo() != null) {
|
||||
addMetadataItem(inflater, layout, false,
|
||||
R.string.metadata_language, streamInfo.getLanguageInfo().getDisplayLanguage());
|
||||
}
|
||||
|
||||
addMetadataItem(inflater, layout, true,
|
||||
R.string.metadata_support, streamInfo.getSupportInfo());
|
||||
addMetadataItem(inflater, layout, true,
|
||||
R.string.metadata_host, streamInfo.getHost());
|
||||
addMetadataItem(inflater, layout, true,
|
||||
R.string.metadata_thumbnail_url, streamInfo.getThumbnailUrl());
|
||||
}
|
||||
|
||||
private void addMetadataItem(final LayoutInflater inflater,
|
||||
final LinearLayout layout,
|
||||
final boolean linkifyContent,
|
||||
@StringRes final int type,
|
||||
@Nullable final String content) {
|
||||
if (isBlank(content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ItemMetadataBinding itemBinding
|
||||
= ItemMetadataBinding.inflate(inflater, layout, false);
|
||||
|
||||
itemBinding.metadataTypeView.setText(type);
|
||||
itemBinding.metadataTypeView.setOnLongClickListener(v -> {
|
||||
ShareUtils.copyToClipboard(requireContext(), content);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (linkifyContent) {
|
||||
TextLinkifier.createLinksFromPlainText(requireContext(),
|
||||
content, itemBinding.metadataContentView);
|
||||
} else {
|
||||
itemBinding.metadataContentView.setText(content);
|
||||
}
|
||||
|
||||
layout.addView(itemBinding.getRoot());
|
||||
}
|
||||
|
||||
private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {
|
||||
if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) {
|
||||
final ItemMetadataTagsBinding itemBinding
|
||||
= ItemMetadataTagsBinding.inflate(inflater, layout, false);
|
||||
|
||||
final List<String> tags = new ArrayList<>(streamInfo.getTags());
|
||||
Collections.sort(tags);
|
||||
for (final String tag : tags) {
|
||||
final Chip chip = (Chip) inflater.inflate(R.layout.chip,
|
||||
itemBinding.metadataTagsChips, false);
|
||||
chip.setText(tag);
|
||||
chip.setOnClickListener(this::onTagClick);
|
||||
chip.setOnLongClickListener(this::onTagLongClick);
|
||||
itemBinding.metadataTagsChips.addView(chip);
|
||||
}
|
||||
|
||||
layout.addView(itemBinding.getRoot());
|
||||
}
|
||||
}
|
||||
|
||||
private void onTagClick(final View chip) {
|
||||
if (getParentFragment() != null) {
|
||||
NavigationHelper.openSearchFragment(getParentFragment().getParentFragmentManager(),
|
||||
streamInfo.getServiceId(), ((Chip) chip).getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onTagLongClick(final View chip) {
|
||||
ShareUtils.copyToClipboard(requireContext(), ((Chip) chip).getText().toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addPrivacyMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {
|
||||
if (streamInfo.getPrivacy() != null) {
|
||||
@StringRes final int contentRes;
|
||||
switch (streamInfo.getPrivacy()) {
|
||||
case PUBLIC:
|
||||
contentRes = R.string.metadata_privacy_public;
|
||||
break;
|
||||
case UNLISTED:
|
||||
contentRes = R.string.metadata_privacy_unlisted;
|
||||
break;
|
||||
case PRIVATE:
|
||||
contentRes = R.string.metadata_privacy_private;
|
||||
break;
|
||||
case INTERNAL:
|
||||
contentRes = R.string.metadata_privacy_internal;
|
||||
break;
|
||||
case OTHER: default:
|
||||
contentRes = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (contentRes != 0) {
|
||||
addMetadataItem(inflater, layout, false,
|
||||
R.string.metadata_privacy, getString(contentRes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.fragments.EmptyFragment;
|
||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
||||
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
|
||||
import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment;
|
||||
import org.schabi.newpipe.ktx.AnimationType;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
|
||||
@@ -153,7 +153,7 @@ public final class VideoDetailFragment
|
||||
|
||||
// tabs
|
||||
private boolean showComments;
|
||||
private boolean showRelatedStreams;
|
||||
private boolean showRelatedItems;
|
||||
private boolean showDescription;
|
||||
private String selectedTabTag;
|
||||
@AttrRes @NonNull final List<Integer> tabIcons = new ArrayList<>();
|
||||
@@ -280,7 +280,7 @@ public final class VideoDetailFragment
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
showComments = prefs.getBoolean(getString(R.string.show_comments_key), true);
|
||||
showRelatedStreams = prefs.getBoolean(getString(R.string.show_next_video_key), true);
|
||||
showRelatedItems = prefs.getBoolean(getString(R.string.show_next_video_key), true);
|
||||
showDescription = prefs.getBoolean(getString(R.string.show_description_key), true);
|
||||
selectedTabTag = prefs.getString(
|
||||
getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
|
||||
@@ -413,7 +413,7 @@ public final class VideoDetailFragment
|
||||
showComments = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
} else if (key.equals(getString(R.string.show_next_video_key))) {
|
||||
showRelatedStreams = sharedPreferences.getBoolean(key, true);
|
||||
showRelatedItems = sharedPreferences.getBoolean(key, true);
|
||||
tabSettingsChanged = true;
|
||||
} else if (key.equals(getString(R.string.show_description_key))) {
|
||||
showComments = sharedPreferences.getBoolean(key, true);
|
||||
@@ -923,26 +923,26 @@ public final class VideoDetailFragment
|
||||
if (shouldShowComments()) {
|
||||
pageAdapter.addFragment(
|
||||
CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG);
|
||||
tabIcons.add(R.drawable.ic_comment_white_24dp);
|
||||
tabIcons.add(R.drawable.ic_comment);
|
||||
tabContentDescriptions.add(R.string.comments_tab_description);
|
||||
}
|
||||
|
||||
if (showRelatedStreams && binding.relatedStreamsLayout == null) {
|
||||
if (showRelatedItems && binding.relatedItemsLayout == null) {
|
||||
// temp empty fragment. will be updated in handleResult
|
||||
pageAdapter.addFragment(new EmptyFragment(false), RELATED_TAB_TAG);
|
||||
tabIcons.add(R.drawable.ic_art_track_white_24dp);
|
||||
tabContentDescriptions.add(R.string.related_streams_tab_description);
|
||||
pageAdapter.addFragment(EmptyFragment.newInstance(false), RELATED_TAB_TAG);
|
||||
tabIcons.add(R.drawable.ic_art_track);
|
||||
tabContentDescriptions.add(R.string.related_items_tab_description);
|
||||
}
|
||||
|
||||
if (showDescription) {
|
||||
// temp empty fragment. will be updated in handleResult
|
||||
pageAdapter.addFragment(new EmptyFragment(false), DESCRIPTION_TAB_TAG);
|
||||
tabIcons.add(R.drawable.ic_description_white_24dp);
|
||||
pageAdapter.addFragment(EmptyFragment.newInstance(false), DESCRIPTION_TAB_TAG);
|
||||
tabIcons.add(R.drawable.ic_description);
|
||||
tabContentDescriptions.add(R.string.description_tab_description);
|
||||
}
|
||||
|
||||
if (pageAdapter.getCount() == 0) {
|
||||
pageAdapter.addFragment(new EmptyFragment(true), EMPTY_TAB_TAG);
|
||||
pageAdapter.addFragment(EmptyFragment.newInstance(true), EMPTY_TAB_TAG);
|
||||
}
|
||||
pageAdapter.notifyDataSetUpdate();
|
||||
|
||||
@@ -974,14 +974,14 @@ public final class VideoDetailFragment
|
||||
}
|
||||
|
||||
private void updateTabs(@NonNull final StreamInfo info) {
|
||||
if (showRelatedStreams) {
|
||||
if (binding.relatedStreamsLayout == null) { // phone
|
||||
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info));
|
||||
if (showRelatedItems) {
|
||||
if (binding.relatedItemsLayout == null) { // phone
|
||||
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedItemsFragment.getInstance(info));
|
||||
} else { // tablet + TV
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(info))
|
||||
.replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info))
|
||||
.commitAllowingStateLoss();
|
||||
binding.relatedStreamsLayout.setVisibility(
|
||||
binding.relatedItemsLayout.setVisibility(
|
||||
player != null && player.isFullscreen() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
@@ -1009,6 +1009,12 @@ public final class VideoDetailFragment
|
||||
}
|
||||
|
||||
public void updateTabLayoutVisibility() {
|
||||
|
||||
if (binding == null) {
|
||||
//If binding is null we do not need to and should not do anything with its object(s)
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageAdapter.getCount() < 2 || binding.viewPager.getVisibility() != View.VISIBLE) {
|
||||
// hide tab layout if there is only one tab or if the view pager is also hidden
|
||||
binding.tabLayout.setVisibility(View.GONE);
|
||||
@@ -1331,8 +1337,8 @@ public final class VideoDetailFragment
|
||||
super.handleError();
|
||||
setErrorImage(R.drawable.not_available_monkey);
|
||||
|
||||
if (binding.relatedStreamsLayout != null) { // hide related streams for tablets
|
||||
binding.relatedStreamsLayout.setVisibility(View.INVISIBLE);
|
||||
if (binding.relatedItemsLayout != null) { // hide related streams for tablets
|
||||
binding.relatedItemsLayout.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
// hide comments / related streams / description tabs
|
||||
@@ -1426,12 +1432,12 @@ public final class VideoDetailFragment
|
||||
binding.detailTitleRootLayout.setClickable(false);
|
||||
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
|
||||
|
||||
if (binding.relatedStreamsLayout != null) {
|
||||
if (showRelatedStreams) {
|
||||
binding.relatedStreamsLayout.setVisibility(
|
||||
if (binding.relatedItemsLayout != null) {
|
||||
if (showRelatedItems) {
|
||||
binding.relatedItemsLayout.setVisibility(
|
||||
player != null && player.isFullscreen() ? View.GONE : View.INVISIBLE);
|
||||
} else {
|
||||
binding.relatedStreamsLayout.setVisibility(View.GONE);
|
||||
binding.relatedItemsLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1843,8 +1849,8 @@ public final class VideoDetailFragment
|
||||
showSystemUi();
|
||||
}
|
||||
|
||||
if (binding.relatedStreamsLayout != null) {
|
||||
binding.relatedStreamsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
|
||||
if (binding.relatedItemsLayout != null) {
|
||||
binding.relatedItemsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
scrollToTop();
|
||||
|
||||
@@ -2274,11 +2280,10 @@ public final class VideoDetailFragment
|
||||
}
|
||||
|
||||
private void setOverlayPlayPauseImage(final boolean playerIsPlaying) {
|
||||
final int attr = playerIsPlaying
|
||||
? R.attr.ic_pause
|
||||
: R.attr.ic_play_arrow;
|
||||
binding.overlayPlayPauseButton.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(activity, attr));
|
||||
final int drawable = playerIsPlaying
|
||||
? R.drawable.ic_pause
|
||||
: R.drawable.ic_play_arrow;
|
||||
binding.overlayPlayPauseButton.setImageResource(drawable);
|
||||
}
|
||||
|
||||
private void setOverlayLook(final AppBarLayout appBar,
|
||||
|
||||
@@ -45,6 +45,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||
|
||||
@@ -124,8 +125,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
/**
|
||||
* If the default implementation of {@link StateSaver.WriteRead} should be used.
|
||||
*
|
||||
* @see StateSaver
|
||||
* @param useDefaultStateSaving Whether the default implementation should be used
|
||||
* @see StateSaver
|
||||
*/
|
||||
public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) {
|
||||
this.useDefaultStateSaving = useDefaultStateSaving;
|
||||
@@ -350,7 +351,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
return;
|
||||
}
|
||||
|
||||
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
|
||||
final List<StreamDialogEntry> entries = new ArrayList<>();
|
||||
|
||||
if (PlayerHolder.getType() != null) {
|
||||
entries.add(StreamDialogEntry.enqueue);
|
||||
@@ -361,7 +362,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share
|
||||
));
|
||||
} else {
|
||||
} else {
|
||||
entries.addAll(Arrays.asList(
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.start_here_on_popup,
|
||||
@@ -369,9 +370,14 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||
StreamDialogEntry.share
|
||||
));
|
||||
}
|
||||
entries.add(StreamDialogEntry.open_in_browser);
|
||||
if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
|
||||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
}
|
||||
|
||||
StreamDialogEntry.setEnabledEntries(entries);
|
||||
|
||||
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
||||
@@ -227,7 +228,11 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
showListFooter(hasMoreItems());
|
||||
} else {
|
||||
infoListAdapter.clearStreamItemList();
|
||||
showEmptyState();
|
||||
// showEmptyState should be called only if there is no item as
|
||||
// well as no header in infoListAdapter
|
||||
if (!(result instanceof ChannelInfo && infoListAdapter.getItemCount() == 1)) {
|
||||
showEmptyState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -449,8 +449,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||
|
||||
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
|
||||
headerBinding.subChannelTitleView.setText(String.format(
|
||||
getString(R.string.channel_created_by),
|
||||
currentInfo.getParentChannelName())
|
||||
getString(R.string.channel_created_by),
|
||||
currentInfo.getParentChannelName())
|
||||
);
|
||||
headerBinding.subChannelTitleView.setVisibility(View.VISIBLE);
|
||||
headerBinding.subChannelAvatarView.setVisibility(View.VISIBLE);
|
||||
@@ -462,7 +462,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||
}
|
||||
|
||||
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
|
||||
// PlaylistControls should be visible only if there is some item in
|
||||
// infoListAdapter other than header
|
||||
if (infoListAdapter.getItemCount() != 1) {
|
||||
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
playlistControlBinding.getRoot().setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
for (final Throwable throwable : result.getErrors()) {
|
||||
if (throwable instanceof ContentNotSupportedException) {
|
||||
|
||||
@@ -59,9 +59,9 @@ import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||
|
||||
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
private CompositeDisposable disposables;
|
||||
@@ -161,9 +161,15 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
StreamDialogEntry.share
|
||||
));
|
||||
}
|
||||
entries.add(StreamDialogEntry.open_in_browser);
|
||||
if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
|
||||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
|
||||
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
}
|
||||
|
||||
StreamDialogEntry.setEnabledEntries(entries);
|
||||
|
||||
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) ->
|
||||
@@ -307,7 +313,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
getResources().getColor(R.color.transparent_background_color));
|
||||
headerBinding.uploaderAvatarView.setImageDrawable(
|
||||
AppCompatResources.getDrawable(requireContext(),
|
||||
resolveResourceIdFromAttr(requireContext(), R.attr.ic_radio))
|
||||
R.drawable.ic_radio)
|
||||
);
|
||||
} else {
|
||||
IMAGE_LOADER.displayImage(avatarUrl, headerBinding.uploaderAvatarView,
|
||||
@@ -423,7 +429,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
@Override
|
||||
public void setTitle(final String title) {
|
||||
super.setTitle(title);
|
||||
headerBinding.playlistTitleView.setText(title);
|
||||
if (headerBinding != null) {
|
||||
headerBinding.playlistTitleView.setText(title);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBookmarkClicked() {
|
||||
@@ -459,13 +467,13 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
return;
|
||||
}
|
||||
|
||||
final int iconAttr = playlistEntity == null
|
||||
? R.attr.ic_playlist_add : R.attr.ic_playlist_check;
|
||||
final int drawable = playlistEntity == null
|
||||
? R.drawable.ic_playlist_add : R.drawable.ic_playlist_add_check;
|
||||
|
||||
final int titleRes = playlistEntity == null
|
||||
? R.string.bookmark_playlist : R.string.unbookmark_playlist;
|
||||
|
||||
playlistBookmarkButton.setIcon(resolveResourceIdFromAttr(activity, iconAttr));
|
||||
playlistBookmarkButton.setIcon(drawable);
|
||||
playlistBookmarkButton.setTitle(titleRes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.CharacterStyle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -139,7 +140,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@State
|
||||
boolean wasSearchFocused = false;
|
||||
|
||||
private Map<Integer, String> menuItemToFilterName;
|
||||
@Nullable private Map<Integer, String> menuItemToFilterName = null;
|
||||
private StreamingService service;
|
||||
private Page nextPage;
|
||||
private boolean isSuggestionsEnabled = true;
|
||||
@@ -455,11 +456,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
final List<String> cf = new ArrayList<>(1);
|
||||
cf.add(menuItemToFilterName.get(item.getItemId()));
|
||||
changeContentFilter(item, cf);
|
||||
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
if (menuItemToFilterName != null) {
|
||||
final List<String> cf = new ArrayList<>(1);
|
||||
cf.add(menuItemToFilterName.get(item.getItemId()));
|
||||
changeContentFilter(item, cf);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -486,6 +488,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
+ lastSearchedString);
|
||||
}
|
||||
searchEditText.setText(searchString);
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
searchEditText.setHintTextColor(searchEditText.getTextColors().withAlpha(128));
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||
searchToolbarContainer.setTranslationX(100);
|
||||
@@ -584,6 +589,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
// Remove rich text formatting
|
||||
for (final CharacterStyle span : s.getSpans(0, s.length(), CharacterStyle.class)) {
|
||||
s.removeSpan(span);
|
||||
}
|
||||
|
||||
final String newText = searchEditText.getText().toString();
|
||||
suggestionPublisher.onNext(newText);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -117,16 +115,8 @@ public class SuggestionListAdapter
|
||||
queryView = rootView.findViewById(R.id.suggestion_search);
|
||||
insertView = rootView.findViewById(R.id.suggestion_insert);
|
||||
|
||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_history);
|
||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_search);
|
||||
}
|
||||
|
||||
private static int resolveResourceIdFromAttr(final Context context,
|
||||
@AttrRes final int attr) {
|
||||
final TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
final int attributeResourceId = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
return attributeResourceId;
|
||||
historyResId = R.drawable.ic_history;
|
||||
searchResId = R.drawable.ic_search;
|
||||
}
|
||||
|
||||
private void updateFrom(final SuggestionItem item) {
|
||||
|
||||
@@ -15,38 +15,38 @@ import androidx.preference.PreferenceManager;
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.RelatedStreamsHeaderBinding;
|
||||
import org.schabi.newpipe.databinding.RelatedItemsHeaderBinding;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.ktx.ViewUtils;
|
||||
import org.schabi.newpipe.util.RelatedStreamInfo;
|
||||
import org.schabi.newpipe.util.RelatedItemInfo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
|
||||
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo>
|
||||
public class RelatedItemsFragment extends BaseListInfoFragment<RelatedItemInfo>
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String INFO_KEY = "related_info_key";
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
private RelatedStreamInfo relatedStreamInfo;
|
||||
private RelatedItemInfo relatedItemInfo;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private RelatedStreamsHeaderBinding headerBinding;
|
||||
private RelatedItemsHeaderBinding headerBinding;
|
||||
|
||||
public static RelatedVideosFragment getInstance(final StreamInfo info) {
|
||||
final RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
public static RelatedItemsFragment getInstance(final StreamInfo info) {
|
||||
final RelatedItemsFragment instance = new RelatedItemsFragment();
|
||||
instance.setInitialData(info);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public RelatedVideosFragment() {
|
||||
public RelatedItemsFragment() {
|
||||
super(UserAction.REQUESTED_STREAM);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_related_streams, container, false);
|
||||
return inflater.inflate(R.layout.fragment_related_items, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,8 +80,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
|
||||
@Override
|
||||
protected ViewBinding getListHeader() {
|
||||
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) {
|
||||
headerBinding = RelatedStreamsHeaderBinding
|
||||
if (relatedItemInfo != null && relatedItemInfo.getRelatedItems() != null) {
|
||||
headerBinding = RelatedItemsHeaderBinding
|
||||
.inflate(activity.getLayoutInflater(), itemsList, false);
|
||||
|
||||
final SharedPreferences pref = PreferenceManager
|
||||
@@ -107,8 +107,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
protected Single<RelatedStreamInfo> loadResult(final boolean forceLoad) {
|
||||
return Single.fromCallable(() -> relatedStreamInfo);
|
||||
protected Single<RelatedItemInfo> loadResult(final boolean forceLoad) {
|
||||
return Single.fromCallable(() -> relatedItemInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,7 +120,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull final RelatedStreamInfo result) {
|
||||
public void handleResult(@NonNull final RelatedItemInfo result) {
|
||||
super.handleResult(result);
|
||||
|
||||
if (headerBinding != null) {
|
||||
@@ -145,23 +145,23 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
|
||||
private void setInitialData(final StreamInfo info) {
|
||||
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
|
||||
if (this.relatedStreamInfo == null) {
|
||||
this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
|
||||
if (this.relatedItemInfo == null) {
|
||||
this.relatedItemInfo = RelatedItemInfo.getInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putSerializable(INFO_KEY, relatedStreamInfo);
|
||||
outState.putSerializable(INFO_KEY, relatedItemInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||
if (serializable instanceof RelatedStreamInfo) {
|
||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||
if (serializable instanceof RelatedItemInfo) {
|
||||
this.relatedItemInfo = (RelatedItemInfo) serializable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe.info_list
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
@@ -29,6 +30,17 @@ class StreamSegmentItem(
|
||||
)
|
||||
}
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title
|
||||
if (item.channelName == null) {
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.GONE
|
||||
// When the channel name is displayed there is less space
|
||||
// and thus the segment title needs to be only one line height.
|
||||
// But when there is no channel name displayed, the title can be two lines long.
|
||||
// The default maxLines value is set to 1 to display all elements in the AS preview,
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).maxLines = 2
|
||||
} else {
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).text = item.channelName
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.VISIBLE
|
||||
}
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewStartSeconds).text =
|
||||
Localization.getDurationString(item.startTimeSeconds.toLong())
|
||||
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
|
||||
|
||||
@@ -137,7 +137,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
|
||||
if (item.getLikeCount() >= 0) {
|
||||
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
|
||||
itemLikesCountView.setText(
|
||||
Localization.shortCount(
|
||||
itemBuilder.getContext(),
|
||||
item.getLikeCount()));
|
||||
} else {
|
||||
itemLikesCountView.setText("-");
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
@@ -14,6 +12,8 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||
|
||||
/*
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
package org.schabi.newpipe.ktx
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.util.Log
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import org.schabi.newpipe.MainActivity
|
||||
|
||||
@@ -34,14 +33,6 @@ fun TextView.animateTextColor(duration: Long, @ColorInt colorStart: Int, @ColorI
|
||||
viewPropertyAnimator.interpolator = FastOutSlowInInterpolator()
|
||||
viewPropertyAnimator.duration = duration
|
||||
viewPropertyAnimator.addUpdateListener { setTextColor(it.animatedValue as Int) }
|
||||
viewPropertyAnimator.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
setTextColor(colorEnd)
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
setTextColor(colorEnd)
|
||||
}
|
||||
})
|
||||
viewPropertyAnimator.addListener(onCancel = { setTextColor(colorEnd) }, onEnd = { setTextColor(colorEnd) })
|
||||
viewPropertyAnimator.start()
|
||||
}
|
||||
|
||||
@@ -58,10 +58,8 @@ tailrec fun Throwable?.hasCause(checkSubtypes: Boolean, vararg causesToCheck: Cl
|
||||
if (causeClass.isAssignableFrom(this.javaClass)) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (causeClass == this.javaClass) {
|
||||
return true
|
||||
}
|
||||
} else if (causeClass == this.javaClass) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isInvisible
|
||||
@@ -106,15 +107,10 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo
|
||||
viewPropertyAnimator.addUpdateListener { animation: ValueAnimator ->
|
||||
backgroundTintListCompat = ColorStateList(empty, intArrayOf(animation.animatedValue as Int))
|
||||
}
|
||||
viewPropertyAnimator.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd))
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
onAnimationEnd(animation)
|
||||
}
|
||||
})
|
||||
viewPropertyAnimator.addListener(
|
||||
onCancel = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) },
|
||||
onEnd = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) }
|
||||
)
|
||||
viewPropertyAnimator.start()
|
||||
}
|
||||
|
||||
@@ -134,17 +130,16 @@ fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator {
|
||||
layoutParams.height = value.toInt()
|
||||
requestLayout()
|
||||
}
|
||||
animator.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
animator.addListener(
|
||||
onCancel = {
|
||||
layoutParams.height = targetHeight
|
||||
requestLayout()
|
||||
},
|
||||
onEnd = {
|
||||
layoutParams.height = targetHeight
|
||||
requestLayout()
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
layoutParams.height = targetHeight
|
||||
requestLayout()
|
||||
}
|
||||
})
|
||||
)
|
||||
animator.start()
|
||||
return animator
|
||||
}
|
||||
|
||||
@@ -126,8 +126,19 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
|
||||
|
||||
public void removeItem(final LocalItem data) {
|
||||
final int index = localItems.indexOf(data);
|
||||
localItems.remove(index);
|
||||
notifyItemRemoved(index + (header != null ? 1 : 0));
|
||||
if (index != -1) {
|
||||
localItems.remove(index);
|
||||
notifyItemRemoved(index + (header != null ? 1 : 0));
|
||||
} else {
|
||||
// this happens when
|
||||
// 1) removeItem is called on infoItemDuplicate as in showStreamItemDialog of
|
||||
// LocalPlaylistFragment in this case need to implement delete object by it's duplicate
|
||||
|
||||
// OR
|
||||
|
||||
// 2)data not in itemList and UI is still not updated so notifyDataSetChanged()
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean swapItems(final int fromAdapterPosition, final int toAdapterPosition) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.schabi.newpipe.local.bookmark;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
@@ -12,6 +10,7 @@ import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
@@ -260,7 +259,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
final EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
|
||||
editText.setText(selectedItem.name);
|
||||
|
||||
final Builder builder = new AlertDialog.Builder(activity);
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setView(dialogView)
|
||||
.setPositiveButton(R.string.rename_playlist, (dialog, which) ->
|
||||
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.schabi.newpipe.local.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
@@ -9,6 +8,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -46,7 +46,7 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||
final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
|
||||
final EditText nameInput = dialogView.findViewById(R.id.playlist_name);
|
||||
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext())
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.create_playlist)
|
||||
.setView(dialogView)
|
||||
.setCancelable(true)
|
||||
@@ -54,7 +54,7 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||
.setPositiveButton(R.string.create, (dialogInterface, i) -> {
|
||||
final String name = nameInput.getText().toString();
|
||||
final LocalPlaylistManager playlistManager =
|
||||
new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
|
||||
new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
|
||||
final Toast successToast = Toast.makeText(getActivity(),
|
||||
R.string.playlist_creation_success,
|
||||
Toast.LENGTH_SHORT);
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.schabi.newpipe.util.KoreUtil;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.StreamDialogEntry;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -54,6 +53,8 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
public class StatisticsPlaylistFragment
|
||||
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
@@ -312,14 +313,13 @@ public class StatisticsPlaylistFragment
|
||||
if (sortMode == StatisticSortMode.LAST_PLAYED) {
|
||||
sortMode = StatisticSortMode.MOST_PLAYED;
|
||||
setTitle(getString(R.string.title_most_played));
|
||||
headerBinding.sortButtonIcon.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_history));
|
||||
headerBinding.sortButtonIcon.setImageResource(R.drawable.ic_history);
|
||||
headerBinding.sortButtonText.setText(R.string.title_last_played);
|
||||
} else {
|
||||
sortMode = StatisticSortMode.LAST_PLAYED;
|
||||
setTitle(getString(R.string.title_last_played));
|
||||
headerBinding.sortButtonIcon.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_filter_list));
|
||||
R.drawable.ic_filter_list);
|
||||
headerBinding.sortButtonText.setText(R.string.title_most_played);
|
||||
}
|
||||
startLoading(true);
|
||||
@@ -358,9 +358,15 @@ public class StatisticsPlaylistFragment
|
||||
StreamDialogEntry.share
|
||||
));
|
||||
}
|
||||
entries.add(StreamDialogEntry.open_in_browser);
|
||||
if (KoreUtil.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
|
||||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
|
||||
if (!isNullOrEmpty(infoItem.getUploaderUrl())) {
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
}
|
||||
|
||||
StreamDialogEntry.setEnabledEntries(entries);
|
||||
|
||||
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
|
||||
|
||||
@@ -66,13 +66,13 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||
|
||||
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
|
||||
// Save the list 10 seconds after the last change occurred
|
||||
private static final long SAVE_DEBOUNCE_MILLIS = 10000;
|
||||
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
||||
|
||||
@State
|
||||
protected Long playlistId;
|
||||
@State
|
||||
@@ -340,7 +340,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() { }
|
||||
public void onComplete() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -361,6 +362,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
} else if (item.getItemId() == R.id.menu_item_rename_playlist) {
|
||||
createRenameDialog();
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -420,7 +423,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
playlistItem.getStreamId());
|
||||
|
||||
final boolean hasState = streamStatesIter.next() != null;
|
||||
if (indexInHistory < 0 || hasState) {
|
||||
if (indexInHistory < 0 || hasState) {
|
||||
notWatchedItems.add(playlistItem);
|
||||
} else if (!thumbnailVideoRemoved
|
||||
&& playlistManager.getPlaylistThumbnail(playlistId)
|
||||
@@ -722,7 +725,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||
final int swipeDir) { }
|
||||
final int swipeDir) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -755,7 +759,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
StreamDialogEntry.append_playlist,
|
||||
StreamDialogEntry.share
|
||||
));
|
||||
} else {
|
||||
} else {
|
||||
entries.addAll(Arrays.asList(
|
||||
StreamDialogEntry.start_here_on_background,
|
||||
StreamDialogEntry.start_here_on_popup,
|
||||
@@ -765,9 +769,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
StreamDialogEntry.share
|
||||
));
|
||||
}
|
||||
entries.add(StreamDialogEntry.open_in_browser);
|
||||
if (KoreUtil.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
|
||||
entries.add(StreamDialogEntry.play_with_kodi);
|
||||
}
|
||||
|
||||
if (!isNullOrEmpty(infoItem.getUploaderUrl())) {
|
||||
entries.add(StreamDialogEntry.show_channel_details);
|
||||
}
|
||||
|
||||
StreamDialogEntry.setEnabledEntries(entries);
|
||||
|
||||
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
enum class FeedGroupIcon(
|
||||
/**
|
||||
@@ -13,51 +10,51 @@ enum class FeedGroupIcon(
|
||||
val id: Int,
|
||||
|
||||
/**
|
||||
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
|
||||
* The drawable resource.
|
||||
*/
|
||||
@AttrRes val drawableResourceAttr: Int
|
||||
@DrawableRes val drawableResource: Int
|
||||
) {
|
||||
ALL(0, R.attr.ic_asterisk),
|
||||
MUSIC(1, R.attr.ic_music_note),
|
||||
EDUCATION(2, R.attr.ic_school),
|
||||
FITNESS(3, R.attr.ic_fitness_center),
|
||||
SPACE(4, R.attr.ic_telescope),
|
||||
COMPUTER(5, R.attr.ic_computer),
|
||||
GAMING(6, R.attr.ic_videogame_asset),
|
||||
SPORTS(7, R.attr.ic_sports),
|
||||
NEWS(8, R.attr.ic_megaphone),
|
||||
FAVORITES(9, R.attr.ic_heart),
|
||||
CAR(10, R.attr.ic_car),
|
||||
MOTORCYCLE(11, R.attr.ic_motorcycle),
|
||||
TREND(12, R.attr.ic_trending_up),
|
||||
MOVIE(13, R.attr.ic_movie),
|
||||
BACKUP(14, R.attr.ic_backup),
|
||||
ART(15, R.attr.ic_palette),
|
||||
PERSON(16, R.attr.ic_person),
|
||||
PEOPLE(17, R.attr.ic_people),
|
||||
MONEY(18, R.attr.ic_money),
|
||||
KIDS(19, R.attr.ic_child_care),
|
||||
FOOD(20, R.attr.ic_fastfood),
|
||||
SMILE(21, R.attr.ic_smile),
|
||||
EXPLORE(22, R.attr.ic_explore),
|
||||
RESTAURANT(23, R.attr.ic_restaurant),
|
||||
MIC(24, R.attr.ic_mic),
|
||||
HEADSET(25, R.attr.ic_headset),
|
||||
RADIO(26, R.attr.ic_radio),
|
||||
SHOPPING_CART(27, R.attr.ic_shopping_cart),
|
||||
WATCH_LATER(28, R.attr.ic_watch_later),
|
||||
WORK(29, R.attr.ic_work),
|
||||
HOT(30, R.attr.ic_kiosk_hot),
|
||||
CHANNEL(31, R.attr.ic_channel),
|
||||
BOOKMARK(32, R.attr.ic_bookmark),
|
||||
PETS(33, R.attr.ic_pets),
|
||||
WORLD(34, R.attr.ic_world),
|
||||
STAR(35, R.attr.ic_stars),
|
||||
SUN(36, R.attr.ic_sunny),
|
||||
RSS(37, R.attr.ic_rss);
|
||||
ALL(0, R.drawable.ic_asterisk),
|
||||
MUSIC(1, R.drawable.ic_music_note),
|
||||
EDUCATION(2, R.drawable.ic_school),
|
||||
FITNESS(3, R.drawable.ic_fitness_center),
|
||||
SPACE(4, R.drawable.ic_telescope),
|
||||
COMPUTER(5, R.drawable.ic_computer),
|
||||
GAMING(6, R.drawable.ic_videogame_asset),
|
||||
SPORTS(7, R.drawable.ic_directions_bike),
|
||||
NEWS(8, R.drawable.ic_megaphone),
|
||||
FAVORITES(9, R.drawable.ic_favorite),
|
||||
CAR(10, R.drawable.ic_directions_car),
|
||||
MOTORCYCLE(11, R.drawable.ic_motorcycle),
|
||||
TREND(12, R.drawable.ic_trending_up),
|
||||
MOVIE(13, R.drawable.ic_movie),
|
||||
BACKUP(14, R.drawable.ic_backup),
|
||||
ART(15, R.drawable.ic_palette),
|
||||
PERSON(16, R.drawable.ic_person),
|
||||
PEOPLE(17, R.drawable.ic_people),
|
||||
MONEY(18, R.drawable.ic_attach_money),
|
||||
KIDS(19, R.drawable.ic_child_care),
|
||||
FOOD(20, R.drawable.ic_fastfood),
|
||||
SMILE(21, R.drawable.ic_insert_emoticon),
|
||||
EXPLORE(22, R.drawable.ic_explore),
|
||||
RESTAURANT(23, R.drawable.ic_restaurant),
|
||||
MIC(24, R.drawable.ic_mic),
|
||||
HEADSET(25, R.drawable.ic_headset),
|
||||
RADIO(26, R.drawable.ic_radio),
|
||||
SHOPPING_CART(27, R.drawable.ic_shopping_cart),
|
||||
WATCH_LATER(28, R.drawable.ic_watch_later),
|
||||
WORK(29, R.drawable.ic_work),
|
||||
HOT(30, R.drawable.ic_whatshot),
|
||||
CHANNEL(31, R.drawable.ic_tv),
|
||||
BOOKMARK(32, R.drawable.ic_bookmark),
|
||||
PETS(33, R.drawable.ic_pets),
|
||||
WORLD(34, R.drawable.ic_public),
|
||||
STAR(35, R.drawable.ic_stars),
|
||||
SUN(36, R.drawable.ic_wb_sunny),
|
||||
RSS(37, R.drawable.ic_rss_feed);
|
||||
|
||||
@DrawableRes
|
||||
fun getDrawableRes(context: Context): Int {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, drawableResourceAttr)
|
||||
fun getDrawableRes(): Int {
|
||||
return drawableResource
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package org.schabi.newpipe.local.subscription;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import icepick.Icepick;
|
||||
import icepick.State;
|
||||
@@ -41,7 +40,7 @@ public class ImportConfirmationDialog extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
||||
assureCorrectAppLanguage(getContext());
|
||||
return new AlertDialog.Builder(getContext(), ThemeHelper.getDialogTheme(getContext()))
|
||||
return new AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.import_network_expensive_warning)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
@@ -17,6 +16,7 @@ import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.preference.PreferenceManager
|
||||
@@ -62,7 +62,6 @@ import org.schabi.newpipe.util.FilePickerActivityHelper
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
import org.schabi.newpipe.util.ShareUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@@ -209,7 +208,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
if (data != null && data.data != null && resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == REQUEST_EXPORT_CODE) {
|
||||
val exportFile = Utils.getFileForUri(data.data!!)
|
||||
if (!exportFile.parentFile.canWrite() || !exportFile.parentFile.canRead()) {
|
||||
val parentFile = exportFile.parentFile!!
|
||||
if (!parentFile.canWrite() || !parentFile.canRead()) {
|
||||
Toast.makeText(activity, R.string.invalid_directory, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
activity.startService(
|
||||
@@ -257,7 +257,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter)
|
||||
feedGroupsSortMenuItem = HeaderWithMenuItem(
|
||||
getString(R.string.feed_groups_header_title),
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort),
|
||||
R.drawable.ic_sort,
|
||||
menuItemOnClickListener = ::openReorderDialog
|
||||
)
|
||||
add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel)))
|
||||
@@ -294,12 +294,16 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
}
|
||||
|
||||
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
|
||||
val commands = arrayOf(getString(R.string.share), getString(R.string.unsubscribe))
|
||||
val commands = arrayOf(
|
||||
getString(R.string.share), getString(R.string.open_in_browser),
|
||||
getString(R.string.unsubscribe)
|
||||
)
|
||||
|
||||
val actions = DialogInterface.OnClickListener { _, i ->
|
||||
when (i) {
|
||||
0 -> ShareUtils.shareText(requireContext(), selectedItem.name, selectedItem.url)
|
||||
1 -> deleteChannel(selectedItem)
|
||||
1 -> ShareUtils.openUrlInBrowser(requireContext(), selectedItem.url)
|
||||
2 -> deleteChannel(selectedItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.local.subscription.dialog
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
@@ -8,10 +9,12 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
@@ -123,6 +126,14 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
_feedGroupCreateBinding = DialogFeedGroupCreateBinding.bind(view)
|
||||
_searchLayoutBinding = feedGroupCreateBinding.subscriptionsHeaderSearchContainer
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
// KitKat doesn't apply container's theme to <include> content
|
||||
val contrastColor = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.contrastColor))
|
||||
searchLayoutBinding.toolbarSearchEditText.setTextColor(contrastColor)
|
||||
searchLayoutBinding.toolbarSearchEditText.setHintTextColor(contrastColor.withAlpha(128))
|
||||
ImageViewCompat.setImageTintList(searchLayoutBinding.toolbarSearchClearIcon, contrastColor)
|
||||
}
|
||||
|
||||
viewModel = ViewModelProvider(
|
||||
this,
|
||||
FeedGroupDialogViewModel.Factory(
|
||||
@@ -306,7 +317,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
|
||||
|
||||
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
|
||||
feedGroupCreateBinding.iconPreview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
|
||||
feedGroupCreateBinding.iconPreview.setImageResource(feedGroupIcon.getDrawableRes())
|
||||
|
||||
if (feedGroupCreateBinding.groupNameInput.text.isNullOrBlank()) {
|
||||
feedGroupCreateBinding.groupNameInput.setText(name)
|
||||
@@ -375,7 +386,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
|
||||
private fun setupIconPicker() {
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
|
||||
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(it) })
|
||||
|
||||
feedGroupCreateBinding.iconSelector.apply {
|
||||
layoutManager = GridLayoutManager(requireContext(), 7, RecyclerView.VERTICAL, false)
|
||||
@@ -404,7 +415,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
|
||||
if (groupId == NO_GROUP_SELECTED) {
|
||||
val icon = selectedIcon ?: FeedGroupIcon.ALL
|
||||
feedGroupCreateBinding.iconPreview.setImageResource(icon.getDrawableRes(requireContext()))
|
||||
feedGroupCreateBinding.iconPreview.setImageResource(icon.getDrawableRes())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ data class FeedGroupCardItem(
|
||||
|
||||
override fun bind(viewBinding: FeedGroupCardItemBinding, position: Int) {
|
||||
viewBinding.title.text = name
|
||||
viewBinding.icon.setImageResource(icon.getDrawableRes(viewBinding.root.context))
|
||||
viewBinding.icon.setImageResource(icon.getDrawableRes())
|
||||
}
|
||||
|
||||
override fun initializeViewBinding(view: View) = FeedGroupCardItemBinding.bind(view)
|
||||
|
||||
@@ -32,7 +32,7 @@ data class FeedGroupReorderItem(
|
||||
|
||||
override fun bind(viewBinding: FeedGroupReorderItemBinding, position: Int) {
|
||||
viewBinding.groupName.text = name
|
||||
viewBinding.groupIcon.setImageResource(icon.getDrawableRes(viewBinding.root.context))
|
||||
viewBinding.groupIcon.setImageResource(icon.getDrawableRes())
|
||||
}
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder<FeedGroupReorderItemBinding>, position: Int, payloads: MutableList<Any>) {
|
||||
|
||||
@@ -86,7 +86,7 @@ class FeedImportExportItem(
|
||||
private fun setupImportFromItems(listHolder: ViewGroup) {
|
||||
val previousBackupItem = addItemView(
|
||||
listHolder.context.getString(R.string.previous_export),
|
||||
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder
|
||||
R.drawable.ic_backup, listHolder
|
||||
)
|
||||
previousBackupItem.setOnClickListener { onImportPreviousSelected() }
|
||||
|
||||
@@ -115,8 +115,7 @@ class FeedImportExportItem(
|
||||
private fun setupExportToItems(listHolder: ViewGroup) {
|
||||
val previousBackupItem = addItemView(
|
||||
listHolder.context.getString(R.string.file),
|
||||
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save),
|
||||
listHolder
|
||||
R.drawable.ic_save, listHolder
|
||||
)
|
||||
previousBackupItem.setOnClickListener { onExportSelected() }
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class HeaderItem(
|
||||
override fun bind(viewBinding: HeaderItemBinding, position: Int) {
|
||||
viewBinding.headerTitle.text = title
|
||||
|
||||
val listener: OnClickListener? = if (onClickListener != null) OnClickListener { onClickListener.invoke() } else null
|
||||
val listener = onClickListener?.let { OnClickListener { onClickListener.invoke() } }
|
||||
viewBinding.root.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.xwray.groupie.viewbinding.BindableItem
|
||||
@@ -9,11 +8,10 @@ import org.schabi.newpipe.databinding.PickerIconItemBinding
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
|
||||
class PickerIconItem(
|
||||
context: Context,
|
||||
val icon: FeedGroupIcon
|
||||
) : BindableItem<PickerIconItemBinding>() {
|
||||
@DrawableRes
|
||||
val iconRes: Int = icon.getDrawableRes(context)
|
||||
val iconRes: Int = icon.getDrawableRes()
|
||||
|
||||
override fun getLayout(): Int = R.layout.picker_icon_item
|
||||
|
||||
|
||||
@@ -50,11 +50,11 @@ public final class NotificationConstants {
|
||||
R.drawable.exo_icon_fastforward,
|
||||
R.drawable.exo_icon_previous,
|
||||
R.drawable.exo_icon_next,
|
||||
R.drawable.ic_pause_white_24dp,
|
||||
R.drawable.ic_hourglass_top_white_24dp,
|
||||
R.drawable.ic_pause,
|
||||
R.drawable.ic_hourglass_top,
|
||||
R.drawable.exo_icon_repeat_all,
|
||||
R.drawable.exo_icon_shuffle_on,
|
||||
R.drawable.ic_close_white_24dp,
|
||||
R.drawable.ic_close,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -273,14 +273,14 @@ public final class NotificationUtil {
|
||||
|| player.getCurrentState() == Player.STATE_BLOCKED
|
||||
|| player.getCurrentState() == Player.STATE_BUFFERING) {
|
||||
// null intent -> show hourglass icon that does nothing when clicked
|
||||
return new NotificationCompat.Action(R.drawable.ic_hourglass_top_white_24dp_png,
|
||||
return new NotificationCompat.Action(R.drawable.ic_hourglass_top,
|
||||
player.getContext().getString(R.string.notification_action_buffering),
|
||||
null);
|
||||
}
|
||||
|
||||
case NotificationConstants.PLAY_PAUSE:
|
||||
if (player.getCurrentState() == Player.STATE_COMPLETED) {
|
||||
return getAction(player, R.drawable.ic_replay_white_24dp_png,
|
||||
return getAction(player, R.drawable.ic_replay,
|
||||
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
|
||||
} else if (player.isPlaying()
|
||||
|| player.getCurrentState() == Player.STATE_PREFLIGHT
|
||||
@@ -315,7 +315,7 @@ public final class NotificationUtil {
|
||||
}
|
||||
|
||||
case NotificationConstants.CLOSE:
|
||||
return getAction(player, R.drawable.ic_close_white_24dp_png,
|
||||
return getAction(player, R.drawable.ic_close,
|
||||
R.string.close, ACTION_CLOSE);
|
||||
|
||||
case NotificationConstants.NOTHING:
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
@@ -456,6 +457,7 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
final boolean playbackSkipSilence) {
|
||||
if (player != null) {
|
||||
player.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
|
||||
onPlaybackParameterChanged(player.getPlaybackParameters());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,15 +591,15 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
switch (state) {
|
||||
case Player.STATE_PAUSED:
|
||||
queueControlBinding.controlPlayPause
|
||||
.setImageResource(R.drawable.ic_play_arrow_white_24dp);
|
||||
.setImageResource(R.drawable.ic_play_arrow);
|
||||
break;
|
||||
case Player.STATE_PLAYING:
|
||||
queueControlBinding.controlPlayPause
|
||||
.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||
.setImageResource(R.drawable.ic_pause);
|
||||
break;
|
||||
case Player.STATE_COMPLETED:
|
||||
queueControlBinding.controlPlayPause
|
||||
.setImageResource(R.drawable.ic_replay_white_24dp);
|
||||
.setImageResource(R.drawable.ic_replay);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -639,7 +641,7 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
queueControlBinding.controlShuffle.setImageAlpha(shuffleAlpha);
|
||||
}
|
||||
|
||||
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
|
||||
private void onPlaybackParameterChanged(@Nullable final PlaybackParameters parameters) {
|
||||
if (parameters != null) {
|
||||
if (menu != null && player != null) {
|
||||
final MenuItem item = menu.findItem(R.id.action_playback_speed);
|
||||
@@ -670,8 +672,7 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
//2) Icon change accordingly to current App Theme
|
||||
// using rootView.getContext() because getApplicationContext() didn't work
|
||||
final Context context = queueControlBinding.getRoot().getContext();
|
||||
item.setIcon(ThemeHelper.resolveResourceIdFromAttr(context,
|
||||
player.isMuted() ? R.attr.ic_volume_off : R.attr.ic_volume_up));
|
||||
item.setIcon(player.isMuted() ? R.drawable.ic_volume_off : R.drawable.ic_volume_up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.provider.Settings;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -51,6 +52,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.AppCompatImageButton;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.DisplayCutoutCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -74,6 +76,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoListener;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
@@ -88,6 +91,7 @@ import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamSegment;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
@@ -446,9 +450,12 @@ public final class Player implements
|
||||
binding.playbackSeekBar.getProgressDrawable()
|
||||
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY));
|
||||
|
||||
qualityPopupMenu = new PopupMenu(context, binding.qualityTextView);
|
||||
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(getContext(),
|
||||
R.style.DarkPopupMenu);
|
||||
|
||||
qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView);
|
||||
playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed);
|
||||
captionPopupMenu = new PopupMenu(context, binding.captionTextView);
|
||||
captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView);
|
||||
|
||||
binding.progressBarLoadingPanel.getIndeterminateDrawable()
|
||||
.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
|
||||
@@ -488,10 +495,16 @@ public final class Player implements
|
||||
// Setup subtitle view
|
||||
simpleExoPlayer.addTextOutput(binding.subtitleView);
|
||||
|
||||
// Setup audio session with onboard equalizer
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// enable media tunneling
|
||||
if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) {
|
||||
Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] "
|
||||
+ "media tunneling disabled in debug preferences");
|
||||
} else if (DeviceUtils.shouldSupportMediaTunneling()) {
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
||||
} else if (DEBUG) {
|
||||
Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,10 +637,10 @@ public final class Player implements
|
||||
&& newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl())
|
||||
&& newQueue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
|
||||
// Player can have state = IDLE when playback is stopped or failed
|
||||
// and we should retry() in this case
|
||||
// and we should retry in this case
|
||||
if (simpleExoPlayer.getPlaybackState()
|
||||
== com.google.android.exoplayer2.Player.STATE_IDLE) {
|
||||
simpleExoPlayer.retry();
|
||||
simpleExoPlayer.prepare();
|
||||
}
|
||||
simpleExoPlayer.seekTo(playQueue.getIndex(), newQueue.getItem().getRecoveryPosition());
|
||||
simpleExoPlayer.setPlayWhenReady(playWhenReady);
|
||||
@@ -638,10 +651,10 @@ public final class Player implements
|
||||
&& !playQueue.isDisposed()) {
|
||||
// Do not re-init the same PlayQueue. Save time
|
||||
// Player can have state = IDLE when playback is stopped or failed
|
||||
// and we should retry() in this case
|
||||
// and we should retry in this case
|
||||
if (simpleExoPlayer.getPlaybackState()
|
||||
== com.google.android.exoplayer2.Player.STATE_IDLE) {
|
||||
simpleExoPlayer.retry();
|
||||
simpleExoPlayer.prepare();
|
||||
}
|
||||
simpleExoPlayer.setPlayWhenReady(playWhenReady);
|
||||
|
||||
@@ -706,7 +719,8 @@ public final class Player implements
|
||||
// Android TV: without it focus will frame the whole player
|
||||
binding.playPauseButton.requestFocus();
|
||||
|
||||
if (simpleExoPlayer.getPlayWhenReady()) {
|
||||
// Note: This is for automatically playing (when "Resume playback" is off), see #6179
|
||||
if (getPlayWhenReady()) {
|
||||
play();
|
||||
} else {
|
||||
pause();
|
||||
@@ -957,7 +971,7 @@ public final class Player implements
|
||||
= LinearLayout.LayoutParams.MATCH_PARENT;
|
||||
binding.secondaryControls.setVisibility(View.INVISIBLE);
|
||||
binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context,
|
||||
R.drawable.ic_expand_more_white_24dp));
|
||||
R.drawable.ic_expand_more));
|
||||
binding.share.setVisibility(View.VISIBLE);
|
||||
binding.openInBrowser.setVisibility(View.VISIBLE);
|
||||
binding.switchMute.setVisibility(View.VISIBLE);
|
||||
@@ -1592,6 +1606,10 @@ public final class Player implements
|
||||
segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress));
|
||||
}
|
||||
|
||||
if (isQueueVisible) {
|
||||
updateQueueTime(currentProgress);
|
||||
}
|
||||
|
||||
final boolean showThumbnail = prefs.getBoolean(
|
||||
context.getString(R.string.show_thumbnail_key), true);
|
||||
// setMetadata only updates the metadata when any of the metadata keys are null
|
||||
@@ -1615,9 +1633,22 @@ public final class Player implements
|
||||
if (exoPlayerIsNull()) {
|
||||
return;
|
||||
}
|
||||
// Use duration of currentItem for non-live streams,
|
||||
// because HLS streams are fragmented
|
||||
// and thus the whole duration is not available to the player
|
||||
// TODO: revert #6307 when introducing proper HLS support
|
||||
final int duration;
|
||||
if (currentItem != null
|
||||
&& currentItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM
|
||||
&& currentItem.getStreamType() != StreamType.LIVE_STREAM) {
|
||||
// convert seconds to milliseconds
|
||||
duration = (int) (currentItem.getDuration() * 1000);
|
||||
} else {
|
||||
duration = (int) simpleExoPlayer.getDuration();
|
||||
}
|
||||
onUpdateProgress(
|
||||
Math.max((int) simpleExoPlayer.getCurrentPosition(), 0),
|
||||
(int) simpleExoPlayer.getDuration(),
|
||||
duration,
|
||||
simpleExoPlayer.getBufferedPercentage()
|
||||
);
|
||||
}
|
||||
@@ -1653,7 +1684,7 @@ public final class Player implements
|
||||
|
||||
saveWasPlaying();
|
||||
if (isPlaying()) {
|
||||
simpleExoPlayer.setPlayWhenReady(false);
|
||||
simpleExoPlayer.pause();
|
||||
}
|
||||
|
||||
showControls(0);
|
||||
@@ -1669,7 +1700,7 @@ public final class Player implements
|
||||
|
||||
seekTo(seekBar.getProgress());
|
||||
if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) {
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
simpleExoPlayer.play();
|
||||
}
|
||||
|
||||
binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
|
||||
@@ -1687,7 +1718,7 @@ public final class Player implements
|
||||
}
|
||||
|
||||
public void saveWasPlaying() {
|
||||
this.wasPlaying = simpleExoPlayer.getPlayWhenReady();
|
||||
this.wasPlaying = getPlayWhenReady();
|
||||
}
|
||||
//endregion
|
||||
|
||||
@@ -1912,7 +1943,7 @@ public final class Player implements
|
||||
}
|
||||
|
||||
@Override // exoplayer listener
|
||||
public void onLoadingChanged(final boolean isLoading) {
|
||||
public void onIsLoadingChanged(final boolean isLoading) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: "
|
||||
+ "isLoading = [" + isLoading + "]");
|
||||
@@ -1956,7 +1987,8 @@ public final class Player implements
|
||||
if (currentState == STATE_BLOCKED) {
|
||||
changeState(STATE_BUFFERING);
|
||||
}
|
||||
simpleExoPlayer.prepare(mediaSource);
|
||||
simpleExoPlayer.setMediaSource(mediaSource);
|
||||
simpleExoPlayer.prepare();
|
||||
}
|
||||
|
||||
public void changeState(final int state) {
|
||||
@@ -2020,7 +2052,7 @@ public final class Player implements
|
||||
animate(binding.loadingPanel, true, 0);
|
||||
animate(binding.surfaceForeground, true, 100);
|
||||
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow);
|
||||
animatePlayButtons(false, 100);
|
||||
binding.getRoot().setKeepScreenOn(false);
|
||||
|
||||
@@ -2049,7 +2081,7 @@ public final class Player implements
|
||||
|
||||
animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
|
||||
() -> {
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_pause);
|
||||
animatePlayButtons(true, 200);
|
||||
if (!isQueueVisible) {
|
||||
binding.playPauseButton.requestFocus();
|
||||
@@ -2068,6 +2100,7 @@ public final class Player implements
|
||||
Log.d(TAG, "onBuffering() called");
|
||||
}
|
||||
binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT);
|
||||
binding.loadingPanel.setVisibility(View.VISIBLE);
|
||||
|
||||
binding.getRoot().setKeepScreenOn(true);
|
||||
|
||||
@@ -2090,7 +2123,7 @@ public final class Player implements
|
||||
|
||||
animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0,
|
||||
() -> {
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow);
|
||||
animatePlayButtons(true, 200);
|
||||
if (!isQueueVisible) {
|
||||
binding.playPauseButton.requestFocus();
|
||||
@@ -2129,7 +2162,7 @@ public final class Player implements
|
||||
|
||||
animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0,
|
||||
() -> {
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
|
||||
binding.playPauseButton.setImageResource(R.drawable.ic_replay);
|
||||
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
|
||||
});
|
||||
|
||||
@@ -2221,7 +2254,7 @@ public final class Player implements
|
||||
Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: "
|
||||
+ "repeatMode = [" + repeatMode + "]");
|
||||
}
|
||||
setRepeatModeButton(binding.repeatButton, repeatMode);
|
||||
setRepeatModeButton(((AppCompatImageButton) binding.repeatButton), repeatMode);
|
||||
onShuffleOrRepeatModeChanged();
|
||||
}
|
||||
|
||||
@@ -2249,7 +2282,7 @@ public final class Player implements
|
||||
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
|
||||
}
|
||||
|
||||
private void setRepeatModeButton(final ImageButton imageButton, final int repeatMode) {
|
||||
private void setRepeatModeButton(final AppCompatImageButton imageButton, final int repeatMode) {
|
||||
switch (repeatMode) {
|
||||
case REPEAT_MODE_OFF:
|
||||
imageButton.setImageResource(R.drawable.exo_controls_repeat_off);
|
||||
@@ -2290,7 +2323,7 @@ public final class Player implements
|
||||
|
||||
private void setMuteButton(final ImageButton button, final boolean isMuted) {
|
||||
button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted
|
||||
? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
|
||||
? R.drawable.ic_volume_off : R.drawable.ic_volume_up));
|
||||
}
|
||||
//endregion
|
||||
|
||||
@@ -2355,6 +2388,12 @@ public final class Player implements
|
||||
break;
|
||||
}
|
||||
case DISCONTINUITY_REASON_SEEK:
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
|
||||
}
|
||||
if (isPrepared) {
|
||||
saveStreamProgressState();
|
||||
}
|
||||
case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
|
||||
case DISCONTINUITY_REASON_INTERNAL:
|
||||
if (playQueue.getIndex() != newWindowIndex) {
|
||||
@@ -2419,10 +2458,8 @@ public final class Player implements
|
||||
setRecovery();
|
||||
reloadPlayQueueManager();
|
||||
break;
|
||||
case ExoPlaybackException.TYPE_OUT_OF_MEMORY:
|
||||
case ExoPlaybackException.TYPE_REMOTE:
|
||||
case ExoPlaybackException.TYPE_RENDERER:
|
||||
case ExoPlaybackException.TYPE_TIMEOUT:
|
||||
default:
|
||||
showUnrecoverableError(error);
|
||||
onPlaybackShutdown();
|
||||
@@ -2627,16 +2664,6 @@ public final class Player implements
|
||||
simpleExoPlayer.seekToDefaultPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // exoplayer override
|
||||
public void onSeekProcessed() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
|
||||
}
|
||||
if (isPrepared) {
|
||||
saveStreamProgressState();
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
@@ -2664,7 +2691,7 @@ public final class Player implements
|
||||
}
|
||||
}
|
||||
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
simpleExoPlayer.play();
|
||||
saveStreamProgressState();
|
||||
}
|
||||
|
||||
@@ -2677,7 +2704,7 @@ public final class Player implements
|
||||
}
|
||||
|
||||
audioReactor.abandonAudioFocus();
|
||||
simpleExoPlayer.setPlayWhenReady(false);
|
||||
simpleExoPlayer.pause();
|
||||
saveStreamProgressState();
|
||||
}
|
||||
|
||||
@@ -2686,7 +2713,7 @@ public final class Player implements
|
||||
Log.d(TAG, "onPlayPause() called");
|
||||
}
|
||||
|
||||
if (isPlaying()) {
|
||||
if (getPlayWhenReady()) {
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
@@ -2734,7 +2761,7 @@ public final class Player implements
|
||||
}
|
||||
seekBy(retrieveSeekDurationFromPreferences(this));
|
||||
triggerProgressUpdate();
|
||||
showAndAnimateControl(R.drawable.ic_fast_forward_white_24dp, true);
|
||||
showAndAnimateControl(R.drawable.ic_fast_forward, true);
|
||||
}
|
||||
|
||||
public void fastRewind() {
|
||||
@@ -2743,7 +2770,7 @@ public final class Player implements
|
||||
}
|
||||
seekBy(-retrieveSeekDurationFromPreferences(this));
|
||||
triggerProgressUpdate();
|
||||
showAndAnimateControl(R.drawable.ic_fast_rewind_white_24dp, true);
|
||||
showAndAnimateControl(R.drawable.ic_fast_rewind, true);
|
||||
}
|
||||
//endregion
|
||||
|
||||
@@ -2965,6 +2992,7 @@ public final class Player implements
|
||||
buildQueue();
|
||||
|
||||
binding.itemsListHeaderTitle.setVisibility(View.GONE);
|
||||
binding.itemsListHeaderDuration.setVisibility(View.VISIBLE);
|
||||
binding.shuffleButton.setVisibility(View.VISIBLE);
|
||||
binding.repeatButton.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -2974,6 +3002,8 @@ public final class Player implements
|
||||
AnimationType.SLIDE_AND_ALPHA);
|
||||
|
||||
binding.itemsList.scrollToPosition(playQueue.getIndex());
|
||||
|
||||
updateQueueTime((int) simpleExoPlayer.getCurrentPosition());
|
||||
}
|
||||
|
||||
private void buildQueue() {
|
||||
@@ -2999,6 +3029,7 @@ public final class Player implements
|
||||
buildSegments();
|
||||
|
||||
binding.itemsListHeaderTitle.setVisibility(View.VISIBLE);
|
||||
binding.itemsListHeaderDuration.setVisibility(View.GONE);
|
||||
binding.shuffleButton.setVisibility(View.GONE);
|
||||
binding.repeatButton.setVisibility(View.GONE);
|
||||
|
||||
@@ -3196,6 +3227,32 @@ public final class Player implements
|
||||
buildPlaybackSpeedMenu();
|
||||
binding.playbackSpeed.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void updateQueueTime(final int currentTime) {
|
||||
final int currentStream = playQueue.getIndex();
|
||||
int before = 0;
|
||||
int after = 0;
|
||||
|
||||
final List<PlayQueueItem> streams = playQueue.getStreams();
|
||||
final int nStreams = streams.size();
|
||||
|
||||
for (int i = 0; i < nStreams; i++) {
|
||||
if (i < currentStream) {
|
||||
before += streams.get(i).getDuration();
|
||||
} else {
|
||||
after += streams.get(i).getDuration();
|
||||
}
|
||||
}
|
||||
|
||||
before *= 1000;
|
||||
after *= 1000;
|
||||
|
||||
binding.itemsListHeaderDuration.setText(
|
||||
String.format("%s/%s",
|
||||
getTimeString(currentTime + before),
|
||||
getTimeString(before + after)
|
||||
));
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
@@ -3691,8 +3748,8 @@ public final class Player implements
|
||||
|| DeviceUtils.isTablet(context))
|
||||
? View.VISIBLE : View.GONE);
|
||||
binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context,
|
||||
isFullscreen ? R.drawable.ic_fullscreen_exit_white_24dp
|
||||
: R.drawable.ic_fullscreen_white_24dp));
|
||||
isFullscreen ? R.drawable.ic_fullscreen_exit
|
||||
: R.drawable.ic_fullscreen));
|
||||
}
|
||||
|
||||
private void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
|
||||
@@ -4012,6 +4069,10 @@ public final class Player implements
|
||||
return !exoPlayerIsNull() && simpleExoPlayer.isPlaying();
|
||||
}
|
||||
|
||||
public boolean getPlayWhenReady() {
|
||||
return !exoPlayerIsNull() && simpleExoPlayer.getPlayWhenReady();
|
||||
}
|
||||
|
||||
private boolean isLoading() {
|
||||
return !exoPlayerIsNull() && simpleExoPlayer.isLoading();
|
||||
}
|
||||
|
||||
@@ -229,8 +229,10 @@ abstract class BasePlayerGestureListener(
|
||||
// because the soft input is visible (the draggable area is currently resized).
|
||||
player.updateScreenSize()
|
||||
player.checkPopupPositionBounds()
|
||||
initialPopupX = player.popupLayoutParams!!.x
|
||||
initialPopupY = player.popupLayoutParams!!.y
|
||||
player.popupLayoutParams?.let {
|
||||
initialPopupX = it.x
|
||||
initialPopupY = it.y
|
||||
}
|
||||
return super.onDown(e)
|
||||
}
|
||||
|
||||
@@ -466,7 +468,7 @@ abstract class BasePlayerGestureListener(
|
||||
// ///////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun getDisplayPortion(e: MotionEvent): DisplayPortion {
|
||||
return if (player.playerType == MainPlayer.PlayerType.POPUP) {
|
||||
return if (player.playerType == MainPlayer.PlayerType.POPUP && player.popupLayoutParams != null) {
|
||||
when {
|
||||
e.x < player.popupLayoutParams!!.width / 3.0 -> DisplayPortion.LEFT
|
||||
e.x > player.popupLayoutParams!!.width * 2.0 / 3.0 -> DisplayPortion.RIGHT
|
||||
|
||||
@@ -26,7 +26,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout>
|
||||
Rect globalRect = new Rect();
|
||||
private boolean skippingInterception = false;
|
||||
private final List<Integer> skipInterceptionOfElements = Arrays.asList(
|
||||
R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
|
||||
R.id.detail_content_root_layout, R.id.relatedItemsLayout,
|
||||
R.id.itemsListPanel, R.id.view_pager, R.id.tab_layout, R.id.bottomControls,
|
||||
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);
|
||||
|
||||
|
||||
@@ -147,10 +147,10 @@ public class PlayerGestureListener
|
||||
|
||||
player.getVolumeImageView().setImageDrawable(
|
||||
AppCompatResources.getDrawable(service, currentProgressPercent <= 0
|
||||
? R.drawable.ic_volume_off_white_24dp
|
||||
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp
|
||||
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp
|
||||
: R.drawable.ic_volume_up_white_24dp)
|
||||
? R.drawable.ic_volume_off
|
||||
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute
|
||||
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down
|
||||
: R.drawable.ic_volume_up)
|
||||
);
|
||||
|
||||
if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
|
||||
@@ -189,10 +189,10 @@ public class PlayerGestureListener
|
||||
player.getBrightnessImageView().setImageDrawable(
|
||||
AppCompatResources.getDrawable(service,
|
||||
currentProgressPercent < 0.25
|
||||
? R.drawable.ic_brightness_low_white_24dp
|
||||
? R.drawable.ic_brightness_low
|
||||
: currentProgressPercent < 0.75
|
||||
? R.drawable.ic_brightness_medium_white_24dp
|
||||
: R.drawable.ic_brightness_high_white_24dp)
|
||||
? R.drawable.ic_brightness_medium
|
||||
: R.drawable.ic_brightness_high)
|
||||
);
|
||||
|
||||
if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {
|
||||
|
||||
@@ -103,13 +103,13 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
|
||||
animateAudio(DUCK_AUDIO_TO, 1.0f);
|
||||
|
||||
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
|
||||
player.setPlayWhenReady(true);
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
|
||||
private void onAudioFocusLoss() {
|
||||
Log.d(TAG, "onAudioFocusLoss() called");
|
||||
player.setPlayWhenReady(false);
|
||||
player.pause();
|
||||
}
|
||||
|
||||
private void onAudioFocusLossCanDuck() {
|
||||
|
||||
@@ -32,8 +32,9 @@ public class LoadController implements LoadControl {
|
||||
|
||||
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
|
||||
builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs,
|
||||
initialPlaybackBufferMs, initialPlaybackBufferMs);
|
||||
internalLoadControl = builder.createDefaultLoadControl();
|
||||
initialPlaybackBufferMs,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
|
||||
internalLoadControl = builder.build();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -47,9 +48,9 @@ public class LoadController implements LoadControl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroupArray,
|
||||
final TrackSelectionArray trackSelectionArray) {
|
||||
internalLoadControl.onTracksSelected(renderers, trackGroupArray, trackSelectionArray);
|
||||
public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroups,
|
||||
final TrackSelectionArray trackSelections) {
|
||||
internalLoadControl.onTracksSelected(renderers, trackGroups, trackSelections);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,8 +36,6 @@ public class MediaSessionManager {
|
||||
@NonNull final Player player,
|
||||
@NonNull final MediaSessionCallback callback) {
|
||||
mediaSession = new MediaSessionCompat(context, TAG);
|
||||
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
|
||||
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
|
||||
mediaSession.setActive(true);
|
||||
|
||||
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
|
||||
|
||||
@@ -180,10 +180,10 @@ public final class PlayerHelper {
|
||||
* if a candidate next video's url already exists in the existing items.
|
||||
* </p>
|
||||
* <p>
|
||||
* The first item in {@link StreamInfo#getRelatedStreams()} is checked first.
|
||||
* The first item in {@link StreamInfo#getRelatedItems()} is checked first.
|
||||
* If it is non-null and is not part of the existing items, it will be used as the next stream.
|
||||
* Otherwise, a random item with non-repeating url will be selected
|
||||
* from the {@link StreamInfo#getRelatedStreams()}.
|
||||
* Otherwise, a random stream with non-repeating url will be selected
|
||||
* from the {@link StreamInfo#getRelatedItems()}. Non-stream items are ignored.
|
||||
* </p>
|
||||
*
|
||||
* @param info currently playing stream
|
||||
@@ -198,7 +198,7 @@ public final class PlayerHelper {
|
||||
urls.add(item.getUrl());
|
||||
}
|
||||
|
||||
final List<InfoItem> relatedItems = info.getRelatedStreams();
|
||||
final List<InfoItem> relatedItems = info.getRelatedItems();
|
||||
if (Utils.isNullOrEmpty(relatedItems)) {
|
||||
return null;
|
||||
}
|
||||
@@ -297,7 +297,7 @@ public final class PlayerHelper {
|
||||
}
|
||||
|
||||
public static long getPreferredFileSize() {
|
||||
return 512 * 1024L;
|
||||
return 2 * 1024 * 1024L; // ExoPlayer CacheDataSink.MIN_RECOMMENDED_FRAGMENT_SIZE
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,29 +40,25 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
*/
|
||||
public abstract class PlayQueue implements Serializable {
|
||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
private ArrayList<PlayQueueItem> backup;
|
||||
private ArrayList<PlayQueueItem> streams;
|
||||
|
||||
@NonNull
|
||||
private final AtomicInteger queueIndex;
|
||||
private final ArrayList<PlayQueueItem> history;
|
||||
private final List<PlayQueueItem> history = new ArrayList<>();
|
||||
|
||||
private List<PlayQueueItem> backup;
|
||||
private List<PlayQueueItem> streams;
|
||||
|
||||
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
|
||||
private transient Flowable<PlayQueueEvent> broadcastReceiver;
|
||||
|
||||
private transient boolean disposed;
|
||||
private transient boolean disposed = false;
|
||||
|
||||
PlayQueue(final int index, final List<PlayQueueItem> startWith) {
|
||||
streams = new ArrayList<>();
|
||||
streams.addAll(startWith);
|
||||
history = new ArrayList<>();
|
||||
streams = new ArrayList<>(startWith);
|
||||
|
||||
if (streams.size() > index) {
|
||||
history.add(streams.get(index));
|
||||
}
|
||||
|
||||
queueIndex = new AtomicInteger(index);
|
||||
disposed = false;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -137,18 +133,36 @@ public abstract class PlayQueue implements Serializable {
|
||||
public synchronized void setIndex(final int index) {
|
||||
final int oldIndex = getIndex();
|
||||
|
||||
int newIndex = index;
|
||||
final int newIndex;
|
||||
|
||||
if (index < 0) {
|
||||
newIndex = 0;
|
||||
} else if (index < streams.size()) {
|
||||
// Regular assignment for index in bounds
|
||||
newIndex = index;
|
||||
} else if (streams.isEmpty()) {
|
||||
// Out of bounds from here on
|
||||
// Need to check if stream is empty to prevent arithmetic error and negative index
|
||||
newIndex = 0;
|
||||
} else if (isComplete()) {
|
||||
// Circular indexing
|
||||
newIndex = index % streams.size();
|
||||
} else {
|
||||
// Index of last element
|
||||
newIndex = streams.size() - 1;
|
||||
}
|
||||
if (index >= streams.size()) {
|
||||
newIndex = isComplete() ? index % streams.size() : streams.size() - 1;
|
||||
}
|
||||
|
||||
queueIndex.set(newIndex);
|
||||
|
||||
if (oldIndex != newIndex) {
|
||||
history.add(streams.get(newIndex));
|
||||
}
|
||||
|
||||
queueIndex.set(newIndex);
|
||||
/*
|
||||
TODO: Documentation states that a SelectEvent will only be emitted if the new index is...
|
||||
different from the old one but this is emitted regardless? Not sure what this what it does
|
||||
exactly so I won't touch it
|
||||
*/
|
||||
broadcast(new SelectEvent(oldIndex, newIndex));
|
||||
}
|
||||
|
||||
@@ -180,8 +194,6 @@ public abstract class PlayQueue implements Serializable {
|
||||
* @return the index of the given item
|
||||
*/
|
||||
public int indexOf(@NonNull final PlayQueueItem item) {
|
||||
// referential equality, can't think of a better way to do this
|
||||
// todo: better than this
|
||||
return streams.indexOf(item);
|
||||
}
|
||||
|
||||
@@ -410,34 +422,42 @@ public abstract class PlayQueue implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles the current play queue.
|
||||
* Shuffles the current play queue
|
||||
* <p>
|
||||
* This method first backs up the existing play queue and item being played.
|
||||
* Then a newly shuffled play queue will be generated along with currently
|
||||
* playing item placed at the beginning of the queue.
|
||||
* This method first backs up the existing play queue and item being played. Then a newly
|
||||
* shuffled play queue will be generated along with currently playing item placed at the
|
||||
* beginning of the queue. This item will also be added to the history.
|
||||
* </p>
|
||||
* <p>
|
||||
* Will emit a {@link ReorderEvent} in any context.
|
||||
* Will emit a {@link ReorderEvent} if shuffled.
|
||||
* </p>
|
||||
*
|
||||
* @implNote Does nothing if the queue has a size <= 2 (the currently playing video must stay on
|
||||
* top, so shuffling a size-2 list does nothing)
|
||||
*/
|
||||
public synchronized void shuffle() {
|
||||
// Can't shuffle an list that's empty or only has one element
|
||||
if (size() <= 2) {
|
||||
return;
|
||||
}
|
||||
// Create a backup if it doesn't already exist
|
||||
if (backup == null) {
|
||||
backup = new ArrayList<>(streams);
|
||||
}
|
||||
final int originIndex = getIndex();
|
||||
final PlayQueueItem current = getItem();
|
||||
|
||||
final int originalIndex = getIndex();
|
||||
final PlayQueueItem currentItem = getItem();
|
||||
|
||||
Collections.shuffle(streams);
|
||||
|
||||
final int newIndex = streams.indexOf(current);
|
||||
if (newIndex != -1) {
|
||||
streams.add(0, streams.remove(newIndex));
|
||||
}
|
||||
// Move currentItem to the head of the queue
|
||||
streams.remove(currentItem);
|
||||
streams.add(0, currentItem);
|
||||
queueIndex.set(0);
|
||||
if (streams.size() > 0) {
|
||||
history.add(streams.get(0));
|
||||
}
|
||||
|
||||
broadcast(new ReorderEvent(originIndex, queueIndex.get()));
|
||||
history.add(currentItem);
|
||||
|
||||
broadcast(new ReorderEvent(originalIndex, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -457,7 +477,6 @@ public abstract class PlayQueue implements Serializable {
|
||||
final int originIndex = getIndex();
|
||||
final PlayQueueItem current = getItem();
|
||||
|
||||
streams.clear();
|
||||
streams = backup;
|
||||
backup = null;
|
||||
|
||||
@@ -500,22 +519,19 @@ public abstract class PlayQueue implements Serializable {
|
||||
* we don't have to do anything with new queue.
|
||||
* This method also gives a chance to track history of items in a queue in
|
||||
* VideoDetailFragment without duplicating items from two identical queues
|
||||
* */
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable final Object obj) {
|
||||
if (!(obj instanceof PlayQueue)
|
||||
|| getStreams().size() != ((PlayQueue) obj).getStreams().size()) {
|
||||
if (!(obj instanceof PlayQueue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PlayQueue other = (PlayQueue) obj;
|
||||
for (int i = 0; i < getStreams().size(); i++) {
|
||||
if (!getItem(i).getUrl().equals(other.getItem(i).getUrl())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return streams.equals(other.streams);
|
||||
}
|
||||
|
||||
return true;
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return streams.hashCode();
|
||||
}
|
||||
|
||||
public boolean isDisposed() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
@@ -43,13 +44,13 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getLiveDashMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
@@ -68,16 +69,16 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getDashMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getHlsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
case C.TYPE_OTHER:
|
||||
return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.schabi.newpipe.util.ListHelper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
|
||||
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
||||
|
||||
public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
@@ -101,12 +100,12 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
||||
if (mimeType == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
|
||||
SELECTION_FLAG_AUTOSELECT,
|
||||
PlayerHelper.captionLanguageOf(context, subtitle));
|
||||
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
|
||||
.createMediaSource(Uri.parse(subtitle.getUrl()), textFormat, TIME_UNSET);
|
||||
.createMediaSource(
|
||||
new MediaItem.Subtitle(Uri.parse(subtitle.getUrl()),
|
||||
mimeType,
|
||||
PlayerHelper.captionLanguageOf(context, subtitle)),
|
||||
TIME_UNSET);
|
||||
mediaSources.add(textSource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import android.os.Bundle;
|
||||
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;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
||||
private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE =
|
||||
@@ -21,8 +21,8 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
||||
private String captionSettingsKey;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.appearance_settings);
|
||||
|
||||
final String themeKey = getString(R.string.theme_key);
|
||||
// the key of the active theme when settings were opened (or recreated after theme change)
|
||||
@@ -58,11 +58,6 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.appearance_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(final Preference preference) {
|
||||
if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) {
|
||||
@@ -89,6 +84,8 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
||||
defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
|
||||
defaultPreferences.edit().putString(themeKey, newValue.toString()).apply();
|
||||
|
||||
ThemeHelper.setDayNightMode(getContext(), newValue.toString());
|
||||
|
||||
if (!newValue.equals(beginningThemeKey) && getActivity() != null) {
|
||||
// if it's not the current theme
|
||||
ActivityCompat.recreate(getActivity());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -10,7 +10,7 @@ import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
@@ -26,6 +26,7 @@ import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
import org.schabi.newpipe.util.FilePathUtils;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
import org.schabi.newpipe.util.ZipHelper;
|
||||
|
||||
@@ -42,6 +43,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
private ContentSettingsManager manager;
|
||||
|
||||
private String importExportDataPathKey;
|
||||
|
||||
private String thumbnailLoadToggleKey;
|
||||
private String youtubeRestrictedModeEnabledKey;
|
||||
|
||||
@@ -50,8 +53,44 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
private String initialLanguage;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
final File homeDir = ContextCompat.getDataDir(requireContext());
|
||||
manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
|
||||
manager.deleteSettingsFile();
|
||||
|
||||
addPreferencesFromResource(R.xml.content_settings);
|
||||
|
||||
importExportDataPathKey = getString(R.string.import_export_data_path);
|
||||
final Preference importDataPreference = findPreference(getString(R.string.import_data));
|
||||
importDataPreference.setOnPreferenceClickListener(p -> {
|
||||
final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
|
||||
FilePickerActivityHelper.MODE_FILE);
|
||||
final String path = defaultPreferences.getString(importExportDataPathKey, "");
|
||||
if (FilePathUtils.isValidDirectoryPath(path)) {
|
||||
i.putExtra(FilePickerActivityHelper.EXTRA_START_PATH, path);
|
||||
}
|
||||
startActivityForResult(i, REQUEST_IMPORT_PATH);
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference exportDataPreference = findPreference(getString(R.string.export_data));
|
||||
exportDataPreference.setOnPreferenceClickListener(p -> {
|
||||
final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
|
||||
FilePickerActivityHelper.MODE_DIR);
|
||||
final String path = defaultPreferences.getString(importExportDataPathKey, "");
|
||||
if (FilePathUtils.isValidDirectoryPath(path)) {
|
||||
i.putExtra(FilePickerActivityHelper.EXTRA_START_PATH, path);
|
||||
}
|
||||
startActivityForResult(i, REQUEST_EXPORT_PATH);
|
||||
return true;
|
||||
});
|
||||
|
||||
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
|
||||
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||
|
||||
@@ -103,37 +142,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
final File homeDir = ContextCompat.getDataDir(requireContext());
|
||||
manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
|
||||
manager.deleteSettingsFile();
|
||||
|
||||
addPreferencesFromResource(R.xml.content_settings);
|
||||
|
||||
final Preference importDataPreference = findPreference(getString(R.string.import_data));
|
||||
importDataPreference.setOnPreferenceClickListener(p -> {
|
||||
final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
|
||||
FilePickerActivityHelper.MODE_FILE);
|
||||
startActivityForResult(i, REQUEST_IMPORT_PATH);
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference exportDataPreference = findPreference(getString(R.string.export_data));
|
||||
exportDataPreference.setOnPreferenceClickListener(p -> {
|
||||
final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
|
||||
FilePickerActivityHelper.MODE_DIR);
|
||||
startActivityForResult(i, REQUEST_EXPORT_PATH);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
@@ -169,15 +177,15 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
if ((requestCode == REQUEST_IMPORT_PATH || requestCode == REQUEST_EXPORT_PATH)
|
||||
&& resultCode == Activity.RESULT_OK && data.getData() != null) {
|
||||
final String path = Utils.getFileForUri(data.getData()).getAbsolutePath();
|
||||
final File file = Utils.getFileForUri(data.getData());
|
||||
|
||||
if (requestCode == REQUEST_EXPORT_PATH) {
|
||||
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
|
||||
exportDatabase(path + "/NewPipeData-" + sdf.format(new Date()) + ".zip");
|
||||
exportDatabase(file);
|
||||
} else {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
|
||||
builder.setMessage(R.string.override_current_data)
|
||||
.setPositiveButton(getString(R.string.finish),
|
||||
(d, id) -> importDatabase(path))
|
||||
(d, id) -> importDatabase(file))
|
||||
.setNegativeButton(android.R.string.cancel,
|
||||
(d, id) -> d.cancel());
|
||||
builder.create().show();
|
||||
@@ -185,26 +193,34 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void exportDatabase(final String path) {
|
||||
private void exportDatabase(@NonNull final File folder) {
|
||||
try {
|
||||
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
|
||||
final String path = folder.getAbsolutePath() + "/NewPipeData-"
|
||||
+ sdf.format(new Date()) + ".zip";
|
||||
|
||||
//checkpoint before export
|
||||
NewPipeDatabase.checkpoint();
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(requireContext());
|
||||
.getDefaultSharedPreferences(requireContext());
|
||||
manager.exportDatabase(preferences, path);
|
||||
|
||||
setImportExportDataPath(folder, false);
|
||||
|
||||
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void importDatabase(final String filePath) {
|
||||
private void importDatabase(@NonNull final File file) {
|
||||
final String filePath = file.getAbsolutePath();
|
||||
|
||||
// check if file is supported
|
||||
if (!ZipHelper.isValidZipFile(filePath)) {
|
||||
Toast.makeText(getContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -215,33 +231,68 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
|
||||
if (!manager.extractDb(filePath)) {
|
||||
Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
.show();
|
||||
}
|
||||
|
||||
//If settings file exist, ask if it should be imported.
|
||||
if (manager.extractSettings(filePath)) {
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(requireContext());
|
||||
alert.setTitle(R.string.import_settings);
|
||||
|
||||
alert.setNegativeButton(android.R.string.no, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
// restart app to properly load db
|
||||
System.exit(0);
|
||||
finishImport(file);
|
||||
});
|
||||
alert.setPositiveButton(getString(R.string.finish), (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
manager.loadSharedPreferences(PreferenceManager
|
||||
.getDefaultSharedPreferences(requireContext()));
|
||||
// restart app to properly load db
|
||||
System.exit(0);
|
||||
.getDefaultSharedPreferences(requireContext()));
|
||||
finishImport(file);
|
||||
});
|
||||
alert.show();
|
||||
} else {
|
||||
// restart app to properly load db
|
||||
System.exit(0);
|
||||
finishImport(file);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save import path and restart system.
|
||||
*
|
||||
* @param file The file of the created backup
|
||||
*/
|
||||
private void finishImport(@NonNull final File file) {
|
||||
if (file.getParentFile() != null) {
|
||||
//immediately because app is about to exit
|
||||
setImportExportDataPath(file.getParentFile(), true);
|
||||
}
|
||||
|
||||
// restart app to properly load db
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private void setImportExportDataPath(@NonNull final File file, final boolean immediately) {
|
||||
final String directoryPath;
|
||||
if (file.isDirectory()) {
|
||||
directoryPath = file.getAbsolutePath();
|
||||
} else {
|
||||
final File parentFile = file.getParentFile();
|
||||
if (parentFile != null) {
|
||||
directoryPath = parentFile.getAbsolutePath();
|
||||
} else {
|
||||
directoryPath = "";
|
||||
}
|
||||
}
|
||||
final SharedPreferences.Editor editor = defaultPreferences
|
||||
.edit()
|
||||
.putString(importExportDataPathKey, directoryPath);
|
||||
if (immediately) {
|
||||
editor.commit();
|
||||
} else {
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -11,8 +10,8 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.nononsenseapps.filepicker.Utils;
|
||||
@@ -46,8 +45,8 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
|
||||
private Context ctx;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.download_settings);
|
||||
|
||||
downloadPathVideoPreference = getString(R.string.download_path_video_key);
|
||||
downloadPathAudioPreference = getString(R.string.download_path_audio_key);
|
||||
@@ -76,11 +75,6 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.download_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
@@ -29,8 +28,9 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
||||
private CompositeDisposable disposables;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.history_settings);
|
||||
|
||||
cacheWipeKey = getString(R.string.metadata_cache_wipe_key);
|
||||
viewsHistoryClearKey = getString(R.string.clear_views_history_key);
|
||||
playbackStatesClearKey = getString(R.string.clear_playback_states_key);
|
||||
@@ -39,11 +39,6 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
||||
disposables = new CompositeDisposable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.history_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(final Preference preference) {
|
||||
if (preference.getKey().equals(cacheWipeKey)) {
|
||||
|
||||
@@ -145,10 +145,8 @@ public class PeertubeInstanceListFragment extends Fragment {
|
||||
final MenuItem restoreItem = menu
|
||||
.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults);
|
||||
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
|
||||
final int restoreIcon = ThemeHelper
|
||||
.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults);
|
||||
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon));
|
||||
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(),
|
||||
R.drawable.ic_settings_backup_restore));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,7 +186,7 @@ public class PeertubeInstanceListFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void restoreDefaults() {
|
||||
new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext()))
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.restore_defaults)
|
||||
.setMessage(R.string.restore_defaults_confirmation)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
@@ -56,11 +56,11 @@ public class SettingsActivity extends AppCompatActivity
|
||||
SettingsLayoutBinding.inflate(getLayoutInflater());
|
||||
setContentView(settingsLayoutBinding.getRoot());
|
||||
|
||||
setSupportActionBar(settingsLayoutBinding.toolbarLayout.toolbar);
|
||||
setSupportActionBar(settingsLayoutBinding.settingsToolbarLayout.toolbar);
|
||||
|
||||
if (savedInstanceBundle == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragment_holder, new MainSettingsFragment())
|
||||
.replace(R.id.settings_fragment_holder, new MainSettingsFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class SettingsActivity extends AppCompatActivity
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
|
||||
R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||
.replace(R.id.fragment_holder, fragment)
|
||||
.replace(R.id.settings_fragment_holder, fragment)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
return true;
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.schabi.newpipe.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
@@ -16,15 +15,10 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.update_settings);
|
||||
|
||||
final String updateToggleKey = getString(R.string.update_app_key);
|
||||
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.update_settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.provider.Settings;
|
||||
import android.text.format.DateUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.ListPreference;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
@@ -23,8 +22,8 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
|
||||
private SharedPreferences.OnSharedPreferenceChangeListener listener;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.video_audio_settings);
|
||||
|
||||
updateSeekOptions();
|
||||
|
||||
@@ -104,11 +103,6 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResource(R.xml.video_audio_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
@@ -17,6 +17,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
@@ -41,9 +42,8 @@ public class NotificationActionsPreference extends Preference {
|
||||
}
|
||||
|
||||
|
||||
private NotificationSlot[] notificationSlots;
|
||||
|
||||
private List<Integer> compactSlots;
|
||||
@Nullable private NotificationSlot[] notificationSlots = null;
|
||||
@Nullable private List<Integer> compactSlots = null;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Lifecycle
|
||||
@@ -85,19 +85,22 @@ public class NotificationActionsPreference extends Preference {
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void saveChanges() {
|
||||
final SharedPreferences.Editor editor = getSharedPreferences().edit();
|
||||
if (compactSlots != null && notificationSlots != null) {
|
||||
final SharedPreferences.Editor editor = getSharedPreferences().edit();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
editor.putInt(getContext().getString(NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]),
|
||||
(i < compactSlots.size() ? compactSlots.get(i) : -1));
|
||||
for (int i = 0; i < 3; i++) {
|
||||
editor.putInt(getContext().getString(
|
||||
NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]),
|
||||
(i < compactSlots.size() ? compactSlots.get(i) : -1));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
editor.putInt(getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
|
||||
notificationSlots[i].selectedAction);
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
editor.putInt(getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
|
||||
notificationSlots[i].selectedAction);
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.schabi.newpipe.settings.tabs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -11,10 +10,10 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
public final class AddTabDialog {
|
||||
private final AlertDialog dialog;
|
||||
@@ -60,7 +59,7 @@ public final class AddTabDialog {
|
||||
private DialogListAdapter(final Context context, final ChooseTabListItem[] items) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.items = items;
|
||||
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot);
|
||||
this.fallbackIcon = R.drawable.ic_whatshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -112,10 +112,8 @@ public class ChooseTabsFragment extends Fragment {
|
||||
final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE,
|
||||
R.string.restore_defaults);
|
||||
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
|
||||
final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(),
|
||||
R.attr.ic_restore_defaults);
|
||||
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon));
|
||||
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(),
|
||||
R.drawable.ic_settings_backup_restore));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,7 +140,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void restoreDefaults() {
|
||||
new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext()))
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.restore_defaults)
|
||||
.setMessage(R.string.restore_defaults_confirmation)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
@@ -241,7 +239,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
case KIOSK:
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||
getString(R.string.kiosk_page_summary),
|
||||
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot)));
|
||||
R.drawable.ic_whatshot));
|
||||
break;
|
||||
case CHANNEL:
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||
@@ -252,8 +250,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
if (!tabList.contains(tab)) {
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||
getString(R.string.default_kiosk_page_summary),
|
||||
ThemeHelper.resolveResourceIdFromAttr(context,
|
||||
R.attr.ic_kiosk_hot)));
|
||||
R.drawable.ic_whatshot));
|
||||
}
|
||||
break;
|
||||
case PLAYLIST:
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -188,7 +187,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_blank_page);
|
||||
return R.drawable.ic_crop_portrait;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,7 +212,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel);
|
||||
return R.drawable.ic_tv;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,7 +238,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_rss);
|
||||
return R.drawable.ic_rss_feed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -264,7 +263,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark);
|
||||
return R.drawable.ic_bookmark;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -289,7 +288,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_history);
|
||||
return R.drawable.ic_history;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -409,7 +408,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel);
|
||||
return R.drawable.ic_tv;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -541,7 +540,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark);
|
||||
return R.drawable.ic_bookmark;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -712,7 +712,7 @@ public class WebMWriter implements Closeable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: in the adove code, find and select the shortest track for the desired kind
|
||||
// TODO: in the above code, find and select the shortest track for the desired kind
|
||||
for (i = 0; i < infoTracks.length; i++) {
|
||||
if (kind == infoTracks[i].trackType) {
|
||||
return i;
|
||||
|
||||
@@ -20,6 +20,16 @@ public final class DeviceUtils {
|
||||
private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
|
||||
private static Boolean isTV = null;
|
||||
|
||||
/*
|
||||
* Devices that do not support media tunneling
|
||||
*/
|
||||
// Formuler Z8 Pro, Z8, CC, Z Alpha, Z+ Neo
|
||||
private static final boolean HI3798MV200 = Build.VERSION.SDK_INT == 24
|
||||
&& Build.DEVICE.equals("Hi3798MV200");
|
||||
// Zephir TS43UHD-2
|
||||
private static final boolean CVT_MT5886_EU_1G = Build.VERSION.SDK_INT == 24
|
||||
&& Build.DEVICE.equals("cvt_mt5886_eu_1g");
|
||||
|
||||
private DeviceUtils() {
|
||||
}
|
||||
|
||||
@@ -88,4 +98,15 @@ public final class DeviceUtils {
|
||||
sp,
|
||||
context.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Some devices have broken tunneled video playback but claim to support it.
|
||||
* See https://github.com/TeamNewPipe/NewPipe/issues/5911
|
||||
* @return false if Kitkat (does not support tunneling) or affected device
|
||||
*/
|
||||
public static boolean shouldSupportMediaTunneling() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
&& !HI3798MV200
|
||||
&& !CVT_MT5886_EU_1G;
|
||||
}
|
||||
}
|
||||
|
||||
22
app/src/main/java/org/schabi/newpipe/util/FilePathUtils.java
Normal file
22
app/src/main/java/org/schabi/newpipe/util/FilePathUtils.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class FilePathUtils {
|
||||
private FilePathUtils() { }
|
||||
|
||||
|
||||
/**
|
||||
* Check that the path is a valid directory path and it exists.
|
||||
*
|
||||
* @param path full path of directory,
|
||||
* @return is path valid or not
|
||||
*/
|
||||
public static boolean isValidDirectoryPath(final String path) {
|
||||
if (path == null || path.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final File file = new File(path);
|
||||
return file.exists() && file.isDirectory();
|
||||
}
|
||||
}
|
||||
@@ -63,20 +63,20 @@ public final class KioskTranslator {
|
||||
case "Top 50":
|
||||
case "New & hot":
|
||||
case "conferences":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_hot);
|
||||
return R.drawable.ic_whatshot;
|
||||
case "Local":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local);
|
||||
return R.drawable.ic_home;
|
||||
case "Recently added":
|
||||
case "recent":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent);
|
||||
return R.drawable.ic_add_circle_outline;
|
||||
case "Most liked":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_thumb_up);
|
||||
return R.drawable.ic_thumb_up;
|
||||
case "live":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_live_tv);
|
||||
return R.drawable.ic_live_tv;
|
||||
case "Featured":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_stars);
|
||||
return R.drawable.ic_stars;
|
||||
case "Radio":
|
||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_radio);
|
||||
return R.drawable.ic_radio;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,8 @@ public final class NavigationHelper {
|
||||
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
|
||||
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
|
||||
|
||||
private NavigationHelper() { }
|
||||
private NavigationHelper() {
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Players
|
||||
@@ -111,18 +112,22 @@ public final class NavigationHelper {
|
||||
public static void playOnMainPlayer(final AppCompatActivity activity,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
final PlayQueueItem item = playQueue.getItem();
|
||||
assert item != null;
|
||||
openVideoDetailFragment(activity, activity.getSupportFragmentManager(),
|
||||
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, false);
|
||||
if (item != null) {
|
||||
openVideoDetailFragment(activity, activity.getSupportFragmentManager(),
|
||||
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void playOnMainPlayer(final Context context,
|
||||
@NonNull final PlayQueue playQueue,
|
||||
final boolean switchingPlayers) {
|
||||
final PlayQueueItem item = playQueue.getItem();
|
||||
assert item != null;
|
||||
openVideoDetail(context,
|
||||
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, switchingPlayers);
|
||||
if (item != null) {
|
||||
openVideoDetail(context,
|
||||
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue,
|
||||
switchingPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
public static void playOnPopupPlayer(final Context context,
|
||||
@@ -591,6 +596,7 @@ public final class NavigationHelper {
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setPackage(context.getString(R.string.kore_package));
|
||||
intent.setData(videoURL);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RelatedStreamInfo extends ListInfo<InfoItem> {
|
||||
public RelatedStreamInfo(final int serviceId, final ListLinkHandler listUrlIdHandler,
|
||||
final String name) {
|
||||
public class RelatedItemInfo extends ListInfo<InfoItem> {
|
||||
public RelatedItemInfo(final int serviceId, final ListLinkHandler listUrlIdHandler,
|
||||
final String name) {
|
||||
super(serviceId, listUrlIdHandler, name);
|
||||
}
|
||||
|
||||
public static RelatedStreamInfo getInfo(final StreamInfo info) {
|
||||
public static RelatedItemInfo getInfo(final StreamInfo info) {
|
||||
final ListLinkHandler handler = new ListLinkHandler(
|
||||
info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
|
||||
final RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo(
|
||||
final RelatedItemInfo relatedItemInfo = new RelatedItemInfo(
|
||||
info.getServiceId(), handler, info.getName());
|
||||
final List<InfoItem> streams = new ArrayList<>(info.getRelatedStreams());
|
||||
relatedStreamInfo.setRelatedItems(streams);
|
||||
return relatedStreamInfo;
|
||||
final List<InfoItem> relatedItems = new ArrayList<>(info.getRelatedItems());
|
||||
relatedItemInfo.setRelatedItems(relatedItems);
|
||||
return relatedItemInfo;
|
||||
}
|
||||
}
|
||||
@@ -72,10 +72,10 @@ public final class StateSaver {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #tryToRestore(SavedState, WriteRead)
|
||||
* @param outState
|
||||
* @param writeRead
|
||||
* @return the saved state
|
||||
* @see #tryToRestore(SavedState, WriteRead)
|
||||
*/
|
||||
public static SavedState tryToRestore(final Bundle outState, final WriteRead writeRead) {
|
||||
if (outState == null || writeRead == null) {
|
||||
@@ -93,6 +93,7 @@ public final class StateSaver {
|
||||
/**
|
||||
* Try to restore the state from memory and disk,
|
||||
* using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead.
|
||||
*
|
||||
* @param savedState
|
||||
* @param writeRead
|
||||
* @return the saved state
|
||||
@@ -143,19 +144,18 @@ public final class StateSaver {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #tryToSave(boolean, String, String, WriteRead)
|
||||
* @param isChangingConfig
|
||||
* @param savedState
|
||||
* @param outState
|
||||
* @param writeRead
|
||||
* @return the saved state or {@code null}
|
||||
* @see #tryToSave(boolean, String, String, WriteRead)
|
||||
*/
|
||||
@Nullable
|
||||
public static SavedState tryToSave(final boolean isChangingConfig,
|
||||
@Nullable final SavedState savedState, final Bundle outState,
|
||||
final WriteRead writeRead) {
|
||||
@NonNull
|
||||
final String currentSavedPrefix;
|
||||
@NonNull final String currentSavedPrefix;
|
||||
if (savedState == null || TextUtils.isEmpty(savedState.getPrefixFileSaved())) {
|
||||
// Generate unique prefix
|
||||
currentSavedPrefix = System.nanoTime() - writeRead.hashCode() + "";
|
||||
@@ -299,8 +299,11 @@ public final class StateSaver {
|
||||
|
||||
cacheDir = new File(cacheDir, CACHE_DIR_NAME);
|
||||
if (cacheDir.exists()) {
|
||||
for (final File file : cacheDir.listFiles()) {
|
||||
file.delete();
|
||||
final File[] list = cacheDir.listFiles();
|
||||
if (list != null) {
|
||||
for (final File file : list) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@ public enum StreamDialogEntry {
|
||||
// enum values with DEFAULT actions //
|
||||
//////////////////////////////////////
|
||||
|
||||
show_channel_details(R.string.show_channel_details, (fragment, item) ->
|
||||
// For some reason `getParentFragmentManager()` doesn't work, but this does.
|
||||
NavigationHelper.openChannelFragment(
|
||||
fragment.requireActivity().getSupportFragmentManager(),
|
||||
item.getServiceId(), item.getUploaderUrl(), item.getUploaderName())
|
||||
),
|
||||
|
||||
/**
|
||||
* Enqueues the stream automatically to the current PlayerType.<br>
|
||||
* <br>
|
||||
@@ -74,14 +81,17 @@ public enum StreamDialogEntry {
|
||||
play_with_kodi(R.string.play_with_kodi_title, (fragment, item) -> {
|
||||
final Uri videoUrl = Uri.parse(item.getUrl());
|
||||
try {
|
||||
NavigationHelper.playWithKore(fragment.getContext(), videoUrl);
|
||||
NavigationHelper.playWithKore(fragment.requireContext(), videoUrl);
|
||||
} catch (final Exception e) {
|
||||
KoreUtil.showInstallKoreDialog(fragment.getActivity());
|
||||
}
|
||||
}),
|
||||
|
||||
share(R.string.share, (fragment, item) ->
|
||||
ShareUtils.shareText(fragment.getContext(), item.getName(), item.getUrl()));
|
||||
ShareUtils.shareText(fragment.getContext(), item.getName(), item.getUrl())),
|
||||
|
||||
open_in_browser(R.string.open_in_browser, (fragment, item) ->
|
||||
ShareUtils.openUrlInBrowser(fragment.getContext(), item.getUrl()));
|
||||
|
||||
|
||||
///////////////
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
@@ -31,6 +30,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@@ -47,6 +47,9 @@ public final class ThemeHelper {
|
||||
* Apply the selected theme (on NewPipe settings) in the context
|
||||
* with the default style (see {@link #setTheme(Context, int)}).
|
||||
*
|
||||
* ThemeHelper.setDayNightMode should be called before
|
||||
* the applying theme for the first time in session
|
||||
*
|
||||
* @param context context that the theme will be applied
|
||||
*/
|
||||
public static void setTheme(final Context context) {
|
||||
@@ -57,6 +60,9 @@ public final class ThemeHelper {
|
||||
* Apply the selected theme (on NewPipe settings) in the context,
|
||||
* themed according with the styles defined for the service .
|
||||
*
|
||||
* ThemeHelper.setDayNightMode should be called before
|
||||
* the applying theme for the first time in session
|
||||
*
|
||||
* @param context context that the theme will be applied
|
||||
* @param serviceId the theme will be styled to the service with this id,
|
||||
* pass -1 to get the default style
|
||||
@@ -120,6 +126,7 @@ public final class ThemeHelper {
|
||||
|
||||
final String selectedThemeKey = getSelectedThemeKey(context);
|
||||
|
||||
|
||||
int baseTheme = R.style.DarkTheme; // default to dark theme
|
||||
if (selectedThemeKey.equals(lightThemeKey)) {
|
||||
baseTheme = R.style.LightTheme;
|
||||
@@ -202,20 +209,6 @@ public final class ThemeHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource id from a resource styled according to the context's theme.
|
||||
*
|
||||
* @param context Android app context
|
||||
* @param attr attribute reference of the resource
|
||||
* @return resource ID
|
||||
*/
|
||||
public static int resolveResourceIdFromAttr(final Context context, @AttrRes final int attr) {
|
||||
final TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
final int attributeResourceId = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
return attributeResourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a color from an attr styled according to the context's theme.
|
||||
*
|
||||
@@ -288,4 +281,21 @@ public final class ThemeHelper {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setDayNightMode(final Context context) {
|
||||
setDayNightMode(context, ThemeHelper.getSelectedThemeKey(context));
|
||||
}
|
||||
|
||||
public static void setDayNightMode(final Context context, final String selectedThemeKey) {
|
||||
final Resources res = context.getResources();
|
||||
|
||||
if (selectedThemeKey.equals(res.getString(R.string.light_theme_key))) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
} else if (selectedThemeKey.equals(res.getString(R.string.dark_theme_key))
|
||||
|| selectedThemeKey.equals(res.getString(R.string.black_theme_key))) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ public class StoredFileHelper implements Serializable {
|
||||
if (!docTree.canRead() || !docTree.canWrite()) return false;
|
||||
try {
|
||||
docFile = createSAF(context, srcType, srcName);
|
||||
if (docFile == null || docFile.getName() == null) return false;
|
||||
if (docFile.getName() == null) return false;
|
||||
result = true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
@@ -354,7 +354,9 @@ public class StoredFileHelper implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private DocumentFile createSAF(@Nullable Context context, String mime, String filename) throws IOException {
|
||||
@NonNull
|
||||
private DocumentFile createSAF(@Nullable Context context, String mime, String filename)
|
||||
throws IOException {
|
||||
DocumentFile res = StoredDirectoryHelper.findFileSAFHelper(context, docTree, filename);
|
||||
|
||||
if (res != null && res.exists() && res.isDirectory()) {
|
||||
|
||||
@@ -55,6 +55,14 @@ public class Deleter {
|
||||
}
|
||||
|
||||
public void append(Mission item) {
|
||||
|
||||
/* If a mission is removed from the list while the Snackbar for a previously
|
||||
* removed item is still showing, commit the action for the previous item
|
||||
* immediately. This prevents Snackbars from stacking up in reverse order.
|
||||
*/
|
||||
mHandler.removeCallbacks(rCommit);
|
||||
commit();
|
||||
|
||||
mIterator.hide(item);
|
||||
items.add(0, item);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package us.shandian.giga.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -19,6 +18,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
@@ -224,10 +224,9 @@ public class MissionsFragment extends Fragment {
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
if (mSwitch != null) {
|
||||
mSwitch.setIcon(ThemeHelper.resolveResourceIdFromAttr(
|
||||
requireContext(), mLinear
|
||||
? R.attr.ic_grid
|
||||
: R.attr.ic_list));
|
||||
mSwitch.setIcon(mLinear
|
||||
? R.drawable.ic_apps
|
||||
: R.drawable.ic_list);
|
||||
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
|
||||
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
||||
}
|
||||
|
||||
@@ -182,12 +182,12 @@ public class Utility {
|
||||
public static int getIconForFileType(FileType type) {
|
||||
switch (type) {
|
||||
case MUSIC:
|
||||
return R.drawable.ic_headset_white_24dp;
|
||||
return R.drawable.ic_headset;
|
||||
default:
|
||||
case VIDEO:
|
||||
return R.drawable.ic_movie_white_24dp;
|
||||
return R.drawable.ic_movie;
|
||||
case SUBTITLE:
|
||||
return R.drawable.ic_subtitles_white_24dp;
|
||||
return R.drawable.ic_subtitles;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user