Merge pull request #6053 from TeamNewPipe/release_0.21.2

Release NewPipe 0.21.2 (968)
This commit is contained in:
Tobi 2021-04-26 18:54:28 +02:00 committed by GitHub
commit 91ca680911
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
513 changed files with 3779 additions and 2165 deletions

View file

@ -13,8 +13,8 @@
- take over the world - take over the world
#### Fixes the following issue(s) #### 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 #### Relies on the following changes
<!-- Delete this if it doesn't apply to you. --> <!-- Delete this if it doesn't apply to you. -->

View file

@ -10,20 +10,22 @@ on:
- master - master
jobs: jobs:
build-and-test: build-and-test-jvm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- name: create and checkout branch - name: create and checkout branch
# push events already checked out the branch # push events already checked out the branch
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
run: git checkout -B ${{ github.head_ref }} run: git checkout -B ${{ github.head_ref }}
- name: set up JDK 1.8 - name: set up JDK 8
uses: actions/setup-java@v1.4.3 uses: actions/setup-java@v2
with: with:
java-version: 1.8 java-version: 8
distribution: "zulu"
- name: Cache Gradle dependencies - name: Cache Gradle dependencies
uses: actions/cache@v2 uses: actions/cache@v2
@ -32,7 +34,7 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-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 run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace
- name: Upload APK - name: Upload APK
@ -40,6 +42,32 @@ jobs:
with: with:
name: app name: app
path: app/build/outputs/apk/debug/*.apk 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: "zulu"
- 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: # sonar:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
@ -48,9 +76,10 @@ jobs:
# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis # fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
# - name: Set up JDK 11 # - name: Set up JDK 11
# uses: actions/setup-java@v1.4.3 # uses: actions/setup-java@v2
# with: # with:
# java-version: 11 # Sonar requires JDK 11 # java-version: 11 # Sonar requires JDK 11
# distribution: "zulu"
# - name: Cache SonarCloud packages # - name: Cache SonarCloud packages
# uses: actions/cache@v2 # uses: actions/cache@v2

View file

@ -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://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://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="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://webchat.freenode.net/#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> <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> </p>
<hr> <hr>
@ -85,7 +85,7 @@ NewPipe apoya varios servicios. Nuestras [documentaciones](https://teamnewpipe.g
## Installación y actualizaciones ## Installación y actualizaciones
Se puede instalar NewPipe usando uno de los métodos siguientes: 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. 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. 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. 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/). 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 ## Licencia
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](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. 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.

View file

@ -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://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://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="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://webchat.freenode.net/#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> <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> </p>
<hr> <hr>
@ -143,7 +143,7 @@ NewPipe プロジェクトはメディアウェブサービスを使用する上
<span id="license"></span> <span id="license"></span>
## ライセンス ## ライセンス
[![GNU GPLv3 のロゴ](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 のロゴ](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe はフリーソフトウェアなので、あなたはあなたの望むように使用、習得、共有、改善を行えます。 NewPipe はフリーソフトウェアなので、あなたはあなたの望むように使用、習得、共有、改善を行えます。
具体的には、フリーソフトウェア財団により公開された [GNU General Public License](https://www.gnu.org/licenses/gpl.html) のバージョン3のライセンスもしくは、(あなたの選択で) いずれかの後継バージョンの規約の元で配布または改変を行うことができます。 具体的には、フリーソフトウェア財団により公開された [GNU General Public License](https://www.gnu.org/licenses/gpl.html) のバージョン3のライセンスもしくは、(あなたの選択で) いずれかの後継バージョンの規約の元で配布または改変を行うことができます。

View file

@ -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://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a> <a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a> <a href="https://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://webchat.freenode.net/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a> <a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p> </p>
<hr> <hr>
@ -139,7 +139,7 @@ NewPipe 프로젝트는 미디어 웹 서비스를 사용하는 것에 대한
그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.net/legal/privacy/)에서 확인할 수 있습니다. 그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.net/legal/privacy/)에서 확인할 수 있습니다.
## License ## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe는 자유 소프트웨어입니다: 당신의 마음대로 이것을 사용하고, 연구하고, 공유하고, 개선할 수 있습니다. NewPipe는 자유 소프트웨어입니다: 당신의 마음대로 이것을 사용하고, 연구하고, 공유하고, 개선할 수 있습니다.
구체적으로 당신은 자유 소프트웨어 재단에서 발행되는, 버전 3 또는 (당신의 선택에 따라)이후 버전의, 구체적으로 당신은 자유 소프트웨어 재단에서 발행되는, 버전 3 또는 (당신의 선택에 따라)이후 버전의,

View file

@ -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://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a> <a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a> <a href="https://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://webchat.freenode.net/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a> <a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p> </p>
<hr> <hr>
@ -87,7 +87,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
## Installation and updates ## Installation and updates
You can install NewPipe using one of the following methods: 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. 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. 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. 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.
@ -137,7 +137,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/). 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 ## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe is Free Software: You can use, study share and improve it at your 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 will. Specifically you can redistribute and/or modify it under the terms of the

View file

@ -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://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a> <a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a> <a href="https://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://webchat.freenode.net/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a> <a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p> </p>
<hr> <hr>
@ -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/). 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 ## Licença
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe é Software Livre: Você pode usar, estudar compartilhamento e melhorá-lo à sua vontade. 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 Especificamente, você pode redistribuir e/ou modificá-lo sob os termos do

View file

@ -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://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a> <a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="Build Status"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a> <a href="https://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://webchat.freenode.net/#newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a> <a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p> </p>
<hr> <hr>
@ -87,9 +87,9 @@ NewPipe suportă servicii multiple. [Documentele](https://teamnewpipe.github.io/
## Instalare şi actualizări ## Instalare şi actualizări
Puteţi instala NewPipe folosind una dintre următoarele metode: 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. 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. 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. 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 +137,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/). 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ţă ## Licenţă
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](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 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 [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

View file

@ -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://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://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="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://webchat.freenode.net/#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> <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> </p>
<hr> <hr>
@ -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/). 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 ## Laysinka
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](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. 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.

View file

@ -17,8 +17,8 @@ android {
resValue "string", "app_name", "NewPipe" resValue "string", "app_name", "NewPipe"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 29 targetSdkVersion 29
versionCode 967 versionCode 968
versionName "0.21.1" versionName "0.21.2"
multiDexEnabled true multiDexEnabled true
@ -96,16 +96,19 @@ android {
} }
ext { ext {
icepickVersion = '3.2.0'
checkstyleVersion = '8.38' checkstyleVersion = '8.38'
stethoVersion = '1.5.1'
leakCanaryVersion = '2.5'
exoPlayerVersion = '2.12.3'
androidxLifecycleVersion = '2.2.0' androidxLifecycleVersion = '2.2.0'
androidxRoomVersion = '2.3.0-alpha03' androidxRoomVersion = '2.3.0-alpha03'
icepickVersion = '3.2.0'
exoPlayerVersion = '2.13.2'
googleAutoServiceVersion = '1.0-rc7'
groupieVersion = '2.8.1' groupieVersion = '2.8.1'
markwonVersion = '4.6.0' markwonVersion = '4.6.0'
googleAutoServiceVersion = '1.0-rc7'
leakCanaryVersion = '2.5'
stethoVersion = '1.5.1'
mockitoVersion = '3.6.0' mockitoVersion = '3.6.0'
} }
@ -171,82 +174,99 @@ sonarqube {
} }
dependencies { dependencies {
/** Desugaring **/
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
/** 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.2'
/** 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 "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
implementation "frankiesardo:icepick:${icepickVersion}" /** AndroidX **/
kapt "frankiesardo:icepick-processor:${icepickVersion}" implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
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"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.documentfile:documentfile:1.0.1' 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-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${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-runtime:${androidxRoomVersion}"
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}" implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
kapt "androidx.room:room-compiler:${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:${groupieVersion}"
implementation "com.xwray:groupie-viewbinding:${groupieVersion}" implementation "com.xwray:groupie-viewbinding:${groupieVersion}"
// Circular ImageView
implementation "de.hdodenhof:circleimageview:3.1.0" implementation "de.hdodenhof:circleimageview:3.1.0"
// Image loading
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5" implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
// Markdown library for Android
implementation "io.noties.markwon:core:${markwonVersion}" implementation "io.noties.markwon:core:${markwonVersion}"
implementation "io.noties.markwon:linkify:${markwonVersion}" implementation "io.noties.markwon:linkify:${markwonVersion}"
// File picker
implementation "com.nononsenseapps:filepicker:4.2.1" implementation "com.nononsenseapps:filepicker:4.2.1"
// Crash reporting
implementation "ch.acra:acra-core:5.7.0" 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:rxjava:3.0.7"
implementation "io.reactivex.rxjava3:rxandroid:3.0.0" implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
// RxJava binding APIs for Android UI widgets
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
// Date and time formatting
implementation "org.ocpsoft.prettytime:prettytime:5.0.0.Final" implementation "org.ocpsoft.prettytime:prettytime:5.0.0.Final"
/** Debugging **/
// Memory leak detection
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}"
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
// Debug bridge for Android
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
/** Testing **/
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation "org.mockito:mockito-inline:${mockitoVersion}" testImplementation "org.mockito:mockito-inline:${mockitoVersion}"

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe.settings;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference; import androidx.preference.Preference;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -11,8 +10,8 @@ import leakcanary.LeakCanary;
public class DebugSettingsFragment extends BasePreferenceFragment { public class DebugSettingsFragment extends BasePreferenceFragment {
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.debug_settings);
final Preference showMemoryLeaksPreference final Preference showMemoryLeaksPreference
= findPreference(getString(R.string.show_memory_leaks_key)); = findPreference(getString(R.string.show_memory_leaks_key));
@ -31,9 +30,4 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
throw new RuntimeException(); throw new RuntimeException();
}); });
} }
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.debug_settings);
}
} }

View file

@ -6,50 +6,50 @@
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.VideoAudioSettingsFragment" 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" android:title="@string/settings_category_video_audio_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.DownloadSettingsFragment" 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" android:title="@string/settings_category_downloads_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.AppearanceSettingsFragment" android:fragment="org.schabi.newpipe.settings.AppearanceSettingsFragment"
android:icon="?attr/ic_palette" android:icon="@drawable/ic_palette"
android:title="@string/settings_category_appearance_title" android:title="@string/settings_category_appearance_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.HistorySettingsFragment" android:fragment="org.schabi.newpipe.settings.HistorySettingsFragment"
android:icon="?attr/ic_history" android:icon="@drawable/ic_history"
android:title="@string/settings_category_history_title" android:title="@string/settings_category_history_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment" android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment"
android:icon="?attr/ic_language" android:icon="@drawable/ic_language"
android:title="@string/content" android:title="@string/content"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.NotificationSettingsFragment" 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" android:title="@string/settings_category_notification_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.UpdateSettingsFragment" 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:key="update_pref_screen_key"
android:title="@string/settings_category_updates_title" android:title="@string/settings_category_updates_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment" 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:key="@string/debug_pref_screen_key"
android:title="@string/settings_category_debug_title" android:title="@string/settings_category_debug_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />

View file

@ -133,6 +133,8 @@ public class MainActivity extends AppCompatActivity {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
TLSSocketFactoryCompat.setAsDefault(); TLSSocketFactoryCompat.setAsDefault();
} }
ThemeHelper.setDayNightMode(this);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
assureCorrectAppLanguage(this); assureCorrectAppLanguage(this);
@ -180,27 +182,27 @@ public class MainActivity extends AppCompatActivity {
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, .add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER,
R.string.tab_subscriptions) R.string.tab_subscriptions)
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel)); .setIcon(R.drawable.ic_tv);
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history) .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 //Settings and About
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about) .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(), toggle = new ActionBarDrawerToggle(this, mainBinding.getRoot(),
toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close); toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close);
@ -346,7 +348,7 @@ public class MainActivity extends AppCompatActivity {
} }
private void showServices() { 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()) { for (final StreamingService s : NewPipe.getServices()) {
final String title = s.getServiceInfo().getName() final String title = s.getServiceInfo().getName()
@ -412,7 +414,7 @@ public class MainActivity extends AppCompatActivity {
} }
private void showTabs() throws ExtractionException { 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 //Tabs
final int currentServiceId = ServiceHelper.getSelectedServiceId(this); final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
@ -430,27 +432,27 @@ public class MainActivity extends AppCompatActivity {
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history) .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 //Settings and About
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings) .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() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about) .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 @Override

