+
+
+*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) .*
+
+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.
+
+COLOCAR NEWPIPE O CUALQUIER FORK (BIFURCACIÓN) REALIZADO DE ELLO EN GOOGLE PLAY STORE VIOLA SUS TÉRMINOS Y CONDICIONES.
+
+## Capturas de pantalla
+
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
+[](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
+[](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
+[](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\]
+
+
+
+
+## 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).
+
+
+
+
+
+## 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).
+
+
+
+
+
+
16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 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.
diff --git a/README.ja.md b/README.ja.md
index c101f385109..685202bf37a 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -2,8 +2,7 @@
-*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).*
경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.
@@ -79,6 +80,7 @@ NewPipe는 여러가지 서비스를 지원합니다. 우리의 [문서](https:/
* SoundCloud \[beta\]
* media.ccc.de \[beta\]
* PeerTube instances \[beta\]
+* Bandcamp \[beta\]
## Updates
NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로 인해), 결국 릴리즈가 발생할 것입니다. 이것들의 형식은 x.xx.x 입니다.
diff --git a/README.md b/README.md
index 80c7e2e88e4..27ede1c0309 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,7 @@
NewPipe
A libre lightweight streaming frontend for Android.
-*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) .*
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
@@ -81,6 +80,7 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
* SoundCloud \[beta\]
* media.ccc.de \[beta\]
* PeerTube instances \[beta\]
+* Bandcamp \[beta\]
@@ -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:
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.
- 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.
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.
diff --git a/README.pt_BR.md b/README.pt_BR.md
index dedb64a7cf6..033b6f0f741 100644
--- a/README.pt_BR.md
+++ b/README.pt_BR.md
@@ -1,7 +1,9 @@
+
NewPipe
Uma interface de streaming leve e gratuita para Android.
-*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).*
AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.
@@ -79,6 +81,7 @@ O NewPipe suporta vários serviços. Nosso [documentação](https://teamnewpipe.
* SoundCloud \[beta\]
* media.ccc.de \[beta\]
* PeerTube instances \[beta\]
+* Bandcamp \[beta\]
## 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:
diff --git a/README.ro.md b/README.ro.md
index 75e3bd5b052..e28cd8cd635 100644
--- a/README.ro.md
+++ b/README.ro.md
@@ -2,8 +2,7 @@
NewPipe
Un front-end de streaming „uşor” liber, pentru Android.
+ * 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.
+ *
+ */
+ 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.
+ *
+ * The calculating follows these rules:
+ *
+ *
+ * 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 85dp 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 15sp on tablets and 16sp on TVs,
+ * see {@link R.id.titleTextView}.
+ *
+ *
+ * Otherwise, the max thumbnail height is the screen height.
+ *
+ *
+ *
+ * @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
public void onLoadingStarted(final String imageUri, final View view) {
if (DEBUG) {
@@ -1207,23 +1286,29 @@ public void onLoadingFailed(final String imageUri, final View view,
@Override
public void onLoadingComplete(final String imageUri, final View view,
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),
loadedImage.getWidth());
+ currentThumbnail = Bitmap.createScaledBitmap(
+ loadedImage,
+ (int) notificationThumbnailWidth,
+ (int) (loadedImage.getHeight()
+ / (loadedImage.getWidth() / notificationThumbnailWidth)),
+ true);
if (DEBUG) {
Log.d(TAG, "Thumbnail - onLoadingComplete() called with: "
+ "imageUri = [" + imageUri + "], view = [" + view + "], "
+ "loadedImage = [" + loadedImage + "], "
+ 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);
+
+ // there is a new thumbnail, thus the end screen thumbnail needs to be changed, too.
+ updateEndScreenThumbnail();
}
@Override
@@ -1432,7 +1517,8 @@ public float getPlaybackPitch() {
}
public boolean getPlaybackSkipSilence() {
- return getPlaybackParameters().skipSilence;
+ return !exoPlayerIsNull() && simpleExoPlayer.getAudioComponent() != null
+ && simpleExoPlayer.getAudioComponent().getSkipSilenceEnabled();
}
public PlaybackParameters getPlaybackParameters() {
@@ -1457,7 +1543,10 @@ public void setPlaybackParameters(final float speed, final float pitch,
savePlaybackParametersToPrefs(this, roundedSpeed, roundedPitch, skipSilence);
simpleExoPlayer.setPlaybackParameters(
- new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
+ new PlaybackParameters(roundedSpeed, roundedPitch));
+ if (simpleExoPlayer.getAudioComponent() != null) {
+ simpleExoPlayer.getAudioComponent().setSkipSilenceEnabled(skipSilence);
+ }
}
//endregion
@@ -2333,6 +2422,7 @@ public void onPlayerError(@NonNull final ExoPlaybackException error) {
case ExoPlaybackException.TYPE_OUT_OF_MEMORY:
case ExoPlaybackException.TYPE_REMOTE:
case ExoPlaybackException.TYPE_RENDERER:
+ case ExoPlaybackException.TYPE_TIMEOUT:
default:
showUnrecoverableError(error);
onPlaybackShutdown();
@@ -3355,7 +3445,7 @@ private void onTextTracksChanged() {
final List availableLanguages = new ArrayList<>(textTracks.length);
for (int i = 0; i < textTracks.length; 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);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
index 0604e6ae83c..ba9a2f1ec4c 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/LoadController.java
@@ -80,12 +80,14 @@ public boolean retainBackBufferFromKeyframe() {
}
@Override
- public boolean shouldContinueLoading(final long bufferedDurationUs,
+ public boolean shouldContinueLoading(final long playbackPositionUs,
+ final long bufferedDurationUs,
final float playbackSpeed) {
if (!preloadingEnabled) {
return false;
}
- return internalLoadControl.shouldContinueLoading(bufferedDurationUs, playbackSpeed);
+ return internalLoadControl.shouldContinueLoading(
+ playbackPositionUs, bufferedDurationUs, playbackSpeed);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
index ccc73e81f97..4324fcd0a0b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java
@@ -484,8 +484,9 @@ public static int nextResizeModeAndSaveToPrefs(final Player player,
break;
}
+ // save the new resize mode so it can be restored in a future session
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;
}
@@ -494,9 +495,7 @@ public static PlaybackParameters retrievePlaybackParametersFromPrefs(final Playe
R.string.playback_speed_key), player.getPlaybackSpeed());
final float pitch = player.getPrefs().getFloat(player.getContext().getString(
R.string.playback_pitch_key), player.getPlaybackPitch());
- final boolean skipSilence = player.getPrefs().getBoolean(player.getContext().getString(
- R.string.playback_skip_silence_key), player.getPlaybackSkipSilence());
- return new PlaybackParameters(speed, pitch, skipSilence);
+ return new PlaybackParameters(speed, pitch);
}
public static void savePlaybackParametersToPrefs(final Player player,
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
index c09a44c08e5..7594f3a1600 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
@@ -5,6 +5,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
@@ -54,6 +55,14 @@ private boolean canRetry() {
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
public void maybeThrowSourceInfoRefreshError() throws IOException {
throw new IOException(error);
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
index cdbf8609b2b..746a9758156 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java
@@ -5,6 +5,8 @@
import androidx.annotation.NonNull;
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.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
@@ -83,6 +85,38 @@ public void removeEventListener(final MediaSourceEventListener 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
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
final boolean isInterruptable) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
index f73a219d7e4..1cd8556270b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java
@@ -3,6 +3,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.upstream.Allocator;
@@ -11,6 +12,14 @@
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
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
@Override
public void maybeThrowSourceInfoRefreshError() { }
diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/report/ErrorInfo.kt
deleted file mode 100644
index 4947d79500c..00000000000
--- a/app/src/main/java/org/schabi/newpipe/report/ErrorInfo.kt
+++ /dev/null
@@ -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)
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
index 8126bd2c56b..e2ac2c20d97 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
@@ -18,40 +18,43 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE =
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;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
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));
- 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);
if (!CAPTIONING_SETTINGS_ACCESSIBLE) {
- final Preference captionSettings = findPreference(captionSettingsKey);
- getPreferenceScreen().removePreference(captionSettings);
+ removePreference(captionSettingsKey);
}
}
@@ -72,4 +75,23 @@ public boolean onPreferenceTreeClick(final Preference 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());
+ }
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index c0639131c08..dbe05bbd2f0 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -21,13 +21,11 @@
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.NewPipeDatabase;
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.localization.ContentCountry;
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.ZipHelper;
@@ -198,7 +196,7 @@ private void exportDatabase(final String path) {
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
} catch (final Exception e) {
- onError(e);
+ ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e);
}
}
@@ -243,20 +241,7 @@ private void importDatabase(final String filePath) {
System.exit(0);
}
} 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));
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
index 98c1ffc30a8..89fabbdde3b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
@@ -1,17 +1,19 @@
package org.schabi.newpipe.settings;
+import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
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.report.ErrorActivity;
-import org.schabi.newpipe.report.ErrorInfo;
-import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.InfoCache;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@@ -46,120 +48,103 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro
public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(cacheWipeKey)) {
InfoCache.getInstance().clearCache();
- Toast.makeText(preference.getContext(), R.string.metadata_cache_wipe_complete_notice,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(requireContext(),
+ 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)) {
- new AlertDialog.Builder(getActivity())
- .setTitle(R.string.delete_view_history_alert)
- .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
- .setPositiveButton(R.string.delete, ((dialog, which) -> {
- final Disposable onDeletePlaybackStates
- = recordManager.deleteCompleteStreamStateHistory()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- howManyDeleted -> Toast.makeText(getActivity(),
- 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()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- howManyDeleted -> Toast.makeText(getActivity(),
- 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)));
+ private static Disposable getDeletePlaybackStatesDisposable(
+ @NonNull final Context context, final HistoryRecordManager recordManager) {
+ return recordManager.deleteCompleteStreamStateHistory()
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ howManyDeleted -> Toast.makeText(context,
+ R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(),
+ throwable -> ErrorActivity.reportError(context,
+ new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
+ "Delete playback states")));
+ }
- 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(onDeletePlaybackStates);
- disposables.add(onClearOrphans);
- disposables.add(onDelete);
- }))
- .create()
- .show();
- }
+ private static Disposable getWholeStreamHistoryDisposable(
+ @NonNull final Context context, final HistoryRecordManager recordManager) {
+ return recordManager.deleteWholeStreamHistory()
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ howManyDeleted -> Toast.makeText(context,
+ R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(),
+ throwable -> ErrorActivity.reportError(context,
+ new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
+ "Delete from history")));
+ }
- if (preference.getKey().equals(playbackStatesClearKey)) {
- new AlertDialog.Builder(getActivity())
- .setTitle(R.string.delete_playback_states_alert)
- .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
- .setPositiveButton(R.string.delete, ((dialog, which) -> {
+ private static Disposable getRemoveOrphanedRecordsDisposable(
+ @NonNull final Context context, final HistoryRecordManager recordManager) {
+ return recordManager.removeOrphanedRecords()
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ howManyDeleted -> { },
+ throwable -> ErrorActivity.reportError(context,
+ new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
+ "Clear orphaned records")));
+ }
- final Disposable onDeletePlaybackStates
- = recordManager.deleteCompleteStreamStateHistory()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- howManyDeleted -> Toast.makeText(getActivity(),
- 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)));
+ private static Disposable getDeleteSearchHistoryDisposable(
+ @NonNull final Context context, final HistoryRecordManager recordManager) {
+ return recordManager.deleteCompleteSearchHistory()
+ .observeOn(AndroidSchedulers.mainThread())
+ .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")));
+ }
- disposables.add(onDeletePlaybackStates);
- }))
- .create()
- .show();
- }
+ public static void openDeleteWatchHistoryDialog(@NonNull final Context context,
+ final HistoryRecordManager recordManager,
+ final CompositeDisposable disposables) {
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.delete_view_history_alert)
+ .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
+ .setPositiveButton(R.string.delete, ((dialog, which) -> {
+ disposables.add(getDeletePlaybackStatesDisposable(context, recordManager));
+ disposables.add(getWholeStreamHistoryDisposable(context, recordManager));
+ disposables.add(getRemoveOrphanedRecordsDisposable(context, recordManager));
+ }))
+ .create()
+ .show();
+ }
- if (preference.getKey().equals(searchHistoryClearKey)) {
- new AlertDialog.Builder(getActivity())
- .setTitle(R.string.delete_search_history_alert)
- .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
- .setPositiveButton(R.string.delete, ((dialog, which) -> {
- final Disposable onDelete = recordManager.deleteCompleteSearchHistory()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- howManyDeleted -> Toast.makeText(getActivity(),
- R.string.search_history_deleted,
- 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();
- }
+ public static void openDeletePlaybackStatesDialog(@NonNull final Context context,
+ final HistoryRecordManager recordManager,
+ final CompositeDisposable disposables) {
+ 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();
+ }
- return super.onPreferenceTreeClick(preference);
+ public static void openDeleteSearchHistoryDialog(@NonNull final Context context,
+ final HistoryRecordManager recordManager,
+ final CompositeDisposable disposables) {
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.delete_search_history_alert)
+ .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
+ .setPositiveButton(R.string.delete, ((dialog, which) ->
+ disposables.add(getDeleteSearchHistoryDisposable(context, recordManager))))
+ .create()
+ .show();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
index afe42d5d812..7f706be779e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
@@ -1,6 +1,5 @@
package org.schabi.newpipe.settings;
-import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -20,10 +19,8 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
+import org.schabi.newpipe.error.ErrorActivity;
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 java.util.List;
@@ -108,7 +105,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup
emptyView.setVisibility(View.GONE);
- final SubscriptionManager subscriptionManager = new SubscriptionManager(getContext());
+ final SubscriptionManager subscriptionManager = new SubscriptionManager(requireContext());
subscriptionManager.subscriptions().toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -122,7 +119,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup
//////////////////////////////////////////////////////////////////////////*/
@Override
- public void onCancel(final DialogInterface dialogInterface) {
+ public void onCancel(@NonNull final DialogInterface dialogInterface) {
super.onCancel(dialogInterface);
if (onCancelListener != null) {
onCancelListener.onCancel();
@@ -156,16 +153,17 @@ private void displayChannels(final List newSubscriptions) {
private Observer> getSubscriptionObserver() {
return new Observer>() {
@Override
- public void onSubscribe(final Disposable d) { }
+ public void onSubscribe(@NonNull final Disposable disposable) { }
@Override
- public void onNext(final List newSubscriptions) {
+ public void onNext(@NonNull final List newSubscriptions) {
displayChannels(newSubscriptions);
}
@Override
- public void onError(final Throwable exception) {
- SelectChannelFragment.this.onError(exception);
+ public void onError(@NonNull final Throwable exception) {
+ ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this,
+ "Loading subscription", exception);
}
@Override
@@ -173,16 +171,6 @@ public void onComplete() { }
};
}
- /*//////////////////////////////////////////////////////////////////////////
- // 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
//////////////////////////////////////////////////////////////////////////*/
@@ -197,6 +185,7 @@ public interface OnCancelListener {
private class SelectChannelAdapter
extends RecyclerView.Adapter {
+ @NonNull
@Override
public SelectChannelItemHolder onCreateViewHolder(final ViewGroup parent,
final int viewType) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
index fc974607b18..5c20b752ccb 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java
@@ -1,6 +1,5 @@
package org.schabi.newpipe.settings;
-import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -16,11 +15,9 @@
import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.error.ErrorActivity;
import org.schabi.newpipe.extractor.NewPipe;
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.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -83,7 +80,7 @@ public View onCreateView(final LayoutInflater inflater, final ViewGroup containe
try {
selectKioskAdapter = new SelectKioskAdapter();
} catch (final Exception e) {
- onError(e);
+ ErrorActivity.reportUiErrorInSnackbar(this, "Selecting kiosk", e);
}
recyclerView.setAdapter(selectKioskAdapter);
@@ -109,16 +106,6 @@ private void clickedItem(final SelectKioskAdapter.Entry entry) {
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
//////////////////////////////////////////////////////////////////////////*/
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
index 16ccd0953e4..63da3274f39 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
@@ -24,11 +24,11 @@
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
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.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.Vector;
@@ -115,8 +115,8 @@ private void displayPlaylists(final List newPlaylists) {
protected void onError(final Throwable e) {
final Activity activity = requireActivity();
- ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo
- .make(UserAction.UI_ERROR, "none", "load_playlists", R.string.app_ui_crash));
+ ErrorActivity.reportErrorInSnackbar(activity, new ErrorInfo(e,
+ UserAction.UI_ERROR, "Loading playlists"));
}
/*//////////////////////////////////////////////////////////////////////////
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
index 9042559c970..c5974642831 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
@@ -7,9 +7,9 @@
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.report.ErrorInfo;
-import org.schabi.newpipe.report.UserAction;
+import org.schabi.newpipe.error.ErrorActivity;
+import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.UserAction;
import static org.schabi.newpipe.MainActivity.DEBUG;
@@ -95,15 +95,13 @@ public static void initMigrations(final Context context, final boolean isFirstRu
} catch (final Exception e) {
// save the version with the last successful migration and report the error
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
- final ErrorInfo errorInfo = ErrorInfo.make(
+ ErrorActivity.reportError(context, new ErrorInfo(
+ e,
UserAction.PREFERENCES_MIGRATION,
- "none",
"Migrating preferences from version " + lastPrefVersion + " to "
+ VERSION + ". "
- + "Error at " + currentVersion + " => " + ++currentVersion,
- 0
- );
- ErrorActivity.reportError(context, e, SettingMigrations.class, null, errorInfo);
+ + "Error at " + currentVersion + " => " + ++currentVersion
+ ));
return;
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
index cbc47392b9f..572741d03a8 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java
@@ -27,10 +27,10 @@
import com.google.android.material.floatingactionbutton.FloatingActionButton;
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.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.SelectKioskFragment;
import org.schabi.newpipe.settings.SelectPlaylistFragment;
@@ -183,10 +183,9 @@ private void addTab(final int tabId) {
final Tab.Type type = typeFrom(tabId);
if (type == null) {
- ErrorActivity.reportError(requireContext(),
- new IllegalStateException("Tab id not found: " + tabId), null, null,
- ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
- "Choosing tabs on settings", 0));
+ ErrorActivity.reportErrorInSnackbar(this,
+ new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId),
+ UserAction.SOMETHING_ELSE, "Choosing tabs on settings"));
return;
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
index ce3874f3975..0ffda2261c1 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
@@ -12,6 +12,9 @@
import org.schabi.newpipe.R;
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.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -25,9 +28,6 @@
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
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.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -483,9 +483,8 @@ private String getDefaultKioskId(final Context context) {
final StreamingService service = NewPipe.getService(kioskServiceId);
kioskId = service.getKioskList().getDefaultKioskId();
} catch (final ExtractionException e) {
- ErrorActivity.reportError(context, e, null, null,
- ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none",
- "Loading default kiosk from selected service", 0));
+ ErrorActivity.reportErrorInSnackbar(context, new ErrorInfo(e,
+ UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service"));
}
return kioskId;
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
index 1afedcaef31..52069fd0e7d 100644
--- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java
@@ -6,8 +6,10 @@
import android.content.res.Configuration;
import android.os.BatteryManager;
import android.os.Build;
+import android.util.TypedValue;
import android.view.KeyEvent;
+import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
@@ -70,4 +72,20 @@ public static boolean isConfirmKey(final int keyCode) {
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());
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 6ee69dcd9a4..af7cafc1518 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -1,6 +1,6 @@
/*
* Copyright 2017 Mauricio Colli
- * Extractors.java is part of NewPipe
+ * ExtractorHelper.java is part of NewPipe
*
* License: GPL-3.0+
* This program is free software: you can redistribute it and/or modify
@@ -20,12 +20,9 @@
package org.schabi.newpipe.util;
import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
@@ -33,7 +30,6 @@
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
@@ -44,23 +40,14 @@
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
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.FeedInfo;
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
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.StreamInfoItem;
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.List;
@@ -274,50 +261,6 @@ public static boolean isCached(final int serviceId, final String url,
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,
* while also making the separator visible. If the list is null or empty, or the user chose not
@@ -331,10 +274,9 @@ public static Disposable showMetaInfoInTextView(@Nullable final List m
final TextView metaInfoTextView,
final View metaInfoSeparator) {
final Context context = metaInfoTextView.getContext();
- final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(context.getString(R.string.show_meta_info_key), true);
-
- if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
+ if (metaInfos == null || metaInfos.isEmpty()
+ || !PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
+ context.getString(R.string.show_meta_info_key), true)) {
metaInfoTextView.setVisibility(View.GONE);
metaInfoSeparator.setVisibility(View.GONE);
return Disposable.empty();
diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java
index d2daaf6cc22..2f0b3e1324b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java
+++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java
@@ -48,6 +48,10 @@ public static String getTranslatedKioskName(final String kioskId, final Context
return c.getString(R.string.recent);
case "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:
return kioskId;
}
@@ -69,6 +73,10 @@ public static int getKioskIcon(final String kioskId, final Context c) {
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_thumb_up);
case "live":
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:
return 0;
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java
index f62f959c47d..674c7844f26 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -20,7 +20,6 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.localization.ContentCountry;
-import org.schabi.newpipe.ktx.OffsetDateTimeKt;
import java.math.BigDecimal;
import java.math.RoundingMode;
@@ -30,7 +29,6 @@
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.List;
import java.util.Locale;
@@ -314,11 +312,7 @@ public static PrettyTime resolvePrettyTime(final Context context) {
}
public static String relativeTime(final OffsetDateTime offsetDateTime) {
- return relativeTime(OffsetDateTimeKt.toCalendar(offsetDateTime));
- }
-
- public static String relativeTime(final Calendar calendarTime) {
- return prettyTime.formatUnrounded(calendarTime);
+ return prettyTime.formatUnrounded(offsetDateTime);
}
private static void changeAppLanguage(final Locale loc, final Resources res) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
index b38edfeb433..d41493a7fc2 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java
@@ -38,6 +38,8 @@ public static int getIcon(final int serviceId) {
return R.drawable.place_holder_gadse;
case 3:
return R.drawable.place_holder_peertube;
+ case 4:
+ return R.drawable.place_holder_bandcamp;
default:
return R.drawable.place_holder_circle;
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
index 5ac4de84ce3..dcfb7ed1907 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
@@ -21,9 +21,10 @@
import android.app.Activity;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
import androidx.annotation.AttrRes;
import androidx.annotation.Nullable;
@@ -39,7 +40,8 @@
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
public final class ThemeHelper {
- private ThemeHelper() { }
+ private ThemeHelper() {
+ }
/**
* Apply the selected theme (on NewPipe settings) in the context
@@ -70,31 +72,12 @@ public static void setTheme(final Context context, final int serviceId) {
* @return whether the light theme is selected
*/
public static boolean isLightThemeSelected(final Context context) {
- return getSelectedThemeString(context).equals(context.getResources()
- .getString(R.string.light_theme_key));
- }
-
+ final String selectedThemeKey = getSelectedThemeKey(context);
+ final Resources res = context.getResources();
- /**
- * Create and return a wrapped context with the default selected theme set.
- *
- * @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);
+ return selectedThemeKey.equals(res.getString(R.string.light_theme_key))
+ || (selectedThemeKey.equals(res.getString(R.string.auto_device_theme_key))
+ && !isDeviceDarkThemeEnabled(context));
}
/**
@@ -130,69 +113,91 @@ public static int getMinWidthDialogTheme(final Context context) {
*/
@StyleRes
public static int getThemeForService(final Context context, final int serviceId) {
- final String lightTheme = context.getResources().getString(R.string.light_theme_key);
- final String darkTheme = context.getResources().getString(R.string.dark_theme_key);
- final String blackTheme = context.getResources().getString(R.string.black_theme_key);
+ final Resources res = context.getResources();
+ final String lightThemeKey = res.getString(R.string.light_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;
- if (selectedTheme.equals(lightTheme)) {
- defaultTheme = R.style.LightTheme;
- } else if (selectedTheme.equals(blackTheme)) {
- defaultTheme = R.style.BlackTheme;
- } else if (selectedTheme.equals(darkTheme)) {
- defaultTheme = R.style.DarkTheme;
+ int baseTheme = R.style.DarkTheme; // default to dark theme
+ if (selectedThemeKey.equals(lightThemeKey)) {
+ baseTheme = R.style.LightTheme;
+ } else if (selectedThemeKey.equals(blackThemeKey)) {
+ baseTheme = R.style.BlackTheme;
+ } else if (selectedThemeKey.equals(automaticDeviceThemeKey)) {
+
+ 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) {
- return defaultTheme;
+ return baseTheme;
}
final StreamingService service;
try {
service = NewPipe.getService(serviceId);
} catch (final ExtractionException ignored) {
- return defaultTheme;
+ return baseTheme;
}
- String themeName = "DarkTheme";
- if (selectedTheme.equals(lightTheme)) {
+ String themeName = "DarkTheme"; // default
+ if (baseTheme == R.style.LightTheme) {
themeName = "LightTheme";
- } else if (selectedTheme.equals(blackTheme)) {
+ } else if (baseTheme == R.style.BlackTheme) {
themeName = "BlackTheme";
- } else if (selectedTheme.equals(darkTheme)) {
- themeName = "DarkTheme";
}
themeName += "." + service.getServiceInfo().getName();
- final int resourceId = context
- .getResources()
+ final int resourceId = context.getResources()
.getIdentifier(themeName, "style", context.getPackageName());
if (resourceId > 0) {
return resourceId;
}
-
- return defaultTheme;
+ return baseTheme;
}
@StyleRes
public static int getSettingsThemeStyle(final Context context) {
- final String lightTheme = context.getResources().getString(R.string.light_theme_key);
- final String darkTheme = context.getResources().getString(R.string.dark_theme_key);
- final String blackTheme = context.getResources().getString(R.string.black_theme_key);
+ final Resources res = context.getResources();
+ final String lightTheme = res.getString(R.string.light_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)) {
return R.style.LightSettingsTheme;
} else if (selectedTheme.equals(blackTheme)) {
return R.style.BlackSettingsTheme;
- } else if (selectedTheme.equals(darkTheme)) {
- return R.style.DarkSettingsTheme;
+ } else if (selectedTheme.equals(automaticDeviceTheme)) {
+ 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 {
- // Fallback
+ // default to dark theme
return R.style.DarkSettingsTheme;
}
}
@@ -229,18 +234,27 @@ public static int resolveColorFromAttr(final Context context, @AttrRes final int
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 defaultTheme = context.getResources().getString(R.string.default_theme_value);
return PreferenceManager.getDefaultSharedPreferences(context)
.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
* action bar.
+ *
* @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,
final CharSequence title) {
@@ -251,4 +265,27 @@ public static void setTitleToAppCompatActivity(@Nullable final Activity activity
}
}
}
+
+ /**
+ * Get the device theme
+ *