mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-25 12:32:31 +01:00
commit
464d0e50b0
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 💬 IRC
|
||||||
|
url: https://webchat.freenode.net/#newpipe
|
||||||
|
about: Chat with us via IRC for quick Q/A
|
||||||
|
- name: 💬 Matrix
|
||||||
|
url: https://matrix.to/#/#freenode_#newpipe:matrix.org
|
||||||
|
about: Chat with us via Matrix for quick Q/A
|
||||||
|
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -1,6 +1,13 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
@ -33,3 +40,34 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: app
|
name: app
|
||||||
path: app/build/outputs/apk/debug/*.apk
|
path: app/build/outputs/apk/debug/*.apk
|
||||||
|
# sonar:
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v2
|
||||||
|
# with:
|
||||||
|
# fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||||
|
|
||||||
|
# - name: Set up JDK 11
|
||||||
|
# uses: actions/setup-java@v1.4.3
|
||||||
|
# with:
|
||||||
|
# java-version: 11 # Sonar requires JDK 11
|
||||||
|
|
||||||
|
# - name: Cache SonarCloud packages
|
||||||
|
# uses: actions/cache@v2
|
||||||
|
# with:
|
||||||
|
# path: ~/.sonar/cache
|
||||||
|
# key: ${{ runner.os }}-sonar
|
||||||
|
# restore-keys: ${{ runner.os }}-sonar
|
||||||
|
|
||||||
|
# - name: Cache Gradle packages
|
||||||
|
# uses: actions/cache@v2
|
||||||
|
# with:
|
||||||
|
# path: ~/.gradle/caches
|
||||||
|
# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
# restore-keys: ${{ runner.os }}-gradle
|
||||||
|
|
||||||
|
# - name: Build and analyze
|
||||||
|
# env:
|
||||||
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||||
|
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
# run: ./gradlew build sonarqube --info
|
||||||
|
140
README.es.md
Normal file
140
README.es.md
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||||
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
|
<h4 align="center">Una interfaz de streaming lijera y libre para Android.</h4>
|
||||||
|
|
||||||
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/badge/Lanzamiento-v0.20.11-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://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://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p align="center"><a href="#capturas-de-pantalla">Capturas de pantalla</a> • <a href="#descripción">Descripción</a> • <a href="#características">Características</a> • <a href="#installación-y-actualizaciones">Installación y actualizaciones</a> • <a href="#contribución">Contribución</a> • <a href="#donar">Donar</a> • <a href="#licencias">Licencias</a></p>
|
||||||
|
<p align="center"><a href="https://newpipe.net">Sitio web</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">Preguntas Frecuentes</a> • <a href="https://newpipe.net/press/">Prensa</a></p>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
*Lea esto en otros idiomas: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md) .*
|
||||||
|
|
||||||
|
<b>AVISO: ESTA ES UNA VERSIÓN BETA, POR LO TANTO, PUEDE ENCONTRAR BUGS (ERRORES). SI ENCUENTRA UNO, ABRA UN ISSUE A TRAVÉS DE NUESTRO REPOSITORIO GITHUB.</b>
|
||||||
|
|
||||||
|
<b>COLOCAR NEWPIPE O CUALQUIER FORK (BIFURCACIÓN) REALIZADO DE ELLO EN GOOGLE PLAY STORE VIOLA SUS TÉRMINOS Y CONDICIONES.</b>
|
||||||
|
|
||||||
|
## Capturas de pantalla
|
||||||
|
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
|
||||||
|
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
|
||||||
|
|
||||||
|
## Descripción
|
||||||
|
NewPipe no usa ninguna librería de framework de Google, ni la API de YouTube. Los sitios web solamente se analizan para extraer la información requerida, asi que esta app se puede usar sin los servicios de Google instalados. Además, no se necesita una cuenta de YouTube para usar NewPipe, lo cual es un software libre de copyleft.
|
||||||
|
|
||||||
|
### Características
|
||||||
|
* Buscar videos
|
||||||
|
* Mostrar información general sobre videos
|
||||||
|
* Mirar videos de YouTube
|
||||||
|
* Escuchar audio de YouTube
|
||||||
|
* Modo popup (reproductor flotante)
|
||||||
|
* Elegir reproductor para mirar el video
|
||||||
|
* Descargar videos
|
||||||
|
* Descargar solamente audio
|
||||||
|
* Abrir video en Kodi
|
||||||
|
* Mostrar videos próximos/relacionados
|
||||||
|
* Buscar a través de YouTube en un idioma específico
|
||||||
|
* Mirar/Bloquear materiales restringidas por edad.
|
||||||
|
* Mostrar información general sobre canales
|
||||||
|
* Buscar canales
|
||||||
|
* Mirar videos de un canal
|
||||||
|
* Apoyo Orbot/Tor (todavía no directamente)
|
||||||
|
* Apoyo 1080p/2K/4K
|
||||||
|
* Ver historias
|
||||||
|
* Subscribirse a canales
|
||||||
|
* Buscar historias
|
||||||
|
* Buscar/mirar listas de reproducción
|
||||||
|
* Mirar listas de reproducción en fila
|
||||||
|
* Poner videos en fila
|
||||||
|
* Listas locales de reproducción
|
||||||
|
* Subtítulos
|
||||||
|
* Apoyo de medios en directo
|
||||||
|
* Mostrar comentarios
|
||||||
|
|
||||||
|
### Servicios apoyados
|
||||||
|
NewPipe apoya varios servicios. Nuestras [documentaciones](https://teamnewpipe.github.io/documentation/) proveen más información en como se puede agregar un servicio nuevo a la app y el extractor. Por favor contáctenos si pretende agregar uno nuevo. Actualmente los servicios apoyados son:
|
||||||
|
|
||||||
|
* YouTube
|
||||||
|
* SoundCloud \[beta\]
|
||||||
|
* media.ccc.de \[beta\]
|
||||||
|
* PeerTube instances \[beta\]
|
||||||
|
* Bandcamp \[beta\]
|
||||||
|
|
||||||
|
<!-- Brecha escondida para mantener compatibles los enlaces viejos. -->
|
||||||
|
<span id="actualizaciones"></span>
|
||||||
|
|
||||||
|
## Installación y actualizaciones
|
||||||
|
Se puede instalar NewPipe usando uno de los métodos siguientes:
|
||||||
|
1. Agregar nuestro repositorio personalizado a F-Droid e instalarlo desde allí. Las instrucciones están aquí: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
|
||||||
|
2. Descargar el archivo APK del enlace [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) e instalarlo.
|
||||||
|
3. Actualizar a través de F-Droid. Este es el método más lento para obtener la actualización, como F-Droid debe reconocer cambios, construir el APK aparte, firmarlo con una clave, y finalmente empujar la actualización a los usuarios.
|
||||||
|
4. Construir un APK de depuración por si mismo. Este es el modo más rápido para realizar nuevas características en su dispositivo, pero es mucho más complicado, asi que recomendamos uno de los otros métodos.
|
||||||
|
|
||||||
|
Recomendamos el método 1 para la mayoría de usuarios. Los APKs instalados usando método 1 o 2 son compatibles el uno con el otro, pero no con las instalaciones usando método 3. Esta es debida a la misma clave digital (la nuestra), siendo utilizado en los métodos 1 y 2, pero una clave digital diferente (la de F-Droid) siendo utilizado en el método 3. Construir un APK de depuración usando método 4 excluye una clave enteramente. Firmando con claves digitales ayuda a asegurar de que un
|
||||||
|
usuario no esté engañado para instalar una actualización maliciosa a una app.
|
||||||
|
|
||||||
|
Mientras tanto, si quiere cambiar los fuentes por alguna razón (por ejemplo, la funcionalidad del nucleo de NewPipe se rompe y F-Droid aun no tiene la actualización), recomendamos el siguiente procedimiento:
|
||||||
|
1. Repaldear sus datos a través de Ajustes > Contenido > Exporta base de datos para guardar su historia, subscripciones, y listas de reproducción
|
||||||
|
2. Desinstalar NewPipe
|
||||||
|
3. Descargar el APK del nuevo fuente e instalarlo.
|
||||||
|
4. Importar los datos del paso 1 a través de Ajustes > Contenido > Importa base de datos.
|
||||||
|
|
||||||
|
## Contribución
|
||||||
|
Si tiene ideas, traducciónes, cambios de diseño, limpieza de código, o cambios grandes de código, su ayuda es siempre bienvenida.
|
||||||
|
Cuanto más realizamos, mejor se pone la aplicación!
|
||||||
|
|
||||||
|
Si quiere involucrarse, fíjese en nuestras [notas de contribución](.github/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
<a href="https://hosted.weblate.org/engage/newpipe/es/">
|
||||||
|
<img src="https://hosted.weblate.org/widgets/newpipe/es/287x66-grey.png" alt="Estado de la traducción" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Donar
|
||||||
|
Si le gusta el NewPipe estaremos felices con una donación. O puede enviar bitcoin o donar a través de Bountysource o Liberapay. Para obtener más información sobre como donar a NewPipe, por favor visita nuestro [sitio web](https://newpipe.net/donate).
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin"></td>
|
||||||
|
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR code" width="100px"></td>
|
||||||
|
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Liberapay_logo_v2_white-on-yellow.svg" alt="Liberapay" width="80px" ></a></td>
|
||||||
|
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visit NewPipe at liberapay.com" width="100px"></a></td>
|
||||||
|
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px"></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px"></a></td>
|
||||||
|
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visit NewPipe at bountysource.com" width="100px"></a></td>
|
||||||
|
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Check out how many bounties you can earn."></a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## Política de privacidad
|
||||||
|
El proyecto NewPipe tiene como objetivo proveer una experience privada y anónima para usar servicios de medios web.
|
||||||
|
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
|
||||||
|
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://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.
|
@ -2,8 +2,7 @@
|
|||||||
<h2 align="center"><b>NewPipe</b></h2>
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
<h4 align="center">自由で軽量な Android 向けストリーミングフロントエンド</h4>
|
<h4 align="center">自由で軽量な Android 向けストリーミングフロントエンド</h4>
|
||||||
|
|
||||||
<!-- F-Droid is extremely out of date, so we hide this for now. -->
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-ja.svg"></a></p>
|
||||||
<!-- <p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-ja.svg"></a></p> -->
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub リリース"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg"></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub リリース"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg"></a>
|
||||||
@ -18,7 +17,7 @@
|
|||||||
<p align="center"><a href="https://newpipe.net">ウェブサイト</a> • <a href="https://newpipe.net/blog/">ブログ</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">ニュース</a></p>
|
<p align="center"><a href="https://newpipe.net">ウェブサイト</a> • <a href="https://newpipe.net/blog/">ブログ</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">ニュース</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
*他の言語で読む: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md), [日本語](README.ja.md), [Română](README.ro.md) 。*
|
*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt.br.md), [日本語](README.ja.md), [Română](README.ro.md) 。*
|
||||||
|
|
||||||
<b>注意: これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。</b>
|
<b>注意: これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。</b>
|
||||||
|
|
||||||
@ -84,6 +83,7 @@ NewPipe は複数のサービスに対応しています。[ドキュメント](
|
|||||||
* SoundCloud \[ベータ\]
|
* SoundCloud \[ベータ\]
|
||||||
* media.ccc.de \[ベータ\]
|
* media.ccc.de \[ベータ\]
|
||||||
* PeerTube インスタンス \[ベータ\]
|
* PeerTube インスタンス \[ベータ\]
|
||||||
|
* Bandcamp \[ベータ\]
|
||||||
|
|
||||||
<!-- Hidden span to keep old links compatible. -->
|
<!-- Hidden span to keep old links compatible. -->
|
||||||
<span id="updates"></span>
|
<span id="updates"></span>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||||
<h2 align="center"><b>NewPipe</b></h2>
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
<h4 align="center">A libre lightweight streaming frontend for Android.</h4>
|
<h4 align="center">A libre lightweight streaming frontend for Android.</h4>
|
||||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
|
||||||
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-ko.svg"></a></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||||
@ -16,7 +17,7 @@
|
|||||||
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
||||||
|
|
||||||
<b>경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.</b>
|
<b>경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.</b>
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ NewPipe는 여러가지 서비스를 지원합니다. 우리의 [문서](https:/
|
|||||||
* SoundCloud \[beta\]
|
* SoundCloud \[beta\]
|
||||||
* media.ccc.de \[beta\]
|
* media.ccc.de \[beta\]
|
||||||
* PeerTube instances \[beta\]
|
* PeerTube instances \[beta\]
|
||||||
|
* Bandcamp \[beta\]
|
||||||
|
|
||||||
## Updates
|
## Updates
|
||||||
NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로 인해), 결국 릴리즈가 발생할 것입니다. 이것들의 형식은 x.xx.x 입니다.
|
NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로 인해), 결국 릴리즈가 발생할 것입니다. 이것들의 형식은 x.xx.x 입니다.
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
<h2 align="center"><b>NewPipe</b></h2>
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
<h4 align="center">A libre lightweight streaming frontend for Android.</h4>
|
<h4 align="center">A libre lightweight streaming frontend for Android.</h4>
|
||||||
|
|
||||||
<!-- F-Droid is extremely out of date, so we hide this for now. -->
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
||||||
<!-- <p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p> -->
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||||
@ -18,7 +17,7 @@
|
|||||||
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md),[Română](README.ro.md) .*
|
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md) .*
|
||||||
|
|
||||||
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
|
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
|
||||||
|
|
||||||
@ -81,6 +80,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
|
|||||||
* SoundCloud \[beta\]
|
* SoundCloud \[beta\]
|
||||||
* media.ccc.de \[beta\]
|
* media.ccc.de \[beta\]
|
||||||
* PeerTube instances \[beta\]
|
* PeerTube instances \[beta\]
|
||||||
|
* Bandcamp \[beta\]
|
||||||
|
|
||||||
<!-- Hidden span to keep old links compatible. -->
|
<!-- Hidden span to keep old links compatible. -->
|
||||||
<span id="updates"></span>
|
<span id="updates"></span>
|
||||||
@ -89,7 +89,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
|
|||||||
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.schabi.org/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. (**IMPORTANT**: as of the time of writing, an issue is preventing releases later than 0.20.1 from being published. Thus, till this issue is solved, if you want to use F-Droid, we recommend method 1.)
|
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.
|
||||||
|
|
||||||
We recommend method 1 for most users. APKs installed using method 1 or 2 are compatible with each other, but not with those installed using method 3. This is due to the same signing key (ours) being used for 1 and 2, but a different signing key (F-Droid's) being used for 3. Building a debug APK using method 4 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app.
|
We recommend method 1 for most users. APKs installed using method 1 or 2 are compatible with each other, but not with those installed using method 3. This is due to the same signing key (ours) being used for 1 and 2, but a different signing key (F-Droid's) being used for 3. Building a debug APK using method 4 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app.
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||||
|
|
||||||
<h2 align="center"><b>NewPipe</b></h2>
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
<h4 align="center">Uma interface de streaming leve e gratuita para Android.</h4>
|
<h4 align="center">Uma interface de streaming leve e gratuita para Android.</h4>
|
||||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
|
||||||
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-pt-br.svg"></a></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||||
@ -16,7 +18,7 @@
|
|||||||
<p align="center"><a href="https://newpipe.net">Site</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
<p align="center"><a href="https://newpipe.net">Site</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Press</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
*Read this in other languages: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
||||||
|
|
||||||
<b>AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.</b>
|
<b>AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.</b>
|
||||||
|
|
||||||
@ -79,6 +81,7 @@ O NewPipe suporta vários serviços. Nosso [documentação](https://teamnewpipe.
|
|||||||
* SoundCloud \[beta\]
|
* SoundCloud \[beta\]
|
||||||
* media.ccc.de \[beta\]
|
* media.ccc.de \[beta\]
|
||||||
* PeerTube instances \[beta\]
|
* PeerTube instances \[beta\]
|
||||||
|
* Bandcamp \[beta\]
|
||||||
|
|
||||||
## Atualizações
|
## Atualizações
|
||||||
Quando uma alteração no código NewPipe (devido à adição de recursos ou fixação de bugs), eventualmente ocorrerá uma versão. Estes estão no formato x.xx.x . A fim de obter esta nova versão, você pode:
|
Quando uma alteração no código NewPipe (devido à adição de recursos ou fixação de bugs), eventualmente ocorrerá uma versão. Estes estão no formato x.xx.x . A fim de obter esta nova versão, você pode:
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
<h2 align="center"><b>NewPipe</b></h2>
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
<h4 align="center">Un front-end de streaming „uşor” liber, pentru Android.</h4>
|
<h4 align="center">Un front-end de streaming „uşor” liber, pentru Android.</h4>
|
||||||
|
|
||||||
<!-- F-Droid is extremely out of date, so we hide this for now. -->
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-ro.svg"></a></p>
|
||||||
<!-- <p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p> -->
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||||
@ -18,7 +17,7 @@
|
|||||||
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Presă</a></p>
|
<p align="center"><a href="https://newpipe.net">Website</a> • <a href="https://newpipe.net/blog/">Blog</a> • <a href="https://newpipe.net/FAQ/">FAQ</a> • <a href="https://newpipe.net/press/">Presă</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
*Citiţi în alte limbi: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md)*
|
*Citiţi în alte limbi: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md)*
|
||||||
|
|
||||||
<b>Atenţionare: ACEASTA ESTE O VERSIUNE BETA, AŞA CĂ S-AR PUTE SĂ ÎNTÂLNIŢI ERORI. DACĂ SE ÎNTÂMPLĂ ACEST LUCRU, DESCHIDEŢI UN ISSUE PRIN REPSITORY-UL NOSTRU GITHUB.</b>
|
<b>Atenţionare: ACEASTA ESTE O VERSIUNE BETA, AŞA CĂ S-AR PUTE SĂ ÎNTÂLNIŢI ERORI. DACĂ SE ÎNTÂMPLĂ ACEST LUCRU, DESCHIDEŢI UN ISSUE PRIN REPSITORY-UL NOSTRU GITHUB.</b>
|
||||||
|
|
||||||
@ -81,6 +80,7 @@ NewPipe suportă servicii multiple. [Documentele](https://teamnewpipe.github.io/
|
|||||||
* SoundCloud \[beta\]
|
* SoundCloud \[beta\]
|
||||||
* media.ccc.de \[beta\]
|
* media.ccc.de \[beta\]
|
||||||
* Instanţe PeerTube \[beta\]
|
* Instanţe PeerTube \[beta\]
|
||||||
|
* Bandcamp \[beta\]
|
||||||
|
|
||||||
<!-- Hidden span to keep old links compatible. -->
|
<!-- Hidden span to keep old links compatible. -->
|
||||||
<span id="updates"></span>
|
<span id="updates"></span>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
|
||||||
<h2 align="center"><b>NewPipe</b></h2>
|
<h2 align="center"><b>NewPipe</b></h2>
|
||||||
<h4 align="center">App bilaash ah oo fudud looguna talagalay in Android-ka wax loogu daawado.</h4>
|
<h4 align="center">App bilaash ah oo fudud looguna talagalay in Android-ka wax loogu daawado.</h4>
|
||||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
|
|
||||||
|
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-so.svg"></a></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="Siidaynta GitHub "><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="Siidaynta GitHub "><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
|
||||||
@ -16,7 +17,7 @@
|
|||||||
<p align="center"><a href="https://newpipe.net">Website-ka</a> • <a href="https://newpipe.net/blog/">Maqaalada</a> • <a href="https://newpipe.net/FAQ/">Su'aalaha Aalaa La-iswaydiiyo</a> • <a href="https://newpipe.net/press/">Warbaahinta</a></p>
|
<p align="center"><a href="https://newpipe.net">Website-ka</a> • <a href="https://newpipe.net/blog/">Maqaalada</a> • <a href="https://newpipe.net/FAQ/">Su'aalaha Aalaa La-iswaydiiyo</a> • <a href="https://newpipe.net/press/">Warbaahinta</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
*Ku akhri luuqad kale: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
*Ku akhri luuqad kale: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md).*
|
||||||
|
|
||||||
<b>DIGNIIN: MIDKAN, NOOCA APP-KA EE HADDA WALI TIJAABO AYUU KU JIRAA, SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO, KA FUR ARIN SHARAXAYA QAYBTANADA ARRIMAHA EE GITHUB-KA.</b>
|
<b>DIGNIIN: MIDKAN, NOOCA APP-KA EE HADDA WALI TIJAABO AYUU KU JIRAA, SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO, KA FUR ARIN SHARAXAYA QAYBTANADA ARRIMAHA EE GITHUB-KA.</b>
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ NewPipe wuxuu taageeraa adeegyo badan. [warqadan](https://teamnewpipe.github.io/
|
|||||||
* SoundCloud \[tijaabo\]
|
* SoundCloud \[tijaabo\]
|
||||||
* media.ccc.de \[tijaabo\]
|
* media.ccc.de \[tijaabo\]
|
||||||
* PeerTube instances \[tijaabo\]
|
* PeerTube instances \[tijaabo\]
|
||||||
|
* Bandcamp \[tijaabo\]
|
||||||
|
|
||||||
## Kushubida iyo cusboonaysiinta
|
## Kushubida iyo cusboonaysiinta
|
||||||
Marka koodhka NewPipe isbadal ku dhaco (wax cusub oo lagusoo kordhiyay ama cilad bixin), ugu dambayn waxaa lasii daayaa mid cusub (Siidayn). Siidaynta qaabkeedu waa x.xx.x . Si aad midka cusub u hesho, waxaad samayn kartaa:
|
Marka koodhka NewPipe isbadal ku dhaco (wax cusub oo lagusoo kordhiyay ama cilad bixin), ugu dambayn waxaa lasii daayaa mid cusub (Siidayn). Siidaynta qaabkeedu waa x.xx.x . Si aad midka cusub u hesho, waxaad samayn kartaa:
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.sonarqube" version "3.1.1"
|
||||||
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
@ -13,8 +17,8 @@ android {
|
|||||||
resValue "string", "app_name", "NewPipe"
|
resValue "string", "app_name", "NewPipe"
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 965
|
versionCode 966
|
||||||
versionName "0.20.11"
|
versionName "0.21.0"
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
@ -96,7 +100,7 @@ ext {
|
|||||||
checkstyleVersion = '8.38'
|
checkstyleVersion = '8.38'
|
||||||
stethoVersion = '1.5.1'
|
stethoVersion = '1.5.1'
|
||||||
leakCanaryVersion = '2.5'
|
leakCanaryVersion = '2.5'
|
||||||
exoPlayerVersion = '2.11.8'
|
exoPlayerVersion = '2.12.3'
|
||||||
androidxLifecycleVersion = '2.2.0'
|
androidxLifecycleVersion = '2.2.0'
|
||||||
androidxRoomVersion = '2.3.0-alpha03'
|
androidxRoomVersion = '2.3.0-alpha03'
|
||||||
groupieVersion = '2.8.1'
|
groupieVersion = '2.8.1'
|
||||||
@ -111,7 +115,7 @@ configurations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkstyle {
|
checkstyle {
|
||||||
configFile rootProject.file('checkstyle.xml')
|
configDir rootProject.file(".")
|
||||||
ignoreFailures false
|
ignoreFailures false
|
||||||
showViolations true
|
showViolations true
|
||||||
toolVersion = checkstyleVersion
|
toolVersion = checkstyleVersion
|
||||||
@ -158,6 +162,14 @@ afterEvaluate {
|
|||||||
preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint
|
preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sonarqube {
|
||||||
|
properties {
|
||||||
|
property "sonar.projectKey", "TeamNewPipe_NewPipe"
|
||||||
|
property "sonar.organization", "teamnewpipe"
|
||||||
|
property "sonar.host.url", "https://sonarcloud.io"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||||
|
|
||||||
@ -180,7 +192,7 @@ dependencies {
|
|||||||
|
|
||||||
// NewPipe dependencies
|
// NewPipe dependencies
|
||||||
// You can use a local version by uncommenting a few lines in settings.gradle
|
// You can use a local version by uncommenting a few lines in settings.gradle
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.20.11'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.0'
|
||||||
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||||
|
|
||||||
implementation "org.jsoup:jsoup:1.13.1"
|
implementation "org.jsoup:jsoup:1.13.1"
|
||||||
@ -233,7 +245,7 @@ dependencies {
|
|||||||
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
|
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
|
||||||
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
|
implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0"
|
||||||
|
|
||||||
implementation "org.ocpsoft.prettytime:prettytime:4.0.6.Final"
|
implementation "org.ocpsoft.prettytime:prettytime:5.0.0.Final"
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.1'
|
testImplementation 'junit:junit:4.13.1'
|
||||||
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
|
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented tests for {@link ErrorInfo}.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class ErrorInfoTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void errorInfoTestParcelable() {
|
||||||
|
final ErrorInfo info = new ErrorInfo(new ParsingException("Hello"),
|
||||||
|
UserAction.USER_REPORT, "request", ServiceList.YouTube.getServiceId());
|
||||||
|
// Obtain a Parcel object and write the parcelable object to it:
|
||||||
|
final Parcel parcel = Parcel.obtain();
|
||||||
|
info.writeToParcel(parcel, 0);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
final ErrorInfo infoFromParcel = (ErrorInfo) ErrorInfo.CREATOR.createFromParcel(parcel);
|
||||||
|
|
||||||
|
assertTrue(Arrays.toString(infoFromParcel.getStackTraces())
|
||||||
|
.contains(ErrorInfoTest.class.getSimpleName()));
|
||||||
|
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
|
||||||
|
assertEquals(ServiceList.YouTube.getServiceInfo().getName(),
|
||||||
|
infoFromParcel.getServiceName());
|
||||||
|
assertEquals("request", infoFromParcel.getRequest());
|
||||||
|
assertEquals(R.string.parsing_error, infoFromParcel.getMessageStringId());
|
||||||
|
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
package org.schabi.newpipe.report;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented tests for {@link ErrorInfo}.
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@LargeTest
|
|
||||||
public class ErrorInfoTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void errorInfoTestParcelable() {
|
|
||||||
final ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
|
||||||
R.string.general_error);
|
|
||||||
// Obtain a Parcel object and write the parcelable object to it:
|
|
||||||
final Parcel parcel = Parcel.obtain();
|
|
||||||
info.writeToParcel(parcel, 0);
|
|
||||||
parcel.setDataPosition(0);
|
|
||||||
final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
|
||||||
|
|
||||||
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
|
|
||||||
assertEquals("youtube", infoFromParcel.getServiceName());
|
|
||||||
assertEquals("request", infoFromParcel.getRequest());
|
|
||||||
assertEquals(R.string.general_error, infoFromParcel.getMessage());
|
|
||||||
|
|
||||||
parcel.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
@ -85,7 +85,7 @@
|
|||||||
android:name=".ExitActivity"
|
android:name=".ExitActivity"
|
||||||
android:label="@string/general_error"
|
android:label="@string/general_error"
|
||||||
android:theme="@android:style/Theme.NoDisplay" />
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity android:name=".report.ErrorActivity" />
|
<activity android:name=".error.ErrorActivity" />
|
||||||
|
|
||||||
<!-- giga get related -->
|
<!-- giga get related -->
|
||||||
<activity
|
<activity
|
||||||
@ -106,7 +106,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ReCaptchaActivity"
|
android:name=".error.ReCaptchaActivity"
|
||||||
android:label="@string/recaptcha" />
|
android:label="@string/recaptcha" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
@ -317,6 +317,22 @@
|
|||||||
<data android:pathPrefix="/accounts/" />
|
<data android:pathPrefix="/accounts/" />
|
||||||
<data android:pathPrefix="/video-channels/" />
|
<data android:pathPrefix="/video-channels/" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Bandcamp filter -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||||
|
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
|
||||||
|
<data android:scheme="http"/>
|
||||||
|
<data android:scheme="https"/>
|
||||||
|
<data android:host="bandcamp.com"/>
|
||||||
|
<data android:host="*.bandcamp.com"/>
|
||||||
|
<data android:pathPrefix="/"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
<service
|
||||||
android:name=".RouterActivity$FetcherService"
|
android:name=".RouterActivity$FetcherService"
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package org.schabi.newpipe;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Created by Christian Schabesberger on 24.12.15.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
|
||||||
* ActivityCommunicator.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton:
|
|
||||||
* Used to send data between certain Activity/Services within the same process.
|
|
||||||
* This can be considered as an ugly hack inside the Android universe.
|
|
||||||
**/
|
|
||||||
public class ActivityCommunicator {
|
|
||||||
|
|
||||||
private static ActivityCommunicator activityCommunicator;
|
|
||||||
private volatile Class returnActivity;
|
|
||||||
|
|
||||||
public static ActivityCommunicator getCommunicator() {
|
|
||||||
if (activityCommunicator == null) {
|
|
||||||
activityCommunicator = new ActivityCommunicator();
|
|
||||||
}
|
|
||||||
return activityCommunicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class getReturnActivity() {
|
|
||||||
return returnActivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReturnActivity(final Class returnActivity) {
|
|
||||||
this.returnActivity = returnActivity;
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,12 +20,13 @@ import org.acra.ACRA;
|
|||||||
import org.acra.config.ACRAConfigurationException;
|
import org.acra.config.ACRAConfigurationException;
|
||||||
import org.acra.config.CoreConfiguration;
|
import org.acra.config.CoreConfiguration;
|
||||||
import org.acra.config.CoreConfigurationBuilder;
|
import org.acra.config.CoreConfigurationBuilder;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
@ -224,14 +225,10 @@ public class App extends MultiDexApplication {
|
|||||||
.setBuildConfigClass(BuildConfig.class)
|
.setBuildConfigClass(BuildConfig.class)
|
||||||
.build();
|
.build();
|
||||||
ACRA.init(this, acraConfig);
|
ACRA.init(this, acraConfig);
|
||||||
} catch (final ACRAConfigurationException ace) {
|
} catch (final ACRAConfigurationException exception) {
|
||||||
ace.printStackTrace();
|
exception.printStackTrace();
|
||||||
ErrorActivity.reportError(this,
|
ErrorActivity.reportError(this, new ErrorInfo(exception,
|
||||||
ace,
|
UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report"));
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
|
||||||
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,19 +10,22 @@ import android.content.pm.Signature;
|
|||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.rxjava3.core.Maybe;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
@ -31,9 +34,11 @@ import java.security.cert.CertificateEncodingException;
|
|||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import io.reactivex.rxjava3.core.Maybe;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
public final class CheckForNewAppVersion {
|
public final class CheckForNewAppVersion {
|
||||||
private CheckForNewAppVersion() { }
|
private CheckForNewAppVersion() { }
|
||||||
@ -58,9 +63,8 @@ public final class CheckForNewAppVersion {
|
|||||||
packageInfo = application.getPackageManager().getPackageInfo(
|
packageInfo = application.getPackageManager().getPackageInfo(
|
||||||
application.getPackageName(), PackageManager.GET_SIGNATURES);
|
application.getPackageName(), PackageManager.GET_SIGNATURES);
|
||||||
} catch (final PackageManager.NameNotFoundException e) {
|
} catch (final PackageManager.NameNotFoundException e) {
|
||||||
ErrorActivity.reportError(application, e, null, null,
|
ErrorActivity.reportError(application, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
|
||||||
"Could not find package info", R.string.app_ui_crash));
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +76,8 @@ public final class CheckForNewAppVersion {
|
|||||||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||||
c = (X509Certificate) cf.generateCertificate(input);
|
c = (X509Certificate) cf.generateCertificate(input);
|
||||||
} catch (final CertificateException e) {
|
} catch (final CertificateException e) {
|
||||||
ErrorActivity.reportError(application, e, null, null,
|
ErrorActivity.reportError(application, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error"));
|
||||||
"Certificate error", R.string.app_ui_crash));
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +86,8 @@ public final class CheckForNewAppVersion {
|
|||||||
final byte[] publicKey = md.digest(c.getEncoded());
|
final byte[] publicKey = md.digest(c.getEncoded());
|
||||||
return byte2HexFormatted(publicKey);
|
return byte2HexFormatted(publicKey);
|
||||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||||
ErrorActivity.reportError(application, e, null, null,
|
ErrorActivity.reportError(application, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key"));
|
||||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.extractor.downloader.Request;
|
import org.schabi.newpipe.extractor.downloader.Request;
|
||||||
import org.schabi.newpipe.extractor.downloader.Response;
|
import org.schabi.newpipe.extractor.downloader.Response;
|
||||||
@ -43,7 +44,7 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
|
|||||||
|
|
||||||
public final class DownloaderImpl extends Downloader {
|
public final class DownloaderImpl extends Downloader {
|
||||||
public static final String USER_AGENT
|
public static final String USER_AGENT
|
||||||
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
= "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0";
|
||||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY
|
||||||
= "youtube_restricted_mode_key";
|
= "youtube_restricted_mode_key";
|
||||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
||||||
|
@ -60,6 +60,7 @@ import org.schabi.newpipe.databinding.DrawerHeaderBinding;
|
|||||||
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
|
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
|
||||||
import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
|
import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
|
||||||
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
|
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
@ -72,7 +73,6 @@ import org.schabi.newpipe.player.Player;
|
|||||||
import org.schabi.newpipe.player.event.OnKeyDownListener;
|
import org.schabi.newpipe.player.event.OnKeyDownListener;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
@ -153,7 +153,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
try {
|
try {
|
||||||
setupDrawer();
|
setupDrawer();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Setting up drawer", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DeviceUtils.isTv(this)) {
|
if (DeviceUtils.isTv(this)) {
|
||||||
@ -238,7 +238,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
try {
|
try {
|
||||||
tabSelected(item);
|
tabSelected(item);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Selecting main page tab", e);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.menu_options_about_group:
|
case R.id.menu_options_about_group:
|
||||||
@ -340,7 +340,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
try {
|
try {
|
||||||
showTabs();
|
showTabs();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -487,7 +487,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
|
drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
|
||||||
getString(R.string.drawer_header_description) + selectedServiceName);
|
getString(R.string.drawer_header_description) + selectedServiceName);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Setting up service toggle", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
final SharedPreferences sharedPreferences
|
final SharedPreferences sharedPreferences
|
||||||
@ -679,19 +679,16 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
||||||
}
|
}
|
||||||
final int id = item.getItemId();
|
|
||||||
|
|
||||||
switch (id) {
|
if (item.getItemId() == android.R.id.home) {
|
||||||
case android.R.id.home:
|
onHomeButtonPressed();
|
||||||
onHomeButtonPressed();
|
return true;
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -799,7 +796,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Handling intent", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,15 +33,29 @@ import androidx.preference.PreferenceManager;
|
|||||||
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||||
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||||
import org.schabi.newpipe.download.DownloadDialog;
|
import org.schabi.newpipe.download.DownloadDialog;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.Info;
|
import org.schabi.newpipe.extractor.Info;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.StreamingService.LinkType;
|
import org.schabi.newpipe.extractor.StreamingService.LinkType;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.player.MainPlayer;
|
import org.schabi.newpipe.player.MainPlayer;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
@ -49,7 +63,6 @@ import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
|||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
@ -84,13 +97,6 @@ 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.
|
||||||
*/
|
*/
|
||||||
public class RouterActivity extends AppCompatActivity {
|
public class RouterActivity extends AppCompatActivity {
|
||||||
public static final String INTERNAL_ROUTE_KEY = "internalRoute";
|
|
||||||
/**
|
|
||||||
* Removes invisible separators (\p{Z}) and punctuation characters including
|
|
||||||
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
|
|
||||||
* more details.
|
|
||||||
*/
|
|
||||||
private static final String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
|
|
||||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
@State
|
@State
|
||||||
protected int currentServiceId = -1;
|
protected int currentServiceId = -1;
|
||||||
@ -100,7 +106,6 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
protected int selectedRadioPosition = -1;
|
protected int selectedRadioPosition = -1;
|
||||||
protected int selectedPreviously = -1;
|
protected int selectedPreviously = -1;
|
||||||
protected String currentUrl;
|
protected String currentUrl;
|
||||||
protected boolean internalRoute = false;
|
|
||||||
private StreamingService currentService;
|
private StreamingService currentService;
|
||||||
private boolean selectionIsDownload = false;
|
private boolean selectionIsDownload = false;
|
||||||
|
|
||||||
@ -123,7 +128,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(final Bundle outState) {
|
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
Icepick.saveInstanceState(this, outState);
|
Icepick.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
@ -145,37 +150,79 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
private void handleUrl(final String url) {
|
private void handleUrl(final String url) {
|
||||||
disposables.add(Observable
|
disposables.add(Observable
|
||||||
.fromCallable(() -> {
|
.fromCallable(() -> {
|
||||||
if (currentServiceId == -1) {
|
try {
|
||||||
currentService = NewPipe.getServiceByUrl(url);
|
if (currentServiceId == -1) {
|
||||||
currentServiceId = currentService.getServiceId();
|
currentService = NewPipe.getServiceByUrl(url);
|
||||||
currentLinkType = currentService.getLinkTypeByUrl(url);
|
currentServiceId = currentService.getServiceId();
|
||||||
currentUrl = url;
|
currentLinkType = currentService.getLinkTypeByUrl(url);
|
||||||
} else {
|
currentUrl = url;
|
||||||
currentService = NewPipe.getService(currentServiceId);
|
} else {
|
||||||
}
|
currentService = NewPipe.getService(currentServiceId);
|
||||||
|
}
|
||||||
|
|
||||||
return currentLinkType != LinkType.NONE;
|
// return whether the url was found to be supported or not
|
||||||
|
return currentLinkType != LinkType.NONE;
|
||||||
|
} catch (final ExtractionException e) {
|
||||||
|
// this can be reached only when the url is completely unsupported
|
||||||
|
return false;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(result -> {
|
.subscribe(isUrlSupported -> {
|
||||||
if (result) {
|
if (isUrlSupported) {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} else {
|
} else {
|
||||||
showUnsupportedUrlDialog(url);
|
showUnsupportedUrlDialog(url);
|
||||||
}
|
}
|
||||||
}, throwable -> handleError(throwable, url)));
|
}, throwable -> handleError(this, new ErrorInfo(throwable,
|
||||||
|
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleError(final Throwable throwable, final String url) {
|
/**
|
||||||
throwable.printStackTrace();
|
* @param context the context. It will be {@code finish()}ed at the end of the handling if it is
|
||||||
|
* an instance of {@link RouterActivity}.
|
||||||
|
* @param errorInfo the error information
|
||||||
|
*/
|
||||||
|
private static void handleError(final Context context, final ErrorInfo errorInfo) {
|
||||||
|
if (errorInfo.getThrowable() != null) {
|
||||||
|
errorInfo.getThrowable().printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
if (throwable instanceof ExtractionException) {
|
if (errorInfo.getThrowable() instanceof ReCaptchaException) {
|
||||||
showUnsupportedUrlDialog(url);
|
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
||||||
|
// Starting ReCaptcha Challenge Activity
|
||||||
|
final Intent intent = new Intent(context, ReCaptchaActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
} else if (errorInfo.getThrowable() != null
|
||||||
|
&& ExceptionUtils.isNetworkRelated(errorInfo.getThrowable())) {
|
||||||
|
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof AgeRestrictedContentException) {
|
||||||
|
Toast.makeText(context, R.string.restricted_video_no_stream,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof GeographicRestrictionException) {
|
||||||
|
Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof PaidContentException) {
|
||||||
|
Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof PrivateContentException) {
|
||||||
|
Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof SoundCloudGoPlusContentException) {
|
||||||
|
Toast.makeText(context, R.string.soundcloud_go_plus_content,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof YoutubeMusicPremiumContentException) {
|
||||||
|
Toast.makeText(context, R.string.youtube_music_premium_content,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof ContentNotAvailableException) {
|
||||||
|
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
|
||||||
|
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
|
||||||
} else {
|
} else {
|
||||||
ExtractorHelper.handleGeneralException(this, -1, url, throwable,
|
ErrorActivity.reportError(context, errorInfo);
|
||||||
UserAction.SOMETHING_ELSE, null);
|
}
|
||||||
finish();
|
|
||||||
|
if (context instanceof RouterActivity) {
|
||||||
|
((RouterActivity) context).finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,7 +547,8 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
.subscribe(intent -> {
|
.subscribe(intent -> {
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
}, throwable -> handleError(throwable, currentUrl))
|
}, throwable -> handleError(this, new ErrorInfo(throwable,
|
||||||
|
UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl)))
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -580,6 +628,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
this.playerChoice = playerChoice;
|
this.playerChoice = playerChoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
|
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
|
||||||
@ -646,9 +695,9 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
if (fetcher != null) {
|
if (fetcher != null) {
|
||||||
fetcher.dispose();
|
fetcher.dispose();
|
||||||
}
|
}
|
||||||
}, throwable -> ExtractorHelper.handleGeneralException(this,
|
}, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction,
|
||||||
choice.serviceId, choice.url, throwable, finalUserAction,
|
choice.url + " opened with " + choice.playerChoice,
|
||||||
", opened with " + choice.playerChoice));
|
choice.serviceId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
|
|||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import kotlin.jvm.Throws
|
|
||||||
|
|
||||||
data class PlaylistStreamEntry(
|
data class PlaylistStreamEntry(
|
||||||
@Embedded
|
@Embedded
|
||||||
|
@ -37,6 +37,9 @@ import org.schabi.newpipe.MainActivity;
|
|||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.RouterActivity;
|
import org.schabi.newpipe.RouterActivity;
|
||||||
import org.schabi.newpipe.databinding.DownloadDialogBinding;
|
import org.schabi.newpipe.databinding.DownloadDialogBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
@ -45,9 +48,6 @@ import org.schabi.newpipe.extractor.stream.Stream;
|
|||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||||
import org.schabi.newpipe.util.FilenameUtils;
|
import org.schabi.newpipe.util.FilenameUtils;
|
||||||
@ -61,7 +61,6 @@ import org.schabi.newpipe.util.ThemeHelper;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@ -591,17 +590,6 @@ public class DownloadDialog extends DialogFragment
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showErrorActivity(final Exception e) {
|
|
||||||
ErrorActivity.reportError(
|
|
||||||
context,
|
|
||||||
Collections.singletonList(e),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ErrorInfo
|
|
||||||
.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareSelectedDownload() {
|
private void prepareSelectedDownload() {
|
||||||
final StoredDirectoryHelper mainStorage;
|
final StoredDirectoryHelper mainStorage;
|
||||||
final MediaFormat format;
|
final MediaFormat format;
|
||||||
@ -684,6 +672,9 @@ public class DownloadDialog extends DialogFragment
|
|||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(getString(R.string.last_used_download_type), selectedMediaType)
|
.putString(getString(R.string.last_used_download_type), selectedMediaType)
|
||||||
.apply();
|
.apply();
|
||||||
|
|
||||||
|
Toast.makeText(context, getString(R.string.download_has_started),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
|
private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
|
||||||
@ -705,7 +696,8 @@ public class DownloadDialog extends DialogFragment
|
|||||||
mainStorage.getTag());
|
mainStorage.getTag());
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
showErrorActivity(e);
|
ErrorActivity.reportErrorInSnackbar(this,
|
||||||
|
new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.acra.ReportField;
|
||||||
import org.acra.data.CrashReportData;
|
import org.acra.data.CrashReportData;
|
||||||
import org.acra.sender.ReportSender;
|
import org.acra.sender.ReportSender;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
@ -32,8 +33,12 @@ public class AcraReportSender implements ReportSender {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
|
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
|
||||||
ErrorActivity.reportError(context, report,
|
ErrorActivity.reportError(context, new ErrorInfo(
|
||||||
ErrorInfo.make(UserAction.UI_ERROR, "none",
|
new String[]{report.getString(ReportField.STACK_TRACE)},
|
||||||
"App crash, UI failure", R.string.app_ui_crash));
|
UserAction.UI_ERROR,
|
||||||
|
ErrorInfo.SERVICE_NONE,
|
||||||
|
"ACRA report",
|
||||||
|
R.string.app_ui_crash,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
@ -8,7 +8,6 @@ import android.graphics.Color;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@ -18,14 +17,11 @@ 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.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.app.NavUtils;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
import org.acra.ReportField;
|
|
||||||
import org.acra.data.CrashReportData;
|
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
@ -34,14 +30,9 @@ import org.schabi.newpipe.util.Localization;
|
|||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.time.LocalDateTime;
|
||||||
import java.io.StringWriter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
@ -70,108 +61,77 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
public static final String TAG = ErrorActivity.class.toString();
|
public static final String TAG = ErrorActivity.class.toString();
|
||||||
// BUNDLE TAGS
|
// BUNDLE TAGS
|
||||||
public static final String ERROR_INFO = "error_info";
|
public static final String ERROR_INFO = "error_info";
|
||||||
public static final String ERROR_LIST = "error_list";
|
|
||||||
|
|
||||||
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
||||||
public static final String ERROR_EMAIL_SUBJECT
|
public static final String ERROR_EMAIL_SUBJECT = "Exception in ";
|
||||||
= "Exception in NewPipe " + BuildConfig.VERSION_NAME;
|
|
||||||
|
|
||||||
public static final String ERROR_GITHUB_ISSUE_URL
|
public static final String ERROR_GITHUB_ISSUE_URL
|
||||||
= "https://github.com/TeamNewPipe/NewPipe/issues";
|
= "https://github.com/TeamNewPipe/NewPipe/issues";
|
||||||
|
|
||||||
private String[] errorList;
|
public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER
|
||||||
|
= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||||
|
|
||||||
|
|
||||||
private ErrorInfo errorInfo;
|
private ErrorInfo errorInfo;
|
||||||
private Class returnActivity;
|
|
||||||
private String currentTimeStamp;
|
private String currentTimeStamp;
|
||||||
|
|
||||||
private ActivityErrorBinding activityErrorBinding;
|
private ActivityErrorBinding activityErrorBinding;
|
||||||
|
|
||||||
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
|
public static void reportError(final Context context, final ErrorInfo errorInfo) {
|
||||||
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR,
|
final Intent intent = new Intent(context, ErrorActivity.class);
|
||||||
"none", "", R.string.app_ui_crash));
|
intent.putExtra(ERROR_INFO, errorInfo);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reportError(final Context context, final List<Throwable> el,
|
public static void reportErrorInSnackbar(final Context context, final ErrorInfo errorInfo) {
|
||||||
final Class returnActivity, final View rootView,
|
final View rootView = context instanceof Activity
|
||||||
final ErrorInfo errorInfo) {
|
? ((Activity) context).findViewById(android.R.id.content) : null;
|
||||||
|
reportErrorInSnackbar(context, rootView, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reportErrorInSnackbar(final Fragment fragment, final ErrorInfo errorInfo) {
|
||||||
|
View rootView = fragment.getView();
|
||||||
|
if (rootView == null && fragment.getActivity() != null) {
|
||||||
|
rootView = fragment.getActivity().findViewById(android.R.id.content);
|
||||||
|
}
|
||||||
|
reportErrorInSnackbar(fragment.requireContext(), rootView, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reportUiErrorInSnackbar(final Context context,
|
||||||
|
final String request,
|
||||||
|
final Throwable throwable) {
|
||||||
|
reportErrorInSnackbar(context, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reportUiErrorInSnackbar(final Fragment fragment,
|
||||||
|
final String request,
|
||||||
|
final Throwable throwable) {
|
||||||
|
reportErrorInSnackbar(fragment, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private static void reportErrorInSnackbar(final Context context,
|
||||||
|
@Nullable final View rootView,
|
||||||
|
final ErrorInfo errorInfo) {
|
||||||
if (rootView != null) {
|
if (rootView != null) {
|
||||||
Snackbar.make(rootView, R.string.error_snackbar_message, 3 * 1000)
|
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
|
||||||
.setActionTextColor(Color.YELLOW)
|
.setActionTextColor(Color.YELLOW)
|
||||||
.setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v ->
|
.setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v ->
|
||||||
startErrorActivity(returnActivity, context, errorInfo, el)).show();
|
reportError(context, errorInfo)).show();
|
||||||
} else {
|
} else {
|
||||||
startErrorActivity(returnActivity, context, errorInfo, el);
|
reportError(context, errorInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void startErrorActivity(final Class returnActivity, final Context context,
|
|
||||||
final ErrorInfo errorInfo, final List<Throwable> el) {
|
|
||||||
final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
|
||||||
ac.setReturnActivity(returnActivity);
|
|
||||||
final Intent intent = new Intent(context, ErrorActivity.class);
|
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
|
||||||
intent.putExtra(ERROR_LIST, elToSl(el));
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reportError(final Context context, final Throwable e,
|
////////////////////////////////////////////////////////////////////////
|
||||||
final Class returnActivity, final View rootView,
|
// Activity lifecycle
|
||||||
final ErrorInfo errorInfo) {
|
////////////////////////////////////////////////////////////////////////
|
||||||
List<Throwable> el = null;
|
|
||||||
if (e != null) {
|
|
||||||
el = new Vector<>();
|
|
||||||
el.add(e);
|
|
||||||
}
|
|
||||||
reportError(context, el, returnActivity, rootView, errorInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// async call
|
|
||||||
public static void reportError(final Handler handler, final Context context,
|
|
||||||
final Throwable e, final Class returnActivity,
|
|
||||||
final View rootView, final ErrorInfo errorInfo) {
|
|
||||||
|
|
||||||
List<Throwable> el = null;
|
|
||||||
if (e != null) {
|
|
||||||
el = new Vector<>();
|
|
||||||
el.add(e);
|
|
||||||
}
|
|
||||||
reportError(handler, context, el, returnActivity, rootView, errorInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// async call
|
|
||||||
public static void reportError(final Handler handler, final Context context,
|
|
||||||
final List<Throwable> el, final Class returnActivity,
|
|
||||||
final View rootView, final ErrorInfo errorInfo) {
|
|
||||||
handler.post(() -> reportError(context, el, returnActivity, rootView, errorInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reportError(final Context context, final CrashReportData report,
|
|
||||||
final ErrorInfo errorInfo) {
|
|
||||||
final String[] el = {report.getString(ReportField.STACK_TRACE)};
|
|
||||||
|
|
||||||
final Intent intent = new Intent(context, ErrorActivity.class);
|
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
|
||||||
intent.putExtra(ERROR_LIST, el);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getStackTrace(final Throwable throwable) {
|
|
||||||
final StringWriter sw = new StringWriter();
|
|
||||||
final PrintWriter pw = new PrintWriter(sw, true);
|
|
||||||
throwable.printStackTrace(pw);
|
|
||||||
return sw.getBuffer().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorList to StringList
|
|
||||||
private static String[] elToSl(final List<Throwable> stackTraces) {
|
|
||||||
final String[] out = new String[stackTraces.size()];
|
|
||||||
for (int i = 0; i < stackTraces.size(); i++) {
|
|
||||||
out[i] = getStackTrace(stackTraces.get(i));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
@ -193,38 +153,28 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
actionBar.setDisplayShowTitleEnabled(true);
|
actionBar.setDisplayShowTitleEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
|
||||||
returnActivity = ac.getReturnActivity();
|
|
||||||
errorInfo = intent.getParcelableExtra(ERROR_INFO);
|
errorInfo = intent.getParcelableExtra(ERROR_INFO);
|
||||||
errorList = intent.getStringArrayExtra(ERROR_LIST);
|
|
||||||
|
|
||||||
// important add guru meditation
|
// important add guru meditation
|
||||||
addGuruMeditation();
|
addGuruMeditation();
|
||||||
currentTimeStamp = getCurrentTimeStamp();
|
currentTimeStamp = CURRENT_TIMESTAMP_FORMATTER.format(LocalDateTime.now());
|
||||||
|
|
||||||
activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
|
activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
|
||||||
openPrivacyPolicyDialog(this, "EMAIL"));
|
openPrivacyPolicyDialog(this, "EMAIL"));
|
||||||
|
|
||||||
activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> {
|
activityErrorBinding.errorReportCopyButton.setOnClickListener(v ->
|
||||||
ShareUtils.copyToClipboard(this, buildMarkdown());
|
ShareUtils.copyToClipboard(this, buildMarkdown()));
|
||||||
});
|
|
||||||
|
|
||||||
activityErrorBinding.errorReportGitHubButton.setOnClickListener(v ->
|
activityErrorBinding.errorReportGitHubButton.setOnClickListener(v ->
|
||||||
openPrivacyPolicyDialog(this, "GITHUB"));
|
openPrivacyPolicyDialog(this, "GITHUB"));
|
||||||
|
|
||||||
// normal bugreport
|
// normal bugreport
|
||||||
buildInfo(errorInfo);
|
buildInfo(errorInfo);
|
||||||
if (errorInfo.getMessage() != 0) {
|
activityErrorBinding.errorMessageView.setText(errorInfo.getMessageStringId());
|
||||||
activityErrorBinding.errorMessageView.setText(errorInfo.getMessage());
|
activityErrorBinding.errorView.setText(formErrorText(errorInfo.getStackTraces()));
|
||||||
} else {
|
|
||||||
activityErrorBinding.errorMessageView.setVisibility(View.GONE);
|
|
||||||
activityErrorBinding.messageWhatHappenedView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
activityErrorBinding.errorView.setText(formErrorText(errorList));
|
|
||||||
|
|
||||||
// print stack trace once again for debugging:
|
// print stack trace once again for debugging:
|
||||||
for (final String e : errorList) {
|
for (final String e : errorInfo.getStackTraces()) {
|
||||||
Log.e(TAG, e);
|
Log.e(TAG, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,15 +189,14 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
final int id = item.getItemId();
|
final int id = item.getItemId();
|
||||||
switch (id) {
|
if (id == android.R.id.home) {
|
||||||
case android.R.id.home:
|
onBackPressed();
|
||||||
goToReturnActivity();
|
} else if (id == R.id.menu_item_share_error) {
|
||||||
break;
|
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
||||||
case R.id.menu_item_share_error:
|
} else {
|
||||||
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
return false;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
||||||
@ -264,7 +213,9 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
final Intent i = new Intent(Intent.ACTION_SENDTO)
|
final Intent i = new Intent(Intent.ACTION_SENDTO)
|
||||||
.setData(Uri.parse("mailto:")) // only email apps should handle this
|
.setData(Uri.parse("mailto:")) // only email apps should handle this
|
||||||
.putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS})
|
.putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS})
|
||||||
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
|
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT
|
||||||
|
+ getString(R.string.app_name) + " "
|
||||||
|
+ BuildConfig.VERSION_NAME)
|
||||||
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
||||||
if (i.resolveActivity(getPackageManager()) != null) {
|
if (i.resolveActivity(getPackageManager()) != null) {
|
||||||
ShareUtils.openIntentInApp(context, i);
|
ShareUtils.openIntentInApp(context, i);
|
||||||
@ -310,17 +261,6 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
return checkedReturnActivity;
|
return checkedReturnActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void goToReturnActivity() {
|
|
||||||
final Class<? extends Activity> checkedReturnActivity = getReturnActivity(returnActivity);
|
|
||||||
if (checkedReturnActivity == null) {
|
|
||||||
super.onBackPressed();
|
|
||||||
} else {
|
|
||||||
final Intent intent = new Intent(this, checkedReturnActivity);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
NavUtils.navigateUpTo(this, intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildInfo(final ErrorInfo info) {
|
private void buildInfo(final ErrorInfo info) {
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
@ -355,7 +295,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
.value("version", BuildConfig.VERSION_NAME)
|
.value("version", BuildConfig.VERSION_NAME)
|
||||||
.value("os", getOsString())
|
.value("os", getOsString())
|
||||||
.value("time", currentTimeStamp)
|
.value("time", currentTimeStamp)
|
||||||
.array("exceptions", Arrays.asList(errorList))
|
.array("exceptions", Arrays.asList(errorInfo.getStackTraces()))
|
||||||
.value("user_comment", activityErrorBinding.errorCommentBox.getText()
|
.value("user_comment", activityErrorBinding.errorCommentBox.getText()
|
||||||
.toString())
|
.toString())
|
||||||
.end()
|
.end()
|
||||||
@ -393,27 +333,27 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
// Collapse all logs to a single paragraph when there are more than one
|
// Collapse all logs to a single paragraph when there are more than one
|
||||||
// to keep the GitHub issue clean.
|
// to keep the GitHub issue clean.
|
||||||
if (errorList.length > 1) {
|
if (errorInfo.getStackTraces().length > 1) {
|
||||||
htmlErrorReport
|
htmlErrorReport
|
||||||
.append("<details><summary><b>Exceptions (")
|
.append("<details><summary><b>Exceptions (")
|
||||||
.append(errorList.length)
|
.append(errorInfo.getStackTraces().length)
|
||||||
.append(")</b></summary><p>\n");
|
.append(")</b></summary><p>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the logs
|
// add the logs
|
||||||
for (int i = 0; i < errorList.length; i++) {
|
for (int i = 0; i < errorInfo.getStackTraces().length; i++) {
|
||||||
htmlErrorReport.append("<details><summary><b>Crash log ");
|
htmlErrorReport.append("<details><summary><b>Crash log ");
|
||||||
if (errorList.length > 1) {
|
if (errorInfo.getStackTraces().length > 1) {
|
||||||
htmlErrorReport.append(i + 1);
|
htmlErrorReport.append(i + 1);
|
||||||
}
|
}
|
||||||
htmlErrorReport.append("</b>")
|
htmlErrorReport.append("</b>")
|
||||||
.append("</summary><p>\n")
|
.append("</summary><p>\n")
|
||||||
.append("\n```\n").append(errorList[i]).append("\n```\n")
|
.append("\n```\n").append(errorInfo.getStackTraces()[i]).append("\n```\n")
|
||||||
.append("</details>\n");
|
.append("</details>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure to close everything
|
// make sure to close everything
|
||||||
if (errorList.length > 1) {
|
if (errorInfo.getStackTraces().length > 1) {
|
||||||
htmlErrorReport.append("</p></details>\n");
|
htmlErrorReport.append("</p></details>\n");
|
||||||
}
|
}
|
||||||
htmlErrorReport.append("<hr>\n");
|
htmlErrorReport.append("<hr>\n");
|
||||||
@ -460,17 +400,4 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
text += "\n" + getString(R.string.guru_meditation);
|
text += "\n" + getString(R.string.guru_meditation);
|
||||||
activityErrorBinding.errorSorryView.setText(text);
|
activityErrorBinding.errorSorryView.setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
//super.onBackPressed();
|
|
||||||
goToReturnActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrentTimeStamp() {
|
|
||||||
final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
|
||||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
||||||
return df.format(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
113
app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
Normal file
113
app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package org.schabi.newpipe.error
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.extractor.Info
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException
|
||||||
|
import org.schabi.newpipe.ktx.isNetworkRelated
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class ErrorInfo(
|
||||||
|
val stackTraces: Array<String>,
|
||||||
|
val userAction: UserAction,
|
||||||
|
val serviceName: String,
|
||||||
|
val request: String,
|
||||||
|
val messageStringId: Int,
|
||||||
|
@Transient // no need to store throwable, all data for report is in other variables
|
||||||
|
var throwable: Throwable? = null
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
throwable: Throwable,
|
||||||
|
userAction: UserAction,
|
||||||
|
serviceName: String,
|
||||||
|
request: String
|
||||||
|
) : this(
|
||||||
|
throwableToStringList(throwable),
|
||||||
|
userAction,
|
||||||
|
serviceName,
|
||||||
|
request,
|
||||||
|
getMessageStringId(throwable, userAction),
|
||||||
|
throwable
|
||||||
|
)
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
throwable: List<Throwable>,
|
||||||
|
userAction: UserAction,
|
||||||
|
serviceName: String,
|
||||||
|
request: String
|
||||||
|
) : this(
|
||||||
|
throwableListToStringList(throwable),
|
||||||
|
userAction,
|
||||||
|
serviceName,
|
||||||
|
request,
|
||||||
|
getMessageStringId(throwable.firstOrNull(), userAction),
|
||||||
|
throwable.firstOrNull()
|
||||||
|
)
|
||||||
|
|
||||||
|
// constructors with single throwable
|
||||||
|
constructor(throwable: Throwable, userAction: UserAction, request: String) :
|
||||||
|
this(throwable, userAction, SERVICE_NONE, request)
|
||||||
|
constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) :
|
||||||
|
this(throwable, userAction, NewPipe.getNameOfService(serviceId), request)
|
||||||
|
constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) :
|
||||||
|
this(throwable, userAction, getInfoServiceName(info), request)
|
||||||
|
|
||||||
|
// constructors with list of throwables
|
||||||
|
constructor(throwable: List<Throwable>, userAction: UserAction, request: String) :
|
||||||
|
this(throwable, userAction, SERVICE_NONE, request)
|
||||||
|
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, serviceId: Int) :
|
||||||
|
this(throwable, userAction, NewPipe.getNameOfService(serviceId), request)
|
||||||
|
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, info: Info?) :
|
||||||
|
this(throwable, userAction, getInfoServiceName(info), request)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SERVICE_NONE = "none"
|
||||||
|
|
||||||
|
private fun getStackTrace(throwable: Throwable): String {
|
||||||
|
StringWriter().use { stringWriter ->
|
||||||
|
PrintWriter(stringWriter, true).use { printWriter ->
|
||||||
|
throwable.printStackTrace(printWriter)
|
||||||
|
return stringWriter.buffer.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun throwableToStringList(throwable: Throwable) = arrayOf(getStackTrace(throwable))
|
||||||
|
|
||||||
|
fun throwableListToStringList(throwable: List<Throwable>) =
|
||||||
|
Array(throwable.size) { i -> getStackTrace(throwable[i]) }
|
||||||
|
|
||||||
|
private fun getInfoServiceName(info: Info?) =
|
||||||
|
if (info == null) SERVICE_NONE else NewPipe.getNameOfService(info.serviceId)
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private fun getMessageStringId(
|
||||||
|
throwable: Throwable?,
|
||||||
|
action: UserAction
|
||||||
|
): Int {
|
||||||
|
return when {
|
||||||
|
throwable is ContentNotAvailableException -> R.string.content_not_available
|
||||||
|
throwable != null && throwable.isNetworkRelated -> R.string.network_error
|
||||||
|
throwable is ContentNotSupportedException -> R.string.content_not_supported
|
||||||
|
throwable is DeobfuscateException -> R.string.youtube_signature_deobfuscation_error
|
||||||
|
throwable is ExtractionException -> R.string.parsing_error
|
||||||
|
action == UserAction.UI_ERROR -> R.string.app_ui_crash
|
||||||
|
action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments
|
||||||
|
action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed
|
||||||
|
action == UserAction.SUBSCRIPTION_UPDATE -> R.string.subscription_update_failed
|
||||||
|
action == UserAction.LOAD_IMAGE -> R.string.could_not_load_thumbnails
|
||||||
|
action == UserAction.DOWNLOAD_OPEN_DIALOG -> R.string.could_not_setup_download_menu
|
||||||
|
else -> R.string.general_error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt
Normal file
132
app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package org.schabi.newpipe.error
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.jakewharton.rxbinding4.view.clicks
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import org.schabi.newpipe.MainActivity
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PaidContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PrivateContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException
|
||||||
|
import org.schabi.newpipe.ktx.animate
|
||||||
|
import org.schabi.newpipe.ktx.isInterruptedCaused
|
||||||
|
import org.schabi.newpipe.ktx.isNetworkRelated
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class ErrorPanelHelper(
|
||||||
|
private val fragment: Fragment,
|
||||||
|
rootView: View,
|
||||||
|
onRetry: Runnable
|
||||||
|
) {
|
||||||
|
private val context: Context = rootView.context!!
|
||||||
|
private val errorPanelRoot: View = rootView.findViewById(R.id.error_panel)
|
||||||
|
private val errorTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_view)
|
||||||
|
private val errorButtonAction: Button = errorPanelRoot.findViewById(R.id.error_button_action)
|
||||||
|
private val errorButtonRetry: Button = errorPanelRoot.findViewById(R.id.error_button_retry)
|
||||||
|
|
||||||
|
private var errorDisposable: Disposable? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
errorDisposable = errorButtonRetry.clicks()
|
||||||
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { onRetry.run() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showError(errorInfo: ErrorInfo) {
|
||||||
|
|
||||||
|
if (errorInfo.throwable != null && errorInfo.throwable!!.isInterruptedCaused) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, "onError() isInterruptedCaused! = [$errorInfo.throwable]")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errorButtonAction.isVisible = true
|
||||||
|
if (errorInfo.throwable is ReCaptchaException) {
|
||||||
|
errorButtonAction.setText(R.string.recaptcha_solve)
|
||||||
|
errorButtonAction.setOnClickListener {
|
||||||
|
// Starting ReCaptcha Challenge Activity
|
||||||
|
val intent = Intent(context, ReCaptchaActivity::class.java)
|
||||||
|
intent.putExtra(
|
||||||
|
ReCaptchaActivity.RECAPTCHA_URL_EXTRA,
|
||||||
|
(errorInfo.throwable as ReCaptchaException).url
|
||||||
|
)
|
||||||
|
fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST)
|
||||||
|
errorButtonAction.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
errorTextView.setText(R.string.recaptcha_request_toast)
|
||||||
|
errorButtonRetry.isVisible = true
|
||||||
|
} else {
|
||||||
|
errorButtonAction.setText(R.string.error_snackbar_action)
|
||||||
|
errorButtonAction.setOnClickListener {
|
||||||
|
ErrorActivity.reportError(context, errorInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide retry button by default, then show only if not unavailable/unsupported content
|
||||||
|
errorButtonRetry.isVisible = false
|
||||||
|
errorTextView.setText(
|
||||||
|
when (errorInfo.throwable) {
|
||||||
|
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
|
||||||
|
is GeographicRestrictionException -> R.string.georestricted_content
|
||||||
|
is PaidContentException -> R.string.paid_content
|
||||||
|
is PrivateContentException -> R.string.private_content
|
||||||
|
is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content
|
||||||
|
is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content
|
||||||
|
is ContentNotAvailableException -> R.string.content_not_available
|
||||||
|
is ContentNotSupportedException -> R.string.content_not_supported
|
||||||
|
else -> {
|
||||||
|
// show retry button only for content which is not unavailable or unsupported
|
||||||
|
errorButtonRetry.isVisible = true
|
||||||
|
if (errorInfo.throwable != null && errorInfo.throwable!!.isNetworkRelated) {
|
||||||
|
R.string.network_error
|
||||||
|
} else {
|
||||||
|
R.string.error_snackbar_message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
errorPanelRoot.animate(true, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showTextError(errorString: String) {
|
||||||
|
errorButtonAction.isVisible = false
|
||||||
|
errorButtonRetry.isVisible = false
|
||||||
|
errorTextView.text = errorString
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() {
|
||||||
|
errorButtonAction.setOnClickListener(null)
|
||||||
|
errorPanelRoot.animate(false, 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isVisible(): Boolean {
|
||||||
|
return errorPanelRoot.isVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
errorButtonAction.setOnClickListener(null)
|
||||||
|
errorButtonRetry.setOnClickListener(null)
|
||||||
|
errorDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG: String = ErrorPanelHelper::class.simpleName!!
|
||||||
|
val DEBUG: Boolean = MainActivity.DEBUG
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@ -20,6 +20,9 @@ import androidx.preference.PreferenceManager;
|
|||||||
import androidx.webkit.WebViewClientCompat;
|
import androidx.webkit.WebViewClientCompat;
|
||||||
|
|
||||||
import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
|
import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
|
||||||
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
|
import org.schabi.newpipe.MainActivity;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user actions that can cause an error.
|
* The user actions that can cause an error.
|
||||||
@ -6,9 +6,12 @@ package org.schabi.newpipe.report;
|
|||||||
public enum UserAction {
|
public enum UserAction {
|
||||||
USER_REPORT("user report"),
|
USER_REPORT("user report"),
|
||||||
UI_ERROR("ui error"),
|
UI_ERROR("ui error"),
|
||||||
SUBSCRIPTION("subscription"),
|
SUBSCRIPTION_CHANGE("subscription change"),
|
||||||
|
SUBSCRIPTION_UPDATE("subscription update"),
|
||||||
|
SUBSCRIPTION_GET("get subscription"),
|
||||||
|
SUBSCRIPTION_IMPORT_EXPORT("subscription import or export"),
|
||||||
LOAD_IMAGE("load image"),
|
LOAD_IMAGE("load image"),
|
||||||
SOMETHING_ELSE("something"),
|
SOMETHING_ELSE("something else"),
|
||||||
SEARCHED("searched"),
|
SEARCHED("searched"),
|
||||||
GET_SUGGESTIONS("get suggestions"),
|
GET_SUGGESTIONS("get suggestions"),
|
||||||
REQUESTED_STREAM("requested stream"),
|
REQUESTED_STREAM("requested stream"),
|
||||||
@ -17,11 +20,15 @@ public enum UserAction {
|
|||||||
REQUESTED_KIOSK("requested kiosk"),
|
REQUESTED_KIOSK("requested kiosk"),
|
||||||
REQUESTED_COMMENTS("requested comments"),
|
REQUESTED_COMMENTS("requested comments"),
|
||||||
REQUESTED_FEED("requested feed"),
|
REQUESTED_FEED("requested feed"),
|
||||||
|
REQUESTED_BOOKMARK("bookmark"),
|
||||||
DELETE_FROM_HISTORY("delete from history"),
|
DELETE_FROM_HISTORY("delete from history"),
|
||||||
PLAY_STREAM("Play stream"),
|
PLAY_STREAM("play stream"),
|
||||||
|
DOWNLOAD_OPEN_DIALOG("download open dialog"),
|
||||||
DOWNLOAD_POSTPROCESSING("download post-processing"),
|
DOWNLOAD_POSTPROCESSING("download post-processing"),
|
||||||
DOWNLOAD_FAILED("download failed"),
|
DOWNLOAD_FAILED("download failed"),
|
||||||
PREFERENCES_MIGRATION("migration of preferences");
|
PREFERENCES_MIGRATION("migration of preferences"),
|
||||||
|
SHARE_TO_NEWPIPE("share to newpipe"),
|
||||||
|
CHECK_FOR_NEW_APP_VERSION("check for new app version");
|
||||||
|
|
||||||
|
|
||||||
private final String message;
|
private final String message;
|
@ -1,42 +1,23 @@
|
|||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
|
|
||||||
import com.jakewharton.rxbinding4.view.RxView;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.MainActivity;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
import org.schabi.newpipe.error.ErrorPanelHelper;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.InfoCache;
|
import org.schabi.newpipe.util.InfoCache;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||||
|
|
||||||
@ -50,11 +31,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
@Nullable
|
@Nullable
|
||||||
private ProgressBar loadingProgressBar;
|
private ProgressBar loadingProgressBar;
|
||||||
|
|
||||||
private Disposable errorDisposable;
|
private ErrorPanelHelper errorPanelHelper;
|
||||||
|
@Nullable
|
||||||
protected View errorPanelRoot;
|
@State
|
||||||
private Button errorButtonRetry;
|
protected ErrorInfo lastPanelError = null;
|
||||||
private TextView errorTextView;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
||||||
@ -69,10 +49,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onResume() {
|
||||||
super.onDestroy();
|
super.onResume();
|
||||||
if (errorDisposable != null) {
|
if (lastPanelError != null) {
|
||||||
errorDisposable.dispose();
|
showError(lastPanelError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,22 +63,17 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
@Override
|
@Override
|
||||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
emptyStateView = rootView.findViewById(R.id.empty_state_view);
|
emptyStateView = rootView.findViewById(R.id.empty_state_view);
|
||||||
loadingProgressBar = rootView.findViewById(R.id.loading_progress_bar);
|
loadingProgressBar = rootView.findViewById(R.id.loading_progress_bar);
|
||||||
|
errorPanelHelper = new ErrorPanelHelper(this, rootView, this::onRetryButtonClicked);
|
||||||
errorPanelRoot = rootView.findViewById(R.id.error_panel);
|
|
||||||
errorButtonRetry = rootView.findViewById(R.id.error_button_retry);
|
|
||||||
errorTextView = rootView.findViewById(R.id.error_message_view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initListeners() {
|
public void onDestroyView() {
|
||||||
super.initListeners();
|
super.onDestroyView();
|
||||||
errorDisposable = RxView.clicks(errorButtonRetry)
|
if (errorPanelHelper != null) {
|
||||||
.debounce(300, TimeUnit.MILLISECONDS)
|
errorPanelHelper.dispose();
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
}
|
||||||
.subscribe(o -> onRetryButtonClicked());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRetryButtonClicked() {
|
protected void onRetryButtonClicked() {
|
||||||
@ -137,7 +112,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
if (loadingProgressBar != null) {
|
if (loadingProgressBar != null) {
|
||||||
animate(loadingProgressBar, true, 400);
|
animate(loadingProgressBar, true, 400);
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 150);
|
hideErrorPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -148,10 +123,9 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
if (loadingProgressBar != null) {
|
if (loadingProgressBar != null) {
|
||||||
animate(loadingProgressBar, false, 0);
|
animate(loadingProgressBar, false, 0);
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 150);
|
hideErrorPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
if (emptyStateView != null) {
|
if (emptyStateView != null) {
|
||||||
@ -160,26 +134,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
if (loadingProgressBar != null) {
|
if (loadingProgressBar != null) {
|
||||||
animate(loadingProgressBar, false, 0);
|
animate(loadingProgressBar, false, 0);
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 150);
|
hideErrorPanel();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "showError() called with: "
|
|
||||||
+ "message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
|
|
||||||
}
|
|
||||||
isLoading.set(false);
|
|
||||||
InfoCache.getInstance().clearCache();
|
|
||||||
hideLoading();
|
|
||||||
|
|
||||||
errorTextView.setText(message);
|
|
||||||
if (showRetryButton) {
|
|
||||||
animate(errorButtonRetry, true, 600);
|
|
||||||
} else {
|
|
||||||
animate(errorButtonRetry, false, 0);
|
|
||||||
}
|
|
||||||
animate(errorPanelRoot, true, 300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -190,120 +145,69 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError() {
|
||||||
|
isLoading.set(false);
|
||||||
|
InfoCache.getInstance().clearCache();
|
||||||
|
if (emptyStateView != null) {
|
||||||
|
animate(emptyStateView, false, 150);
|
||||||
|
}
|
||||||
|
if (loadingProgressBar != null) {
|
||||||
|
animate(loadingProgressBar, false, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Error handling
|
// Error handling
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
/**
|
public final void showError(final ErrorInfo errorInfo) {
|
||||||
* Default implementation handles some general exceptions.
|
handleError();
|
||||||
*
|
|
||||||
* @param exception The exception that should be handled
|
|
||||||
* @return If the exception was handled
|
|
||||||
*/
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onError() called with: exception = [" + exception + "]");
|
|
||||||
}
|
|
||||||
isLoading.set(false);
|
|
||||||
|
|
||||||
if (isDetached() || isRemoving()) {
|
if (isDetached() || isRemoving()) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, "onError() is detached or removing = [" + exception + "]");
|
Log.w(TAG, "showError() is detached or removing = [" + errorInfo + "]");
|
||||||
}
|
}
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ExceptionUtils.isInterruptedCaused(exception)) {
|
errorPanelHelper.showError(errorInfo);
|
||||||
|
lastPanelError = errorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void showTextError(@NonNull final String errorString) {
|
||||||
|
handleError();
|
||||||
|
|
||||||
|
if (isDetached() || isRemoving()) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
|
Log.w(TAG, "showTextError() is detached or removing = [" + errorString + "]");
|
||||||
}
|
}
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception instanceof ReCaptchaException) {
|
errorPanelHelper.showTextError(errorString);
|
||||||
onReCaptchaException((ReCaptchaException) exception);
|
|
||||||
return true;
|
|
||||||
} else if (exception instanceof ContentNotAvailableException) {
|
|
||||||
showError(getString(R.string.content_not_available), false);
|
|
||||||
return true;
|
|
||||||
} else if (ExceptionUtils.isNetworkRelated(exception)) {
|
|
||||||
showError(getString(R.string.network_error), true);
|
|
||||||
return true;
|
|
||||||
} else if (exception instanceof ContentNotSupportedException) {
|
|
||||||
showError(getString(R.string.content_not_supported), false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onReCaptchaException(final ReCaptchaException exception) {
|
public final void hideErrorPanel() {
|
||||||
if (DEBUG) {
|
errorPanelHelper.hide();
|
||||||
Log.d(TAG, "onReCaptchaException() called");
|
lastPanelError = null;
|
||||||
}
|
|
||||||
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
|
||||||
// Starting ReCaptcha Challenge Activity
|
|
||||||
final Intent intent = new Intent(activity, ReCaptchaActivity.class);
|
|
||||||
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl());
|
|
||||||
startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST);
|
|
||||||
|
|
||||||
showError(getString(R.string.recaptcha_request_toast), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUnrecoverableError(final Throwable exception, final UserAction userAction,
|
public final boolean isErrorPanelVisible() {
|
||||||
final String serviceName, final String request,
|
return errorPanelHelper.isVisible();
|
||||||
@StringRes final int errorId) {
|
|
||||||
onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName,
|
|
||||||
request, errorId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUnrecoverableError(final List<Throwable> exception, final UserAction userAction,
|
|
||||||
final String serviceName, final String request,
|
|
||||||
@StringRes final int errorId) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null,
|
|
||||||
ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
|
|
||||||
request == null ? "none" : request, errorId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showSnackBarError(final Throwable exception, final UserAction userAction,
|
|
||||||
final String serviceName, final String request,
|
|
||||||
@StringRes final int errorId) {
|
|
||||||
showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request,
|
|
||||||
errorId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a SnackBar and only call
|
* Show a SnackBar and only call
|
||||||
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorInfo)}
|
* {@link ErrorActivity#reportErrorInSnackbar(androidx.fragment.app.Fragment, ErrorInfo)}
|
||||||
* IF we a find a valid view (otherwise the error screen appears).
|
* IF we a find a valid view (otherwise the error screen appears).
|
||||||
*
|
*
|
||||||
* @param exception List of the exceptions to show
|
* @param errorInfo The error information
|
||||||
* @param userAction The user action that caused the exception
|
|
||||||
* @param serviceName The service where the exception happened
|
|
||||||
* @param request The page that was requested
|
|
||||||
* @param errorId The ID of the error
|
|
||||||
*/
|
*/
|
||||||
public void showSnackBarError(final List<Throwable> exception, final UserAction userAction,
|
public void showSnackBarError(final ErrorInfo errorInfo) {
|
||||||
final String serviceName, final String request,
|
|
||||||
@StringRes final int errorId) {
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "showSnackBarError() called with: "
|
Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]");
|
||||||
+ "exception = [" + exception + "], userAction = [" + userAction + "], "
|
|
||||||
+ "request = [" + request + "], errorId = [" + errorId + "]");
|
|
||||||
}
|
}
|
||||||
View rootView = activity != null ? activity.findViewById(android.R.id.content) : null;
|
ErrorActivity.reportErrorInSnackbar(this, errorInfo);
|
||||||
if (rootView == null && getView() != null) {
|
|
||||||
rootView = getView();
|
|
||||||
}
|
|
||||||
if (rootView == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
|
||||||
ErrorInfo.make(userAction, serviceName, request, errorId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,18 @@ import org.schabi.newpipe.BaseFragment;
|
|||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
public class EmptyFragment extends BaseFragment {
|
public class EmptyFragment extends BaseFragment {
|
||||||
|
final boolean showMessage;
|
||||||
|
|
||||||
|
public EmptyFragment(final boolean showMessage) {
|
||||||
|
this.showMessage = showMessage;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
final Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_empty, container, false);
|
final View view = inflater.inflate(R.layout.fragment_empty, container, false);
|
||||||
|
view.findViewById(R.id.empty_state_view).setVisibility(
|
||||||
|
showMessage ? View.VISIBLE : View.GONE);
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import android.view.ViewGroup;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
|
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
|
||||||
@ -25,10 +24,8 @@ import com.google.android.material.tabs.TabLayout;
|
|||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.FragmentMainBinding;
|
import org.schabi.newpipe.databinding.FragmentMainBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.tabs.Tab;
|
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;
|
||||||
@ -128,7 +125,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
public void onCreateOptionsMenu(@NonNull final Menu menu,
|
||||||
|
@NonNull final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
||||||
@ -144,15 +142,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.action_search) {
|
||||||
case R.id.action_search:
|
try {
|
||||||
try {
|
NavigationHelper.openSearchFragment(getFM(),
|
||||||
NavigationHelper.openSearchFragment(getFM(),
|
ServiceHelper.getSelectedServiceId(activity), "");
|
||||||
ServiceHelper.getSelectedServiceId(activity), "");
|
} catch (final Exception e) {
|
||||||
} catch (final Exception e) {
|
ErrorActivity.reportUiErrorInSnackbar(this, "Opening search fragment", e);
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
@ -241,8 +238,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
ErrorActivity.reportError(context, throwable, null, null, ErrorInfo
|
ErrorActivity.reportUiErrorInSnackbar(context, "Getting fragment item", throwable);
|
||||||
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
|
||||||
return new BlankFragment();
|
return new BlankFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +250,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemPosition(final Object object) {
|
public int getItemPosition(@NonNull final Object object) {
|
||||||
// Causes adapter to reload all Fragments when
|
// Causes adapter to reload all Fragments when
|
||||||
// notifyDataSetChanged is called
|
// notifyDataSetChanged is called
|
||||||
return POSITION_NONE;
|
return POSITION_NONE;
|
||||||
|
@ -7,7 +7,7 @@ public interface ViewContract<I> {
|
|||||||
|
|
||||||
void showEmptyState();
|
void showEmptyState();
|
||||||
|
|
||||||
void showError(String message, boolean showRetryButton);
|
|
||||||
|
|
||||||
void handleResult(I result);
|
void handleResult(I result);
|
||||||
|
|
||||||
|
void handleError();
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,10 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
@ -56,14 +54,16 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
|
|||||||
|
|
||||||
import org.schabi.newpipe.App;
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
||||||
import org.schabi.newpipe.download.DownloadDialog;
|
import org.schabi.newpipe.download.DownloadDialog;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.Stream;
|
import org.schabi.newpipe.extractor.stream.Stream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
@ -71,6 +71,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
|
|||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.fragments.BackPressable;
|
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.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.RelatedVideosFragment;
|
||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
@ -86,9 +87,6 @@ import org.schabi.newpipe.player.helper.PlayerHolder;
|
|||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
@ -151,6 +149,7 @@ public final class VideoDetailFragment
|
|||||||
private static final String COMMENTS_TAB_TAG = "COMMENTS";
|
private static final String COMMENTS_TAB_TAG = "COMMENTS";
|
||||||
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
||||||
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
||||||
|
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
private boolean showComments;
|
private boolean showComments;
|
||||||
@ -526,7 +525,7 @@ public final class VideoDetailFragment
|
|||||||
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
||||||
subChannelUrl, subChannelName);
|
subChannelUrl, subChannelName);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,13 +683,12 @@ public final class VideoDetailFragment
|
|||||||
binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
||||||
|
|
||||||
if (!isEmpty(info.getThumbnailUrl())) {
|
if (!isEmpty(info.getThumbnailUrl())) {
|
||||||
final String infoServiceName = NewPipe.getNameOfService(info.getServiceId());
|
|
||||||
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
|
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingFailed(final String imageUri, final View view,
|
public void onLoadingFailed(final String imageUri, final View view,
|
||||||
final FailReason failReason) {
|
final FailReason failReason) {
|
||||||
showSnackBarError(failReason.getCause(), UserAction.LOAD_IMAGE,
|
showSnackBarError(new ErrorInfo(failReason.getCause(), UserAction.LOAD_IMAGE,
|
||||||
infoServiceName, imageUri, R.string.could_not_load_thumbnails);
|
imageUri, info));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -906,10 +904,8 @@ public final class VideoDetailFragment
|
|||||||
openVideoPlayer();
|
openVideoPlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, throwable -> {
|
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
|
||||||
isLoading.set(false);
|
url == null ? "no url" : url, serviceId)));
|
||||||
onError(throwable);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -932,18 +928,22 @@ public final class VideoDetailFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showRelatedStreams && binding.relatedStreamsLayout == null) {
|
if (showRelatedStreams && binding.relatedStreamsLayout == null) {
|
||||||
//temp empty fragment. will be updated in handleResult
|
// temp empty fragment. will be updated in handleResult
|
||||||
pageAdapter.addFragment(new Fragment(), 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_white_24dp);
|
||||||
tabContentDescriptions.add(R.string.related_streams_tab_description);
|
tabContentDescriptions.add(R.string.related_streams_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 Fragment(), 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_white_24dp);
|
||||||
tabContentDescriptions.add(R.string.description_tab_description);
|
tabContentDescriptions.add(R.string.description_tab_description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pageAdapter.getCount() == 0) {
|
||||||
|
pageAdapter.addFragment(new EmptyFragment(true), EMPTY_TAB_TAG);
|
||||||
|
}
|
||||||
pageAdapter.notifyDataSetUpdate();
|
pageAdapter.notifyDataSetUpdate();
|
||||||
|
|
||||||
if (pageAdapter.getCount() >= 2) {
|
if (pageAdapter.getCount() >= 2) {
|
||||||
@ -1327,8 +1327,8 @@ public final class VideoDetailFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
public void handleError() {
|
||||||
super.showError(message, showRetryButton);
|
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.relatedStreamsLayout != null) { // hide related streams for tablets
|
||||||
@ -1341,8 +1341,8 @@ public final class VideoDetailFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void hideAgeRestrictedContent() {
|
private void hideAgeRestrictedContent() {
|
||||||
showError(getString(R.string.restricted_video,
|
showTextError(getString(R.string.restricted_video,
|
||||||
getString(R.string.show_age_restricted_content_title)), false);
|
getString(R.string.show_age_restricted_content_title)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupBroadcastReceiver() {
|
private void setupBroadcastReceiver() {
|
||||||
@ -1548,11 +1548,19 @@ public final class VideoDetailFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!info.getErrors().isEmpty()) {
|
if (!info.getErrors().isEmpty()) {
|
||||||
showSnackBarError(info.getErrors(),
|
// Bandcamp fan pages are not yet supported and thus a ContentNotAvailableException is
|
||||||
UserAction.REQUESTED_STREAM,
|
// thrown. This is not an error and thus should not be shown to the user.
|
||||||
NewPipe.getNameOfService(info.getServiceId()),
|
for (final Throwable throwable : info.getErrors()) {
|
||||||
info.getUrl(),
|
if (throwable instanceof ContentNotSupportedException
|
||||||
0);
|
&& "Fan pages are not supported".equals(throwable.getMessage())) {
|
||||||
|
info.getErrors().remove(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.getErrors().isEmpty()) {
|
||||||
|
showSnackBarError(new ErrorInfo(info.getErrors(),
|
||||||
|
UserAction.REQUESTED_STREAM, info.getUrl(), info));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM
|
binding.detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM
|
||||||
@ -1592,6 +1600,10 @@ public final class VideoDetailFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void openDownloadDialog() {
|
public void openDownloadDialog() {
|
||||||
|
if (currentInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
|
final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
|
||||||
downloadDialog.setVideoStreams(sortedVideoStreams);
|
downloadDialog.setVideoStreams(sortedVideoStreams);
|
||||||
@ -1601,18 +1613,9 @@ public final class VideoDetailFragment
|
|||||||
|
|
||||||
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
final ErrorInfo info = ErrorInfo.make(UserAction.UI_ERROR,
|
ErrorActivity.reportErrorInSnackbar(activity,
|
||||||
ServiceList.all()
|
new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog",
|
||||||
.get(currentInfo
|
currentInfo));
|
||||||
.getServiceId())
|
|
||||||
.getServiceInfo()
|
|
||||||
.getName(), "",
|
|
||||||
R.string.could_not_setup_download_menu);
|
|
||||||
|
|
||||||
ErrorActivity.reportError(activity,
|
|
||||||
e,
|
|
||||||
activity.getClass(),
|
|
||||||
activity.findViewById(android.R.id.content), info);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1620,24 +1623,6 @@ public final class VideoDetailFragment
|
|||||||
// Stream Results
|
// Stream Results
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof YoutubeStreamExtractor.DeobfuscateException
|
|
||||||
? R.string.youtube_signature_deobfuscation_error
|
|
||||||
: exception instanceof ExtractionException
|
|
||||||
? R.string.parsing_error
|
|
||||||
: R.string.general_error;
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProgressInfo(@NonNull final StreamInfo info) {
|
private void updateProgressInfo(@NonNull final StreamInfo info) {
|
||||||
if (positionSubscriber != null) {
|
if (positionSubscriber != null) {
|
||||||
positionSubscriber.dispose();
|
positionSubscriber.dispose();
|
||||||
@ -1853,6 +1838,7 @@ public final class VideoDetailFragment
|
|||||||
|
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
hideSystemUiIfNeeded();
|
hideSystemUiIfNeeded();
|
||||||
|
binding.overlayPlayPauseButton.requestFocus();
|
||||||
} else {
|
} else {
|
||||||
showSystemUi();
|
showSystemUi();
|
||||||
}
|
}
|
||||||
@ -2278,7 +2264,7 @@ public final class VideoDetailFragment
|
|||||||
private void updateOverlayData(@Nullable final String overlayTitle,
|
private void updateOverlayData(@Nullable final String overlayTitle,
|
||||||
@Nullable final String uploader,
|
@Nullable final String uploader,
|
||||||
@Nullable final String thumbnailUrl) {
|
@Nullable final String thumbnailUrl) {
|
||||||
binding.overlayTitleTextView.setText(isEmpty(title) ? "" : title);
|
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
|
||||||
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
|
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
|
||||||
binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark);
|
binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark);
|
||||||
if (!isEmpty(thumbnailUrl)) {
|
if (!isEmpty(thumbnailUrl)) {
|
||||||
|
@ -14,7 +14,6 @@ import android.view.View;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
@ -22,6 +21,7 @@ import androidx.viewbinding.ViewBinding;
|
|||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.PignateFooterBinding;
|
import org.schabi.newpipe.databinding.PignateFooterBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||||
@ -33,7 +33,6 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
|||||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
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;
|
||||||
@ -47,6 +46,7 @@ import java.util.List;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||||
|
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||||
|
|
||||||
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
implements ListViewContract<I, N>, StateSaver.WriteRead,
|
implements ListViewContract<I, N>, StateSaver.WriteRead,
|
||||||
@ -292,7 +292,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
selectedItem.getUrl(),
|
selectedItem.getUrl(),
|
||||||
selectedItem.getName());
|
selectedItem.getName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiErrorInSnackbar(
|
||||||
|
BaseListFragment.this, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -307,7 +308,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
selectedItem.getUrl(),
|
selectedItem.getUrl(),
|
||||||
selectedItem.getName());
|
selectedItem.getName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiErrorInSnackbar(BaseListFragment.this,
|
||||||
|
"Opening playlist fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -406,23 +408,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showLoading() {
|
||||||
|
super.showLoading();
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hideLoading() {
|
public void hideLoading() {
|
||||||
super.hideLoading();
|
super.hideLoading();
|
||||||
animate(itemsList, true, 300);
|
animate(itemsList, true, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
super.showError(message, showRetryButton);
|
|
||||||
showListFooter(false);
|
|
||||||
animate(itemsList, false, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
super.showEmptyState();
|
super.showEmptyState();
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -439,6 +441,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError() {
|
||||||
|
super.handleError();
|
||||||
|
showListFooter(false);
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||||
final String key) {
|
final String key) {
|
||||||
|
@ -7,12 +7,17 @@ import android.view.View;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
@ -30,10 +35,15 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
@State
|
@State
|
||||||
protected String url;
|
protected String url;
|
||||||
|
|
||||||
|
private final UserAction errorUserAction;
|
||||||
protected I currentInfo;
|
protected I currentInfo;
|
||||||
protected Page currentNextPage;
|
protected Page currentNextPage;
|
||||||
protected Disposable currentWorker;
|
protected Disposable currentWorker;
|
||||||
|
|
||||||
|
protected BaseListInfoFragment(final UserAction errorUserAction) {
|
||||||
|
this.errorUserAction = errorUserAction;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
@ -133,7 +143,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
currentInfo = result;
|
currentInfo = result;
|
||||||
currentNextPage = result.getNextPage();
|
currentNextPage = result.getNextPage();
|
||||||
handleResult(result);
|
handleResult(result);
|
||||||
}, this::onError);
|
}, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, errorUserAction,
|
||||||
|
"Start loading: " + url, serviceId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,10 +173,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
handleNextItems(InfoItemsPage);
|
handleNextItems(InfoItemsPage);
|
||||||
}, (@NonNull Throwable throwable) -> {
|
}, (@NonNull Throwable throwable) ->
|
||||||
isLoading.set(false);
|
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(throwable,
|
||||||
onError(throwable);
|
errorUserAction, "Loading more items: " + url, serviceId)));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forbidDownwardFocusScroll() {
|
private void forbidDownwardFocusScroll() {
|
||||||
@ -182,10 +193,16 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
@Override
|
@Override
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
currentNextPage = result.getNextPage();
|
currentNextPage = result.getNextPage();
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
|
|
||||||
showListFooter(hasMoreItems());
|
showListFooter(hasMoreItems());
|
||||||
|
|
||||||
|
if (!result.getErrors().isEmpty()) {
|
||||||
|
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), errorUserAction,
|
||||||
|
"Get next items of: " + url, serviceId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -213,6 +230,18 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
showEmptyState();
|
showEmptyState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!result.getErrors().isEmpty()) {
|
||||||
|
final List<Throwable> errors = new ArrayList<>(result.getErrors());
|
||||||
|
// handling ContentNotSupportedException not to show the error but an appropriate string
|
||||||
|
// so that crashes won't be sent uselessly and the user will understand what happened
|
||||||
|
errors.removeIf(throwable -> throwable instanceof ContentNotSupportedException);
|
||||||
|
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(),
|
||||||
|
errorUserAction, "Start loading: " + url, serviceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -224,4 +253,14 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
|||||||
this.url = u;
|
this.url = u;
|
||||||
this.name = !TextUtils.isEmpty(title) ? title : "";
|
this.name = !TextUtils.isEmpty(title) ? title : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dynamicallyShowErrorPanelOrSnackbar(final ErrorInfo errorInfo) {
|
||||||
|
if (infoListAdapter.getItemCount() == 0) {
|
||||||
|
// show error panel only if no items already visible
|
||||||
|
showError(errorInfo);
|
||||||
|
} else {
|
||||||
|
isLoading.set(false);
|
||||||
|
showSnackBarError(errorInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import android.widget.Button;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.viewbinding.ViewBinding;
|
import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
@ -27,20 +26,19 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
|||||||
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
|
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
|
||||||
import org.schabi.newpipe.databinding.FragmentChannelBinding;
|
import org.schabi.newpipe.databinding.FragmentChannelBinding;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
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.extractor.exceptions.ExtractionException;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
@ -91,6 +89,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChannelFragment() {
|
||||||
|
super(UserAction.REQUESTED_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
@ -217,9 +219,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
private void monitorSubscription(final ChannelInfo info) {
|
private void monitorSubscription(final ChannelInfo info) {
|
||||||
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
||||||
animate(headerBinding.channelSubscribeButton, false, 100);
|
animate(headerBinding.channelSubscribeButton, false, 100);
|
||||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
|
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_GET,
|
||||||
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
"Get subscription status", currentInfo));
|
||||||
"Get subscription status", 0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final Observable<List<SubscriptionEntity>> observable = subscriptionManager
|
final Observable<List<SubscriptionEntity>> observable = subscriptionManager
|
||||||
@ -269,11 +270,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
onUnrecoverableError(throwable,
|
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_UPDATE,
|
||||||
UserAction.SUBSCRIPTION,
|
"Updating subscription for " + info.getUrl(), info));
|
||||||
NewPipe.getNameOfService(info.getServiceId()),
|
|
||||||
"Updating Subscription for " + info.getUrl(),
|
|
||||||
R.string.subscription_update_failed);
|
|
||||||
|
|
||||||
disposables.add(subscriptionManager.updateChannelInfo(info)
|
disposables.add(subscriptionManager.updateChannelInfo(info)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@ -290,11 +288,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
onUnrecoverableError(throwable,
|
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_CHANGE,
|
||||||
UserAction.SUBSCRIPTION,
|
"Changing subscription for " + currentInfo.getUrl(), currentInfo));
|
||||||
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
|
||||||
"Subscription Change",
|
|
||||||
R.string.subscription_change_failed);
|
|
||||||
|
|
||||||
/* Emit clicks from main thread unto io thread */
|
/* Emit clicks from main thread unto io thread */
|
||||||
return RxView.clicks(subscribeButton)
|
return RxView.clicks(subscribeButton)
|
||||||
@ -408,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
currentInfo.getParentChannelUrl(),
|
currentInfo.getParentChannelUrl(),
|
||||||
currentInfo.getParentChannelName());
|
currentInfo.getParentChannelName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
} else if (DEBUG) {
|
} else if (DEBUG) {
|
||||||
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
||||||
@ -469,27 +464,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
|
|
||||||
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
|
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
final List<Throwable> errors = new ArrayList<>(result.getErrors());
|
for (final Throwable throwable : result.getErrors()) {
|
||||||
if (!errors.isEmpty()) {
|
if (throwable instanceof ContentNotSupportedException) {
|
||||||
|
showContentNotSupported();
|
||||||
// handling ContentNotSupportedException not to show the error but an appropriate string
|
|
||||||
// so that crashes won't be sent uselessly and the user will understand what happened
|
|
||||||
errors.removeIf(throwable -> {
|
|
||||||
if (throwable instanceof ContentNotSupportedException) {
|
|
||||||
showContentNotSupported();
|
|
||||||
}
|
|
||||||
return throwable instanceof ContentNotSupportedException;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
showSnackBarError(errors, UserAction.REQUESTED_CHANNEL,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disposables != null) {
|
disposables.clear();
|
||||||
disposables.clear();
|
|
||||||
}
|
|
||||||
if (subscribeButtonMonitor != null) {
|
if (subscribeButtonMonitor != null) {
|
||||||
subscribeButtonMonitor.dispose();
|
subscribeButtonMonitor.dispose();
|
||||||
}
|
}
|
||||||
@ -539,38 +520,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
currentInfo.getNextPage(), streamItems, index);
|
currentInfo.getNextPage(), streamItems, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_CHANNEL,
|
|
||||||
NewPipe.getNameOfService(serviceId),
|
|
||||||
"Get next page of: " + url,
|
|
||||||
R.string.general_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof ExtractionException
|
|
||||||
? R.string.parsing_error : R.string.general_error;
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -11,12 +11,11 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||||
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.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
@ -25,13 +24,17 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
|||||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
|
||||||
public static CommentsFragment getInstance(final int serviceId, final String url,
|
public static CommentsFragment getInstance(final int serviceId, final String url,
|
||||||
final String name) {
|
final String name) {
|
||||||
final CommentsFragment instance = new CommentsFragment();
|
final CommentsFragment instance = new CommentsFragment();
|
||||||
instance.setInitialData(serviceId, url, name);
|
instance.setInitialData(serviceId, url, name);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommentsFragment() {
|
||||||
|
super(UserAction.REQUESTED_COMMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -67,52 +70,13 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
|||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showLoading() {
|
|
||||||
super.showLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull final CommentsInfo result) {
|
public void handleResult(@NonNull final CommentsInfo result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
ViewUtils.slideUp(requireView(), 120, 150, 0.06f);
|
ViewUtils.slideUp(requireView(), 120, 150, 0.06f);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
|
||||||
NewPipe.getNameOfService(serviceId), "Get next page of: " + url,
|
|
||||||
R.string.general_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideLoading();
|
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -2,14 +2,16 @@ package org.schabi.newpipe.fragments.list.kiosk;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
|
||||||
public class DefaultKioskFragment extends KioskFragment {
|
public class DefaultKioskFragment extends KioskFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -46,8 +48,8 @@ public class DefaultKioskFragment extends KioskFragment {
|
|||||||
currentInfo = null;
|
currentInfo = null;
|
||||||
currentNextPage = null;
|
currentNextPage = null;
|
||||||
} catch (final ExtractionException e) {
|
} catch (final ExtractionException e) {
|
||||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
showError(new ErrorInfo(e, UserAction.REQUESTED_KIOSK,
|
||||||
"Loading default kiosk from selected service", 0);
|
"Loading default kiosk for selected service"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
@ -20,7 +22,6 @@ import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
|||||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
@ -28,8 +29,6 @@ import org.schabi.newpipe.util.Localization;
|
|||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 23.09.17.
|
* Created by Christian Schabesberger on 23.09.17.
|
||||||
* <p>
|
* <p>
|
||||||
@ -82,6 +81,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KioskFragment() {
|
||||||
|
super(UserAction.REQUESTED_KIOSK);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -102,9 +105,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
try {
|
try {
|
||||||
setTitle(kioskTranslatedName);
|
setTitle(kioskTranslatedName);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onUnrecoverableError(e, UserAction.UI_ERROR,
|
showSnackBarError(new ErrorInfo(e, UserAction.UI_ERROR, "Setting kiosk title"));
|
||||||
"none",
|
|
||||||
"none", R.string.app_ui_crash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,34 +158,11 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showLoading() {
|
|
||||||
super.showLoading();
|
|
||||||
animate(itemsList, false, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull final KioskInfo result) {
|
public void handleResult(@NonNull final KioskInfo result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
name = kioskTranslatedName;
|
name = kioskTranslatedName;
|
||||||
setTitle(kioskTranslatedName);
|
setTitle(kioskTranslatedName);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_KIOSK,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId),
|
|
||||||
"Get next page of: " + url, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.viewbinding.ViewBinding;
|
import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
@ -25,11 +24,12 @@ import org.schabi.newpipe.R;
|
|||||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
|
import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
@ -40,8 +40,6 @@ import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
|||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
@ -62,6 +60,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
|||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
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.util.ThemeHelper.resolveResourceIdFromAttr;
|
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||||
|
|
||||||
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
@ -87,6 +86,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlaylistFragment() {
|
||||||
|
super(UserAction.REQUESTED_PLAYLIST);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -262,7 +265,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
animate(headerBinding.getRoot(), false, 200);
|
animate(headerBinding.getRoot(), false, 200);
|
||||||
animate(itemsList, false, 100);
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
|
||||||
IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView);
|
IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView);
|
||||||
animate(headerBinding.uploaderLayout, false, 200);
|
animate(headerBinding.uploaderLayout, false, 200);
|
||||||
@ -284,7 +287,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
||||||
result.getUploaderUrl(), result.getUploaderName());
|
result.getUploaderUrl(), result.getUploaderName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -315,8 +318,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
.localizeStreamCount(getContext(), result.getStreamCount()));
|
.localizeStreamCount(getContext(), result.getStreamCount()));
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
result.getUrl(), result));
|
||||||
}
|
}
|
||||||
|
|
||||||
remotePlaylistManager.getPlaylist(result)
|
remotePlaylistManager.getPlaylist(result)
|
||||||
@ -363,33 +366,6 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
|
||||||
NewPipe.getNameOfService(serviceId), "Get next page of: " + url, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof ExtractionException
|
|
||||||
? R.string.parsing_error : R.string.general_error;
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -434,8 +410,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable t) {
|
public void onError(final Throwable throwable) {
|
||||||
PlaylistFragment.this.onError(t);
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Get playlist bookmarks"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -460,12 +437,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
if (currentInfo != null && playlistEntity == null) {
|
if (currentInfo != null && playlistEntity == null) {
|
||||||
action = remotePlaylistManager.onBookmark(currentInfo)
|
action = remotePlaylistManager.onBookmark(currentInfo)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
|
.subscribe(ignored -> { /* Do nothing */ }, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Adding playlist bookmark")));
|
||||||
} else if (playlistEntity != null) {
|
} else if (playlistEntity != null) {
|
||||||
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
|
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doFinally(() -> playlistEntity = null)
|
.doFinally(() -> playlistEntity = null)
|
||||||
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
|
.subscribe(ignored -> { /* Do nothing */ }, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Deleting playlist bookmark")));
|
||||||
} else {
|
} else {
|
||||||
action = Disposable.empty();
|
action = Disposable.empty();
|
||||||
}
|
}
|
||||||
|
@ -35,16 +35,18 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||||
import org.schabi.newpipe.databinding.FragmentSearchBinding;
|
import org.schabi.newpipe.databinding.FragmentSearchBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.MetaInfo;
|
import org.schabi.newpipe.extractor.MetaInfo;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|
||||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||||
import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
|
import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
|
||||||
@ -54,9 +56,6 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
|
|||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
@ -162,11 +161,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
private EditText searchEditText;
|
private EditText searchEditText;
|
||||||
private View searchClear;
|
private View searchClear;
|
||||||
|
|
||||||
private TextView correctSuggestion;
|
|
||||||
private TextView metaInfoTextView;
|
|
||||||
private View metaInfoSeparator;
|
|
||||||
|
|
||||||
private View suggestionsPanel;
|
|
||||||
private boolean suggestionsPanelVisible = false;
|
private boolean suggestionsPanelVisible = false;
|
||||||
|
|
||||||
/*////////////////////////////////////////////////////////////////////////*/
|
/*////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -258,20 +252,23 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
try {
|
try {
|
||||||
service = NewPipe.getService(serviceId);
|
service = NewPipe.getService(serviceId);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportError(getActivity(), e, requireActivity().getClass(),
|
ErrorActivity.reportUiErrorInSnackbar(this,
|
||||||
requireActivity().findViewById(android.R.id.content),
|
"Getting service for id " + serviceId, e);
|
||||||
ErrorInfo.make(UserAction.UI_ERROR,
|
}
|
||||||
"",
|
|
||||||
"", R.string.general_error));
|
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||||
|
initSuggestionObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(searchString)) {
|
if (!TextUtils.isEmpty(searchString)) {
|
||||||
if (wasLoading.getAndSet(false)) {
|
if (wasLoading.getAndSet(false)) {
|
||||||
search(searchString, contentFilter, sortFilter);
|
search(searchString, contentFilter, sortFilter);
|
||||||
|
return;
|
||||||
} else if (infoListAdapter.getItemsList().isEmpty()) {
|
} else if (infoListAdapter.getItemsList().isEmpty()) {
|
||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
search(searchString, contentFilter, sortFilter);
|
search(searchString, contentFilter, sortFilter);
|
||||||
} else if (!isLoading.get() && !wasSearchFocused) {
|
return;
|
||||||
|
} else if (!isLoading.get() && !wasSearchFocused && lastPanelError == null) {
|
||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
showEmptyState();
|
showEmptyState();
|
||||||
}
|
}
|
||||||
@ -281,11 +278,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
handleSearchSuggestion();
|
handleSearchSuggestion();
|
||||||
|
|
||||||
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
||||||
metaInfoTextView, metaInfoSeparator));
|
searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator));
|
||||||
|
|
||||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
|
||||||
initSuggestionObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
|
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
@ -367,10 +360,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||||
|
|
||||||
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
|
|
||||||
metaInfoTextView = rootView.findViewById(R.id.search_meta_info_text_view);
|
|
||||||
metaInfoSeparator = rootView.findViewById(R.id.search_meta_info_separator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -413,7 +402,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
searchEditText.setText("");
|
searchEditText.setText("");
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 200);
|
hideErrorPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,7 +529,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||||
}
|
}
|
||||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
if (isSuggestionsEnabled && !isErrorPanelVisible()) {
|
||||||
showSuggestionsPanel();
|
showSuggestionsPanel();
|
||||||
}
|
}
|
||||||
if (DeviceUtils.isTv(getContext())) {
|
if (DeviceUtils.isTv(getContext())) {
|
||||||
@ -553,8 +542,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
Log.d(TAG, "onFocusChange() called with: "
|
Log.d(TAG, "onFocusChange() called with: "
|
||||||
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
||||||
}
|
}
|
||||||
if (isSuggestionsEnabled && hasFocus
|
if (isSuggestionsEnabled && hasFocus && !isErrorPanelVisible()) {
|
||||||
&& errorPanelRoot.getVisibility() != View.VISIBLE) {
|
|
||||||
showSuggestionsPanel();
|
showSuggestionsPanel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -704,9 +692,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
howManyDeleted -> suggestionPublisher
|
howManyDeleted -> suggestionPublisher
|
||||||
.onNext(searchEditText.getText().toString()),
|
.onNext(searchEditText.getText().toString()),
|
||||||
throwable -> showSnackBarError(throwable,
|
throwable -> showSnackBarError(new ErrorInfo(throwable,
|
||||||
UserAction.DELETE_FROM_HISTORY, "none",
|
UserAction.DELETE_FROM_HISTORY,
|
||||||
"Deleting item failed", R.string.general_error));
|
"Deleting item failed")));
|
||||||
disposables.add(onDelete);
|
disposables.add(onDelete);
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
@ -733,14 +721,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
suggestionDisposable.dispose();
|
suggestionDisposable.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Observable<String> observable = suggestionPublisher
|
suggestionDisposable = suggestionPublisher
|
||||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||||
.startWithItem(searchString != null
|
.startWithItem(searchString != null
|
||||||
? searchString
|
? searchString
|
||||||
: "")
|
: "")
|
||||||
.filter(ss -> isSuggestionsEnabled);
|
.filter(ss -> isSuggestionsEnabled)
|
||||||
|
|
||||||
suggestionDisposable = observable
|
|
||||||
.switchMap(query -> {
|
.switchMap(query -> {
|
||||||
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
|
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
|
||||||
.getRelatedSearches(query, 3, 25);
|
.getRelatedSearches(query, 3, 25);
|
||||||
@ -763,8 +749,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.suggestionsFor(serviceId, query)
|
.suggestionsFor(serviceId, query)
|
||||||
.onErrorReturn(throwable -> {
|
.onErrorReturn(throwable -> {
|
||||||
if (!ExceptionUtils.isNetworkRelated(throwable)) {
|
if (!ExceptionUtils.isNetworkRelated(throwable)) {
|
||||||
showSnackBarError(throwable, UserAction.GET_SUGGESTIONS,
|
showSnackBarError(new ErrorInfo(throwable,
|
||||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
UserAction.GET_SUGGESTIONS, searchString, serviceId));
|
||||||
}
|
}
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
})
|
})
|
||||||
@ -800,7 +786,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
if (listNotification.isOnNext()) {
|
if (listNotification.isOnNext()) {
|
||||||
handleSuggestions(listNotification.getValue());
|
handleSuggestions(listNotification.getValue());
|
||||||
} else if (listNotification.isOnError()) {
|
} else if (listNotification.isOnError()) {
|
||||||
onSuggestionError(listNotification.getError());
|
showError(new ErrorInfo(listNotification.getError(),
|
||||||
|
UserAction.GET_SUGGESTIONS, searchString, serviceId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -832,8 +819,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.subscribe(intent -> {
|
.subscribe(intent -> {
|
||||||
getFM().popBackStackImmediate();
|
getFM().popBackStackImmediate();
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}, throwable ->
|
}, throwable -> showTextError(getString(R.string.unsupported_url))));
|
||||||
showError(getString(R.string.unsupported_url), false)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
@ -844,15 +830,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
this.searchString = theSearchString;
|
this.searchString = theSearchString;
|
||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
hideSuggestionsPanel();
|
hideSuggestionsPanel();
|
||||||
|
showMetaInfoInTextView(null, searchBinding.searchMetaInfoTextView,
|
||||||
|
searchBinding.searchMetaInfoSeparator);
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
|
|
||||||
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
|
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
ignored -> {
|
ignored -> { },
|
||||||
},
|
throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED,
|
||||||
error -> showSnackBarError(error, UserAction.SEARCHED,
|
theSearchString, serviceId))
|
||||||
NewPipe.getNameOfService(serviceId), theSearchString, 0)
|
|
||||||
));
|
));
|
||||||
suggestionPublisher.onNext(theSearchString);
|
suggestionPublisher.onNext(theSearchString);
|
||||||
startLoading(false);
|
startLoading(false);
|
||||||
@ -872,7 +859,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
||||||
.subscribe(this::handleResult, this::onError);
|
.subscribe(this::handleResult, this::onItemError);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,7 +882,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||||
.subscribe(this::handleNextItems, this::onError);
|
.subscribe(this::handleNextItems, this::onItemError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -909,6 +896,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onItemError(final Throwable exception) {
|
||||||
|
if (exception instanceof SearchExtractor.NothingFoundException) {
|
||||||
|
infoListAdapter.clearStreamItemList();
|
||||||
|
showEmptyState();
|
||||||
|
} else {
|
||||||
|
showError(new ErrorInfo(exception, UserAction.SEARCHED, searchString, serviceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -945,26 +941,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
searchBinding.suggestionsList.smoothScrollToPosition(0);
|
searchBinding.suggestionsList.smoothScrollToPosition(0);
|
||||||
searchBinding.suggestionsList.post(() -> suggestionListAdapter.setItems(suggestions));
|
searchBinding.suggestionsList.post(() -> suggestionListAdapter.setItems(suggestions));
|
||||||
|
|
||||||
if (suggestionsPanelVisible && errorPanelRoot.getVisibility() == View.VISIBLE) {
|
if (suggestionsPanelVisible && isErrorPanelVisible()) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSuggestionError(final Throwable exception) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
|
|
||||||
}
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof ParsingException
|
|
||||||
? R.string.parsing_error
|
|
||||||
: R.string.general_error;
|
|
||||||
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
|
|
||||||
NewPipe.getNameOfService(serviceId), searchString, errorId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -975,13 +956,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
super.showError(message, showRetryButton);
|
|
||||||
hideSuggestionsPanel();
|
|
||||||
hideKeyboardSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Search Results
|
// Search Results
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -992,8 +966,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
if (!exceptions.isEmpty()
|
if (!exceptions.isEmpty()
|
||||||
&& !(exceptions.size() == 1
|
&& !(exceptions.size() == 1
|
||||||
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
|
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
searchString, serviceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
searchSuggestion = result.getSearchSuggestion();
|
searchSuggestion = result.getSearchSuggestion();
|
||||||
@ -1002,8 +976,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
// List<MetaInfo> cannot be bundled without creating some containers
|
// List<MetaInfo> cannot be bundled without creating some containers
|
||||||
metaInfo = new MetaInfo[result.getMetaInfo().size()];
|
metaInfo = new MetaInfo[result.getMetaInfo().size()];
|
||||||
metaInfo = result.getMetaInfo().toArray(metaInfo);
|
metaInfo = result.getMetaInfo().toArray(metaInfo);
|
||||||
disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView,
|
disposables.add(showMetaInfoInTextView(result.getMetaInfo(),
|
||||||
metaInfoSeparator));
|
searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator));
|
||||||
|
|
||||||
handleSearchSuggestion();
|
handleSearchSuggestion();
|
||||||
|
|
||||||
@ -1061,33 +1035,20 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
nextPage = result.getNextPage();
|
nextPage = result.getNextPage();
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId),
|
|
||||||
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||||
+ "pageIds: " + nextPage.getIds() + ", "
|
+ "pageIds: " + nextPage.getIds() + ", "
|
||||||
+ "pageCookies: " + nextPage.getCookies(), 0);
|
+ "pageCookies: " + nextPage.getCookies(),
|
||||||
|
serviceId));
|
||||||
}
|
}
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(final Throwable exception) {
|
public void handleError() {
|
||||||
if (super.onError(exception)) {
|
super.handleError();
|
||||||
return true;
|
hideSuggestionsPanel();
|
||||||
}
|
hideKeyboardSearch();
|
||||||
|
|
||||||
if (exception instanceof SearchExtractor.NothingFoundException) {
|
|
||||||
infoListAdapter.clearStreamItemList();
|
|
||||||
showEmptyState();
|
|
||||||
} else {
|
|
||||||
final int errorId = exception instanceof ParsingException
|
|
||||||
? R.string.parsing_error
|
|
||||||
: R.string.general_error;
|
|
||||||
onUnrecoverableError(exception, UserAction.SEARCHED,
|
|
||||||
NewPipe.getNameOfService(serviceId), searchString, errorId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -1113,9 +1074,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
howManyDeleted -> suggestionPublisher
|
howManyDeleted -> suggestionPublisher
|
||||||
.onNext(searchEditText.getText().toString()),
|
.onNext(searchEditText.getText().toString()),
|
||||||
throwable -> showSnackBarError(throwable,
|
throwable -> showSnackBarError(new ErrorInfo(throwable,
|
||||||
UserAction.DELETE_FROM_HISTORY, "none",
|
UserAction.DELETE_FROM_HISTORY, "Deleting item failed")));
|
||||||
"Deleting item failed", R.string.general_error));
|
|
||||||
disposables.add(onDelete);
|
disposables.add(onDelete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,11 @@ 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.RelatedStreamsHeaderBinding;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
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.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.RelatedStreamInfo;
|
import org.schabi.newpipe.util.RelatedStreamInfo;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@ -47,6 +46,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RelatedVideosFragment() {
|
||||||
|
super(UserAction.REQUESTED_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -125,43 +128,9 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
}
|
}
|
||||||
ViewUtils.slideUp(requireView(), 120, 96, 0.06f);
|
ViewUtils.slideUp(requireView(), 120, 96, 0.06f);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(serviceId),
|
|
||||||
"Get next page of: " + url,
|
|
||||||
R.string.general_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideLoading();
|
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -190,11 +159,9 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
|||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
||||||
super.onRestoreInstanceState(savedState);
|
super.onRestoreInstanceState(savedState);
|
||||||
if (savedState != null) {
|
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||||
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
if (serializable instanceof RelatedStreamInfo) {
|
||||||
if (serializable instanceof RelatedStreamInfo) {
|
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,11 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
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.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
@ -171,15 +171,15 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
|||||||
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
||||||
try {
|
try {
|
||||||
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
|
||||||
NavigationHelper.openChannelFragment(
|
NavigationHelper.openChannelFragment(
|
||||||
activity.getSupportFragmentManager(),
|
activity.getSupportFragmentManager(),
|
||||||
item.getServiceId(),
|
item.getServiceId(),
|
||||||
item.getUploaderUrl(),
|
item.getUploaderUrl(),
|
||||||
item.getUploaderName());
|
item.getUploaderName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
ErrorActivity.reportUiErrorInSnackbar(activity, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
package org.schabi.newpipe.ktx
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.temporal.ChronoField
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.GregorianCalendar
|
|
||||||
import java.util.TimeZone
|
|
||||||
|
|
||||||
// This method is a modified version of GregorianCalendar.from(ZonedDateTime).
|
|
||||||
// Math.addExact() and Math.multiplyExact() are desugared even though lint displays a warning.
|
|
||||||
@SuppressWarnings("NewApi")
|
|
||||||
fun OffsetDateTime.toCalendar(): Calendar {
|
|
||||||
val cal = GregorianCalendar(TimeZone.getTimeZone("UTC"))
|
|
||||||
val offsetDateTimeUTC = withOffsetSameInstant(ZoneOffset.UTC)
|
|
||||||
cal.gregorianChange = Date(Long.MIN_VALUE)
|
|
||||||
cal.firstDayOfWeek = Calendar.MONDAY
|
|
||||||
cal.minimalDaysInFirstWeek = 4
|
|
||||||
try {
|
|
||||||
cal.timeInMillis = Math.addExact(
|
|
||||||
Math.multiplyExact(offsetDateTimeUTC.toEpochSecond(), 1000),
|
|
||||||
offsetDateTimeUTC[ChronoField.MILLI_OF_SECOND].toLong()
|
|
||||||
)
|
|
||||||
} catch (ex: ArithmeticException) {
|
|
||||||
throw IllegalArgumentException(ex)
|
|
||||||
}
|
|
||||||
return cal
|
|
||||||
}
|
|
@ -319,6 +319,16 @@ fun View.slideUp(duration: Long, delay: Long, @FloatRange(from = 0.0, to = 1.0)
|
|||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instead of hiding normally using [animate], which would make
|
||||||
|
* the recycler view unable to capture touches after being hidden, this just animates the alpha
|
||||||
|
* value setting it to `0.0` after `200` milliseconds.
|
||||||
|
*/
|
||||||
|
fun View.animateHideRecyclerViewAllowingScrolling() {
|
||||||
|
// not hiding normally because the view needs to still capture touches and allow scroll
|
||||||
|
animate().alpha(0.0f).setDuration(200).start()
|
||||||
|
}
|
||||||
|
|
||||||
enum class AnimationType {
|
enum class AnimationType {
|
||||||
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
|
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
|
|||||||
import org.schabi.newpipe.fragments.list.ListViewContract;
|
import org.schabi.newpipe.fragments.list.ListViewContract;
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||||
|
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This fragment is design to be used with persistent data such as
|
* This fragment is design to be used with persistent data such as
|
||||||
@ -184,7 +185,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
if (itemsList != null) {
|
if (itemsList != null) {
|
||||||
animate(itemsList, false, 200);
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
}
|
}
|
||||||
if (headerRootBinding != null) {
|
if (headerRootBinding != null) {
|
||||||
animate(headerRootBinding.getRoot(), false, 200);
|
animate(headerRootBinding.getRoot(), false, 200);
|
||||||
@ -202,19 +203,6 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
super.showError(message, showRetryButton);
|
|
||||||
showListFooter(false);
|
|
||||||
|
|
||||||
if (itemsList != null) {
|
|
||||||
animate(itemsList, false, 200);
|
|
||||||
}
|
|
||||||
if (headerRootBinding != null) {
|
|
||||||
animate(headerRootBinding.getRoot(), false, 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
super.showEmptyState();
|
super.showEmptyState();
|
||||||
@ -249,9 +237,18 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(final Throwable exception) {
|
public void handleError() {
|
||||||
|
super.handleError();
|
||||||
resetFragment();
|
resetFragment();
|
||||||
return super.onError(exception);
|
|
||||||
|
showListFooter(false);
|
||||||
|
|
||||||
|
if (itemsList != null) {
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
}
|
||||||
|
if (headerRootBinding != null) {
|
||||||
|
animate(headerRootBinding.getRoot(), false, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,10 +23,11 @@ import org.schabi.newpipe.database.LocalItem;
|
|||||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
|
||||||
@ -206,7 +207,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
BookmarkFragment.this.onError(exception);
|
showError(new ErrorInfo(exception,
|
||||||
|
UserAction.REQUESTED_BOOKMARK, "Loading playlists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -237,17 +239,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
// Fragment Error Handling
|
// Fragment Error Handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
|
||||||
"none", "Bookmark", R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void resetFragment() {
|
protected void resetFragment() {
|
||||||
super.resetFragment();
|
super.resetFragment();
|
||||||
@ -295,8 +286,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
.setPositiveButton(R.string.delete, (dialog, i) ->
|
.setPositiveButton(R.string.delete, (dialog, i) ->
|
||||||
disposables.add(deleteReactor
|
disposables.add(deleteReactor
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> { /*Do nothing on success*/ }, this::onError))
|
.subscribe(ignored -> { /*Do nothing on success*/ }, throwable ->
|
||||||
)
|
showError(new ErrorInfo(throwable,
|
||||||
|
UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Deleting playlist")))))
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
@ -314,7 +307,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
localPlaylistManager.renamePlaylist(id, name);
|
localPlaylistManager.renamePlaylist(id, name);
|
||||||
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
|
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
|
.subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
|
||||||
|
new ErrorInfo(throwable,
|
||||||
|
UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Changing playlist name")));
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,17 +38,18 @@ import icepick.State
|
|||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.databinding.FragmentFeedBinding
|
import org.schabi.newpipe.databinding.FragmentFeedBinding
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo
|
||||||
|
import org.schabi.newpipe.error.UserAction
|
||||||
import org.schabi.newpipe.fragments.list.BaseListFragment
|
import org.schabi.newpipe.fragments.list.BaseListFragment
|
||||||
import org.schabi.newpipe.ktx.animate
|
import org.schabi.newpipe.ktx.animate
|
||||||
|
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
|
||||||
import org.schabi.newpipe.local.feed.service.FeedLoadService
|
import org.schabi.newpipe.local.feed.service.FeedLoadService
|
||||||
import org.schabi.newpipe.report.UserAction
|
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import java.util.Calendar
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
private var _feedBinding: FragmentFeedBinding? = null
|
private var _feedBinding: FragmentFeedBinding? = null
|
||||||
private val feedBinding get() = _feedBinding!!
|
private val feedBinding get() = _feedBinding!!
|
||||||
private val errorBinding get() = _feedBinding!!.errorPanel
|
|
||||||
|
|
||||||
private lateinit var viewModel: FeedViewModel
|
private lateinit var viewModel: FeedViewModel
|
||||||
@State
|
@State
|
||||||
@ -57,7 +58,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
|
|
||||||
private var groupId = FeedGroupEntity.GROUP_ALL_ID
|
private var groupId = FeedGroupEntity.GROUP_ALL_ID
|
||||||
private var groupName = ""
|
private var groupName = ""
|
||||||
private var oldestSubscriptionUpdate: Calendar? = null
|
private var oldestSubscriptionUpdate: OffsetDateTime? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
@ -106,7 +107,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
override fun initListeners() {
|
override fun initListeners() {
|
||||||
super.initListeners()
|
super.initListeners()
|
||||||
feedBinding.refreshRootView.setOnClickListener { reloadContent() }
|
feedBinding.refreshRootView.setOnClickListener { reloadContent() }
|
||||||
feedBinding.swiperefresh.setOnRefreshListener { reloadContent() }
|
feedBinding.swipeRefreshLayout.setOnRefreshListener { reloadContent() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
@ -171,50 +172,26 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun showLoading() {
|
override fun showLoading() {
|
||||||
|
super.showLoading()
|
||||||
|
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
|
||||||
feedBinding.refreshRootView.animate(false, 0)
|
feedBinding.refreshRootView.animate(false, 0)
|
||||||
feedBinding.itemsList.animate(false, 0)
|
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(true, 200)
|
|
||||||
feedBinding.loadingProgressText.animate(true, 200)
|
feedBinding.loadingProgressText.animate(true, 200)
|
||||||
|
feedBinding.swipeRefreshLayout.isRefreshing = true
|
||||||
feedBinding.emptyStateView.root.animate(false, 0)
|
|
||||||
errorBinding.root.animate(false, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideLoading() {
|
override fun hideLoading() {
|
||||||
|
super.hideLoading()
|
||||||
feedBinding.refreshRootView.animate(true, 200)
|
feedBinding.refreshRootView.animate(true, 200)
|
||||||
feedBinding.itemsList.animate(true, 300)
|
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(false, 0)
|
|
||||||
feedBinding.loadingProgressText.animate(false, 0)
|
feedBinding.loadingProgressText.animate(false, 0)
|
||||||
|
feedBinding.swipeRefreshLayout.isRefreshing = false
|
||||||
feedBinding.emptyStateView.root.animate(false, 0)
|
|
||||||
errorBinding.root.animate(false, 0)
|
|
||||||
feedBinding.swiperefresh.isRefreshing = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmptyState() {
|
override fun showEmptyState() {
|
||||||
|
super.showEmptyState()
|
||||||
|
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
|
||||||
feedBinding.refreshRootView.animate(true, 200)
|
feedBinding.refreshRootView.animate(true, 200)
|
||||||
feedBinding.itemsList.animate(false, 0)
|
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(false, 0)
|
|
||||||
feedBinding.loadingProgressText.animate(false, 0)
|
feedBinding.loadingProgressText.animate(false, 0)
|
||||||
|
feedBinding.swipeRefreshLayout.isRefreshing = false
|
||||||
feedBinding.emptyStateView.root.animate(true, 800)
|
|
||||||
errorBinding.root.animate(false, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showError(message: String, showRetryButton: Boolean) {
|
|
||||||
infoListAdapter.clearStreamItemList()
|
|
||||||
feedBinding.refreshRootView.animate(false, 120)
|
|
||||||
feedBinding.itemsList.animate(false, 120)
|
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(false, 120)
|
|
||||||
feedBinding.loadingProgressText.animate(false, 120)
|
|
||||||
|
|
||||||
errorBinding.errorMessageView.text = message
|
|
||||||
errorBinding.errorButtonRetry.animate(showRetryButton, if (showRetryButton) 600 else 0)
|
|
||||||
errorBinding.root.animate(true, 300)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleResult(result: FeedState) {
|
override fun handleResult(result: FeedState) {
|
||||||
@ -227,6 +204,15 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
updateRefreshViewState()
|
updateRefreshViewState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleError() {
|
||||||
|
super.handleError()
|
||||||
|
infoListAdapter.clearStreamItemList()
|
||||||
|
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
|
||||||
|
feedBinding.refreshRootView.animate(false, 0)
|
||||||
|
feedBinding.loadingProgressText.animate(false, 0)
|
||||||
|
feedBinding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleProgressState(progressState: FeedState.ProgressState) {
|
private fun handleProgressState(progressState: FeedState.ProgressState) {
|
||||||
showLoading()
|
showLoading()
|
||||||
|
|
||||||
@ -266,13 +252,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadedState.itemsErrors.isNotEmpty()) {
|
|
||||||
showSnackBarError(
|
|
||||||
loadedState.itemsErrors, UserAction.REQUESTED_FEED,
|
|
||||||
"none", "Loading feed", R.string.general_error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadedState.items.isEmpty()) {
|
if (loadedState.items.isEmpty()) {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
} else {
|
} else {
|
||||||
@ -281,12 +260,13 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
||||||
hideLoading()
|
return if (errorState.error == null) {
|
||||||
errorState.error?.let {
|
hideLoading()
|
||||||
onError(errorState.error)
|
false
|
||||||
return true
|
} else {
|
||||||
|
showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed"))
|
||||||
|
true
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRelativeTimeViews() {
|
private fun updateRelativeTimeViews() {
|
||||||
@ -295,12 +275,10 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRefreshViewState() {
|
private fun updateRefreshViewState() {
|
||||||
val oldestSubscriptionUpdateText = when {
|
feedBinding.refreshText.text = getString(
|
||||||
oldestSubscriptionUpdate != null -> Localization.relativeTime(oldestSubscriptionUpdate!!)
|
R.string.feed_oldest_subscription_update,
|
||||||
else -> "—"
|
oldestSubscriptionUpdate?.let { Localization.relativeTime(it) } ?: "—"
|
||||||
}
|
)
|
||||||
|
|
||||||
feedBinding.refreshText.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
@ -320,18 +298,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||||||
listState = null
|
listState = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(exception: Throwable): Boolean {
|
|
||||||
if (super.onError(exception)) return true
|
|
||||||
|
|
||||||
if (useAsFrontPage) {
|
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KEY_GROUP_ID = "ARG_GROUP_ID"
|
const val KEY_GROUP_ID = "ARG_GROUP_ID"
|
||||||
const val KEY_GROUP_NAME = "ARG_GROUP_NAME"
|
const val KEY_GROUP_NAME = "ARG_GROUP_NAME"
|
||||||
|
@ -2,7 +2,7 @@ package org.schabi.newpipe.local.feed
|
|||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import java.util.Calendar
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
sealed class FeedState {
|
sealed class FeedState {
|
||||||
data class ProgressState(
|
data class ProgressState(
|
||||||
@ -13,7 +13,7 @@ sealed class FeedState {
|
|||||||
|
|
||||||
data class LoadedState(
|
data class LoadedState(
|
||||||
val items: List<StreamInfoItem>,
|
val items: List<StreamInfoItem>,
|
||||||
val oldestUpdate: Calendar? = null,
|
val oldestUpdate: OffsetDateTime? = null,
|
||||||
val notLoadedCount: Long,
|
val notLoadedCount: Long,
|
||||||
val itemsErrors: List<Throwable> = emptyList()
|
val itemsErrors: List<Throwable> = emptyList()
|
||||||
) : FeedState()
|
) : FeedState()
|
||||||
|
@ -11,7 +11,6 @@ import io.reactivex.rxjava3.functions.Function4
|
|||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.ktx.toCalendar
|
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager
|
import org.schabi.newpipe.local.feed.service.FeedEventManager
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||||
@ -48,13 +47,11 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
|
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
|
||||||
val oldestUpdateCalendar = oldestUpdate?.toCalendar()
|
|
||||||
|
|
||||||
mutableStateLiveData.postValue(
|
mutableStateLiveData.postValue(
|
||||||
when (event) {
|
when (event) {
|
||||||
is IdleEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount)
|
is IdleEvent -> FeedState.LoadedState(listFromDB, oldestUpdate, notLoadedCount)
|
||||||
is ProgressEvent -> FeedState.ProgressState(event.currentProgress, event.maxProgress, event.progressMessage)
|
is ProgressEvent -> FeedState.ProgressState(event.currentProgress, event.maxProgress, event.progressMessage)
|
||||||
is SuccessResultEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount, event.itemsErrors)
|
is SuccessResultEvent -> FeedState.LoadedState(listFromDB, oldestUpdate, notLoadedCount, event.itemsErrors)
|
||||||
is ErrorResultEvent -> FeedState.ErrorState(event.error)
|
is ErrorResultEvent -> FeedState.ErrorState(event.error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,6 @@ 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 androidx.viewbinding.ViewBinding;
|
import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
@ -27,6 +26,8 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
|||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
|
import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
@ -34,10 +35,7 @@ import org.schabi.newpipe.local.BaseLocalListFragment;
|
|||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.settings.HistorySettingsFragment;
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
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;
|
||||||
@ -49,6 +47,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
@ -163,48 +162,11 @@ public class StatisticsPlaylistFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.action_history_clear) {
|
||||||
case R.id.action_history_clear:
|
HistorySettingsFragment
|
||||||
new AlertDialog.Builder(activity)
|
.openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
|
||||||
.setTitle(R.string.delete_view_history_alert)
|
} else {
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
return super.onOptionsItemSelected(item);
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
|
||||||
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
howManyDeleted -> Toast.makeText(getContext(),
|
|
||||||
R.string.watch_history_deleted,
|
|
||||||
Toast.LENGTH_SHORT).show(),
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete view history",
|
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
howManyDeleted -> {
|
|
||||||
},
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete search history",
|
|
||||||
R.string.general_error)));
|
|
||||||
disposables.add(onClearOrphans);
|
|
||||||
disposables.add(onDelete);
|
|
||||||
}))
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -228,7 +190,7 @@ public class StatisticsPlaylistFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
itemsListState = Objects.requireNonNull(itemsList.getLayoutManager()).onSaveInstanceState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -287,7 +249,8 @@ public class StatisticsPlaylistFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
StatisticsPlaylistFragment.this.onError(exception);
|
showError(
|
||||||
|
new ErrorInfo(exception, UserAction.SOMETHING_ELSE, "History Statistics"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -313,7 +276,7 @@ public class StatisticsPlaylistFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemListAdapter.addItems(processResult(result));
|
itemListAdapter.addItems(processResult(result));
|
||||||
if (itemsListState != null) {
|
if (itemsListState != null && itemsList.getLayoutManager() != null) {
|
||||||
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||||
itemsListState = null;
|
itemsListState = null;
|
||||||
}
|
}
|
||||||
@ -341,17 +304,6 @@ public class StatisticsPlaylistFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
|
||||||
"none", "History Statistics", R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -439,9 +391,8 @@ public class StatisticsPlaylistFragment
|
|||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
throwable -> showSnackBarError(throwable,
|
throwable -> showSnackBarError(new ErrorInfo(throwable,
|
||||||
UserAction.DELETE_FROM_HISTORY, "none",
|
UserAction.DELETE_FROM_HISTORY, "Deleting item")));
|
||||||
"Deleting item failed", R.string.general_error));
|
|
||||||
|
|
||||||
disposables.add(onDelete);
|
disposables.add(onDelete);
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
|
|||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding;
|
import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
@ -42,7 +44,6 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
|||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
@ -110,7 +111,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
|
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
|
||||||
debouncedSaveSignal = PublishSubject.create();
|
debouncedSaveSignal = PublishSubject.create();
|
||||||
|
|
||||||
disposables = new CompositeDisposable();
|
disposables = new CompositeDisposable();
|
||||||
@ -334,7 +335,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
LocalPlaylistFragment.this.onError(exception);
|
showError(new ErrorInfo(exception, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Loading local playlist"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -344,25 +346,23 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.menu_item_remove_watched) {
|
||||||
case R.id.menu_item_remove_watched:
|
if (!isRemovingWatched) {
|
||||||
if (!isRemovingWatched) {
|
new AlertDialog.Builder(requireContext())
|
||||||
new AlertDialog.Builder(requireContext())
|
.setMessage(R.string.remove_watched_popup_warning)
|
||||||
.setMessage(R.string.remove_watched_popup_warning)
|
.setTitle(R.string.remove_watched_popup_title)
|
||||||
.setTitle(R.string.remove_watched_popup_title)
|
.setPositiveButton(R.string.yes,
|
||||||
.setPositiveButton(R.string.yes,
|
(DialogInterface d, int id) -> removeWatchedStreams(false))
|
||||||
(DialogInterface d, int id) -> removeWatchedStreams(false))
|
.setNeutralButton(
|
||||||
.setNeutralButton(
|
R.string.remove_watched_popup_yes_and_partially_watched_videos,
|
||||||
R.string.remove_watched_popup_yes_and_partially_watched_videos,
|
(DialogInterface d, int id) -> removeWatchedStreams(true))
|
||||||
(DialogInterface d, int id) -> removeWatchedStreams(true))
|
.setNegativeButton(R.string.cancel,
|
||||||
.setNegativeButton(R.string.cancel,
|
(DialogInterface d, int id) -> d.cancel())
|
||||||
(DialogInterface d, int id) -> d.cancel())
|
.create()
|
||||||
.create()
|
.show();
|
||||||
.show();
|
}
|
||||||
}
|
} else {
|
||||||
break;
|
return super.onOptionsItemSelected(item);
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -455,7 +455,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
|
|
||||||
hideLoading();
|
hideLoading();
|
||||||
isRemovingWatched = false;
|
isRemovingWatched = false;
|
||||||
}, this::onError));
|
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Removing watched videos, partially watched=" + removePartiallyWatched))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -511,17 +512,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
|
||||||
"none", "Local Playlist", R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Playlist Metadata/Streams Manipulation
|
// Playlist Metadata/Streams Manipulation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -562,7 +552,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
|
|
||||||
final Disposable disposable = playlistManager.renamePlaylist(playlistId, title)
|
final Disposable disposable = playlistManager.renamePlaylist(playlistId, title)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
|
.subscribe(longs -> { /*Do nothing on success*/ }, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Renaming playlist")));
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,7 +575,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
final Disposable disposable = playlistManager
|
final Disposable disposable = playlistManager
|
||||||
.changePlaylistThumbnail(playlistId, thumbnailUrl)
|
.changePlaylistThumbnail(playlistId, thumbnailUrl)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignore -> successToast.show(), this::onError);
|
.subscribe(ignore -> successToast.show(), throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Changing playlist thumbnail")));
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,7 +626,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
return debouncedSaveSignal
|
return debouncedSaveSignal
|
||||||
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> saveImmediate(), this::onError);
|
.subscribe(ignored -> saveImmediate(), throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
|
||||||
|
"Debounced saver")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveImmediate() {
|
private void saveImmediate() {
|
||||||
@ -669,7 +665,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
isModified.set(false);
|
isModified.set(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this::onError
|
throwable -> showError(new ErrorInfo(throwable,
|
||||||
|
UserAction.REQUESTED_BOOKMARK, "Saving playlist"))
|
||||||
);
|
);
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
@ -683,7 +680,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
return new ItemTouchHelper.SimpleCallback(directions,
|
return new ItemTouchHelper.SimpleCallback(directions,
|
||||||
ItemTouchHelper.ACTION_STATE_IDLE) {
|
ItemTouchHelper.ACTION_STATE_IDLE) {
|
||||||
@Override
|
@Override
|
||||||
public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView,
|
public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView,
|
||||||
final int viewSize,
|
final int viewSize,
|
||||||
final int viewSizeOutOfBounds,
|
final int viewSizeOutOfBounds,
|
||||||
final int totalSize,
|
final int totalSize,
|
||||||
@ -696,9 +693,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMove(final RecyclerView recyclerView,
|
public boolean onMove(@NonNull final RecyclerView recyclerView,
|
||||||
final RecyclerView.ViewHolder source,
|
@NonNull final RecyclerView.ViewHolder source,
|
||||||
final RecyclerView.ViewHolder target) {
|
@NonNull final RecyclerView.ViewHolder target) {
|
||||||
if (source.getItemViewType() != target.getItemViewType()
|
if (source.getItemViewType() != target.getItemViewType()
|
||||||
|| itemListAdapter == null) {
|
|| itemListAdapter == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -724,7 +721,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { }
|
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||||
|
final int swipeDir) { }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
|||||||
import org.schabi.newpipe.databinding.DialogTitleBinding
|
import org.schabi.newpipe.databinding.DialogTitleBinding
|
||||||
import org.schabi.newpipe.databinding.FeedItemCarouselBinding
|
import org.schabi.newpipe.databinding.FeedItemCarouselBinding
|
||||||
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
|
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo
|
||||||
|
import org.schabi.newpipe.error.UserAction
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||||
import org.schabi.newpipe.fragments.BaseStateFragment
|
import org.schabi.newpipe.fragments.BaseStateFragment
|
||||||
import org.schabi.newpipe.ktx.animate
|
import org.schabi.newpipe.ktx.animate
|
||||||
@ -56,7 +58,6 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
|||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
||||||
import org.schabi.newpipe.report.UserAction
|
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper
|
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
|
||||||
@ -288,8 +289,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||||||
binding.itemsList.adapter = groupAdapter
|
binding.itemsList.adapter = groupAdapter
|
||||||
|
|
||||||
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
|
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
|
||||||
viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(this::handleResult) })
|
viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) }
|
||||||
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, { it?.let(this::handleFeedGroups) })
|
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) { it?.let(this::handleFeedGroups) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
|
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
|
||||||
@ -381,7 +382,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SubscriptionState.ErrorState -> {
|
is SubscriptionState.ErrorState -> {
|
||||||
result.error?.let { onError(result.error) }
|
result.error?.let {
|
||||||
|
showError(ErrorInfo(result.error, UserAction.SOMETHING_ELSE, "Subscriptions"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -412,17 +415,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||||||
binding.itemsList.animate(true, 200)
|
binding.itemsList.animate(true, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
// Fragment Error Handling
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
override fun onError(exception: Throwable): Boolean {
|
|
||||||
if (super.onError(exception)) return true
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Subscriptions", R.string.general_error)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Grid Mode
|
// Grid Mode
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -22,13 +22,13 @@ import com.nononsenseapps.filepicker.Utils;
|
|||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
@ -84,10 +84,12 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
|||||||
|
|
||||||
setupServiceVariables();
|
setupServiceVariables();
|
||||||
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
|
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
|
||||||
ErrorActivity.reportError(activity, Collections.emptyList(), null, null,
|
ErrorActivity.reportErrorInSnackbar(activity,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE,
|
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
|
||||||
NewPipe.getNameOfService(currentServiceId),
|
NewPipe.getNameOfService(currentServiceId),
|
||||||
"Service don't support importing", R.string.general_error));
|
"Service does not support importing subscriptions",
|
||||||
|
R.string.general_error,
|
||||||
|
null));
|
||||||
activity.finish();
|
activity.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,6 @@ import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
|||||||
import org.schabi.newpipe.util.DeviceUtils
|
import org.schabi.newpipe.util.DeviceUtils
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import kotlin.collections.contains
|
|
||||||
|
|
||||||
class FeedGroupDialog : DialogFragment(), BackPressable {
|
class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||||
private var _feedGroupCreateBinding: DialogFeedGroupCreateBinding? = null
|
private var _feedGroupCreateBinding: DialogFeedGroupCreateBinding? = null
|
||||||
|
@ -24,6 +24,10 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo
|
|||||||
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.map
|
||||||
|
import kotlin.collections.sortedBy
|
||||||
|
|
||||||
class FeedGroupReorderDialog : DialogFragment() {
|
class FeedGroupReorderDialog : DialogFragment() {
|
||||||
private var _binding: DialogFeedGroupReorderBinding? = null
|
private var _binding: DialogFeedGroupReorderBinding? = null
|
||||||
|
@ -35,15 +35,14 @@ import androidx.core.app.ServiceCompat;
|
|||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@ -152,13 +151,10 @@ public abstract class BaseImportExportService extends Service {
|
|||||||
postErrorResult(null, null);
|
postErrorResult(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void stopAndReportError(@Nullable final Throwable error, final String request) {
|
protected void stopAndReportError(final Throwable throwable, final String request) {
|
||||||
stopService();
|
stopService();
|
||||||
|
ErrorActivity.reportError(this, new ErrorInfo(
|
||||||
final ErrorInfo errorInfo = ErrorInfo
|
throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request));
|
||||||
.make(UserAction.SUBSCRIPTION, "unknown", request, R.string.general_error);
|
|
||||||
ErrorActivity.reportError(this, error != null ? Collections.singletonList(error)
|
|
||||||
: Collections.emptyList(), null, null, errorInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void postErrorResult(final String title, final String text) {
|
protected void postErrorResult(final String title, final String text) {
|
||||||
|
@ -601,7 +601,8 @@ public final class Player implements
|
|||||||
final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this);
|
final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this);
|
||||||
final float playbackSpeed = savedParameters.speed;
|
final float playbackSpeed = savedParameters.speed;
|
||||||
final float playbackPitch = savedParameters.pitch;
|
final float playbackPitch = savedParameters.pitch;
|
||||||
final boolean playbackSkipSilence = savedParameters.skipSilence;
|
final boolean playbackSkipSilence = getPrefs().getBoolean(getContext().getString(
|
||||||
|
R.string.playback_skip_silence_key), getPlaybackSkipSilence());
|
||||||
|
|
||||||
final boolean samePlayQueue = playQueue != null && playQueue.equals(newQueue);
|
final boolean samePlayQueue = playQueue != null && playQueue.equals(newQueue);
|
||||||
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
||||||
@ -1129,6 +1130,12 @@ public final class Player implements
|
|||||||
// Close it because when changing orientation from portrait
|
// Close it because when changing orientation from portrait
|
||||||
// (in fullscreen mode) the size of queue layout can be larger than the screen size
|
// (in fullscreen mode) the size of queue layout can be larger than the screen size
|
||||||
closeItemsList();
|
closeItemsList();
|
||||||
|
// When the orientation changed, the screen height might be smaller.
|
||||||
|
// If the end screen thumbnail is not re-scaled,
|
||||||
|
// it can be larger than the current screen height
|
||||||
|
// and thus enlarging the whole player.
|
||||||
|
// This causes the seekbar to be ouf the visible area.
|
||||||
|
updateEndScreenThumbnail();
|
||||||
break;
|
break;
|
||||||
case Intent.ACTION_SCREEN_ON:
|
case Intent.ACTION_SCREEN_ON:
|
||||||
// Interrupt playback only when screen turns on
|
// Interrupt playback only when screen turns on
|
||||||
@ -1187,6 +1194,78 @@ public final class Player implements
|
|||||||
.loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this);
|
.loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale the player audio / end screen thumbnail down if necessary.
|
||||||
|
* <p>
|
||||||
|
* This is necessary when the thumbnail's height is larger than the device's height
|
||||||
|
* and thus is enlarging the player's height
|
||||||
|
* causing the bottom playback controls to be out of the visible screen.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public void updateEndScreenThumbnail() {
|
||||||
|
if (currentThumbnail == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float endScreenHeight = calculateMaxEndScreenThumbnailHeight();
|
||||||
|
|
||||||
|
final Bitmap endScreenBitmap = Bitmap.createScaledBitmap(
|
||||||
|
currentThumbnail,
|
||||||
|
(int) (currentThumbnail.getWidth()
|
||||||
|
/ (currentThumbnail.getHeight() / endScreenHeight)),
|
||||||
|
(int) endScreenHeight,
|
||||||
|
true);
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Thumbnail - updateEndScreenThumbnail() called with: "
|
||||||
|
+ "currentThumbnail = [" + currentThumbnail + "], "
|
||||||
|
+ currentThumbnail.getWidth() + "x" + currentThumbnail.getHeight()
|
||||||
|
+ ", scaled end screen height = " + endScreenHeight
|
||||||
|
+ ", scaled end screen width = " + endScreenBitmap.getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.endScreen.setImageBitmap(endScreenBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the maximum allowed height for the {@link R.id.endScreen}
|
||||||
|
* to prevent it from enlarging the player.
|
||||||
|
* <p>
|
||||||
|
* The calculating follows these rules:
|
||||||
|
* <ul>
|
||||||
|
* <li>
|
||||||
|
* Show at least stream title and content creator on TVs and tablets
|
||||||
|
* when in landscape (always the case for TVs) and not in fullscreen mode.
|
||||||
|
* This requires to have at least <code>85dp</code> free space for {@link R.id.detail_root}
|
||||||
|
* and additional space for the stream title text size
|
||||||
|
* ({@link R.id.detail_title_root_layout}).
|
||||||
|
* The text size is <code>15sp</code> on tablets and <code>16sp</code> on TVs,
|
||||||
|
* see {@link R.id.titleTextView}.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* Otherwise, the max thumbnail height is the screen height.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return the maximum height for the end screen thumbnail
|
||||||
|
*/
|
||||||
|
private float calculateMaxEndScreenThumbnailHeight() {
|
||||||
|
// ensure that screenHeight is initialized and thus not 0
|
||||||
|
updateScreenSize();
|
||||||
|
|
||||||
|
if (DeviceUtils.isTv(context) && !isFullscreen) {
|
||||||
|
final int videoInfoHeight =
|
||||||
|
DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context);
|
||||||
|
return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight);
|
||||||
|
} else if (DeviceUtils.isTablet(context) && service.isLandscape() && !isFullscreen) {
|
||||||
|
final int videoInfoHeight =
|
||||||
|
DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context);
|
||||||
|
return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight);
|
||||||
|
} else { // fullscreen player: max height is the device height
|
||||||
|
return Math.min(currentThumbnail.getHeight(), screenHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingStarted(final String imageUri, final View view) {
|
public void onLoadingStarted(final String imageUri, final View view) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -1207,23 +1286,29 @@ public final class Player implements
|
|||||||
@Override
|
@Override
|
||||||
public void onLoadingComplete(final String imageUri, final View view,
|
public void onLoadingComplete(final String imageUri, final View view,
|
||||||
final Bitmap loadedImage) {
|
final Bitmap loadedImage) {
|
||||||
final float width = Math.min(
|
// scale down the notification thumbnail for performance
|
||||||
|
final float notificationThumbnailWidth = Math.min(
|
||||||
context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
|
context.getResources().getDimension(R.dimen.player_notification_thumbnail_width),
|
||||||
loadedImage.getWidth());
|
loadedImage.getWidth());
|
||||||
|
currentThumbnail = Bitmap.createScaledBitmap(
|
||||||
|
loadedImage,
|
||||||
|
(int) notificationThumbnailWidth,
|
||||||
|
(int) (loadedImage.getHeight()
|
||||||
|
/ (loadedImage.getWidth() / notificationThumbnailWidth)),
|
||||||
|
true);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
|
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
|
||||||
+ "imageUri = [" + imageUri + "], view = [" + view + "], "
|
+ "imageUri = [" + imageUri + "], view = [" + view + "], "
|
||||||
+ "loadedImage = [" + loadedImage + "], "
|
+ "loadedImage = [" + loadedImage + "], "
|
||||||
+ loadedImage.getWidth() + "x" + loadedImage.getHeight()
|
+ loadedImage.getWidth() + "x" + loadedImage.getHeight()
|
||||||
+ ", scaled width = " + width);
|
+ ", scaled notification width = " + notificationThumbnailWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentThumbnail = Bitmap.createScaledBitmap(loadedImage,
|
|
||||||
(int) width,
|
|
||||||
(int) (loadedImage.getHeight() / (loadedImage.getWidth() / width)), true);
|
|
||||||
binding.endScreen.setImageBitmap(loadedImage);
|
|
||||||
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
|
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false);
|
||||||
|
|
||||||
|
// there is a new thumbnail, thus the end screen thumbnail needs to be changed, too.
|
||||||
|
updateEndScreenThumbnail();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1432,7 +1517,8 @@ public final class Player implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean getPlaybackSkipSilence() {
|
public boolean getPlaybackSkipSilence() {
|
||||||
return getPlaybackParameters().skipSilence;
|
return !exoPlayerIsNull() && simpleExoPlayer.getAudioComponent() != null
|
||||||
|
&& simpleExoPlayer.getAudioComponent().getSkipSilenceEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlaybackParameters getPlaybackParameters() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
@ -1457,7 +1543,10 @@ public final class Player implements
|
|||||||
|
|
||||||
savePlaybackParametersToPrefs(this, roundedSpeed, roundedPitch, skipSilence);
|
savePlaybackParametersToPrefs(this, roundedSpeed, roundedPitch, skipSilence);
|
||||||
simpleExoPlayer.setPlaybackParameters(
|
simpleExoPlayer.setPlaybackParameters(
|
||||||
new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
|
new PlaybackParameters(roundedSpeed, roundedPitch));
|
||||||
|
if (simpleExoPlayer.getAudioComponent() != null) {
|
||||||
|
simpleExoPlayer.getAudioComponent().setSkipSilenceEnabled(skipSilence);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@ -2333,6 +2422,7 @@ public final class Player implements
|
|||||||
case ExoPlaybackException.TYPE_OUT_OF_MEMORY:
|
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();
|
||||||
@ -3355,7 +3445,7 @@ public final class Player implements
|
|||||||
final List<String> availableLanguages = new ArrayList<>(textTracks.length);
|
final List<String> availableLanguages = new ArrayList<>(textTracks.length);
|
||||||
for (int i = 0; i < textTracks.length; i++) {
|
for (int i = 0; i < textTracks.length; i++) {
|
||||||
final TrackGroup textTrack = textTracks.get(i);
|
final TrackGroup textTrack = textTracks.get(i);
|
||||||
if (textTrack.length > 0 && textTrack.getFormat(0) != null) {
|
if (textTrack.length > 0) {
|
||||||
availableLanguages.add(textTrack.getFormat(0).language);
|
availableLanguages.add(textTrack.getFormat(0).language);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,12 +80,14 @@ public class LoadController implements LoadControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldContinueLoading(final long bufferedDurationUs,
|
public boolean shouldContinueLoading(final long playbackPositionUs,
|
||||||
|
final long bufferedDurationUs,
|
||||||
final float playbackSpeed) {
|
final float playbackSpeed) {
|
||||||
if (!preloadingEnabled) {
|
if (!preloadingEnabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return internalLoadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
|
return internalLoadControl.shouldContinueLoading(
|
||||||
|
playbackPositionUs, bufferedDurationUs, playbackSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -484,8 +484,9 @@ public final class PlayerHelper {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save the new resize mode so it can be restored in a future session
|
||||||
player.getPrefs().edit().putInt(
|
player.getPrefs().edit().putInt(
|
||||||
player.getContext().getString(R.string.last_resize_mode), resizeMode).apply();
|
player.getContext().getString(R.string.last_resize_mode), newResizeMode).apply();
|
||||||
return newResizeMode;
|
return newResizeMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,9 +495,7 @@ public final class PlayerHelper {
|
|||||||
R.string.playback_speed_key), player.getPlaybackSpeed());
|
R.string.playback_speed_key), player.getPlaybackSpeed());
|
||||||
final float pitch = player.getPrefs().getFloat(player.getContext().getString(
|
final float pitch = player.getPrefs().getFloat(player.getContext().getString(
|
||||||
R.string.playback_pitch_key), player.getPlaybackPitch());
|
R.string.playback_pitch_key), player.getPlaybackPitch());
|
||||||
final boolean skipSilence = player.getPrefs().getBoolean(player.getContext().getString(
|
return new PlaybackParameters(speed, pitch);
|
||||||
R.string.playback_skip_silence_key), player.getPlaybackSkipSilence());
|
|
||||||
return new PlaybackParameters(speed, pitch, skipSilence);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void savePlaybackParametersToPrefs(final Player player,
|
public static void savePlaybackParametersToPrefs(final Player player,
|
||||||
|
@ -5,6 +5,7 @@ import android.util.Log;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
@ -54,6 +55,14 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo
|
|||||||
return System.currentTimeMillis() >= retryTimestamp;
|
return System.currentTimeMillis() >= retryTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link MediaItem} whose media is provided by the source.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MediaItem getMediaItem() {
|
||||||
|
return MediaItem.fromUri(playQueueItem.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||||
throw new IOException(error);
|
throw new IOException(error);
|
||||||
|
@ -5,6 +5,8 @@ import android.os.Handler;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||||
@ -83,6 +85,38 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
|||||||
source.removeEventListener(eventListener);
|
source.removeEventListener(eventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link DrmSessionEventListener} to the list of listeners which are notified of DRM
|
||||||
|
* events for this media source.
|
||||||
|
*
|
||||||
|
* @param handler A handler on the which listener events will be posted.
|
||||||
|
* @param eventListener The listener to be added.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addDrmEventListener(final Handler handler,
|
||||||
|
final DrmSessionEventListener eventListener) {
|
||||||
|
source.addDrmEventListener(handler, eventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a {@link DrmSessionEventListener} from the list of listeners which are notified of
|
||||||
|
* DRM events for this media source.
|
||||||
|
*
|
||||||
|
* @param eventListener The listener to be removed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeDrmEventListener(final DrmSessionEventListener eventListener) {
|
||||||
|
source.removeDrmEventListener(eventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link MediaItem} whose media is provided by the source.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MediaItem getMediaItem() {
|
||||||
|
return source.getMediaItem();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
|
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
|
||||||
final boolean isInterruptable) {
|
final boolean isInterruptable) {
|
||||||
|
@ -3,6 +3,7 @@ package org.schabi.newpipe.player.mediasource;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
@ -11,6 +12,14 @@ import com.google.android.exoplayer2.upstream.TransferListener;
|
|||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
|
|
||||||
public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
||||||
|
/**
|
||||||
|
* Returns the {@link MediaItem} whose media is provided by the source.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MediaItem getMediaItem() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Do nothing, so this will stall the playback
|
// Do nothing, so this will stall the playback
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowSourceInfoRefreshError() { }
|
public void maybeThrowSourceInfoRefreshError() { }
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package org.schabi.newpipe.report
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import kotlinx.android.parcel.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
class ErrorInfo(
|
|
||||||
val userAction: UserAction?,
|
|
||||||
val serviceName: String,
|
|
||||||
val request: String,
|
|
||||||
@field:StringRes @param:StringRes val message: Int
|
|
||||||
) : Parcelable {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun make(
|
|
||||||
userAction: UserAction?,
|
|
||||||
serviceName: String,
|
|
||||||
request: String,
|
|
||||||
@StringRes message: Int
|
|
||||||
) = ErrorInfo(userAction, serviceName, request, message)
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,40 +18,43 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
|||||||
private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE =
|
private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE =
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||||
|
|
||||||
/**
|
|
||||||
* Theme that was applied when the settings was opened (or recreated after a theme change).
|
|
||||||
*/
|
|
||||||
private String startThemeKey;
|
|
||||||
private final Preference.OnPreferenceChangeListener themePreferenceChange
|
|
||||||
= new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
|
|
||||||
defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
|
|
||||||
defaultPreferences.edit()
|
|
||||||
.putString(getString(R.string.theme_key), newValue.toString()).apply();
|
|
||||||
|
|
||||||
if (!newValue.equals(startThemeKey) && getActivity() != null) {
|
|
||||||
// If it's not the current theme
|
|
||||||
ActivityCompat.recreate(requireActivity());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private String captionSettingsKey;
|
private String captionSettingsKey;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
final String themeKey = getString(R.string.theme_key);
|
final String themeKey = getString(R.string.theme_key);
|
||||||
startThemeKey = defaultPreferences
|
// the key of the active theme when settings were opened (or recreated after theme change)
|
||||||
|
final String startThemeKey = defaultPreferences
|
||||||
.getString(themeKey, getString(R.string.default_theme_value));
|
.getString(themeKey, getString(R.string.default_theme_value));
|
||||||
findPreference(themeKey).setOnPreferenceChangeListener(themePreferenceChange);
|
final String autoDeviceThemeKey = getString(R.string.auto_device_theme_key);
|
||||||
|
findPreference(themeKey).setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
if (newValue.toString().equals(autoDeviceThemeKey)) {
|
||||||
|
Toast.makeText(getContext(), getString(R.string.select_night_theme_toast),
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
applyThemeChange(startThemeKey, themeKey, newValue);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
final String nightThemeKey = getString(R.string.night_theme_key);
|
||||||
|
if (startThemeKey.equals(autoDeviceThemeKey)) {
|
||||||
|
final String startNightThemeKey = defaultPreferences
|
||||||
|
.getString(nightThemeKey, getString(R.string.default_night_theme_value));
|
||||||
|
|
||||||
|
findPreference(nightThemeKey).setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
applyThemeChange(startNightThemeKey, nightThemeKey, newValue);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
removePreference(nightThemeKey);
|
||||||
|
}
|
||||||
|
|
||||||
captionSettingsKey = getString(R.string.caption_settings_key);
|
captionSettingsKey = getString(R.string.caption_settings_key);
|
||||||
if (!CAPTIONING_SETTINGS_ACCESSIBLE) {
|
if (!CAPTIONING_SETTINGS_ACCESSIBLE) {
|
||||||
final Preference captionSettings = findPreference(captionSettingsKey);
|
removePreference(captionSettingsKey);
|
||||||
getPreferenceScreen().removePreference(captionSettings);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,4 +75,23 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
|||||||
|
|
||||||
return super.onPreferenceTreeClick(preference);
|
return super.onPreferenceTreeClick(preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removePreference(final String preferenceKey) {
|
||||||
|
final Preference preference = findPreference(preferenceKey);
|
||||||
|
if (preference != null) {
|
||||||
|
getPreferenceScreen().removePreference(preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyThemeChange(final String beginningThemeKey,
|
||||||
|
final String themeKey,
|
||||||
|
final Object newValue) {
|
||||||
|
defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply();
|
||||||
|
defaultPreferences.edit().putString(themeKey, newValue.toString()).apply();
|
||||||
|
|
||||||
|
if (!newValue.equals(beginningThemeKey) && getActivity() != null) {
|
||||||
|
// if it's not the current theme
|
||||||
|
ActivityCompat.recreate(getActivity());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,11 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
|||||||
import org.schabi.newpipe.DownloaderImpl;
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||||
import org.schabi.newpipe.util.ZipHelper;
|
import org.schabi.newpipe.util.ZipHelper;
|
||||||
|
|
||||||
@ -198,7 +196,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||||||
|
|
||||||
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onError(e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,20 +241,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onError(e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Error
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void onError(final Throwable e) {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
ErrorActivity.reportError(activity, e,
|
|
||||||
activity.getClass(),
|
|
||||||
null,
|
|
||||||
ErrorInfo.make(UserAction.UI_ERROR,
|
|
||||||
"none", "", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.InfoCache;
|
import org.schabi.newpipe.util.InfoCache;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
@ -46,120 +48,103 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
|||||||
public boolean onPreferenceTreeClick(final Preference preference) {
|
public boolean onPreferenceTreeClick(final Preference preference) {
|
||||||
if (preference.getKey().equals(cacheWipeKey)) {
|
if (preference.getKey().equals(cacheWipeKey)) {
|
||||||
InfoCache.getInstance().clearCache();
|
InfoCache.getInstance().clearCache();
|
||||||
Toast.makeText(preference.getContext(), R.string.metadata_cache_wipe_complete_notice,
|
Toast.makeText(requireContext(),
|
||||||
Toast.LENGTH_SHORT).show();
|
R.string.metadata_cache_wipe_complete_notice, Toast.LENGTH_SHORT).show();
|
||||||
|
} else if (preference.getKey().equals(viewsHistoryClearKey)) {
|
||||||
|
openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
|
||||||
|
} else if (preference.getKey().equals(playbackStatesClearKey)) {
|
||||||
|
openDeletePlaybackStatesDialog(requireContext(), recordManager, disposables);
|
||||||
|
} else if (preference.getKey().equals(searchHistoryClearKey)) {
|
||||||
|
openDeleteSearchHistoryDialog(requireContext(), recordManager, disposables);
|
||||||
|
} else {
|
||||||
|
return super.onPreferenceTreeClick(preference);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (preference.getKey().equals(viewsHistoryClearKey)) {
|
private static Disposable getDeletePlaybackStatesDisposable(
|
||||||
new AlertDialog.Builder(getActivity())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
.setTitle(R.string.delete_view_history_alert)
|
return recordManager.deleteCompleteStreamStateHistory()
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.subscribe(
|
||||||
final Disposable onDeletePlaybackStates
|
howManyDeleted -> Toast.makeText(context,
|
||||||
= recordManager.deleteCompleteStreamStateHistory()
|
R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(),
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
throwable -> ErrorActivity.reportError(context,
|
||||||
.subscribe(
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
"Delete playback states")));
|
||||||
R.string.watch_history_states_deleted,
|
}
|
||||||
Toast.LENGTH_SHORT).show(),
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete playback states",
|
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
|
private static Disposable getWholeStreamHistoryDisposable(
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
.subscribe(
|
return recordManager.deleteWholeStreamHistory()
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
R.string.watch_history_deleted,
|
.subscribe(
|
||||||
Toast.LENGTH_SHORT).show(),
|
howManyDeleted -> Toast.makeText(context,
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(),
|
||||||
throwable,
|
throwable -> ErrorActivity.reportError(context,
|
||||||
SettingsActivity.class, null,
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
ErrorInfo.make(
|
"Delete from history")));
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
}
|
||||||
"none",
|
|
||||||
"Delete view history",
|
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
private static Disposable getRemoveOrphanedRecordsDisposable(
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
.subscribe(
|
return recordManager.removeOrphanedRecords()
|
||||||
howManyDeleted -> {
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
},
|
.subscribe(
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
howManyDeleted -> { },
|
||||||
throwable,
|
throwable -> ErrorActivity.reportError(context,
|
||||||
SettingsActivity.class, null,
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
ErrorInfo.make(
|
"Clear orphaned records")));
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
}
|
||||||
"none",
|
|
||||||
"Delete search history",
|
|
||||||
R.string.general_error)));
|
|
||||||
disposables.add(onDeletePlaybackStates);
|
|
||||||
disposables.add(onClearOrphans);
|
|
||||||
disposables.add(onDelete);
|
|
||||||
}))
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preference.getKey().equals(playbackStatesClearKey)) {
|
private static Disposable getDeleteSearchHistoryDisposable(
|
||||||
new AlertDialog.Builder(getActivity())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
.setTitle(R.string.delete_playback_states_alert)
|
return recordManager.deleteCompleteSearchHistory()
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.subscribe(
|
||||||
|
howManyDeleted -> Toast.makeText(context,
|
||||||
|
R.string.search_history_deleted, Toast.LENGTH_SHORT).show(),
|
||||||
|
throwable -> ErrorActivity.reportError(context,
|
||||||
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
|
"Delete search history")));
|
||||||
|
}
|
||||||
|
|
||||||
final Disposable onDeletePlaybackStates
|
public static void openDeleteWatchHistoryDialog(@NonNull final Context context,
|
||||||
= recordManager.deleteCompleteStreamStateHistory()
|
final HistoryRecordManager recordManager,
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
final CompositeDisposable disposables) {
|
||||||
.subscribe(
|
new AlertDialog.Builder(context)
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
.setTitle(R.string.delete_view_history_alert)
|
||||||
R.string.watch_history_states_deleted,
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
Toast.LENGTH_SHORT).show(),
|
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager));
|
||||||
throwable,
|
disposables.add(getWholeStreamHistoryDisposable(context, recordManager));
|
||||||
SettingsActivity.class, null,
|
disposables.add(getRemoveOrphanedRecordsDisposable(context, recordManager));
|
||||||
ErrorInfo.make(
|
}))
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
.create()
|
||||||
"none",
|
.show();
|
||||||
"Delete playback states",
|
}
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
disposables.add(onDeletePlaybackStates);
|
public static void openDeletePlaybackStatesDialog(@NonNull final Context context,
|
||||||
}))
|
final HistoryRecordManager recordManager,
|
||||||
.create()
|
final CompositeDisposable disposables) {
|
||||||
.show();
|
new AlertDialog.Builder(context)
|
||||||
}
|
.setTitle(R.string.delete_playback_states_alert)
|
||||||
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
|
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||||
|
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager))))
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
if (preference.getKey().equals(searchHistoryClearKey)) {
|
public static void openDeleteSearchHistoryDialog(@NonNull final Context context,
|
||||||
new AlertDialog.Builder(getActivity())
|
final HistoryRecordManager recordManager,
|
||||||
.setTitle(R.string.delete_search_history_alert)
|
final CompositeDisposable disposables) {
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
new AlertDialog.Builder(context)
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.setTitle(R.string.delete_search_history_alert)
|
||||||
final Disposable onDelete = recordManager.deleteCompleteSearchHistory()
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||||
.subscribe(
|
disposables.add(getDeleteSearchHistoryDisposable(context, recordManager))))
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
.create()
|
||||||
R.string.search_history_deleted,
|
.show();
|
||||||
Toast.LENGTH_SHORT).show(),
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete search history",
|
|
||||||
R.string.general_error)));
|
|
||||||
disposables.add(onDelete);
|
|
||||||
}))
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onPreferenceTreeClick(preference);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -20,10 +19,8 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
|||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -108,7 +105,7 @@ public class SelectChannelFragment extends DialogFragment {
|
|||||||
emptyView.setVisibility(View.GONE);
|
emptyView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
|
||||||
final SubscriptionManager subscriptionManager = new SubscriptionManager(getContext());
|
final SubscriptionManager subscriptionManager = new SubscriptionManager(requireContext());
|
||||||
subscriptionManager.subscriptions().toObservable()
|
subscriptionManager.subscriptions().toObservable()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
@ -122,7 +119,7 @@ public class SelectChannelFragment extends DialogFragment {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel(final DialogInterface dialogInterface) {
|
public void onCancel(@NonNull final DialogInterface dialogInterface) {
|
||||||
super.onCancel(dialogInterface);
|
super.onCancel(dialogInterface);
|
||||||
if (onCancelListener != null) {
|
if (onCancelListener != null) {
|
||||||
onCancelListener.onCancel();
|
onCancelListener.onCancel();
|
||||||
@ -156,16 +153,17 @@ public class SelectChannelFragment extends DialogFragment {
|
|||||||
private Observer<List<SubscriptionEntity>> getSubscriptionObserver() {
|
private Observer<List<SubscriptionEntity>> getSubscriptionObserver() {
|
||||||
return new Observer<List<SubscriptionEntity>>() {
|
return new Observer<List<SubscriptionEntity>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(final Disposable d) { }
|
public void onSubscribe(@NonNull final Disposable disposable) { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(final List<SubscriptionEntity> newSubscriptions) {
|
public void onNext(@NonNull final List<SubscriptionEntity> newSubscriptions) {
|
||||||
displayChannels(newSubscriptions);
|
displayChannels(newSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(@NonNull final Throwable exception) {
|
||||||
SelectChannelFragment.this.onError(exception);
|
ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this,
|
||||||
|
"Loading subscription", exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -173,16 +171,6 @@ public class SelectChannelFragment extends DialogFragment {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Error
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void onError(final Throwable e) {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo
|
|
||||||
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Interfaces
|
// Interfaces
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -197,6 +185,7 @@ public class SelectChannelFragment extends DialogFragment {
|
|||||||
|
|
||||||
private class SelectChannelAdapter
|
private class SelectChannelAdapter
|
||||||
extends RecyclerView.Adapter<SelectChannelAdapter.SelectChannelItemHolder> {
|
extends RecyclerView.Adapter<SelectChannelAdapter.SelectChannelItemHolder> {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SelectChannelItemHolder onCreateViewHolder(final ViewGroup parent,
|
public SelectChannelItemHolder onCreateViewHolder(final ViewGroup parent,
|
||||||
final int viewType) {
|
final int viewType) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -16,11 +15,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
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 org.schabi.newpipe.util.ThemeHelper;
|
||||||
@ -83,7 +80,7 @@ public class SelectKioskFragment extends DialogFragment {
|
|||||||
try {
|
try {
|
||||||
selectKioskAdapter = new SelectKioskAdapter();
|
selectKioskAdapter = new SelectKioskAdapter();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onError(e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Selecting kiosk", e);
|
||||||
}
|
}
|
||||||
recyclerView.setAdapter(selectKioskAdapter);
|
recyclerView.setAdapter(selectKioskAdapter);
|
||||||
|
|
||||||
@ -109,16 +106,6 @@ public class SelectKioskFragment extends DialogFragment {
|
|||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Error
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void onError(final Throwable e) {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo
|
|
||||||
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Interfaces
|
// Interfaces
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -24,11 +24,11 @@ import org.schabi.newpipe.database.LocalItem;
|
|||||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
@ -115,8 +115,8 @@ public class SelectPlaylistFragment extends DialogFragment {
|
|||||||
|
|
||||||
protected void onError(final Throwable e) {
|
protected void onError(final Throwable e) {
|
||||||
final Activity activity = requireActivity();
|
final Activity activity = requireActivity();
|
||||||
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo
|
ErrorActivity.reportErrorInSnackbar(activity, new ErrorInfo(e,
|
||||||
.make(UserAction.UI_ERROR, "none", "load_playlists", R.string.app_ui_crash));
|
UserAction.UI_ERROR, "Loading playlists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -7,9 +7,9 @@ import android.util.Log;
|
|||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
|
|
||||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||||
|
|
||||||
@ -95,15 +95,13 @@ public final class SettingMigrations {
|
|||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
// save the version with the last successful migration and report the error
|
// save the version with the last successful migration and report the error
|
||||||
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
|
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
|
||||||
final ErrorInfo errorInfo = ErrorInfo.make(
|
ErrorActivity.reportError(context, new ErrorInfo(
|
||||||
|
e,
|
||||||
UserAction.PREFERENCES_MIGRATION,
|
UserAction.PREFERENCES_MIGRATION,
|
||||||
"none",
|
|
||||||
"Migrating preferences from version " + lastPrefVersion + " to "
|
"Migrating preferences from version " + lastPrefVersion + " to "
|
||||||
+ VERSION + ". "
|
+ VERSION + ". "
|
||||||
+ "Error at " + currentVersion + " => " + ++currentVersion,
|
+ "Error at " + currentVersion + " => " + ++currentVersion
|
||||||
0
|
));
|
||||||
);
|
|
||||||
ErrorActivity.reportError(context, e, SettingMigrations.class, null, errorInfo);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,10 +27,10 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.SelectChannelFragment;
|
import org.schabi.newpipe.settings.SelectChannelFragment;
|
||||||
import org.schabi.newpipe.settings.SelectKioskFragment;
|
import org.schabi.newpipe.settings.SelectKioskFragment;
|
||||||
import org.schabi.newpipe.settings.SelectPlaylistFragment;
|
import org.schabi.newpipe.settings.SelectPlaylistFragment;
|
||||||
@ -183,10 +183,9 @@ public class ChooseTabsFragment extends Fragment {
|
|||||||
final Tab.Type type = typeFrom(tabId);
|
final Tab.Type type = typeFrom(tabId);
|
||||||
|
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
ErrorActivity.reportError(requireContext(),
|
ErrorActivity.reportErrorInSnackbar(this,
|
||||||
new IllegalStateException("Tab id not found: " + tabId), null, null,
|
new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId),
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.SOMETHING_ELSE, "Choosing tabs on settings"));
|
||||||
"Choosing tabs on settings", 0));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ import com.grack.nanojson.JsonSink;
|
|||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.LocalItem.LocalItemType;
|
import org.schabi.newpipe.database.LocalItem.LocalItemType;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
@ -25,9 +28,6 @@ import org.schabi.newpipe.local.feed.FeedFragment;
|
|||||||
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
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.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
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 org.schabi.newpipe.util.ThemeHelper;
|
||||||
@ -483,9 +483,8 @@ public abstract class Tab {
|
|||||||
final StreamingService service = NewPipe.getService(kioskServiceId);
|
final StreamingService service = NewPipe.getService(kioskServiceId);
|
||||||
kioskId = service.getKioskList().getDefaultKioskId();
|
kioskId = service.getKioskList().getDefaultKioskId();
|
||||||
} catch (final ExtractionException e) {
|
} catch (final ExtractionException e) {
|
||||||
ErrorActivity.reportError(context, e, null, null,
|
ErrorActivity.reportErrorInSnackbar(context, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none",
|
UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service"));
|
||||||
"Loading default kiosk from selected service", 0));
|
|
||||||
}
|
}
|
||||||
return kioskId;
|
return kioskId;
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ import android.content.pm.PackageManager;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import androidx.annotation.Dimension;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
@ -70,4 +72,20 @@ public final class DeviceUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int dpToPx(@Dimension(unit = Dimension.DP) final int dp,
|
||||||
|
@NonNull final Context context) {
|
||||||
|
return (int) TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
dp,
|
||||||
|
context.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int spToPx(@Dimension(unit = Dimension.SP) final int sp,
|
||||||
|
@NonNull final Context context) {
|
||||||
|
return (int) TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_SP,
|
||||||
|
sp,
|
||||||
|
context.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
* Copyright 2017 Mauricio Colli <mauriciocolli@outlook.com>
|
||||||
* Extractors.java is part of NewPipe
|
* ExtractorHelper.java is part of NewPipe
|
||||||
*
|
*
|
||||||
* License: GPL-3.0+
|
* License: GPL-3.0+
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -20,12 +20,9 @@
|
|||||||
package org.schabi.newpipe.util;
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
@ -33,7 +30,6 @@ import androidx.preference.PreferenceManager;
|
|||||||
|
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.extractor.Info;
|
import org.schabi.newpipe.extractor.Info;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||||
@ -44,23 +40,14 @@ import org.schabi.newpipe.extractor.Page;
|
|||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
|
||||||
import org.schabi.newpipe.extractor.feed.FeedExtractor;
|
import org.schabi.newpipe.extractor.feed.FeedExtractor;
|
||||||
import org.schabi.newpipe.extractor.feed.FeedInfo;
|
import org.schabi.newpipe.extractor.feed.FeedInfo;
|
||||||
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -274,50 +261,6 @@ public final class ExtractorHelper {
|
|||||||
return null != loadFromCache(serviceId, url, infoType).blockingGet();
|
return null != loadFromCache(serviceId, url, infoType).blockingGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple and general error handler that show a Toast for known exceptions,
|
|
||||||
* and for others, opens the report error activity with the (optional) error message.
|
|
||||||
*
|
|
||||||
* @param context Android app context
|
|
||||||
* @param serviceId the service the exception happened in
|
|
||||||
* @param url the URL where the exception happened
|
|
||||||
* @param exception the exception to be handled
|
|
||||||
* @param userAction the action of the user that caused the exception
|
|
||||||
* @param optionalErrorMessage the optional error message
|
|
||||||
*/
|
|
||||||
public static void handleGeneralException(final Context context, final int serviceId,
|
|
||||||
final String url, final Throwable exception,
|
|
||||||
final UserAction userAction,
|
|
||||||
final String optionalErrorMessage) {
|
|
||||||
final Handler handler = new Handler(context.getMainLooper());
|
|
||||||
|
|
||||||
handler.post(() -> {
|
|
||||||
if (exception instanceof ReCaptchaException) {
|
|
||||||
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
|
||||||
// Starting ReCaptcha Challenge Activity
|
|
||||||
final Intent intent = new Intent(context, ReCaptchaActivity.class);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(intent);
|
|
||||||
} else if (ExceptionUtils.isNetworkRelated(exception)) {
|
|
||||||
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
|
|
||||||
} else if (exception instanceof ContentNotAvailableException) {
|
|
||||||
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
|
|
||||||
} else if (exception instanceof ContentNotSupportedException) {
|
|
||||||
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
|
|
||||||
} else {
|
|
||||||
final int errorId = exception instanceof YoutubeStreamExtractor.DeobfuscateException
|
|
||||||
? R.string.youtube_signature_deobfuscation_error
|
|
||||||
: exception instanceof ParsingException
|
|
||||||
? R.string.parsing_error : R.string.general_error;
|
|
||||||
ErrorActivity.reportError(handler, context, exception, MainActivity.class, null,
|
|
||||||
ErrorInfo.make(userAction, serviceId == -1 ? "none"
|
|
||||||
: NewPipe.getNameOfService(serviceId),
|
|
||||||
url + (optionalErrorMessage == null ? ""
|
|
||||||
: optionalErrorMessage), errorId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats the text contained in the meta info list as HTML and puts it into the text view,
|
* Formats the text contained in the meta info list as HTML and puts it into the text view,
|
||||||
* while also making the separator visible. If the list is null or empty, or the user chose not
|
* while also making the separator visible. If the list is null or empty, or the user chose not
|
||||||
@ -331,10 +274,9 @@ public final class ExtractorHelper {
|
|||||||
final TextView metaInfoTextView,
|
final TextView metaInfoTextView,
|
||||||
final View metaInfoSeparator) {
|
final View metaInfoSeparator) {
|
||||||
final Context context = metaInfoTextView.getContext();
|
final Context context = metaInfoTextView.getContext();
|
||||||
final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context)
|
if (metaInfos == null || metaInfos.isEmpty()
|
||||||
.getBoolean(context.getString(R.string.show_meta_info_key), true);
|
|| !PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
|
||||||
|
context.getString(R.string.show_meta_info_key), true)) {
|
||||||
if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
|
|
||||||
metaInfoTextView.setVisibility(View.GONE);
|
metaInfoTextView.setVisibility(View.GONE);
|
||||||
metaInfoSeparator.setVisibility(View.GONE);
|
metaInfoSeparator.setVisibility(View.GONE);
|
||||||
return Disposable.empty();
|
return Disposable.empty();
|
||||||
|
@ -48,6 +48,10 @@ public final class KioskTranslator {
|
|||||||
return c.getString(R.string.recent);
|
return c.getString(R.string.recent);
|
||||||
case "live":
|
case "live":
|
||||||
return c.getString(R.string.duration_live);
|
return c.getString(R.string.duration_live);
|
||||||
|
case "Featured":
|
||||||
|
return c.getString(R.string.featured);
|
||||||
|
case "Radio":
|
||||||
|
return c.getString(R.string.radio);
|
||||||
default:
|
default:
|
||||||
return kioskId;
|
return kioskId;
|
||||||
}
|
}
|
||||||
@ -69,6 +73,10 @@ public final class KioskTranslator {
|
|||||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_thumb_up);
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_thumb_up);
|
||||||
case "live":
|
case "live":
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_live_tv);
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_live_tv);
|
||||||
|
case "Featured":
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_stars);
|
||||||
|
case "Radio":
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_radio);
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import org.ocpsoft.prettytime.units.Decade;
|
|||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.ktx.OffsetDateTimeKt;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
@ -30,7 +29,6 @@ import java.time.ZoneId;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.FormatStyle;
|
import java.time.format.FormatStyle;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@ -314,11 +312,7 @@ public final class Localization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String relativeTime(final OffsetDateTime offsetDateTime) {
|
public static String relativeTime(final OffsetDateTime offsetDateTime) {
|
||||||
return relativeTime(OffsetDateTimeKt.toCalendar(offsetDateTime));
|
return prettyTime.formatUnrounded(offsetDateTime);
|
||||||
}
|
|
||||||
|
|
||||||
public static String relativeTime(final Calendar calendarTime) {
|
|
||||||
return prettyTime.formatUnrounded(calendarTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void changeAppLanguage(final Locale loc, final Resources res) {
|
private static void changeAppLanguage(final Locale loc, final Resources res) {
|
||||||
|
@ -38,6 +38,8 @@ public final class ServiceHelper {
|
|||||||
return R.drawable.place_holder_gadse;
|
return R.drawable.place_holder_gadse;
|
||||||
case 3:
|
case 3:
|
||||||
return R.drawable.place_holder_peertube;
|
return R.drawable.place_holder_peertube;
|
||||||
|
case 4:
|
||||||
|
return R.drawable.place_holder_bandcamp;
|
||||||
default:
|
default:
|
||||||
return R.drawable.place_holder_circle;
|
return R.drawable.place_holder_circle;
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,10 @@ package org.schabi.newpipe.util;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.ContextThemeWrapper;
|
|
||||||
|
|
||||||
import androidx.annotation.AttrRes;
|
import androidx.annotation.AttrRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -39,7 +40,8 @@ import org.schabi.newpipe.extractor.StreamingService;
|
|||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
|
||||||
public final class ThemeHelper {
|
public final class ThemeHelper {
|
||||||
private ThemeHelper() { }
|
private ThemeHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the selected theme (on NewPipe settings) in the context
|
* Apply the selected theme (on NewPipe settings) in the context
|
||||||
@ -70,31 +72,12 @@ public final class ThemeHelper {
|
|||||||
* @return whether the light theme is selected
|
* @return whether the light theme is selected
|
||||||
*/
|
*/
|
||||||
public static boolean isLightThemeSelected(final Context context) {
|
public static boolean isLightThemeSelected(final Context context) {
|
||||||
return getSelectedThemeString(context).equals(context.getResources()
|
final String selectedThemeKey = getSelectedThemeKey(context);
|
||||||
.getString(R.string.light_theme_key));
|
final Resources res = context.getResources();
|
||||||
}
|
|
||||||
|
|
||||||
|
return selectedThemeKey.equals(res.getString(R.string.light_theme_key))
|
||||||
/**
|
|| (selectedThemeKey.equals(res.getString(R.string.auto_device_theme_key))
|
||||||
* Create and return a wrapped context with the default selected theme set.
|
&& !isDeviceDarkThemeEnabled(context));
|
||||||
*
|
|
||||||
* @param baseContext the base context for the wrapper
|
|
||||||
* @return a wrapped-styled context
|
|
||||||
*/
|
|
||||||
public static Context getThemedContext(final Context baseContext) {
|
|
||||||
return new ContextThemeWrapper(baseContext, getThemeForService(baseContext, -1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the selected theme without being styled to any service.
|
|
||||||
* See {@link #getThemeForService(Context, int)}.
|
|
||||||
*
|
|
||||||
* @param context context to get the selected theme
|
|
||||||
* @return the selected style (the default one)
|
|
||||||
*/
|
|
||||||
@StyleRes
|
|
||||||
public static int getDefaultTheme(final Context context) {
|
|
||||||
return getThemeForService(context, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,69 +113,91 @@ public final class ThemeHelper {
|
|||||||
*/
|
*/
|
||||||
@StyleRes
|
@StyleRes
|
||||||
public static int getThemeForService(final Context context, final int serviceId) {
|
public static int getThemeForService(final Context context, final int serviceId) {
|
||||||
final String lightTheme = context.getResources().getString(R.string.light_theme_key);
|
final Resources res = context.getResources();
|
||||||
final String darkTheme = context.getResources().getString(R.string.dark_theme_key);
|
final String lightThemeKey = res.getString(R.string.light_theme_key);
|
||||||
final String blackTheme = context.getResources().getString(R.string.black_theme_key);
|
final String blackThemeKey = res.getString(R.string.black_theme_key);
|
||||||
|
final String automaticDeviceThemeKey = res.getString(R.string.auto_device_theme_key);
|
||||||
|
|
||||||
final String selectedTheme = getSelectedThemeString(context);
|
final String selectedThemeKey = getSelectedThemeKey(context);
|
||||||
|
|
||||||
int defaultTheme = R.style.DarkTheme;
|
int baseTheme = R.style.DarkTheme; // default to dark theme
|
||||||
if (selectedTheme.equals(lightTheme)) {
|
if (selectedThemeKey.equals(lightThemeKey)) {
|
||||||
defaultTheme = R.style.LightTheme;
|
baseTheme = R.style.LightTheme;
|
||||||
} else if (selectedTheme.equals(blackTheme)) {
|
} else if (selectedThemeKey.equals(blackThemeKey)) {
|
||||||
defaultTheme = R.style.BlackTheme;
|
baseTheme = R.style.BlackTheme;
|
||||||
} else if (selectedTheme.equals(darkTheme)) {
|
} else if (selectedThemeKey.equals(automaticDeviceThemeKey)) {
|
||||||
defaultTheme = R.style.DarkTheme;
|
|
||||||
|
if (isDeviceDarkThemeEnabled(context)) {
|
||||||
|
// use the dark theme variant preferred by the user
|
||||||
|
final String selectedNightThemeKey = getSelectedNightThemeKey(context);
|
||||||
|
if (selectedNightThemeKey.equals(blackThemeKey)) {
|
||||||
|
baseTheme = R.style.BlackTheme;
|
||||||
|
} else {
|
||||||
|
baseTheme = R.style.DarkTheme;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// there is only one day theme
|
||||||
|
baseTheme = R.style.LightTheme;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceId <= -1) {
|
if (serviceId <= -1) {
|
||||||
return defaultTheme;
|
return baseTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
final StreamingService service;
|
final StreamingService service;
|
||||||
try {
|
try {
|
||||||
service = NewPipe.getService(serviceId);
|
service = NewPipe.getService(serviceId);
|
||||||
} catch (final ExtractionException ignored) {
|
} catch (final ExtractionException ignored) {
|
||||||
return defaultTheme;
|
return baseTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
String themeName = "DarkTheme";
|
String themeName = "DarkTheme"; // default
|
||||||
if (selectedTheme.equals(lightTheme)) {
|
if (baseTheme == R.style.LightTheme) {
|
||||||
themeName = "LightTheme";
|
themeName = "LightTheme";
|
||||||
} else if (selectedTheme.equals(blackTheme)) {
|
} else if (baseTheme == R.style.BlackTheme) {
|
||||||
themeName = "BlackTheme";
|
themeName = "BlackTheme";
|
||||||
} else if (selectedTheme.equals(darkTheme)) {
|
|
||||||
themeName = "DarkTheme";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
themeName += "." + service.getServiceInfo().getName();
|
themeName += "." + service.getServiceInfo().getName();
|
||||||
final int resourceId = context
|
final int resourceId = context.getResources()
|
||||||
.getResources()
|
|
||||||
.getIdentifier(themeName, "style", context.getPackageName());
|
.getIdentifier(themeName, "style", context.getPackageName());
|
||||||
|
|
||||||
if (resourceId > 0) {
|
if (resourceId > 0) {
|
||||||
return resourceId;
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
return baseTheme;
|
||||||
return defaultTheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@StyleRes
|
@StyleRes
|
||||||
public static int getSettingsThemeStyle(final Context context) {
|
public static int getSettingsThemeStyle(final Context context) {
|
||||||
final String lightTheme = context.getResources().getString(R.string.light_theme_key);
|
final Resources res = context.getResources();
|
||||||
final String darkTheme = context.getResources().getString(R.string.dark_theme_key);
|
final String lightTheme = res.getString(R.string.light_theme_key);
|
||||||
final String blackTheme = context.getResources().getString(R.string.black_theme_key);
|
final String blackTheme = res.getString(R.string.black_theme_key);
|
||||||
|
final String automaticDeviceTheme = res.getString(R.string.auto_device_theme_key);
|
||||||
|
|
||||||
final String selectedTheme = getSelectedThemeString(context);
|
|
||||||
|
final String selectedTheme = getSelectedThemeKey(context);
|
||||||
|
|
||||||
if (selectedTheme.equals(lightTheme)) {
|
if (selectedTheme.equals(lightTheme)) {
|
||||||
return R.style.LightSettingsTheme;
|
return R.style.LightSettingsTheme;
|
||||||
} else if (selectedTheme.equals(blackTheme)) {
|
} else if (selectedTheme.equals(blackTheme)) {
|
||||||
return R.style.BlackSettingsTheme;
|
return R.style.BlackSettingsTheme;
|
||||||
} else if (selectedTheme.equals(darkTheme)) {
|
} else if (selectedTheme.equals(automaticDeviceTheme)) {
|
||||||
return R.style.DarkSettingsTheme;
|
if (isDeviceDarkThemeEnabled(context)) {
|
||||||
|
// use the dark theme variant preferred by the user
|
||||||
|
final String selectedNightTheme = getSelectedNightThemeKey(context);
|
||||||
|
if (selectedNightTheme.equals(blackTheme)) {
|
||||||
|
return R.style.BlackSettingsTheme;
|
||||||
|
} else {
|
||||||
|
return R.style.DarkSettingsTheme;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// there is only one day theme
|
||||||
|
return R.style.LightSettingsTheme;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback
|
// default to dark theme
|
||||||
return R.style.DarkSettingsTheme;
|
return R.style.DarkSettingsTheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,18 +234,27 @@ public final class ThemeHelper {
|
|||||||
return value.data;
|
return value.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getSelectedThemeString(final Context context) {
|
private static String getSelectedThemeKey(final Context context) {
|
||||||
final String themeKey = context.getString(R.string.theme_key);
|
final String themeKey = context.getString(R.string.theme_key);
|
||||||
final String defaultTheme = context.getResources().getString(R.string.default_theme_value);
|
final String defaultTheme = context.getResources().getString(R.string.default_theme_value);
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getString(themeKey, defaultTheme);
|
.getString(themeKey, defaultTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getSelectedNightThemeKey(final Context context) {
|
||||||
|
final String nightThemeKey = context.getString(R.string.night_theme_key);
|
||||||
|
final String defaultNightTheme = context.getResources()
|
||||||
|
.getString(R.string.default_night_theme_value);
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getString(nightThemeKey, defaultNightTheme);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the title to the activity, if the activity is an {@link AppCompatActivity} and has an
|
* Sets the title to the activity, if the activity is an {@link AppCompatActivity} and has an
|
||||||
* action bar.
|
* action bar.
|
||||||
|
*
|
||||||
* @param activity the activity to set the title of
|
* @param activity the activity to set the title of
|
||||||
* @param title the title to set to the activity
|
* @param title the title to set to the activity
|
||||||
*/
|
*/
|
||||||
public static void setTitleToAppCompatActivity(@Nullable final Activity activity,
|
public static void setTitleToAppCompatActivity(@Nullable final Activity activity,
|
||||||
final CharSequence title) {
|
final CharSequence title) {
|
||||||
@ -251,4 +265,27 @@ public final class ThemeHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the device theme
|
||||||
|
* <p>
|
||||||
|
* It will return true if the device 's theme is dark, false otherwise.
|
||||||
|
* <p>
|
||||||
|
* From https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#java
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @return true:dark theme, false:light or unknown
|
||||||
|
*/
|
||||||
|
public static boolean isDeviceDarkThemeEnabled(final Context context) {
|
||||||
|
final int deviceTheme = context.getResources().getConfiguration().uiMode
|
||||||
|
& Configuration.UI_MODE_NIGHT_MASK;
|
||||||
|
switch (deviceTheme) {
|
||||||
|
case Configuration.UI_MODE_NIGHT_YES:
|
||||||
|
return true;
|
||||||
|
case Configuration.UI_MODE_NIGHT_UNDEFINED:
|
||||||
|
case Configuration.UI_MODE_NIGHT_NO:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,9 @@ import com.google.android.material.snackbar.Snackbar;
|
|||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
|
|
||||||
@ -583,16 +583,12 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
|
|||||||
try {
|
try {
|
||||||
service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName();
|
service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
service = "-";
|
service = ErrorInfo.SERVICE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorActivity.reportError(
|
ErrorActivity.reportError(mContext,
|
||||||
mContext,
|
new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action,
|
||||||
mission.errObject,
|
service, request.toString(), reason, null));
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ErrorInfo.make(action, service, request.toString(), reason)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearFinishedDownloads(boolean delete) {
|
public void clearFinishedDownloads(boolean delete) {
|
||||||
|
BIN
app/src/main/res/drawable-nodpi/place_holder_bandcamp.png
Normal file
BIN
app/src/main/res/drawable-nodpi/place_holder_bandcamp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
@ -219,7 +219,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detail_title_root_layout"
|
android:layout_below="@id/detail_title_root_layout"
|
||||||
@ -703,6 +703,8 @@
|
|||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusedByDefault="true"
|
||||||
app:srcCompat="?attr/ic_play_arrow"
|
app:srcCompat="?attr/ic_play_arrow"
|
||||||
tools:ignore="ContentDescription,RtlHardcoded" />
|
tools:ignore="ContentDescription,RtlHardcoded" />
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".report.ErrorActivity">
|
tools:context=".error.ErrorActivity">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
layout="@layout/toolbar_layout"
|
layout="@layout/toolbar_layout"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
@ -17,11 +17,24 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="Network error" />
|
tools:text="Network error" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/error_button_action"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/error_snackbar_action"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:theme="@style/ServiceColoredButton" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/error_button_retry"
|
android:id="@+id/error_button_retry"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:text="@string/retry"
|
android:text="@string/retry"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
@ -52,11 +52,11 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginTop="50dp"
|
android:layout_marginTop="16dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -14,16 +13,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="90dp"
|
android:layout_marginTop="90dp" />
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:background="?attr/toolbar_shadow"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swiperefresh"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@+id/refresh_root_view">
|
android:layout_below="@+id/refresh_root_view">
|
||||||
@ -122,7 +122,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
@ -104,11 +104,10 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerInParent="true"
|
||||||
android:layout_marginTop="50dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/items_list"
|
android:layout_below="@id/items_list"
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
android:id="@+id/detail_thumbnail_image_view"
|
android:id="@+id/detail_thumbnail_image_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="200dp"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:contentDescription="@string/detail_thumbnail_view_description"
|
android:contentDescription="@string/detail_thumbnail_view_description"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
@ -206,7 +207,7 @@
|
|||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detail_title_root_layout"
|
android:layout_below="@id/detail_title_root_layout"
|
||||||
@ -675,6 +676,8 @@
|
|||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusedByDefault="true"
|
||||||
app:srcCompat="?attr/ic_play_arrow"
|
app:srcCompat="?attr/ic_play_arrow"
|
||||||
tools:ignore="ContentDescription,RtlHardcoded" />
|
tools:ignore="ContentDescription,RtlHardcoded" />
|
||||||
|
|
||||||
|
@ -702,4 +702,20 @@
|
|||||||
<string name="show_description_summary">قم بإيقاف التشغيل لإخفاء وصف الفيديو والمعلومات الإضافية</string>
|
<string name="show_description_summary">قم بإيقاف التشغيل لإخفاء وصف الفيديو والمعلومات الإضافية</string>
|
||||||
<string name="show_description_title">إظهار الوصف</string>
|
<string name="show_description_title">إظهار الوصف</string>
|
||||||
<string name="open_with">فتح مع</string>
|
<string name="open_with">فتح مع</string>
|
||||||
|
<string name="paid_content">يتوفر هذا المحتوى فقط للمستخدمين الذين قاموا بالدفع، لذلك لا يمكن بثها أو تنزيلها من قبل NewPipe.</string>
|
||||||
|
<string name="youtube_music_premium_content">يتوفر هذا الفيديو فقط لأعضاء YouTube Music Premium ، لذلك لا يمكن بثه أو تنزيله من قبل NewPipe.</string>
|
||||||
|
<string name="private_content">هذا المحتوى خاص، لذلك لا يمكن دفقه أو تنزيله بواسطة NewPipe.</string>
|
||||||
|
<string name="soundcloud_go_plus_content">هذا هو مسار SoundCloud Go+ ، على الأقل في بلدك ، لذلك لا يمكن بثها أو تنزيلها من قبل NewPipe.</string>
|
||||||
|
<string name="georestricted_content">هذا المحتوى غير متوفر في بلدك.</string>
|
||||||
|
<string name="crash_the_app">اغلق التطبيق قسريا</string>
|
||||||
|
<string name="restricted_video_no_stream">هذا الفيديو مقيد بالفئة العمرية.
|
||||||
|
\nنظرًا لسياسات YouTube الجديدة المتعلقة بمقاطع الفيديو المقيدة بالفئة العمرية ، لا يمكن لـ NewPipe الوصول إلى أي من مقاطع الفيديو الخاصة بها وبالتالي يتعذر تشغيلها.</string>
|
||||||
|
<string name="radio">إذاعة</string>
|
||||||
|
<string name="featured">المميزة</string>
|
||||||
|
<string name="recaptcha_solve">حل</string>
|
||||||
|
<string name="download_has_started">بدأ التنزيل</string>
|
||||||
|
<string name="select_night_theme_toast">يمكنك اختيار نسقك الليلي المفضل أدناه</string>
|
||||||
|
<string name="night_theme_summary">حدد موضوعك الليلي المفضل - %s</string>
|
||||||
|
<string name="auto_device_theme_title">تلقائي (سمة الجهاز)</string>
|
||||||
|
<string name="night_theme_title">الثيم الليلي</string>
|
||||||
</resources>
|
</resources>
|
@ -525,7 +525,7 @@
|
|||||||
<string name="permission_display_over_apps">授予在其他应用上层显示的权限</string>
|
<string name="permission_display_over_apps">授予在其他应用上层显示的权限</string>
|
||||||
<string name="app_language_title">Newpipe应用语言</string>
|
<string name="app_language_title">Newpipe应用语言</string>
|
||||||
<string name="systems_language">[系统默认]</string>
|
<string name="systems_language">[系统默认]</string>
|
||||||
<string name="subtitle_activity_recaptcha">完成后请按\"完成(Done)\"</string>
|
<string name="subtitle_activity_recaptcha">完成后请按“完成”</string>
|
||||||
<string name="recaptcha_done_button">完成</string>
|
<string name="recaptcha_done_button">完成</string>
|
||||||
<string name="videos_string">视频</string>
|
<string name="videos_string">视频</string>
|
||||||
<plurals name="seconds">
|
<plurals name="seconds">
|
||||||
@ -657,4 +657,20 @@
|
|||||||
<string name="show_description_summary">显示视频描述和其他信息</string>
|
<string name="show_description_summary">显示视频描述和其他信息</string>
|
||||||
<string name="open_with">用…打开</string>
|
<string name="open_with">用…打开</string>
|
||||||
<string name="no_app_to_open_intent">设备上没有应用可以打开</string>
|
<string name="no_app_to_open_intent">设备上没有应用可以打开</string>
|
||||||
|
<string name="crash_the_app">让应用崩溃</string>
|
||||||
|
<string name="paid_content">此内容仅对已付费的用户可用,因此NewPipe 无法流式传输或下载该内容。</string>
|
||||||
|
<string name="youtube_music_premium_content">该视频仅供 YouTube Music Premium 会员使用,NewPipe 无法流式传输或下载该视频。</string>
|
||||||
|
<string name="private_content">此内容是私有的,因此 NewPipe 无法流式传输或下载该内容。</string>
|
||||||
|
<string name="soundcloud_go_plus_content">这是 SoundCloud Go +曲目,至少在你所在的国家/地区如此 NewPipe 无法流式传输或下载它。</string>
|
||||||
|
<string name="georestricted_content">此内容在你所在的国家/地区不可用。</string>
|
||||||
|
<string name="restricted_video_no_stream">这个视频有年龄限制
|
||||||
|
\n 由于 YouTube 新的针对此类视频的政策,NewPipe 无法访问其任何视频流,因此无法播放它。</string>
|
||||||
|
<string name="recaptcha_solve">处理</string>
|
||||||
|
<string name="radio">电台</string>
|
||||||
|
<string name="featured">精选</string>
|
||||||
|
<string name="auto_device_theme_title">自动(系统主题)</string>
|
||||||
|
<string name="download_has_started">下载已开始</string>
|
||||||
|
<string name="select_night_theme_toast">在此选择您最喜欢的夜间主题</string>
|
||||||
|
<string name="night_theme_summary">选择你最喜欢的夜间主题 - %s</string>
|
||||||
|
<string name="night_theme_title">夜间主题</string>
|
||||||
</resources>
|
</resources>
|
@ -370,7 +370,7 @@
|
|||||||
<string name="feed_update_threshold_summary">শেষ হালনাগাদের পর একটি সাবস্ক্রিপশনের আগের সময় সেকেলে বিবেচিত — %s</string>
|
<string name="feed_update_threshold_summary">শেষ হালনাগাদের পর একটি সাবস্ক্রিপশনের আগের সময় সেকেলে বিবেচিত — %s</string>
|
||||||
<string name="feed_update_threshold_title">ফিড হালনাগাদ প্রবেশস্থল</string>
|
<string name="feed_update_threshold_title">ফিড হালনাগাদ প্রবেশস্থল</string>
|
||||||
<string name="settings_category_feed_title">ফিড</string>
|
<string name="settings_category_feed_title">ফিড</string>
|
||||||
<string name="feed_group_show_only_ungrouped_subscriptions">শুধুমাত্র আনগ্রুপড সাবস্ক্রিপশনসমূহ দেখান</string>
|
<string name="feed_group_show_only_ungrouped_subscriptions">শুধুমাত্র অদলবদ্ধ সদস্যতা দেখাও</string>
|
||||||
<string name="feed_group_dialog_delete_message">আপনি কি এ গ্রুপটি মুছতে চান\?</string>
|
<string name="feed_group_dialog_delete_message">আপনি কি এ গ্রুপটি মুছতে চান\?</string>
|
||||||
<string name="feed_group_dialog_empty_name">খালি গ্রুপ নেম</string>
|
<string name="feed_group_dialog_empty_name">খালি গ্রুপ নেম</string>
|
||||||
<plurals name="feed_group_dialog_selection_count">
|
<plurals name="feed_group_dialog_selection_count">
|
||||||
|
@ -534,4 +534,24 @@
|
|||||||
<string name="subscribers_count_not_available">সাব্সক্রাইবার গণনা পাওয়া যায়নি</string>
|
<string name="subscribers_count_not_available">সাব্সক্রাইবার গণনা পাওয়া যায়নি</string>
|
||||||
<string name="detail_drag_description">সাজাতে টানো</string>
|
<string name="detail_drag_description">সাজাতে টানো</string>
|
||||||
<string name="copy_for_github">প্রতিবেদন অনুলিপি করো</string>
|
<string name="copy_for_github">প্রতিবেদন অনুলিপি করো</string>
|
||||||
|
<string name="feed_group_show_only_ungrouped_subscriptions">শুধুমাত্র অদলবদ্ধ সদস্যতা দেখাও</string>
|
||||||
|
<string name="feed_oldest_subscription_update">ফিড শেষে হালনাগাদ করা হয়েছে: %s</string>
|
||||||
|
<string name="downloads_storage_ask_title">কোথায় ডাইনলোড করবো জিজ্ঞেস করো</string>
|
||||||
|
<string name="pause_downloads_on_mobile">পরিমাপকৃত নেটওয়ার্কে বাধা দাও</string>
|
||||||
|
<string name="error_download_resource_gone">এই ডাউনলোড উদ্ধার করা যাচ্ছে না</string>
|
||||||
|
<string name="overwrite_failed">এই ফাইলের উপর লেখা যাচ্ছে না</string>
|
||||||
|
<string name="app_update_notification_content_title">নিউপাইপ হালনাগাদ আছে!</string>
|
||||||
|
<string name="minimize_on_exit_popup_description">পপআপ প্লেয়ারে ক্ষুদ্রকরণ করো</string>
|
||||||
|
<string name="minimize_on_exit_background_description">প্লেয়ার পটভূমিতে ক্ষুদ্রকরণ করো</string>
|
||||||
|
<string name="minimize_on_exit_title">অ্যাপ পরিবর্তনে ক্ষুদ্রকরণ করো</string>
|
||||||
|
<string name="unhook_checkbox">আনহুক (বিকৃতি হতে পারে)</string>
|
||||||
|
<string name="crash_the_app">অ্যাপ বন্ধ করে দাও</string>
|
||||||
|
<string name="show_meta_info_title">মেটা তথ্য দেখাও</string>
|
||||||
|
<string name="hash_channel_name">ভিডিও হ্যাশের বিজ্ঞপ্তি</string>
|
||||||
|
<string name="show_memory_leaks">মেমরি ক্ষয় দেখাও</string>
|
||||||
|
<string name="clear_cookie_title">রিক্যাপচা কুকি পরিষ্কার করো</string>
|
||||||
|
<string name="channel_created_by">%s দ্বারা তৈরি</string>
|
||||||
|
<string name="detail_sub_channel_thumbnail_view_description">চ্যানেলের অবতার প্রতিচ্ছবি</string>
|
||||||
|
<string name="feed_use_dedicated_fetch_method_disable_button">দ্রুত মোড বন্ধ করো</string>
|
||||||
|
<string name="feed_use_dedicated_fetch_method_enable_button">দ্রুত মোড চালু করো</string>
|
||||||
</resources>
|
</resources>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user