View file

@ -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.AUDIO;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO; 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. * 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) new AlertDialog.Builder(context)
.setTitle(R.string.unsupported_url) .setTitle(R.string.unsupported_url)
.setMessage(R.string.unsupported_url_dialog_message) .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, .setPositiveButton(R.string.open_in_browser,
(dialog, which) -> ShareUtils.openUrlInBrowser(this, url)) (dialog, which) -> ShareUtils.openUrlInBrowser(this, url))
.setNegativeButton(R.string.share, .setNegativeButton(R.string.share,
@ -373,7 +372,7 @@ public class RouterActivity extends AppCompatActivity {
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot(); final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot();
radioButton.setText(item.description); radioButton.setText(item.description);
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton, TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(radioButton,
AppCompatResources.getDrawable(getApplicationContext(), item.icon), AppCompatResources.getDrawable(themeWrapperContext, item.icon),
null, null, null); null, null, null);
radioButton.setChecked(false); radioButton.setChecked(false);
radioButton.setId(id++); radioButton.setId(id++);
@ -427,16 +426,16 @@ public class RouterActivity extends AppCompatActivity {
final AdapterChoiceItem videoPlayer = new AdapterChoiceItem( final AdapterChoiceItem videoPlayer = new AdapterChoiceItem(
getString(R.string.video_player_key), getString(R.string.video_player), 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( final AdapterChoiceItem showInfo = new AdapterChoiceItem(
getString(R.string.show_info_key), getString(R.string.show_info), 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( final AdapterChoiceItem popupPlayer = new AdapterChoiceItem(
getString(R.string.popup_player_key), getString(R.string.popup_player), 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( final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
getString(R.string.background_player_key), getString(R.string.background_player), 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 (linkType == LinkType.STREAM) {
if (isExtVideoEnabled) { if (isExtVideoEnabled) {
@ -481,7 +480,7 @@ public class RouterActivity extends AppCompatActivity {
returnList.add(new AdapterChoiceItem(getString(R.string.download_key), returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
getString(R.string.download), getString(R.string.download),
resolveResourceIdFromAttr(context, R.attr.ic_file_download))); R.drawable.ic_file_download));
return returnList; return returnList;
} }

View file

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

View 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
}
}

View file

@ -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);
}
}
}

View 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
}
}
}

View file

@ -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();
});
}
}

View file

@ -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()
}
}
}
}

View file

@ -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() { }
}

View file

@ -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")
}

View file

@ -394,8 +394,7 @@ public class DownloadDialog extends DialogFragment
} }
toolbar.setTitle(R.string.download_dialog_title); toolbar.setTitle(R.string.download_dialog_title);
toolbar.setNavigationIcon( toolbar.setNavigationIcon(R.drawable.ic_arrow_back);
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_arrow_back));
toolbar.inflateMenu(R.menu.dialog_url); toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss()); toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss());
toolbar.setNavigationContentDescription(R.string.cancel); toolbar.setNavigationContentDescription(R.string.cancel);

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.error; package org.schabi.newpipe.error;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
@ -16,6 +15,7 @@ import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -137,6 +137,8 @@ public class ErrorActivity extends AppCompatActivity {
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this); assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ThemeHelper.setDayNightMode(this);
ThemeHelper.setTheme(this); ThemeHelper.setTheme(this);
activityErrorBinding = ActivityErrorBinding.inflate(getLayoutInflater()); activityErrorBinding = ActivityErrorBinding.inflate(getLayoutInflater());

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.fragments; package org.schabi.newpipe.fragments;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; 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.settings.tabs.TabsManager;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -87,10 +85,10 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
binding = FragmentMainBinding.bind(rootView); binding = FragmentMainBinding.bind(rootView);
binding.mainTabLayout.setTabIconTint(ColorStateList.valueOf(
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
binding.mainTabLayout.setupWithViewPager(binding.pager); binding.mainTabLayout.setupWithViewPager(binding.pager);
binding.mainTabLayout.addOnTabSelectedListener(this); binding.mainTabLayout.addOnTabSelectedListener(this);
binding.mainTabLayout.setTabRippleColor(binding.mainTabLayout.getTabRippleColor()
.withAlpha(32));
setupTabs(); setupTabs();
} }

View file

@ -73,7 +73,7 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.EmptyFragment; import org.schabi.newpipe.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment; 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.ktx.AnimationType;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog; import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
@ -153,7 +153,7 @@ public final class VideoDetailFragment
// tabs // tabs
private boolean showComments; private boolean showComments;
private boolean showRelatedStreams; private boolean showRelatedItems;
private boolean showDescription; private boolean showDescription;
private String selectedTabTag; private String selectedTabTag;
@AttrRes @NonNull final List<Integer> tabIcons = new ArrayList<>(); @AttrRes @NonNull final List<Integer> tabIcons = new ArrayList<>();
@ -280,7 +280,7 @@ public final class VideoDetailFragment
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
showComments = prefs.getBoolean(getString(R.string.show_comments_key), true); 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); showDescription = prefs.getBoolean(getString(R.string.show_description_key), true);
selectedTabTag = prefs.getString( selectedTabTag = prefs.getString(
getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG); getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
@ -413,7 +413,7 @@ public final class VideoDetailFragment
showComments = sharedPreferences.getBoolean(key, true); showComments = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true; tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_next_video_key))) { } else if (key.equals(getString(R.string.show_next_video_key))) {
showRelatedStreams = sharedPreferences.getBoolean(key, true); showRelatedItems = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true; tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_description_key))) { } else if (key.equals(getString(R.string.show_description_key))) {
showComments = sharedPreferences.getBoolean(key, true); showComments = sharedPreferences.getBoolean(key, true);
@ -923,21 +923,21 @@ public final class VideoDetailFragment
if (shouldShowComments()) { if (shouldShowComments()) {
pageAdapter.addFragment( pageAdapter.addFragment(
CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG); 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); 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 // temp empty fragment. will be updated in handleResult
pageAdapter.addFragment(new EmptyFragment(false), RELATED_TAB_TAG); pageAdapter.addFragment(new EmptyFragment(false), RELATED_TAB_TAG);
tabIcons.add(R.drawable.ic_art_track_white_24dp); tabIcons.add(R.drawable.ic_art_track);
tabContentDescriptions.add(R.string.related_streams_tab_description); tabContentDescriptions.add(R.string.related_items_tab_description);
} }
if (showDescription) { if (showDescription) {
// temp empty fragment. will be updated in handleResult // temp empty fragment. will be updated in handleResult
pageAdapter.addFragment(new EmptyFragment(false), DESCRIPTION_TAB_TAG); pageAdapter.addFragment(new EmptyFragment(false), DESCRIPTION_TAB_TAG);
tabIcons.add(R.drawable.ic_description_white_24dp); tabIcons.add(R.drawable.ic_description);
tabContentDescriptions.add(R.string.description_tab_description); tabContentDescriptions.add(R.string.description_tab_description);
} }
@ -974,14 +974,14 @@ public final class VideoDetailFragment
} }
private void updateTabs(@NonNull final StreamInfo info) { private void updateTabs(@NonNull final StreamInfo info) {
if (showRelatedStreams) { if (showRelatedItems) {
if (binding.relatedStreamsLayout == null) { // phone if (binding.relatedItemsLayout == null) { // phone
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info)); pageAdapter.updateItem(RELATED_TAB_TAG, RelatedItemsFragment.getInstance(info));
} else { // tablet + TV } else { // tablet + TV
getChildFragmentManager().beginTransaction() getChildFragmentManager().beginTransaction()
.replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(info)) .replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info))
.commitAllowingStateLoss(); .commitAllowingStateLoss();
binding.relatedStreamsLayout.setVisibility( binding.relatedItemsLayout.setVisibility(
player != null && player.isFullscreen() ? View.GONE : View.VISIBLE); player != null && player.isFullscreen() ? View.GONE : View.VISIBLE);
} }
} }
@ -1009,6 +1009,12 @@ public final class VideoDetailFragment
} }
public void updateTabLayoutVisibility() { 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) { 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 // hide tab layout if there is only one tab or if the view pager is also hidden
binding.tabLayout.setVisibility(View.GONE); binding.tabLayout.setVisibility(View.GONE);
@ -1331,8 +1337,8 @@ public final class VideoDetailFragment
super.handleError(); super.handleError();
setErrorImage(R.drawable.not_available_monkey); setErrorImage(R.drawable.not_available_monkey);
if (binding.relatedStreamsLayout != null) { // hide related streams for tablets if (binding.relatedItemsLayout != null) { // hide related streams for tablets
binding.relatedStreamsLayout.setVisibility(View.INVISIBLE); binding.relatedItemsLayout.setVisibility(View.INVISIBLE);
} }
// hide comments / related streams / description tabs // hide comments / related streams / description tabs
@ -1426,12 +1432,12 @@ public final class VideoDetailFragment
binding.detailTitleRootLayout.setClickable(false); binding.detailTitleRootLayout.setClickable(false);
binding.detailSecondaryControlPanel.setVisibility(View.GONE); binding.detailSecondaryControlPanel.setVisibility(View.GONE);
if (binding.relatedStreamsLayout != null) { if (binding.relatedItemsLayout != null) {
if (showRelatedStreams) { if (showRelatedItems) {
binding.relatedStreamsLayout.setVisibility( binding.relatedItemsLayout.setVisibility(
player != null && player.isFullscreen() ? View.GONE : View.INVISIBLE); player != null && player.isFullscreen() ? View.GONE : View.INVISIBLE);
} else { } else {
binding.relatedStreamsLayout.setVisibility(View.GONE); binding.relatedItemsLayout.setVisibility(View.GONE);
} }
} }
@ -1843,8 +1849,8 @@ public final class VideoDetailFragment
showSystemUi(); showSystemUi();
} }
if (binding.relatedStreamsLayout != null) { if (binding.relatedItemsLayout != null) {
binding.relatedStreamsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE); binding.relatedItemsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
} }
scrollToTop(); scrollToTop();
@ -2274,11 +2280,10 @@ public final class VideoDetailFragment
} }
private void setOverlayPlayPauseImage(final boolean playerIsPlaying) { private void setOverlayPlayPauseImage(final boolean playerIsPlaying) {
final int attr = playerIsPlaying final int drawable = playerIsPlaying
? R.attr.ic_pause ? R.drawable.ic_pause
: R.attr.ic_play_arrow; : R.drawable.ic_play_arrow;
binding.overlayPlayPauseButton.setImageResource( binding.overlayPlayPauseButton.setImageResource(drawable);
ThemeHelper.resolveResourceIdFromAttr(activity, attr));
} }
private void setOverlayLook(final AppBarLayout appBar, private void setOverlayLook(final AppBarLayout appBar,

View file

@ -45,6 +45,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Queue; 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.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; 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. * If the default implementation of {@link StateSaver.WriteRead} should be used.
* *
* @see StateSaver
* @param useDefaultStateSaving Whether the default implementation should be used * @param useDefaultStateSaving Whether the default implementation should be used
* @see StateSaver
*/ */
public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) { public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) {
this.useDefaultStateSaving = useDefaultStateSaving; this.useDefaultStateSaving = useDefaultStateSaving;
@ -350,7 +351,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
return; return;
} }
final ArrayList<StreamDialogEntry> entries = new ArrayList<>(); final List<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getType() != null) { if (PlayerHolder.getType() != null) {
entries.add(StreamDialogEntry.enqueue); entries.add(StreamDialogEntry.enqueue);
@ -372,6 +373,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) { if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi); entries.add(StreamDialogEntry.play_with_kodi);
} }
if (!isNullOrEmpty(item.getUploaderUrl())) {
entries.add(StreamDialogEntry.show_channel_details);
}
StreamDialogEntry.setEnabledEntries(entries); StreamDialogEntry.setEnabledEntries(entries);
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),

View file

@ -12,6 +12,7 @@ import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.ListInfo; import org.schabi.newpipe.extractor.ListInfo;
import org.schabi.newpipe.extractor.Page; import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.views.NewPipeRecyclerView; import org.schabi.newpipe.views.NewPipeRecyclerView;
@ -227,9 +228,13 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
showListFooter(hasMoreItems()); showListFooter(hasMoreItems());
} else { } else {
infoListAdapter.clearStreamItemList(); infoListAdapter.clearStreamItemList();
// showEmptyState should be called only if there is no item as
// well as no header in infoListAdapter
if (!(result instanceof ChannelInfo && infoListAdapter.getItemCount() == 1)) {
showEmptyState(); showEmptyState();
} }
} }
}
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
final List<Throwable> errors = new ArrayList<>(result.getErrors()); final List<Throwable> errors = new ArrayList<>(result.getErrors());

View file

@ -462,7 +462,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl())); menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
} }
// PlaylistControls should be visible only if there is some item in
// infoListAdapter other than header
if (infoListAdapter.getItemCount() != 1) {
playlistControlBinding.getRoot().setVisibility(View.VISIBLE); playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
} else {
playlistControlBinding.getRoot().setVisibility(View.GONE);
}
for (final Throwable throwable : result.getErrors()) { for (final Throwable throwable : result.getErrors()) {
if (throwable instanceof ContentNotSupportedException) { if (throwable instanceof ContentNotSupportedException) {

View file

@ -61,7 +61,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> { public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
private CompositeDisposable disposables; private CompositeDisposable disposables;
@ -307,7 +306,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
getResources().getColor(R.color.transparent_background_color)); getResources().getColor(R.color.transparent_background_color));
headerBinding.uploaderAvatarView.setImageDrawable( headerBinding.uploaderAvatarView.setImageDrawable(
AppCompatResources.getDrawable(requireContext(), AppCompatResources.getDrawable(requireContext(),
resolveResourceIdFromAttr(requireContext(), R.attr.ic_radio)) R.drawable.ic_radio)
); );
} else { } else {
IMAGE_LOADER.displayImage(avatarUrl, headerBinding.uploaderAvatarView, IMAGE_LOADER.displayImage(avatarUrl, headerBinding.uploaderAvatarView,
@ -423,8 +422,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
@Override @Override
public void setTitle(final String title) { public void setTitle(final String title) {
super.setTitle(title); super.setTitle(title);
if (headerBinding != null) {
headerBinding.playlistTitleView.setText(title); headerBinding.playlistTitleView.setText(title);
} }
}
private void onBookmarkClicked() { private void onBookmarkClicked() {
if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get() if (isBookmarkButtonReady == null || !isBookmarkButtonReady.get()
@ -459,13 +460,13 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
return; return;
} }
final int iconAttr = playlistEntity == null final int drawable = playlistEntity == null
? R.attr.ic_playlist_add : R.attr.ic_playlist_check; ? R.drawable.ic_playlist_add : R.drawable.ic_playlist_add_check;
final int titleRes = playlistEntity == null final int titleRes = playlistEntity == null
? R.string.bookmark_playlist : R.string.unbookmark_playlist; ? R.string.bookmark_playlist : R.string.unbookmark_playlist;
playlistBookmarkButton.setIcon(resolveResourceIdFromAttr(activity, iconAttr)); playlistBookmarkButton.setIcon(drawable);
playlistBookmarkButton.setTitle(titleRes); playlistBookmarkButton.setTitle(titleRes);
} }
} }

View file

@ -139,7 +139,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State @State
boolean wasSearchFocused = false; boolean wasSearchFocused = false;
private Map<Integer, String> menuItemToFilterName; @Nullable private Map<Integer, String> menuItemToFilterName = null;
private StreamingService service; private StreamingService service;
private Page nextPage; private Page nextPage;
private boolean isSuggestionsEnabled = true; private boolean isSuggestionsEnabled = true;
@ -455,11 +455,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} }
@Override @Override
public boolean onOptionsItemSelected(final MenuItem item) { public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (menuItemToFilterName != null) {
final List<String> cf = new ArrayList<>(1); final List<String> cf = new ArrayList<>(1);
cf.add(menuItemToFilterName.get(item.getItemId())); cf.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, cf); changeContentFilter(item, cf);
}
return true; return true;
} }
@ -486,6 +487,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
+ lastSearchedString); + lastSearchedString);
} }
searchEditText.setText(searchString); 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())) { if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
searchToolbarContainer.setTranslationX(100); searchToolbarContainer.setTranslationX(100);

View file

@ -1,14 +1,12 @@
package org.schabi.newpipe.fragments.list.search; package org.schabi.newpipe.fragments.list.search;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -117,16 +115,8 @@ public class SuggestionListAdapter
queryView = rootView.findViewById(R.id.suggestion_search); queryView = rootView.findViewById(R.id.suggestion_search);
insertView = rootView.findViewById(R.id.suggestion_insert); insertView = rootView.findViewById(R.id.suggestion_insert);
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_history); historyResId = R.drawable.ic_history;
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_search); searchResId = R.drawable.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;
} }
private void updateFrom(final SuggestionItem item) { private void updateFrom(final SuggestionItem item) {

View file

@ -15,38 +15,38 @@ import androidx.preference.PreferenceManager;
import androidx.viewbinding.ViewBinding; import androidx.viewbinding.ViewBinding;
import org.schabi.newpipe.R; 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.error.UserAction;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.util.RelatedStreamInfo; import org.schabi.newpipe.util.RelatedItemInfo;
import java.io.Serializable; import java.io.Serializable;
import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo> public class RelatedItemsFragment extends BaseListInfoFragment<RelatedItemInfo>
implements SharedPreferences.OnSharedPreferenceChangeListener { implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String INFO_KEY = "related_info_key"; private static final String INFO_KEY = "related_info_key";
private final CompositeDisposable disposables = new CompositeDisposable(); private final CompositeDisposable disposables = new CompositeDisposable();
private RelatedStreamInfo relatedStreamInfo; private RelatedItemInfo relatedItemInfo;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private RelatedStreamsHeaderBinding headerBinding; private RelatedItemsHeaderBinding headerBinding;
public static RelatedVideosFragment getInstance(final StreamInfo info) { public static RelatedItemsFragment getInstance(final StreamInfo info) {
final RelatedVideosFragment instance = new RelatedVideosFragment(); final RelatedItemsFragment instance = new RelatedItemsFragment();
instance.setInitialData(info); instance.setInitialData(info);
return instance; return instance;
} }
public RelatedVideosFragment() { public RelatedItemsFragment() {
super(UserAction.REQUESTED_STREAM); super(UserAction.REQUESTED_STREAM);
} }
@ -63,7 +63,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
public View onCreateView(@NonNull final LayoutInflater inflater, public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) { @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 @Override
@ -80,8 +80,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
@Override @Override
protected ViewBinding getListHeader() { protected ViewBinding getListHeader() {
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) { if (relatedItemInfo != null && relatedItemInfo.getRelatedItems() != null) {
headerBinding = RelatedStreamsHeaderBinding headerBinding = RelatedItemsHeaderBinding
.inflate(activity.getLayoutInflater(), itemsList, false); .inflate(activity.getLayoutInflater(), itemsList, false);
final SharedPreferences pref = PreferenceManager final SharedPreferences pref = PreferenceManager
@ -107,8 +107,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected Single<RelatedStreamInfo> loadResult(final boolean forceLoad) { protected Single<RelatedItemInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo); return Single.fromCallable(() -> relatedItemInfo);
} }
@Override @Override
@ -120,7 +120,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
} }
@Override @Override
public void handleResult(@NonNull final RelatedStreamInfo result) { public void handleResult(@NonNull final RelatedItemInfo result) {
super.handleResult(result); super.handleResult(result);
if (headerBinding != null) { if (headerBinding != null) {
@ -145,23 +145,23 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
private void setInitialData(final StreamInfo info) { private void setInitialData(final StreamInfo info) {
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName()); super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
if (this.relatedStreamInfo == null) { if (this.relatedItemInfo == null) {
this.relatedStreamInfo = RelatedStreamInfo.getInfo(info); this.relatedItemInfo = RelatedItemInfo.getInfo(info);
} }
} }
@Override @Override
public void onSaveInstanceState(@NonNull final Bundle outState) { public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedStreamInfo); outState.putSerializable(INFO_KEY, relatedItemInfo);
} }
@Override @Override
protected void onRestoreInstanceState(@NonNull final Bundle savedState) { protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState); super.onRestoreInstanceState(savedState);
final Serializable serializable = savedState.getSerializable(INFO_KEY); final Serializable serializable = savedState.getSerializable(INFO_KEY);
if (serializable instanceof RelatedStreamInfo) { if (serializable instanceof RelatedItemInfo) {
this.relatedStreamInfo = (RelatedStreamInfo) serializable; this.relatedItemInfo = (RelatedItemInfo) serializable;
} }
} }

View file

@ -1,13 +1,13 @@
package org.schabi.newpipe.info_list; package org.schabi.newpipe.info_list;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;

View file

@ -1,5 +1,6 @@
package org.schabi.newpipe.info_list package org.schabi.newpipe.info_list
import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.nostra13.universalimageloader.core.ImageLoader import com.nostra13.universalimageloader.core.ImageLoader
@ -29,6 +30,17 @@ class StreamSegmentItem(
) )
} }
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title 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 = viewHolder.root.findViewById<TextView>(R.id.textViewStartSeconds).text =
Localization.getDurationString(item.startTimeSeconds.toLong()) Localization.getDurationString(item.startTimeSeconds.toLong())
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) } viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }

View file

@ -4,8 +4,6 @@ import android.text.TextUtils;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; 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.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import androidx.preference.PreferenceManager;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;
/* /*

View file

@ -1,7 +1,5 @@
package org.schabi.newpipe.local.bookmark; package org.schabi.newpipe.local.bookmark;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log; import android.util.Log;
@ -12,6 +10,7 @@ import android.widget.EditText;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import org.reactivestreams.Subscriber; 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); final EditText editText = dialogView.findViewById(R.id.playlist_name_edit_text);
editText.setText(selectedItem.name); editText.setText(selectedItem.name);
final Builder builder = new AlertDialog.Builder(activity); final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(dialogView) builder.setView(dialogView)
.setPositiveButton(R.string.rename_playlist, (dialog, which) -> .setPositiveButton(R.string.rename_playlist, (dialog, which) ->
changeLocalPlaylistName(selectedItem.uid, editText.getText().toString())) changeLocalPlaylistName(selectedItem.uid, editText.getText().toString()))

View file

@ -1,6 +1,5 @@
package org.schabi.newpipe.local.dialog; package org.schabi.newpipe.local.dialog;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
@ -9,6 +8,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R; 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 View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null);
final EditText nameInput = dialogView.findViewById(R.id.playlist_name); 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) .setTitle(R.string.create_playlist)
.setView(dialogView) .setView(dialogView)
.setCancelable(true) .setCancelable(true)
@ -54,7 +54,7 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
.setPositiveButton(R.string.create, (dialogInterface, i) -> { .setPositiveButton(R.string.create, (dialogInterface, i) -> {
final String name = nameInput.getText().toString(); final String name = nameInput.getText().toString();
final LocalPlaylistManager playlistManager = final LocalPlaylistManager playlistManager =
new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
final Toast successToast = Toast.makeText(getActivity(), final Toast successToast = Toast.makeText(getActivity(),
R.string.playlist_creation_success, R.string.playlist_creation_success,
Toast.LENGTH_SHORT); Toast.LENGTH_SHORT);

View file

@ -40,7 +40,6 @@ import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.util.StreamDialogEntry;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -312,14 +311,13 @@ public class StatisticsPlaylistFragment
if (sortMode == StatisticSortMode.LAST_PLAYED) { if (sortMode == StatisticSortMode.LAST_PLAYED) {
sortMode = StatisticSortMode.MOST_PLAYED; sortMode = StatisticSortMode.MOST_PLAYED;
setTitle(getString(R.string.title_most_played)); setTitle(getString(R.string.title_most_played));
headerBinding.sortButtonIcon.setImageResource( headerBinding.sortButtonIcon.setImageResource(R.drawable.ic_history);
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_history));
headerBinding.sortButtonText.setText(R.string.title_last_played); headerBinding.sortButtonText.setText(R.string.title_last_played);
} else { } else {
sortMode = StatisticSortMode.LAST_PLAYED; sortMode = StatisticSortMode.LAST_PLAYED;
setTitle(getString(R.string.title_last_played)); setTitle(getString(R.string.title_last_played));
headerBinding.sortButtonIcon.setImageResource( headerBinding.sortButtonIcon.setImageResource(
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_filter_list)); R.drawable.ic_filter_list);
headerBinding.sortButtonText.setText(R.string.title_most_played); headerBinding.sortButtonText.setText(R.string.title_most_played);
} }
startLoading(true); startLoading(true);

View file

@ -72,7 +72,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
// Save the list 10 seconds after the last change occurred // Save the list 10 seconds after the last change occurred
private static final long SAVE_DEBOUNCE_MILLIS = 10000; private static final long SAVE_DEBOUNCE_MILLIS = 10000;
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12; private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
@State @State
protected Long playlistId; protected Long playlistId;
@State @State
@ -340,7 +339,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
@Override @Override
public void onComplete() { } public void onComplete() {
}
}; };
} }
@ -361,6 +361,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
.create() .create()
.show(); .show();
} }
} else if (item.getItemId() == R.id.menu_item_rename_playlist) {
createRenameDialog();
} else { } else {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -722,7 +724,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override @Override
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int swipeDir) { } final int swipeDir) {
}
}; };
} }

View file

@ -1,10 +1,7 @@
package org.schabi.newpipe.local.subscription package org.schabi.newpipe.local.subscription
import android.content.Context
import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.util.ThemeHelper
enum class FeedGroupIcon( enum class FeedGroupIcon(
/** /**
@ -13,51 +10,51 @@ enum class FeedGroupIcon(
val id: Int, 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), ALL(0, R.drawable.ic_asterisk),
MUSIC(1, R.attr.ic_music_note), MUSIC(1, R.drawable.ic_music_note),
EDUCATION(2, R.attr.ic_school), EDUCATION(2, R.drawable.ic_school),
FITNESS(3, R.attr.ic_fitness_center), FITNESS(3, R.drawable.ic_fitness_center),
SPACE(4, R.attr.ic_telescope), SPACE(4, R.drawable.ic_telescope),
COMPUTER(5, R.attr.ic_computer), COMPUTER(5, R.drawable.ic_computer),
GAMING(6, R.attr.ic_videogame_asset), GAMING(6, R.drawable.ic_videogame_asset),
SPORTS(7, R.attr.ic_sports), SPORTS(7, R.drawable.ic_directions_bike),
NEWS(8, R.attr.ic_megaphone), NEWS(8, R.drawable.ic_megaphone),
FAVORITES(9, R.attr.ic_heart), FAVORITES(9, R.drawable.ic_favorite),
CAR(10, R.attr.ic_car), CAR(10, R.drawable.ic_directions_car),
MOTORCYCLE(11, R.attr.ic_motorcycle), MOTORCYCLE(11, R.drawable.ic_motorcycle),
TREND(12, R.attr.ic_trending_up), TREND(12, R.drawable.ic_trending_up),
MOVIE(13, R.attr.ic_movie), MOVIE(13, R.drawable.ic_movie),
BACKUP(14, R.attr.ic_backup), BACKUP(14, R.drawable.ic_backup),
ART(15, R.attr.ic_palette), ART(15, R.drawable.ic_palette),
PERSON(16, R.attr.ic_person), PERSON(16, R.drawable.ic_person),
PEOPLE(17, R.attr.ic_people), PEOPLE(17, R.drawable.ic_people),
MONEY(18, R.attr.ic_money), MONEY(18, R.drawable.ic_attach_money),
KIDS(19, R.attr.ic_child_care), KIDS(19, R.drawable.ic_child_care),
FOOD(20, R.attr.ic_fastfood), FOOD(20, R.drawable.ic_fastfood),
SMILE(21, R.attr.ic_smile), SMILE(21, R.drawable.ic_insert_emoticon),
EXPLORE(22, R.attr.ic_explore), EXPLORE(22, R.drawable.ic_explore),
RESTAURANT(23, R.attr.ic_restaurant), RESTAURANT(23, R.drawable.ic_restaurant),
MIC(24, R.attr.ic_mic), MIC(24, R.drawable.ic_mic),
HEADSET(25, R.attr.ic_headset), HEADSET(25, R.drawable.ic_headset),
RADIO(26, R.attr.ic_radio), RADIO(26, R.drawable.ic_radio),
SHOPPING_CART(27, R.attr.ic_shopping_cart), SHOPPING_CART(27, R.drawable.ic_shopping_cart),
WATCH_LATER(28, R.attr.ic_watch_later), WATCH_LATER(28, R.drawable.ic_watch_later),
WORK(29, R.attr.ic_work), WORK(29, R.drawable.ic_work),
HOT(30, R.attr.ic_kiosk_hot), HOT(30, R.drawable.ic_whatshot),
CHANNEL(31, R.attr.ic_channel), CHANNEL(31, R.drawable.ic_tv),
BOOKMARK(32, R.attr.ic_bookmark), BOOKMARK(32, R.drawable.ic_bookmark),
PETS(33, R.attr.ic_pets), PETS(33, R.drawable.ic_pets),
WORLD(34, R.attr.ic_world), WORLD(34, R.drawable.ic_public),
STAR(35, R.attr.ic_stars), STAR(35, R.drawable.ic_stars),
SUN(36, R.attr.ic_sunny), SUN(36, R.drawable.ic_wb_sunny),
RSS(37, R.attr.ic_rss); RSS(37, R.drawable.ic_rss_feed);
@DrawableRes @DrawableRes
fun getDrawableRes(context: Context): Int { fun getDrawableRes(): Int {
return ThemeHelper.resolveResourceIdFromAttr(context, drawableResourceAttr) return drawableResource
} }
} }

View file

@ -1,17 +1,16 @@
package org.schabi.newpipe.local.subscription; package org.schabi.newpipe.local.subscription;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import icepick.Icepick; import icepick.Icepick;
import icepick.State; import icepick.State;
@ -41,7 +40,7 @@ public class ImportConfirmationDialog extends DialogFragment {
@Override @Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
assureCorrectAppLanguage(getContext()); assureCorrectAppLanguage(getContext());
return new AlertDialog.Builder(getContext(), ThemeHelper.getDialogTheme(getContext())) return new AlertDialog.Builder(getContext())
.setMessage(R.string.import_network_expensive_warning) .setMessage(R.string.import_network_expensive_warning)
.setCancelable(true) .setCancelable(true)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.local.subscription package org.schabi.newpipe.local.subscription
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
@ -17,6 +16,7 @@ import android.view.MenuInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager 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.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ShareUtils import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -257,7 +256,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter) feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter)
feedGroupsSortMenuItem = HeaderWithMenuItem( feedGroupsSortMenuItem = HeaderWithMenuItem(
getString(R.string.feed_groups_header_title), getString(R.string.feed_groups_header_title),
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_sort), R.drawable.ic_sort,
menuItemOnClickListener = ::openReorderDialog menuItemOnClickListener = ::openReorderDialog
) )
add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel))) add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel)))

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.subscription.dialog package org.schabi.newpipe.local.subscription.dialog
import android.app.Dialog import android.app.Dialog
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
@ -12,6 +13,7 @@ import androidx.core.content.getSystemService
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
@ -123,6 +125,14 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
_feedGroupCreateBinding = DialogFeedGroupCreateBinding.bind(view) _feedGroupCreateBinding = DialogFeedGroupCreateBinding.bind(view)
_searchLayoutBinding = feedGroupCreateBinding.subscriptionsHeaderSearchContainer _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(resources.getColor(R.color.contrastColor))
searchLayoutBinding.toolbarSearchEditText.setTextColor(contrastColor)
searchLayoutBinding.toolbarSearchEditText.setHintTextColor(contrastColor.withAlpha(128))
ImageViewCompat.setImageTintList(searchLayoutBinding.toolbarSearchClearIcon, contrastColor)
}
viewModel = ViewModelProvider( viewModel = ViewModelProvider(
this, this,
FeedGroupDialogViewModel.Factory( FeedGroupDialogViewModel.Factory(
@ -306,7 +316,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
groupSortOrder = feedGroupEntity?.sortOrder ?: -1 groupSortOrder = feedGroupEntity?.sortOrder ?: -1
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!! val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
feedGroupCreateBinding.iconPreview.setImageResource(feedGroupIcon.getDrawableRes(requireContext())) feedGroupCreateBinding.iconPreview.setImageResource(feedGroupIcon.getDrawableRes())
if (feedGroupCreateBinding.groupNameInput.text.isNullOrBlank()) { if (feedGroupCreateBinding.groupNameInput.text.isNullOrBlank()) {
feedGroupCreateBinding.groupNameInput.setText(name) feedGroupCreateBinding.groupNameInput.setText(name)
@ -404,7 +414,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
if (groupId == NO_GROUP_SELECTED) { if (groupId == NO_GROUP_SELECTED) {
val icon = selectedIcon ?: FeedGroupIcon.ALL val icon = selectedIcon ?: FeedGroupIcon.ALL
feedGroupCreateBinding.iconPreview.setImageResource(icon.getDrawableRes(requireContext())) feedGroupCreateBinding.iconPreview.setImageResource(icon.getDrawableRes())
} }
} }

View file

@ -25,7 +25,7 @@ data class FeedGroupCardItem(
override fun bind(viewBinding: FeedGroupCardItemBinding, position: Int) { override fun bind(viewBinding: FeedGroupCardItemBinding, position: Int) {
viewBinding.title.text = name 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) override fun initializeViewBinding(view: View) = FeedGroupCardItemBinding.bind(view)

View file

@ -32,7 +32,7 @@ data class FeedGroupReorderItem(
override fun bind(viewBinding: FeedGroupReorderItemBinding, position: Int) { override fun bind(viewBinding: FeedGroupReorderItemBinding, position: Int) {
viewBinding.groupName.text = name 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>) { override fun bind(viewHolder: GroupieViewHolder<FeedGroupReorderItemBinding>, position: Int, payloads: MutableList<Any>) {

View file

@ -86,7 +86,7 @@ class FeedImportExportItem(
private fun setupImportFromItems(listHolder: ViewGroup) { private fun setupImportFromItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView( val previousBackupItem = addItemView(
listHolder.context.getString(R.string.previous_export), listHolder.context.getString(R.string.previous_export),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_backup), listHolder R.drawable.ic_backup, listHolder
) )
previousBackupItem.setOnClickListener { onImportPreviousSelected() } previousBackupItem.setOnClickListener { onImportPreviousSelected() }
@ -115,8 +115,7 @@ class FeedImportExportItem(
private fun setupExportToItems(listHolder: ViewGroup) { private fun setupExportToItems(listHolder: ViewGroup) {
val previousBackupItem = addItemView( val previousBackupItem = addItemView(
listHolder.context.getString(R.string.file), listHolder.context.getString(R.string.file),
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), R.drawable.ic_save, listHolder
listHolder
) )
previousBackupItem.setOnClickListener { onExportSelected() } previousBackupItem.setOnClickListener { onExportSelected() }
} }

View file

@ -13,7 +13,7 @@ class PickerIconItem(
val icon: FeedGroupIcon val icon: FeedGroupIcon
) : BindableItem<PickerIconItemBinding>() { ) : BindableItem<PickerIconItemBinding>() {
@DrawableRes @DrawableRes
val iconRes: Int = icon.getDrawableRes(context) val iconRes: Int = icon.getDrawableRes()
override fun getLayout(): Int = R.layout.picker_icon_item override fun getLayout(): Int = R.layout.picker_icon_item

View file

@ -50,11 +50,11 @@ public final class NotificationConstants {
R.drawable.exo_icon_fastforward, R.drawable.exo_icon_fastforward,
R.drawable.exo_icon_previous, R.drawable.exo_icon_previous,
R.drawable.exo_icon_next, R.drawable.exo_icon_next,
R.drawable.ic_pause_white_24dp, R.drawable.ic_pause,
R.drawable.ic_hourglass_top_white_24dp, R.drawable.ic_hourglass_top,
R.drawable.exo_icon_repeat_all, R.drawable.exo_icon_repeat_all,
R.drawable.exo_icon_shuffle_on, R.drawable.exo_icon_shuffle_on,
R.drawable.ic_close_white_24dp, R.drawable.ic_close,
}; };

View file

@ -273,14 +273,14 @@ public final class NotificationUtil {
|| player.getCurrentState() == Player.STATE_BLOCKED || player.getCurrentState() == Player.STATE_BLOCKED
|| player.getCurrentState() == Player.STATE_BUFFERING) { || player.getCurrentState() == Player.STATE_BUFFERING) {
// null intent -> show hourglass icon that does nothing when clicked // 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), player.getContext().getString(R.string.notification_action_buffering),
null); null);
} }
case NotificationConstants.PLAY_PAUSE: case NotificationConstants.PLAY_PAUSE:
if (player.getCurrentState() == Player.STATE_COMPLETED) { 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); R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
} else if (player.isPlaying() } else if (player.isPlaying()
|| player.getCurrentState() == Player.STATE_PREFLIGHT || player.getCurrentState() == Player.STATE_PREFLIGHT
@ -315,7 +315,7 @@ public final class NotificationUtil {
} }
case NotificationConstants.CLOSE: 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); R.string.close, ACTION_CLOSE);
case NotificationConstants.NOTHING: case NotificationConstants.NOTHING:

View file

@ -589,15 +589,15 @@ public final class PlayQueueActivity extends AppCompatActivity
switch (state) { switch (state) {
case Player.STATE_PAUSED: case Player.STATE_PAUSED:
queueControlBinding.controlPlayPause queueControlBinding.controlPlayPause
.setImageResource(R.drawable.ic_play_arrow_white_24dp); .setImageResource(R.drawable.ic_play_arrow);
break; break;
case Player.STATE_PLAYING: case Player.STATE_PLAYING:
queueControlBinding.controlPlayPause queueControlBinding.controlPlayPause
.setImageResource(R.drawable.ic_pause_white_24dp); .setImageResource(R.drawable.ic_pause);
break; break;
case Player.STATE_COMPLETED: case Player.STATE_COMPLETED:
queueControlBinding.controlPlayPause queueControlBinding.controlPlayPause
.setImageResource(R.drawable.ic_replay_white_24dp); .setImageResource(R.drawable.ic_replay);
break; break;
default: default:
break; break;
@ -670,8 +670,7 @@ public final class PlayQueueActivity extends AppCompatActivity
//2) Icon change accordingly to current App Theme //2) Icon change accordingly to current App Theme
// using rootView.getContext() because getApplicationContext() didn't work // using rootView.getContext() because getApplicationContext() didn't work
final Context context = queueControlBinding.getRoot().getContext(); final Context context = queueControlBinding.getRoot().getContext();
item.setIcon(ThemeHelper.resolveResourceIdFromAttr(context, item.setIcon(player.isMuted() ? R.drawable.ic_volume_off : R.drawable.ic_volume_up);
player.isMuted() ? R.attr.ic_volume_off : R.attr.ic_volume_up));
} }
} }
} }

View file

@ -26,6 +26,7 @@ import android.provider.Settings;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -51,6 +52,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.view.DisplayCutoutCompat; import androidx.core.view.DisplayCutoutCompat;
import androidx.core.view.ViewCompat; 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.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView; import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 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.exoplayer2.video.VideoListener;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
@ -446,9 +449,12 @@ public final class Player implements
binding.playbackSeekBar.getProgressDrawable() binding.playbackSeekBar.getProgressDrawable()
.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); .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); playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed);
captionPopupMenu = new PopupMenu(context, binding.captionTextView); captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView);
binding.progressBarLoadingPanel.getIndeterminateDrawable() binding.progressBarLoadingPanel.getIndeterminateDrawable()
.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
@ -488,10 +494,12 @@ public final class Player implements
// Setup subtitle view // Setup subtitle view
simpleExoPlayer.addTextOutput(binding.subtitleView); simpleExoPlayer.addTextOutput(binding.subtitleView);
// Setup audio session with onboard equalizer // enable media tunneling
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (DeviceUtils.shouldSupportMediaTunneling()) {
trackSelector.setParameters(trackSelector.buildUponParameters() trackSelector.setParameters(
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context))); trackSelector.buildUponParameters().setTunnelingEnabled(true));
} else if (DEBUG) {
Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling");
} }
} }
@ -624,10 +632,10 @@ public final class Player implements
&& newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl()) && newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl())
&& newQueue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) { && newQueue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
// Player can have state = IDLE when playback is stopped or failed // 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() if (simpleExoPlayer.getPlaybackState()
== com.google.android.exoplayer2.Player.STATE_IDLE) { == com.google.android.exoplayer2.Player.STATE_IDLE) {
simpleExoPlayer.retry(); simpleExoPlayer.prepare();
} }
simpleExoPlayer.seekTo(playQueue.getIndex(), newQueue.getItem().getRecoveryPosition()); simpleExoPlayer.seekTo(playQueue.getIndex(), newQueue.getItem().getRecoveryPosition());
simpleExoPlayer.setPlayWhenReady(playWhenReady); simpleExoPlayer.setPlayWhenReady(playWhenReady);
@ -638,10 +646,10 @@ public final class Player implements
&& !playQueue.isDisposed()) { && !playQueue.isDisposed()) {
// Do not re-init the same PlayQueue. Save time // Do not re-init the same PlayQueue. Save time
// Player can have state = IDLE when playback is stopped or failed // 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() if (simpleExoPlayer.getPlaybackState()
== com.google.android.exoplayer2.Player.STATE_IDLE) { == com.google.android.exoplayer2.Player.STATE_IDLE) {
simpleExoPlayer.retry(); simpleExoPlayer.prepare();
} }
simpleExoPlayer.setPlayWhenReady(playWhenReady); simpleExoPlayer.setPlayWhenReady(playWhenReady);
@ -706,11 +714,7 @@ public final class Player implements
// Android TV: without it focus will frame the whole player // Android TV: without it focus will frame the whole player
binding.playPauseButton.requestFocus(); binding.playPauseButton.requestFocus();
if (simpleExoPlayer.getPlayWhenReady()) { playPause();
play();
} else {
pause();
}
} }
NavigationHelper.sendPlayerStartedEvent(context); NavigationHelper.sendPlayerStartedEvent(context);
} }
@ -957,7 +961,7 @@ public final class Player implements
= LinearLayout.LayoutParams.MATCH_PARENT; = LinearLayout.LayoutParams.MATCH_PARENT;
binding.secondaryControls.setVisibility(View.INVISIBLE); binding.secondaryControls.setVisibility(View.INVISIBLE);
binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context,
R.drawable.ic_expand_more_white_24dp)); R.drawable.ic_expand_more));
binding.share.setVisibility(View.VISIBLE); binding.share.setVisibility(View.VISIBLE);
binding.openInBrowser.setVisibility(View.VISIBLE); binding.openInBrowser.setVisibility(View.VISIBLE);
binding.switchMute.setVisibility(View.VISIBLE); binding.switchMute.setVisibility(View.VISIBLE);
@ -1592,6 +1596,10 @@ public final class Player implements
segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress));
} }
if (isQueueVisible) {
updateQueueTime(currentProgress);
}
final boolean showThumbnail = prefs.getBoolean( final boolean showThumbnail = prefs.getBoolean(
context.getString(R.string.show_thumbnail_key), true); context.getString(R.string.show_thumbnail_key), true);
// setMetadata only updates the metadata when any of the metadata keys are null // setMetadata only updates the metadata when any of the metadata keys are null
@ -1653,7 +1661,7 @@ public final class Player implements
saveWasPlaying(); saveWasPlaying();
if (isPlaying()) { if (isPlaying()) {
simpleExoPlayer.setPlayWhenReady(false); simpleExoPlayer.pause();
} }
showControls(0); showControls(0);
@ -1669,7 +1677,7 @@ public final class Player implements
seekTo(seekBar.getProgress()); seekTo(seekBar.getProgress());
if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) { if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) {
simpleExoPlayer.setPlayWhenReady(true); simpleExoPlayer.play();
} }
binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
@ -1687,7 +1695,7 @@ public final class Player implements
} }
public void saveWasPlaying() { public void saveWasPlaying() {
this.wasPlaying = simpleExoPlayer.getPlayWhenReady(); this.wasPlaying = getPlayWhenReady();
} }
//endregion //endregion
@ -1912,7 +1920,7 @@ public final class Player implements
} }
@Override // exoplayer listener @Override // exoplayer listener
public void onLoadingChanged(final boolean isLoading) { public void onIsLoadingChanged(final boolean isLoading) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: " Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: "
+ "isLoading = [" + isLoading + "]"); + "isLoading = [" + isLoading + "]");
@ -1956,7 +1964,8 @@ public final class Player implements
if (currentState == STATE_BLOCKED) { if (currentState == STATE_BLOCKED) {
changeState(STATE_BUFFERING); changeState(STATE_BUFFERING);
} }
simpleExoPlayer.prepare(mediaSource); simpleExoPlayer.setMediaSource(mediaSource);
simpleExoPlayer.prepare();
} }
public void changeState(final int state) { public void changeState(final int state) {
@ -2020,7 +2029,7 @@ public final class Player implements
animate(binding.loadingPanel, true, 0); animate(binding.loadingPanel, true, 0);
animate(binding.surfaceForeground, true, 100); 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); animatePlayButtons(false, 100);
binding.getRoot().setKeepScreenOn(false); binding.getRoot().setKeepScreenOn(false);
@ -2049,7 +2058,7 @@ public final class Player implements
animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, 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); animatePlayButtons(true, 200);
if (!isQueueVisible) { if (!isQueueVisible) {
binding.playPauseButton.requestFocus(); binding.playPauseButton.requestFocus();
@ -2068,6 +2077,7 @@ public final class Player implements
Log.d(TAG, "onBuffering() called"); Log.d(TAG, "onBuffering() called");
} }
binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT); binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT);
binding.loadingPanel.setVisibility(View.VISIBLE);
binding.getRoot().setKeepScreenOn(true); binding.getRoot().setKeepScreenOn(true);
@ -2090,7 +2100,7 @@ public final class Player implements
animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, 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); animatePlayButtons(true, 200);
if (!isQueueVisible) { if (!isQueueVisible) {
binding.playPauseButton.requestFocus(); binding.playPauseButton.requestFocus();
@ -2129,7 +2139,7 @@ public final class Player implements
animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0, 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); animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
}); });
@ -2221,7 +2231,7 @@ public final class Player implements
Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: " Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: "
+ "repeatMode = [" + repeatMode + "]"); + "repeatMode = [" + repeatMode + "]");
} }
setRepeatModeButton(binding.repeatButton, repeatMode); setRepeatModeButton(((AppCompatImageButton) binding.repeatButton), repeatMode);
onShuffleOrRepeatModeChanged(); onShuffleOrRepeatModeChanged();
} }
@ -2249,7 +2259,7 @@ public final class Player implements
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); 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) { switch (repeatMode) {
case REPEAT_MODE_OFF: case REPEAT_MODE_OFF:
imageButton.setImageResource(R.drawable.exo_controls_repeat_off); imageButton.setImageResource(R.drawable.exo_controls_repeat_off);
@ -2290,7 +2300,7 @@ public final class Player implements
private void setMuteButton(final ImageButton button, final boolean isMuted) { private void setMuteButton(final ImageButton button, final boolean isMuted) {
button.setImageDrawable(AppCompatResources.getDrawable(context, 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 //endregion
@ -2355,6 +2365,12 @@ public final class Player implements
break; break;
} }
case DISCONTINUITY_REASON_SEEK: case DISCONTINUITY_REASON_SEEK:
if (DEBUG) {
Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
}
if (isPrepared) {
saveStreamProgressState();
}
case DISCONTINUITY_REASON_SEEK_ADJUSTMENT: case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
case DISCONTINUITY_REASON_INTERNAL: case DISCONTINUITY_REASON_INTERNAL:
if (playQueue.getIndex() != newWindowIndex) { if (playQueue.getIndex() != newWindowIndex) {
@ -2419,10 +2435,8 @@ public final class Player implements
setRecovery(); setRecovery();
reloadPlayQueueManager(); reloadPlayQueueManager();
break; break;
case ExoPlaybackException.TYPE_OUT_OF_MEMORY:
case ExoPlaybackException.TYPE_REMOTE: case ExoPlaybackException.TYPE_REMOTE:
case ExoPlaybackException.TYPE_RENDERER: case ExoPlaybackException.TYPE_RENDERER:
case ExoPlaybackException.TYPE_TIMEOUT:
default: default:
showUnrecoverableError(error); showUnrecoverableError(error);
onPlaybackShutdown(); onPlaybackShutdown();
@ -2627,16 +2641,6 @@ public final class Player implements
simpleExoPlayer.seekToDefaultPosition(); simpleExoPlayer.seekToDefaultPosition();
} }
} }
@Override // exoplayer override
public void onSeekProcessed() {
if (DEBUG) {
Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
}
if (isPrepared) {
saveStreamProgressState();
}
}
//endregion //endregion
@ -2664,7 +2668,7 @@ public final class Player implements
} }
} }
simpleExoPlayer.setPlayWhenReady(true); simpleExoPlayer.play();
saveStreamProgressState(); saveStreamProgressState();
} }
@ -2677,7 +2681,7 @@ public final class Player implements
} }
audioReactor.abandonAudioFocus(); audioReactor.abandonAudioFocus();
simpleExoPlayer.setPlayWhenReady(false); simpleExoPlayer.pause();
saveStreamProgressState(); saveStreamProgressState();
} }
@ -2686,7 +2690,7 @@ public final class Player implements
Log.d(TAG, "onPlayPause() called"); Log.d(TAG, "onPlayPause() called");
} }
if (isPlaying()) { if (getPlayWhenReady()) {
pause(); pause();
} else { } else {
play(); play();
@ -2734,7 +2738,7 @@ public final class Player implements
} }
seekBy(retrieveSeekDurationFromPreferences(this)); seekBy(retrieveSeekDurationFromPreferences(this));
triggerProgressUpdate(); triggerProgressUpdate();
showAndAnimateControl(R.drawable.ic_fast_forward_white_24dp, true); showAndAnimateControl(R.drawable.ic_fast_forward, true);
} }
public void fastRewind() { public void fastRewind() {
@ -2743,7 +2747,7 @@ public final class Player implements
} }
seekBy(-retrieveSeekDurationFromPreferences(this)); seekBy(-retrieveSeekDurationFromPreferences(this));
triggerProgressUpdate(); triggerProgressUpdate();
showAndAnimateControl(R.drawable.ic_fast_rewind_white_24dp, true); showAndAnimateControl(R.drawable.ic_fast_rewind, true);
} }
//endregion //endregion
@ -2965,6 +2969,7 @@ public final class Player implements
buildQueue(); buildQueue();
binding.itemsListHeaderTitle.setVisibility(View.GONE); binding.itemsListHeaderTitle.setVisibility(View.GONE);
binding.itemsListHeaderDuration.setVisibility(View.VISIBLE);
binding.shuffleButton.setVisibility(View.VISIBLE); binding.shuffleButton.setVisibility(View.VISIBLE);
binding.repeatButton.setVisibility(View.VISIBLE); binding.repeatButton.setVisibility(View.VISIBLE);
@ -2974,6 +2979,8 @@ public final class Player implements
AnimationType.SLIDE_AND_ALPHA); AnimationType.SLIDE_AND_ALPHA);
binding.itemsList.scrollToPosition(playQueue.getIndex()); binding.itemsList.scrollToPosition(playQueue.getIndex());
updateQueueTime((int) simpleExoPlayer.getCurrentPosition());
} }
private void buildQueue() { private void buildQueue() {
@ -3196,6 +3203,32 @@ public final class Player implements
buildPlaybackSpeedMenu(); buildPlaybackSpeedMenu();
binding.playbackSpeed.setVisibility(View.VISIBLE); 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 //endregion
@ -3691,8 +3724,8 @@ public final class Player implements
|| DeviceUtils.isTablet(context)) || DeviceUtils.isTablet(context))
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context, binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context,
isFullscreen ? R.drawable.ic_fullscreen_exit_white_24dp isFullscreen ? R.drawable.ic_fullscreen_exit
: R.drawable.ic_fullscreen_white_24dp)); : R.drawable.ic_fullscreen));
} }
private void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { private void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) {
@ -4012,6 +4045,10 @@ public final class Player implements
return !exoPlayerIsNull() && simpleExoPlayer.isPlaying(); return !exoPlayerIsNull() && simpleExoPlayer.isPlaying();
} }
public boolean getPlayWhenReady() {
return !exoPlayerIsNull() && simpleExoPlayer.getPlayWhenReady();
}
private boolean isLoading() { private boolean isLoading() {
return !exoPlayerIsNull() && simpleExoPlayer.isLoading(); return !exoPlayerIsNull() && simpleExoPlayer.isLoading();
} }

View file

@ -229,8 +229,10 @@ abstract class BasePlayerGestureListener(
// because the soft input is visible (the draggable area is currently resized). // because the soft input is visible (the draggable area is currently resized).
player.updateScreenSize() player.updateScreenSize()
player.checkPopupPositionBounds() player.checkPopupPositionBounds()
initialPopupX = player.popupLayoutParams!!.x player.popupLayoutParams?.let {
initialPopupY = player.popupLayoutParams!!.y initialPopupX = it.x
initialPopupY = it.y
}
return super.onDown(e) return super.onDown(e)
} }
@ -466,7 +468,7 @@ abstract class BasePlayerGestureListener(
// /////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////
private fun getDisplayPortion(e: MotionEvent): DisplayPortion { private fun getDisplayPortion(e: MotionEvent): DisplayPortion {
return if (player.playerType == MainPlayer.PlayerType.POPUP) { return if (player.playerType == MainPlayer.PlayerType.POPUP && player.popupLayoutParams != null) {
when { when {
e.x < player.popupLayoutParams!!.width / 3.0 -> DisplayPortion.LEFT e.x < player.popupLayoutParams!!.width / 3.0 -> DisplayPortion.LEFT
e.x > player.popupLayoutParams!!.width * 2.0 / 3.0 -> DisplayPortion.RIGHT e.x > player.popupLayoutParams!!.width * 2.0 / 3.0 -> DisplayPortion.RIGHT

View file

@ -26,7 +26,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout>
Rect globalRect = new Rect(); Rect globalRect = new Rect();
private boolean skippingInterception = false; private boolean skippingInterception = false;
private final List<Integer> skipInterceptionOfElements = Arrays.asList( 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.itemsListPanel, R.id.view_pager, R.id.tab_layout, R.id.bottomControls,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);

View file

@ -147,10 +147,10 @@ public class PlayerGestureListener
player.getVolumeImageView().setImageDrawable( player.getVolumeImageView().setImageDrawable(
AppCompatResources.getDrawable(service, currentProgressPercent <= 0 AppCompatResources.getDrawable(service, currentProgressPercent <= 0
? R.drawable.ic_volume_off_white_24dp ? R.drawable.ic_volume_off
: currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_24dp : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute
: currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_24dp : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down
: R.drawable.ic_volume_up_white_24dp) : R.drawable.ic_volume_up)
); );
if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) {
@ -189,10 +189,10 @@ public class PlayerGestureListener
player.getBrightnessImageView().setImageDrawable( player.getBrightnessImageView().setImageDrawable(
AppCompatResources.getDrawable(service, AppCompatResources.getDrawable(service,
currentProgressPercent < 0.25 currentProgressPercent < 0.25
? R.drawable.ic_brightness_low_white_24dp ? R.drawable.ic_brightness_low
: currentProgressPercent < 0.75 : currentProgressPercent < 0.75
? R.drawable.ic_brightness_medium_white_24dp ? R.drawable.ic_brightness_medium
: R.drawable.ic_brightness_high_white_24dp) : R.drawable.ic_brightness_high)
); );
if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) { if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) {

View file

@ -103,13 +103,13 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
animateAudio(DUCK_AUDIO_TO, 1.0f); animateAudio(DUCK_AUDIO_TO, 1.0f);
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) { if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
player.setPlayWhenReady(true); player.play();
} }
} }
private void onAudioFocusLoss() { private void onAudioFocusLoss() {
Log.d(TAG, "onAudioFocusLoss() called"); Log.d(TAG, "onAudioFocusLoss() called");
player.setPlayWhenReady(false); player.pause();
} }
private void onAudioFocusLossCanDuck() { private void onAudioFocusLossCanDuck() {
@ -148,7 +148,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) { public void onAudioSessionIdChanged(final EventTime eventTime, final int audioSessionId) {
if (!PlayerHelper.isUsingDSP()) { if (!PlayerHelper.isUsingDSP()) {
return; return;
} }

View file

@ -4,7 +4,7 @@ import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
public class LoadController implements LoadControl { public class LoadController implements LoadControl {
@ -33,7 +33,7 @@ public class LoadController implements LoadControl {
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder(); final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs, builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs,
initialPlaybackBufferMs, initialPlaybackBufferMs); initialPlaybackBufferMs, initialPlaybackBufferMs);
internalLoadControl = builder.createDefaultLoadControl(); internalLoadControl = builder.build();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -47,9 +47,9 @@ public class LoadController implements LoadControl {
} }
@Override @Override
public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroupArray, public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroups,
final TrackSelectionArray trackSelectionArray) { final ExoTrackSelection[] trackSelections) {
internalLoadControl.onTracksSelected(renderers, trackGroupArray, trackSelectionArray); internalLoadControl.onTracksSelected(renderers, trackGroups, trackSelections);
} }
@Override @Override
@ -92,11 +92,12 @@ public class LoadController implements LoadControl {
@Override @Override
public boolean shouldStartPlayback(final long bufferedDurationUs, final float playbackSpeed, public boolean shouldStartPlayback(final long bufferedDurationUs, final float playbackSpeed,
final boolean rebuffering) { final boolean rebuffering, final long targetLiveOffsetUs) {
final boolean isInitialPlaybackBufferFilled final boolean isInitialPlaybackBufferFilled
= bufferedDurationUs >= this.initialPlaybackBufferUs * playbackSpeed; = bufferedDurationUs >= this.initialPlaybackBufferUs * playbackSpeed;
final boolean isInternalStartingPlayback = internalLoadControl final boolean isInternalStartingPlayback = internalLoadControl
.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering); .shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering,
targetLiveOffsetUs);
return isInitialPlaybackBufferFilled || isInternalStartingPlayback; return isInitialPlaybackBufferFilled || isInternalStartingPlayback;
} }

View file

@ -36,8 +36,6 @@ public class MediaSessionManager {
@NonNull final Player player, @NonNull final Player player,
@NonNull final MediaSessionCallback callback) { @NonNull final MediaSessionCallback callback) {
mediaSession = new MediaSessionCompat(context, TAG); mediaSession = new MediaSessionCompat(context, TAG);
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setActive(true); mediaSession.setActive(true);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder() mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()

View file

@ -23,7 +23,7 @@ import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
@ -180,10 +180,10 @@ public final class PlayerHelper {
* if a candidate next video's url already exists in the existing items. * if a candidate next video's url already exists in the existing items.
* </p> * </p>
* <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. * 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 * Otherwise, a random stream with non-repeating url will be selected
* from the {@link StreamInfo#getRelatedStreams()}. * from the {@link StreamInfo#getRelatedItems()}. Non-stream items are ignored.
* </p> * </p>
* *
* @param info currently playing stream * @param info currently playing stream
@ -198,7 +198,7 @@ public final class PlayerHelper {
urls.add(item.getUrl()); urls.add(item.getUrl());
} }
final List<InfoItem> relatedItems = info.getRelatedStreams(); final List<InfoItem> relatedItems = info.getRelatedItems();
if (Utils.isNullOrEmpty(relatedItems)) { if (Utils.isNullOrEmpty(relatedItems)) {
return null; return null;
} }
@ -323,7 +323,7 @@ public final class PlayerHelper {
return 60000; return 60000;
} }
public static TrackSelection.Factory getQualitySelector() { public static ExoTrackSelection.Factory getQualitySelector() {
return new AdaptiveTrackSelection.Factory( return new AdaptiveTrackSelection.Factory(
1000, 1000,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,

View file

@ -13,7 +13,7 @@ import com.google.android.exoplayer2.RendererCapabilities.Capabilities;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
/** /**
@ -28,7 +28,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
private String preferredTextLanguage; private String preferredTextLanguage;
public CustomTrackSelector(final Context context, public CustomTrackSelector(final Context context,
final TrackSelection.Factory adaptiveTrackSelectionFactory) { final ExoTrackSelection.Factory adaptiveTrackSelectionFactory) {
super(context, adaptiveTrackSelectionFactory); super(context, adaptiveTrackSelectionFactory);
} }
@ -50,7 +50,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
@Override @Override
@Nullable @Nullable
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack( protected Pair<ExoTrackSelection.Definition, TextTrackScore> selectTextTrack(
final TrackGroupArray groups, final TrackGroupArray groups,
@NonNull final int[][] formatSupport, @NonNull final int[][] formatSupport,
@NonNull final Parameters params, @NonNull final Parameters params,
@ -86,7 +86,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
} }
} }
return selectedGroup == null ? null return selectedGroup == null ? null
: Pair.create(new TrackSelection.Definition(selectedGroup, selectedTrackIndex), : Pair.create(new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex),
Assertions.checkNotNull(selectedTrackScore)); Assertions.checkNotNull(selectedTrackScore));
} }
} }

View file

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -43,13 +44,13 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
switch (type) { switch (type) {
case C.TYPE_SS: case C.TYPE_SS:
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata) return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri); .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_DASH: case C.TYPE_DASH:
return dataSource.getLiveDashMediaSourceFactory().setTag(metadata) return dataSource.getLiveDashMediaSourceFactory().setTag(metadata)
.createMediaSource(uri); .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_HLS: case C.TYPE_HLS:
return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata) return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri); .createMediaSource(MediaItem.fromUri(uri));
default: default:
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
} }
@ -68,16 +69,16 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
switch (type) { switch (type) {
case C.TYPE_SS: case C.TYPE_SS:
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata) return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri); .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_DASH: case C.TYPE_DASH:
return dataSource.getDashMediaSourceFactory().setTag(metadata) return dataSource.getDashMediaSourceFactory().setTag(metadata)
.createMediaSource(uri); .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_HLS: case C.TYPE_HLS:
return dataSource.getHlsMediaSourceFactory().setTag(metadata) return dataSource.getHlsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri); .createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_OTHER: case C.TYPE_OTHER:
return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata) return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata)
.createMediaSource(uri); .createMediaSource(MediaItem.fromUri(uri));
default: default:
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
} }

View file

@ -6,7 +6,7 @@ import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.MergingMediaSource;
@ -22,7 +22,6 @@ import org.schabi.newpipe.util.ListHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
import static com.google.android.exoplayer2.C.TIME_UNSET; import static com.google.android.exoplayer2.C.TIME_UNSET;
public class VideoPlaybackResolver implements PlaybackResolver { public class VideoPlaybackResolver implements PlaybackResolver {
@ -101,12 +100,12 @@ public class VideoPlaybackResolver implements PlaybackResolver {
if (mimeType == null) { if (mimeType == null) {
continue; continue;
} }
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
SELECTION_FLAG_AUTOSELECT,
PlayerHelper.captionLanguageOf(context, subtitle));
final MediaSource textSource = dataSource.getSampleMediaSourceFactory() 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); mediaSources.add(textSource);
} }
} }

View file

@ -7,12 +7,12 @@ import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.preference.Preference; import androidx.preference.Preference;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ThemeHelper;
public class AppearanceSettingsFragment extends BasePreferenceFragment { public class AppearanceSettingsFragment extends BasePreferenceFragment {
private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE = private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE =
@ -21,8 +21,8 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
private String captionSettingsKey; private String captionSettingsKey;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.appearance_settings);
final String themeKey = getString(R.string.theme_key); final String themeKey = getString(R.string.theme_key);
// the key of the active theme when settings were opened (or recreated after theme change) // 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 @Override
public boolean onPreferenceTreeClick(final Preference preference) { public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) { 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().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
defaultPreferences.edit().putString(themeKey, newValue.toString()).apply(); defaultPreferences.edit().putString(themeKey, newValue.toString()).apply();
ThemeHelper.setDayNightMode(getContext(), newValue.toString());
if (!newValue.equals(beginningThemeKey) && getActivity() != null) { if (!newValue.equals(beginningThemeKey) && getActivity() != null) {
// if it's not the current theme // if it's not the current theme
ActivityCompat.recreate(getActivity()); ActivityCompat.recreate(getActivity());

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -10,7 +9,7 @@ import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -50,8 +49,35 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private String initialLanguage; private String initialLanguage;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
super.onCreate(savedInstanceState); 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;
});
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled); youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
@ -103,37 +129,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
return super.onPreferenceTreeClick(preference); 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 @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -174,7 +169,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
exportDatabase(path + "/NewPipeData-" + sdf.format(new Date()) + ".zip"); exportDatabase(path + "/NewPipeData-" + sdf.format(new Date()) + ".zip");
} else { } else {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
builder.setMessage(R.string.override_current_data) builder.setMessage(R.string.override_current_data)
.setPositiveButton(getString(R.string.finish), .setPositiveButton(getString(R.string.finish),
(d, id) -> importDatabase(path)) (d, id) -> importDatabase(path))
@ -220,7 +215,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
//If settings file exist, ask if it should be imported. //If settings file exist, ask if it should be imported.
if (manager.extractSettings(filePath)) { 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.setTitle(R.string.import_settings);
alert.setNegativeButton(android.R.string.no, (dialog, which) -> { alert.setNegativeButton(android.R.string.no, (dialog, which) -> {

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -11,8 +10,8 @@ import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference; import androidx.preference.Preference;
import com.nononsenseapps.filepicker.Utils; import com.nononsenseapps.filepicker.Utils;
@ -46,8 +45,8 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
private Context ctx; private Context ctx;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.download_settings);
downloadPathVideoPreference = getString(R.string.download_path_video_key); downloadPathVideoPreference = getString(R.string.download_path_video_key);
downloadPathAudioPreference = getString(R.string.download_path_audio_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 @Override
public void onAttach(final Context context) { public void onAttach(final Context context) {
super.onAttach(context); super.onAttach(context);

View file

@ -5,7 +5,6 @@ import android.os.Bundle;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference; import androidx.preference.Preference;
@ -29,8 +28,9 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
private CompositeDisposable disposables; private CompositeDisposable disposables;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.history_settings);
cacheWipeKey = getString(R.string.metadata_cache_wipe_key); cacheWipeKey = getString(R.string.metadata_cache_wipe_key);
viewsHistoryClearKey = getString(R.string.clear_views_history_key); viewsHistoryClearKey = getString(R.string.clear_views_history_key);
playbackStatesClearKey = getString(R.string.clear_playback_states_key); playbackStatesClearKey = getString(R.string.clear_playback_states_key);
@ -39,11 +39,6 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
disposables = new CompositeDisposable(); disposables = new CompositeDisposable();
} }
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.history_settings);
}
@Override @Override
public boolean onPreferenceTreeClick(final Preference preference) { public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(cacheWipeKey)) { if (preference.getKey().equals(cacheWipeKey)) {

View file

@ -145,10 +145,8 @@ public class PeertubeInstanceListFragment extends Fragment {
final MenuItem restoreItem = menu final MenuItem restoreItem = menu
.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults); .add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults);
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(),
final int restoreIcon = ThemeHelper R.drawable.ic_settings_backup_restore));
.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults);
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon));
} }
@Override @Override
@ -188,7 +186,7 @@ public class PeertubeInstanceListFragment extends Fragment {
} }
private void restoreDefaults() { private void restoreDefaults() {
new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext())) new AlertDialog.Builder(requireContext())
.setTitle(R.string.restore_defaults) .setTitle(R.string.restore_defaults)
.setMessage(R.string.restore_defaults_confirmation) .setMessage(R.string.restore_defaults_confirmation)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)

View file

@ -56,11 +56,11 @@ public class SettingsActivity extends AppCompatActivity
SettingsLayoutBinding.inflate(getLayoutInflater()); SettingsLayoutBinding.inflate(getLayoutInflater());
setContentView(settingsLayoutBinding.getRoot()); setContentView(settingsLayoutBinding.getRoot());
setSupportActionBar(settingsLayoutBinding.toolbarLayout.toolbar); setSupportActionBar(settingsLayoutBinding.settingsToolbarLayout.toolbar);
if (savedInstanceBundle == null) { if (savedInstanceBundle == null) {
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_holder, new MainSettingsFragment()) .replace(R.id.settings_fragment_holder, new MainSettingsFragment())
.commit(); .commit();
} }
@ -102,7 +102,7 @@ public class SettingsActivity extends AppCompatActivity
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
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) .addToBackStack(null)
.commit(); .commit();
return true; return true;

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe.settings;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference; import androidx.preference.Preference;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -16,15 +15,10 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
}; };
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.update_settings);
final String updateToggleKey = getString(R.string.update_app_key); final String updateToggleKey = getString(R.string.update_app_key);
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange); findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
} }
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.update_settings);
}
} }

View file

@ -8,7 +8,6 @@ import android.provider.Settings;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
@ -23,8 +22,8 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
private SharedPreferences.OnSharedPreferenceChangeListener listener; private SharedPreferences.OnSharedPreferenceChangeListener listener;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.video_audio_settings);
updateSeekOptions(); 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 @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();

View file

@ -17,6 +17,7 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.DrawableCompat;
@ -41,9 +42,8 @@ public class NotificationActionsPreference extends Preference {
} }
private NotificationSlot[] notificationSlots; @Nullable private NotificationSlot[] notificationSlots = null;
@Nullable private List<Integer> compactSlots = null;
private List<Integer> compactSlots;
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Lifecycle // Lifecycle
@ -85,10 +85,12 @@ public class NotificationActionsPreference extends Preference {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
private void saveChanges() { private void saveChanges() {
if (compactSlots != null && notificationSlots != null) {
final SharedPreferences.Editor editor = getSharedPreferences().edit(); final SharedPreferences.Editor editor = getSharedPreferences().edit();
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
editor.putInt(getContext().getString(NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]), editor.putInt(getContext().getString(
NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]),
(i < compactSlots.size() ? compactSlots.get(i) : -1)); (i < compactSlots.size() ? compactSlots.get(i) : -1));
} }
@ -99,6 +101,7 @@ public class NotificationActionsPreference extends Preference {
editor.apply(); editor.apply();
} }
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View file

@ -1,6 +1,5 @@
package org.schabi.newpipe.settings.tabs; package org.schabi.newpipe.settings.tabs;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -11,10 +10,10 @@ import android.widget.TextView;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
public final class AddTabDialog { public final class AddTabDialog {
private final AlertDialog dialog; private final AlertDialog dialog;
@ -60,7 +59,7 @@ public final class AddTabDialog {
private DialogListAdapter(final Context context, final ChooseTabListItem[] items) { private DialogListAdapter(final Context context, final ChooseTabListItem[] items) {
this.inflater = LayoutInflater.from(context); this.inflater = LayoutInflater.from(context);
this.items = items; this.items = items;
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot); this.fallbackIcon = R.drawable.ic_whatshot;
} }
@Override @Override

View file

@ -112,10 +112,8 @@ public class ChooseTabsFragment extends Fragment {
final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE,
R.string.restore_defaults); R.string.restore_defaults);
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(),
final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.drawable.ic_settings_backup_restore));
R.attr.ic_restore_defaults);
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon));
} }
@Override @Override
@ -142,7 +140,7 @@ public class ChooseTabsFragment extends Fragment {
} }
private void restoreDefaults() { private void restoreDefaults() {
new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext())) new AlertDialog.Builder(requireContext())
.setTitle(R.string.restore_defaults) .setTitle(R.string.restore_defaults)
.setMessage(R.string.restore_defaults_confirmation) .setMessage(R.string.restore_defaults_confirmation)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
@ -241,7 +239,7 @@ public class ChooseTabsFragment extends Fragment {
case KIOSK: case KIOSK:
returnList.add(new ChooseTabListItem(tab.getTabId(), returnList.add(new ChooseTabListItem(tab.getTabId(),
getString(R.string.kiosk_page_summary), getString(R.string.kiosk_page_summary),
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot))); R.drawable.ic_whatshot));
break; break;
case CHANNEL: case CHANNEL:
returnList.add(new ChooseTabListItem(tab.getTabId(), returnList.add(new ChooseTabListItem(tab.getTabId(),
@ -252,8 +250,7 @@ public class ChooseTabsFragment extends Fragment {
if (!tabList.contains(tab)) { if (!tabList.contains(tab)) {
returnList.add(new ChooseTabListItem(tab.getTabId(), returnList.add(new ChooseTabListItem(tab.getTabId(),
getString(R.string.default_kiosk_page_summary), getString(R.string.default_kiosk_page_summary),
ThemeHelper.resolveResourceIdFromAttr(context, R.drawable.ic_whatshot));
R.attr.ic_kiosk_hot)));
} }
break; break;
case PLAYLIST: case PLAYLIST:

View file

@ -30,7 +30,6 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment;
import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.Objects; import java.util.Objects;
@ -188,7 +187,7 @@ public abstract class Tab {
@DrawableRes @DrawableRes
@Override @Override
public int getTabIconRes(final Context context) { public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_blank_page); return R.drawable.ic_crop_portrait;
} }
@Override @Override
@ -213,7 +212,7 @@ public abstract class Tab {
@DrawableRes @DrawableRes
@Override @Override
public int getTabIconRes(final Context context) { public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel); return R.drawable.ic_tv;
} }
@Override @Override
@ -239,7 +238,7 @@ public abstract class Tab {
@DrawableRes @DrawableRes
@Override @Override
public int getTabIconRes(final Context context) { public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_rss); return R.drawable.ic_rss_feed;
} }
@Override @Override
@ -264,7 +263,7 @@ public abstract class Tab {
@DrawableRes @DrawableRes
@Override @Override
public int getTabIconRes(final Context context) { public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark); return R.drawable.ic_bookmark;
} }
@Override @Override
@ -289,7 +288,7 @@ public abstract class Tab {
@DrawableRes @DrawableRes
@Override @Override
public int getTabIconRes(final Context context) { public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_history); return R.drawable.ic_history;
} }
@Override @Override
@ -409,7 +408,7 @@ public abstract class Tab {
@DrawableRes @DrawableRes
@Override @Override
public int getTabIconRes(final Context context) { public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel); return R.drawable.ic_tv;
} }
@Override @Override
@ -541,7 +540,7 @@ public abstract class Tab {
@DrawableRes @DrawableRes
@Override @Override
public int getTabIconRes(final Context context) { public int getTabIconRes(final Context context) {
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark); return R.drawable.ic_bookmark;
} }
@Override @Override

View file

@ -20,6 +20,16 @@ public final class DeviceUtils {
private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
private static Boolean isTV = null; 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() { private DeviceUtils() {
} }
@ -88,4 +98,15 @@ public final class DeviceUtils {
sp, sp,
context.getResources().getDisplayMetrics()); 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;
}
} }

View file

@ -63,20 +63,20 @@ public final class KioskTranslator {
case "Top 50": case "Top 50":
case "New & hot": case "New & hot":
case "conferences": case "conferences":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_hot); return R.drawable.ic_whatshot;
case "Local": case "Local":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local); return R.drawable.ic_home;
case "Recently added": case "Recently added":
case "recent": case "recent":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent); return R.drawable.ic_add_circle_outline;
case "Most liked": case "Most liked":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_thumb_up); return R.drawable.ic_thumb_up;
case "live": case "live":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_live_tv); return R.drawable.ic_live_tv;
case "Featured": case "Featured":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_stars); return R.drawable.ic_stars;
case "Radio": case "Radio":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_radio); return R.drawable.ic_radio;
default: default:
return 0; return 0;
} }

View file

@ -62,7 +62,8 @@ public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
private NavigationHelper() { } private NavigationHelper() {
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Players // Players
@ -111,18 +112,22 @@ public final class NavigationHelper {
public static void playOnMainPlayer(final AppCompatActivity activity, public static void playOnMainPlayer(final AppCompatActivity activity,
@NonNull final PlayQueue playQueue) { @NonNull final PlayQueue playQueue) {
final PlayQueueItem item = playQueue.getItem(); final PlayQueueItem item = playQueue.getItem();
assert item != null; if (item != null) {
openVideoDetailFragment(activity, activity.getSupportFragmentManager(), openVideoDetailFragment(activity, activity.getSupportFragmentManager(),
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, false); item.getServiceId(), item.getUrl(), item.getTitle(), playQueue,
false);
}
} }
public static void playOnMainPlayer(final Context context, public static void playOnMainPlayer(final Context context,
@NonNull final PlayQueue playQueue, @NonNull final PlayQueue playQueue,
final boolean switchingPlayers) { final boolean switchingPlayers) {
final PlayQueueItem item = playQueue.getItem(); final PlayQueueItem item = playQueue.getItem();
assert item != null; if (item != null) {
openVideoDetail(context, openVideoDetail(context,
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, switchingPlayers); item.getServiceId(), item.getUrl(), item.getTitle(), playQueue,
switchingPlayers);
}
} }
public static void playOnPopupPlayer(final Context context, public static void playOnPopupPlayer(final Context context,

View file

@ -9,19 +9,19 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class RelatedStreamInfo extends ListInfo<InfoItem> { public class RelatedItemInfo extends ListInfo<InfoItem> {
public RelatedStreamInfo(final int serviceId, final ListLinkHandler listUrlIdHandler, public RelatedItemInfo(final int serviceId, final ListLinkHandler listUrlIdHandler,
final String name) { final String name) {
super(serviceId, listUrlIdHandler, name); super(serviceId, listUrlIdHandler, name);
} }
public static RelatedStreamInfo getInfo(final StreamInfo info) { public static RelatedItemInfo getInfo(final StreamInfo info) {
final ListLinkHandler handler = new ListLinkHandler( final ListLinkHandler handler = new ListLinkHandler(
info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null); info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
final RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo( final RelatedItemInfo relatedItemInfo = new RelatedItemInfo(
info.getServiceId(), handler, info.getName()); info.getServiceId(), handler, info.getName());
final List<InfoItem> streams = new ArrayList<>(info.getRelatedStreams()); final List<InfoItem> relatedItems = new ArrayList<>(info.getRelatedItems());
relatedStreamInfo.setRelatedItems(streams); relatedItemInfo.setRelatedItems(relatedItems);
return relatedStreamInfo; return relatedItemInfo;
} }
} }

View file

@ -72,10 +72,10 @@ public final class StateSaver {
} }
/** /**
* @see #tryToRestore(SavedState, WriteRead)
* @param outState * @param outState
* @param writeRead * @param writeRead
* @return the saved state * @return the saved state
* @see #tryToRestore(SavedState, WriteRead)
*/ */
public static SavedState tryToRestore(final Bundle outState, final WriteRead writeRead) { public static SavedState tryToRestore(final Bundle outState, final WriteRead writeRead) {
if (outState == null || writeRead == null) { if (outState == null || writeRead == null) {
@ -93,6 +93,7 @@ public final class StateSaver {
/** /**
* Try to restore the state from memory and disk, * Try to restore the state from memory and disk,
* using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead. * using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead.
*
* @param savedState * @param savedState
* @param writeRead * @param writeRead
* @return the saved state * @return the saved state
@ -143,19 +144,18 @@ public final class StateSaver {
} }
/** /**
* @see #tryToSave(boolean, String, String, WriteRead)
* @param isChangingConfig * @param isChangingConfig
* @param savedState * @param savedState
* @param outState * @param outState
* @param writeRead * @param writeRead
* @return the saved state or {@code null} * @return the saved state or {@code null}
* @see #tryToSave(boolean, String, String, WriteRead)
*/ */
@Nullable @Nullable
public static SavedState tryToSave(final boolean isChangingConfig, public static SavedState tryToSave(final boolean isChangingConfig,
@Nullable final SavedState savedState, final Bundle outState, @Nullable final SavedState savedState, final Bundle outState,
final WriteRead writeRead) { final WriteRead writeRead) {
@NonNull @NonNull final String currentSavedPrefix;
final String currentSavedPrefix;
if (savedState == null || TextUtils.isEmpty(savedState.getPrefixFileSaved())) { if (savedState == null || TextUtils.isEmpty(savedState.getPrefixFileSaved())) {
// Generate unique prefix // Generate unique prefix
currentSavedPrefix = System.nanoTime() - writeRead.hashCode() + ""; currentSavedPrefix = System.nanoTime() - writeRead.hashCode() + "";
@ -299,11 +299,14 @@ public final class StateSaver {
cacheDir = new File(cacheDir, CACHE_DIR_NAME); cacheDir = new File(cacheDir, CACHE_DIR_NAME);
if (cacheDir.exists()) { if (cacheDir.exists()) {
for (final File file : cacheDir.listFiles()) { final File[] list = cacheDir.listFiles();
if (list != null) {
for (final File file : list) {
file.delete(); file.delete();
} }
} }
} }
}
/** /**
* Used for describe how to save/read the objects. * Used for describe how to save/read the objects.

View file

@ -24,6 +24,12 @@ public enum StreamDialogEntry {
// enum values with DEFAULT actions // // 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.getActivity().getSupportFragmentManager(),
item.getServiceId(), item.getUploaderUrl(), item.getUploaderName())
),
/** /**
* Enqueues the stream automatically to the current PlayerType.<br> * Enqueues the stream automatically to the current PlayerType.<br>
* <br> * <br>

View file

@ -23,7 +23,6 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.TypedValue; import android.util.TypedValue;
import androidx.annotation.AttrRes; import androidx.annotation.AttrRes;
@ -31,6 +30,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.StyleRes; import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -47,6 +47,9 @@ public final class ThemeHelper {
* Apply the selected theme (on NewPipe settings) in the context * Apply the selected theme (on NewPipe settings) in the context
* with the default style (see {@link #setTheme(Context, int)}). * 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 * @param context context that the theme will be applied
*/ */
public static void setTheme(final Context context) { 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, * Apply the selected theme (on NewPipe settings) in the context,
* themed according with the styles defined for the service . * 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 context context that the theme will be applied
* @param serviceId the theme will be styled to the service with this id, * @param serviceId the theme will be styled to the service with this id,
* pass -1 to get the default style * pass -1 to get the default style
@ -120,6 +126,7 @@ public final class ThemeHelper {
final String selectedThemeKey = getSelectedThemeKey(context); final String selectedThemeKey = getSelectedThemeKey(context);
int baseTheme = R.style.DarkTheme; // default to dark theme int baseTheme = R.style.DarkTheme; // default to dark theme
if (selectedThemeKey.equals(lightThemeKey)) { if (selectedThemeKey.equals(lightThemeKey)) {
baseTheme = R.style.LightTheme; 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. * Get a color from an attr styled according to the context's theme.
* *
@ -288,4 +281,21 @@ public final class ThemeHelper {
return false; 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);
}
}
} }

View file

@ -1,7 +1,6 @@
package us.shandian.giga.ui.fragment; package us.shandian.giga.ui.fragment;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -19,6 +18,7 @@ import android.view.ViewGroup;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
@ -224,10 +224,9 @@ public class MissionsFragment extends Fragment {
mList.setAdapter(mAdapter); mList.setAdapter(mAdapter);
if (mSwitch != null) { if (mSwitch != null) {
mSwitch.setIcon(ThemeHelper.resolveResourceIdFromAttr( mSwitch.setIcon(mLinear
requireContext(), mLinear ? R.drawable.ic_apps
? R.attr.ic_grid : R.drawable.ic_list);
: R.attr.ic_list));
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list); mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
mPrefs.edit().putBoolean("linear", mLinear).apply(); mPrefs.edit().putBoolean("linear", mLinear).apply();
} }

View file

@ -182,12 +182,12 @@ public class Utility {
public static int getIconForFileType(FileType type) { public static int getIconForFileType(FileType type) {
switch (type) { switch (type) {
case MUSIC: case MUSIC:
return R.drawable.ic_headset_white_24dp; return R.drawable.ic_headset;
default: default:
case VIDEO: case VIDEO:
return R.drawable.ic_movie_white_24dp; return R.drawable.ic_movie;
case SUBTITLE: case SUBTITLE:
return R.drawable.ic_subtitles_white_24dp; return R.drawable.ic_subtitles;
} }
} }

View file

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 358 B

View file

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

View file

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 511 B

View file

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 244 B

View file

Before

Width:  |  Height:  |  Size: 223 B

After

Width:  |  Height:  |  Size: 223 B

View file

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 345 B

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