From aea912f4995ccf890a921afcbab5435503cbbbba Mon Sep 17 00:00:00 2001 From: Mikhail Barashkov Date: Fri, 12 Feb 2021 11:58:15 +0300 Subject: [PATCH 01/57] Implement "pause/play" toggle on hardware keyboard space button. --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 1 + app/src/main/res/layout/fragment_video_detail.xml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index cb4cfb8b6bd..7f800a30450 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -1850,6 +1850,7 @@ public void onFullscreenStateChanged(final boolean fullscreen) { if (fullscreen) { hideSystemUiIfNeeded(); + binding.overlayPlayPauseButton.requestFocus(); } else { showSystemUi(); } diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 246e9b4d99b..498b16b9c38 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -675,6 +675,8 @@ android:background="?attr/selectableItemBackground" android:padding="10dp" android:scaleType="center" + android:focusable="true" + android:focusedByDefault="true" app:srcCompat="?attr/ic_play_arrow" tools:ignore="ContentDescription,RtlHardcoded" /> From 80161c36c6d3ed20414547af293414cd829ce386 Mon Sep 17 00:00:00 2001 From: Mikhail Barashkov Date: Fri, 12 Feb 2021 12:17:46 +0300 Subject: [PATCH 02/57] Apply the space button shortcut to large screens as well --- app/src/main/res/layout-large-land/fragment_video_detail.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 14459b49489..42bd4bb91c5 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -703,6 +703,8 @@ android:background="?attr/selectableItemBackground" android:padding="10dp" android:scaleType="center" + android:focusable="true" + android:focusedByDefault="true" app:srcCompat="?attr/ic_play_arrow" tools:ignore="ContentDescription,RtlHardcoded" /> From eba0b07782b2520c4aadd2e9df4f2f86528d6834 Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 19 Jan 2021 09:27:29 +0100 Subject: [PATCH 03/57] Update to ExoPlayer 2.12.3 --- app/build.gradle | 2 +- .../org/schabi/newpipe/player/Player.java | 9 +++-- .../newpipe/player/helper/LoadController.java | 7 ++-- .../newpipe/player/helper/PlayerHelper.java | 4 +-- .../player/mediasource/FailedMediaSource.java | 9 +++++ .../player/mediasource/LoadedMediaSource.java | 34 +++++++++++++++++++ .../mediasource/PlaceholderMediaSource.java | 9 +++++ 7 files changed, 64 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 486502cd868..37eca2ed5cf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,7 +96,7 @@ ext { checkstyleVersion = '8.38' stethoVersion = '1.5.1' leakCanaryVersion = '2.5' - exoPlayerVersion = '2.11.8' + exoPlayerVersion = '2.12.3' androidxLifecycleVersion = '2.2.0' androidxRoomVersion = '2.3.0-alpha03' groupieVersion = '2.8.1' diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index ba8e856df73..535a5033ad8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -601,7 +601,8 @@ public void handleIntent(@NonNull final Intent intent) { final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this); final float playbackSpeed = savedParameters.speed; 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 int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode()); @@ -1432,7 +1433,8 @@ public float getPlaybackPitch() { } public boolean getPlaybackSkipSilence() { - return getPlaybackParameters().skipSilence; + return simpleExoPlayer != null + && simpleExoPlayer.getAudioComponent().getSkipSilenceEnabled(); } public PlaybackParameters getPlaybackParameters() { @@ -1457,7 +1459,8 @@ 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)); + simpleExoPlayer.getAudioComponent().setSkipSilenceEnabled(skipSilence); } //endregion 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..162872267a6 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,13 @@ public boolean retainBackBufferFromKeyframe() { } @Override - public boolean shouldContinueLoading(final long bufferedDurationUs, - final float playbackSpeed) { + 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..f653531eb83 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 @@ -494,9 +494,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() { } From 8978187c6429407abf6f5b23ed1bc3f67bcaf30d Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 16 Feb 2021 16:54:44 +0100 Subject: [PATCH 04/57] Improve code style and fix some warnings Removed a textTrack null check on a now- NonNull method Added a error type switch case (TIMEOUT) --- app/src/main/java/org/schabi/newpipe/player/Player.java | 9 ++++++--- .../org/schabi/newpipe/player/helper/LoadController.java | 7 ++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 535a5033ad8..0a10a2cb731 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -1433,7 +1433,7 @@ public float getPlaybackPitch() { } public boolean getPlaybackSkipSilence() { - return simpleExoPlayer != null + return !exoPlayerIsNull() && simpleExoPlayer.getAudioComponent() != null && simpleExoPlayer.getAudioComponent().getSkipSilenceEnabled(); } @@ -1460,7 +1460,9 @@ public void setPlaybackParameters(final float speed, final float pitch, savePlaybackParametersToPrefs(this, roundedSpeed, roundedPitch, skipSilence); simpleExoPlayer.setPlaybackParameters( new PlaybackParameters(roundedSpeed, roundedPitch)); - simpleExoPlayer.getAudioComponent().setSkipSilenceEnabled(skipSilence); + if (simpleExoPlayer.getAudioComponent() != null) { + simpleExoPlayer.getAudioComponent().setSkipSilenceEnabled(skipSilence); + } } //endregion @@ -2336,6 +2338,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(); @@ -3358,7 +3361,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 162872267a6..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 @@ -81,12 +81,13 @@ public boolean retainBackBufferFromKeyframe() { @Override public boolean shouldContinueLoading(final long playbackPositionUs, - final long bufferedDurationUs, final float playbackSpeed) { + final long bufferedDurationUs, + final float playbackSpeed) { if (!preloadingEnabled) { return false; } - return internalLoadControl.shouldContinueLoading(playbackPositionUs, bufferedDurationUs, - playbackSpeed); + return internalLoadControl.shouldContinueLoading( + playbackPositionUs, bufferedDurationUs, playbackSpeed); } @Override From b236bb407b8f15f324d4fcbdfa60f0ec25f9f1ab Mon Sep 17 00:00:00 2001 From: FireMasterK <20838718+FireMasterK@users.noreply.github.com> Date: Sat, 20 Feb 2021 16:49:37 +0530 Subject: [PATCH 05/57] Change UA to privacy.resistFingerprinting. --- app/src/main/java/org/schabi/newpipe/DownloaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 50972fb2f37..a81c812df8c 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -43,7 +43,7 @@ public final class DownloaderImpl extends Downloader { 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 = "youtube_restricted_mode_key"; public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000"; From 85a468bda9e45098335a557b7e79a65665943aa7 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Fri, 5 Mar 2021 22:44:28 +0100 Subject: [PATCH 06/57] Update translations: app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated using Weblate (French) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Italian) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Slovenian) Currently translated at 75.1% (469 of 624 strings) Translated using Weblate (Greek) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Polish) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Albanian) Currently translated at 98.2% (613 of 624 strings) Translated using Weblate (Norwegian Bokmål) Currently translated at 96.4% (602 of 624 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Kurdish (Northern)) Currently translated at 100.0% (624 of 624 strings) --- .../main/res/values-b+zh+HANS+CN/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-he/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-kmr/strings.xml | 1 + app/src/main/res/values-nb-rNO/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 9 +- app/src/main/res/values-pt-rPT/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-sl/strings.xml | 163 +++++++++++++++++- app/src/main/res/values-sq/strings.xml | 15 ++ app/src/main/res/values-zh-rTW/strings.xml | 1 + 14 files changed, 186 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index b5cefcabc9b..4d2ac263fd3 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -657,4 +657,5 @@ 显示视频描述和其他信息 用…打开 设备上没有应用可以打开 + 让应用崩溃 \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 5b00bc0a496..80f6c32ba49 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -662,4 +662,5 @@ Εμφάνιση περιγραφής Άνοιγμα με Καμία εφαρμογή στη συσκευή σας δεν μπορεί το ανοίξει + Κατάρρευση εφαρμογής \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d98a95ec39b..c1afa106d27 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -664,4 +664,5 @@ Commentaires Désactiver pour masquer la description de la vidéo et les informations supplémentaires Afficher la description + Plante l\'application \ No newline at end of file diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index d90ec25d9a2..7ebc93c4d6c 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -682,4 +682,5 @@ הצגת תיאור פתיחה באמצעות אין יישומון על המכשיר שלך שיכול לפתוח את זה + להקריס את היישומון \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index cabc0043459..d469b4144b2 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -662,4 +662,5 @@ Mostra descrizione Apri con Sul tuo dispositivo non c\'è alcuna app che può aprirlo + Fai crashare l\'app \ No newline at end of file diff --git a/app/src/main/res/values-kmr/strings.xml b/app/src/main/res/values-kmr/strings.xml index f20ac0faa17..75ca42bc87d 100644 --- a/app/src/main/res/values-kmr/strings.xml +++ b/app/src/main/res/values-kmr/strings.xml @@ -663,4 +663,5 @@ Lîsansên partiya sêyemîn Derbar Mîhengên + Serlêdanê kilît bikin \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 01786212194..25f86d1b160 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -662,4 +662,5 @@ Skru av for å skjule videobeskrivelse og ytterligere info Vis beskrivelse Åpne med + Krasj programmet \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 0f06a1fd3a0..b3aa620eceb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -672,4 +672,5 @@ Otwórz za pomocą Żadna aplikacja na Twoim urządzeniu nie może tego otworzyć Powiązane strumienie + Awaria aplikacji \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 8ee7dd8efd1..5456bd6b7df 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -335,10 +335,10 @@ \n6. A partir do arquivo zip baixado, retire o arquivo .json (geralmente em \"YouTube e YouTube Music/assinaturas/assinaturas.json\") e importe aqui. Importe um perfil do SoundCloud digitando o URL ou seu ID: \n -\n1. Ative o \"modo desktop\" no navegador (o site está indisponível em celulares) -\n2. Acesse este URL: %1$s -\n3. Logue quando solicitado -\n4. Copie o URL do perfil redirecionado +\n1. Ative o \"modo desktop\" no navegador (o site está indisponível em celulares) +\n2. Acesse este URL: %1$s +\n3. Logue quando solicitado +\n4. Copie o URL do perfil redirecionado. seuID, soundcloud.com/seuid Tenha em mente que esta operação poderá usar bastante a conexão com a internet. \n @@ -662,4 +662,5 @@ Mostrar descrição Abrir com Nenhum aplicativo em seu dispositivo pode abrir isso + O aplicativo parou \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index bb20d52be58..e1fef787551 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -662,4 +662,5 @@ Desative para ocultar a descrição do vídeo e informações adicionais Mostrar descrição Abrir com + A app travou \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 71200df95a2..5f1467eeb43 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -662,4 +662,5 @@ Desative para ocultar a descrição do vídeo e informações adicionais Mostrar descrição Abrir com + A app travou \ No newline at end of file diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index ca2c76cc72c..2e2d08d6a88 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -25,8 +25,8 @@ Pokaži možnost \"Predvajaj s Kodi\" Privzet zapis zvoka Zvok - Prejem - Pokaži naslednji video in podobne posnetke + Prenesi + Pokaži naslednje in podobne posnetke Zapis naslova URL ni podprt. Privzeti jezik vsebine Video in Zvok @@ -165,9 +165,9 @@ odpiranje v pojavnem načinu Znak za zamenjavo Črke in številke Večina posebnih znakov - Preišči zgodovino + Zgodovina iskanja Iskalne poizvedbe shranjuj krajevno - Zgodovina + Zgodovina ogledov Sledi zgodovini predvajanih posnetkov Nadaljuj po prekinitvi Nadaljuj s predvajanjem po prekinitvi (na primer zaradi telefonskega klica) @@ -230,7 +230,7 @@ odpiranje v pojavnem načinu Spletišče Storitev Neveljaven naslov URL - Prejmi datoteko pretoka. + Prenesi datoteko pretoka. Pokaži podrobnosti Zaznamki Dodaj k @@ -348,13 +348,13 @@ odpiranje v pojavnem načinu Kanali Posodobitve Samo HTTPS URL-ji so podprti - Prikaži namig \"Drži za dodajanje\" + Prikaži namig \"drži za dodajanje\" Počisti podatke Pozicija v seznamih - Avtomatsko predvajaj naslednji video + Samodejno predvajaj naslednji video Izbriši shranjene metapodatke Predshramba za slike je bila izbrisana - Prikaži komentar + Prikaži komentarje Izberi Zavihek Nov Zavihek Odjava @@ -367,4 +367,151 @@ odpiranje v pojavnem načinu Izbriši piškotke, ki jih NewPipe shrani, ko rešite reCAPTCHA Izbrišite piškodke reCAPTCHA Nadaljuj s predvajanjem + Povrni zadnji položaj predvajanja + Samodejno uvrščanje + Predpomnjeni metapodatki so bili odstranjeni + Prikaži meta informacije + Onemogoči da se ustavi prikazovanje komentarjev + Izklopite, če želite preprečiti nalaganje sličic, s tem bo varčeval na podatkih in uporabi spomina. Spremembe bodo izbrisale predpomnilnik v spominu in na disku. + Dejavna vrsta bo zamenjana + Preklop na drugi predvajanik lahko zamenja vašo čakalno vrsto + Vprašaj za potrditev pred čiščenjem vrste + Prepustite, da Android izbere barvo obvestila na podlagi glavne barve v sličici (upoštevajte, da to ni na voljo v vseh napravah) + Obarvajte obvestilo + Nič + Nalaganje + Naključno + Ponovi + Izberete lahko največ 3 dejanja, ki se bodo prikazala v kompaktnem obvestilu! + Uredite vsako obvestilo z klikom na obvestilo. Izberite do 3 obvestila, ki se bodo prikazala v kompaktnem obvestilu z uporabo potrditvenega polja na desni. + Gumb za peto dejanje + Gumb za četrto dejanje + Gumb za tretje dejanje + Gumb za drugo dejanje + Gumb za prvo dejanje + Povečaj sličico videa, ki je prikazana v obvestilu iz razmerja 16:9 v razmerje 1:1 (lahko pride do popačenja) + Zruši aplikacijo + Spremeni velikost besedila podnapisov in stil ozadja v predvajalniku. Zahteva ponovni zagon aplikacije, da učinkuje. + Podnapisi + Samodejno ustvarjeno + Prilagodi zaslonu + Ni podnapisov + Samodejno ustvarjenjo (nalagalca ni bilo mogoče najti) + Sličica seznama predvajanja je bila spremenjena. + Na seznamu predvajanja + Odstrani zaznamek + Shrani seznam predvajanja med zaznamke + Nastavi kot sličico seznama predvajanja + Vklopi zvok + Utišaj + Ime + Predvajalnik v pojavnem oknu + Privzeto dejanje ob odprtju vsebine — %s + Prednostno dejanje \'odpri\' + Zapri omaro + Odpri omaro + Začni predvajati v pojavnem oknu + Začni s predvajanjem tukaj + Uvrščeno + Uvrsti + Pridržite za uvrstitev + Predvajaj seznam + Konference + Priljubljeno + Kiosk + To bo nadomestilo vaše trenutne nastavitve. + Opozorilo: ni mogoče uvoziti vseh datotek. + Izberi seznam predvajanja + Ali ste prepričani, da želite izbrisati vse elemente iz zgodovine\? + Ali želite odstraniti ta element iz zgodovine ogledov\? + NewPipe je prosta in odprta programska oprema: lahko jo uporabljaš, preučuješ in izboljšaš po želji. Lahko jo distributiraš in/ali spremeniš pod pogoji GNU General Public Licence, kot jo je izdala Free Software Foundation v različici 3 ali po izbiri v katerikoli novejši različici. + Projekt NewPipe jemlje vašo zasebnost zelo resno. Aplikacija zato ne zbira kakršnih koli podatkov brez vašega dovoljenja. +\nPolitika zasebnosti NewPipe-a podrobno pojasnjuje, kateri podatki so poslani in shranjeni, ko pošljete poročilo o zrušitvi. + Pomagaj + NewPipe razvijajo prostovoljci, ki preživljajo svoj prosti čas, da vam prinašajo najboljšo uporabniško izkušnjo. Pomagajte razvijalcem pri izdelavi še boljšega NewPipe-a medtem ko uživajo skodelico kave. + Za predvajanje te datoteke ni nameščena nobena aplikacija + Pritisni \"končano\" ko je rešena + Odstranjen 1 element. + Izračun zgoščevalne funkcije je v teku + ∞ videoposnetkov + 100+ videov + + %s poslušalec + %s poslušalca + %s poslušalcev + %s poslušalcev + + + %s gledalec + %s gledalca + %s gledalcev + %s gledalcev + + Število naročnikov ni na voljo + Vklop/izklop storitve, trenutno izbrana: + Povlecite za preureditev + Opis + Podobni pretoki + Komentarji + Prosimo preverite, če težava, ki opisuje vašo zrušitev aplikacije že obstaja. Ko ustvarite dvojne pripombe, vzamete naš čas, ki bi ga lahko porabili z odpravljanjem dejanske napake. + Prijavite na GitHub-u + Kopiraj oblikovano poročilo + Odobrite dovoljenje, da se ta aplikacija prikaže pred drugimi aplikacijami + Ali želite obnoviti privzete vrednosti\? + Povrni privzeto + Nobeden pretok ni na voljo za prenos + Ime datoteke ne sme biti prazno + Datoteka ne obstaja ali ji manjka dovoljenje za branje ali pisanje + Ni takšne datoteke/vira vsebine + Zunanji predvajalniki ne podpirajo teh vrst povezav + Obnavljanje od napake predvajanika + Prišlo je do neobnovljive napake v predvajalniku + Zunanja shramba ni na voljo + Pomoč + Izbriše zgodovino ključnih besed za iskanje + Položaji predvajalnika so bili izbrisani. + Ali želite izbrisati vse položaje predvajalnika\? + Izbriše vse položaje predvajalnika + Izbriši položaje predvajalnika + Izbriše zgodovino predvajanih videoposnetkov in položajev predvajalnika + Prepiše vašo trenutno zgodovino in naročnine + Piškotki reCAPTCHA so bili izbrisani + Obvestila za zgoščevanje videa v teku + Obvestilo hash-a videa + Obvestila za nove verzije NewPipe + Izvajalci + Albumi + Pesmi + Dogodki + Posnetki + Videoposnetki + Ta video ima omejitev starosti. +\n +\nVklopite \"%1$s\" v nastavitvah, če ga želite predvajati. + Youtube ponuja \"omejeni način\", ki skrije potencialno vsebino za odrasle + Vklop YouTubovega \"omejenega načina\" + Prikaz vsebin, ki so morda neprimerne za otroke zaradi omejitve starosti (kot na primer 18+) + Uvrščeno v predvajalnik v pojavnem oknu + Uvrščeno na predvajalnik v ozadju + Obvestilo + Instanca že obstaja + Validacija instance ni bila mogoča + Vnesite URL instance + Dodaj instanco + Najdite instance, ki so vam všeč na %s + Izberite vaše najljubše instance PeerTuba + Instance PeerTube + Privzeta država vsebine + URL-ja ni bilo mogoče prepoznati. Želite odpreti z drugo aplikacijo\? + Pokaži nasvet, ko boste pritisnili na ozadje ali pojavno okno v \"Podrobnosti:\" + Samodejno predvajanje + Pokaži indikator položaja predvajalnika na seznamih + Uporabi poteze za nadzor svetlosti predvajalnika + Poteza za nadzor svetlosti + Uporabi poteze za nadzor glasnosti predvajalnika + Nadzor potez za glasnost + Izklopite, če želite skriti opis videa in dodatnih informacij + Prikaži opis + Spremenite mapo z prenosi + Prikazujem rezultate za %s \ No newline at end of file diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index ac4793a0a0e..9f1e6922133 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -637,4 +637,19 @@ Biskotat reCAPTCHA janë pastruar Pastroni biskotat reCAPTCHA Ngjyros njoftimin + Nuk ka app në këtë pajisje që mund ta ngarkojë + Kapitujt + Të fundit + Shfaq pamjen miniaturë + Duke llogaritur hash + Përshkrimi + Streams të ngjashme + Komentet + Njoftimet mbi progresin e hash-imit të videove + Njoftimi për Hash e Videos + Shfaq të dhënat meta + Ç\'aktivizoje për të fshehur përshkrimin e videos dhe informacione shtesë + Shfaq përshkrimin + Lejoje Android që të modifikojë ngjyrën e njoftimit bazuar tek ngjyra kryesore në pamjen miniaturë (mbani mend që kjo nuk është e disponueshme në të gjitha pajisjet) + Hape me \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8be326dfac7..b1401212662 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -652,4 +652,5 @@ 顯示描述 開啟以 您裝置上沒有應用程式可以打開這個 + 應用程式當機 \ No newline at end of file From 9e1744f904e597ffab91c61f70dfc9ce0a802fa1 Mon Sep 17 00:00:00 2001 From: iamthesenate1 <77978836+iamthesenate1@users.noreply.github.com> Date: Sat, 6 Mar 2021 12:20:01 +0200 Subject: [PATCH 07/57] Add spcace after the comma, before Rommanian. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80c7e2e88e4..f1791de4cc9 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@

WebsiteBlogFAQPress


-*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), [한국어](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. From 37a96d063f2db6ff9f7082f6ae43a0a188abdd34 Mon Sep 17 00:00:00 2001 From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com> Date: Sun, 10 Jan 2021 14:13:20 +0100 Subject: [PATCH 08/57] Add different error messages for SoundCloud and YouTube unavailable contents Add new error strings for the six new exceptions created in the extractor and catch these exceptions. Extractor is, of course, updated with this PR. --- app/build.gradle | 2 +- .../newpipe/fragments/BaseStateFragment.java | 30 +++++++++++++++++-- .../schabi/newpipe/util/ExtractorHelper.java | 23 +++++++++++++- app/src/main/res/values-fr/strings.xml | 3 +- app/src/main/res/values/strings.xml | 8 ++++- 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9a6430f53a1..7c504e96895 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -180,7 +180,7 @@ dependencies { // NewPipe dependencies // 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:7e6f464407fc1a2c8fb0886d294093526a6ef0f1" implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" implementation "org.jsoup:jsoup:1.13.1" diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index f876b767c3c..cf02af4a932 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -20,9 +20,15 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; +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.ExceptionUtils; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorInfo; @@ -223,12 +229,30 @@ protected boolean onError(final Throwable exception) { if (exception instanceof ReCaptchaException) { 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 AgeRestrictedContentException) { + showError(getString(R.string.restricted_video_no_stream), false); + return true; + } else if (exception instanceof GeographicRestrictionException) { + showError(getString(R.string.georestricted_content), false); + return true; + } else if (exception instanceof PaidContentException) { + showError(getString(R.string.paid_content), false); + return true; + } else if (exception instanceof PrivateContentException) { + showError(getString(R.string.private_content), false); + return true; + } else if (exception instanceof SoundCloudGoPlusContentException) { + showError(getString(R.string.soundcloud_go_plus_content), false); + return true; + } else if (exception instanceof YoutubeMusicPremiumContentException) { + showError(getString(R.string.youtube_music_premium_content), false); + return true; + } else if (exception instanceof ContentNotAvailableException) { + showError(getString(R.string.content_not_available), false); + return true; } else if (exception instanceof ContentNotSupportedException) { showError(getString(R.string.content_not_supported), false); return true; 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..d6e3a0e8066 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 @@ -44,10 +44,16 @@ 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.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.ParsingException; +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.feed.FeedExtractor; import org.schabi.newpipe.extractor.feed.FeedInfo; import org.schabi.newpipe.extractor.kiosk.KioskInfo; @@ -300,6 +306,21 @@ public static void handleGeneralException(final Context context, final int servi context.startActivity(intent); } else if (ExceptionUtils.isNetworkRelated(exception)) { Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); + } else if (exception instanceof AgeRestrictedContentException) { + Toast.makeText(context, R.string.restricted_video_no_stream, + Toast.LENGTH_LONG).show(); + } else if (exception instanceof GeographicRestrictionException) { + Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show(); + } else if (exception instanceof PaidContentException) { + Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show(); + } else if (exception instanceof PrivateContentException) { + Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show(); + } else if (exception instanceof SoundCloudGoPlusContentException) { + Toast.makeText(context, R.string.soundcloud_go_plus_content, + Toast.LENGTH_LONG).show(); + } else if (exception instanceof YoutubeMusicPremiumContentException) { + Toast.makeText(context, R.string.youtube_music_premium_content, + 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) { diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c1afa106d27..c241254db82 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -665,4 +665,5 @@ Désactiver pour masquer la description de la vidéo et les informations supplémentaires Afficher la description Plante l\'application - \ No newline at end of file + Ce contenu n\'est pas disponible dans votre pays. + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a13c2b0364..a8a3436456e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + Tap \"Search\" to get started \n @@ -164,6 +164,7 @@ Turn on YouTube\'s \"Restricted Mode\" YouTube provides a \"Restricted Mode\" which hides potentially mature content This video is age restricted.\n\nTurn on \"%1$s\" in the settings if you want to see it. + This video is age-restricted.\nDue to new YouTube policies with age-restricted videos, NewPipe cannot access any of its video streams and thus is unable to play it. Live Downloads Downloads @@ -700,4 +701,9 @@ Recent Chapters No app on your device can open this + This content is not available in your country. + This is a SoundCloud Go+ track, at least in your country, so it cannot be streamed or downloaded by NewPipe. + This content is private, so it cannot be streamed or downloaded by NewPipe. + This video is available only to YouTube Music Premium members, so it cannot be streamed or downloaded by NewPipe. + This content is only available to users who have paid, so it cannot be streamed or downloaded by NewPipe. From 8518933ca87268e6b8008990be46a10bb3ddbef3 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Sun, 7 Mar 2021 16:53:22 +0100 Subject: [PATCH 09/57] Update translations Translated using Weblate (Russian) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (French) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Russian) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Czech) Currently translated at 100.0% (624 of 624 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (624 of 624 strings) --- app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 7c2ce49c130..dbcdf951e84 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -672,4 +672,5 @@ Na Vašem zařízení není aplikace, která to umí otevřít Podobné strýmy Vypnout pro skrytí popisu videa a doplňkové informace + Zbořit aplikaci \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c241254db82..52697431363 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -664,6 +664,6 @@ Commentaires Désactiver pour masquer la description de la vidéo et les informations supplémentaires Afficher la description - Plante l\'application + Faire planter l\'application Ce contenu n\'est pas disponible dans votre pays. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 681987cad6d..db26e27ed05 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -673,4 +673,5 @@ Показать описание Открыть в Подходящее приложение не найдено + Сбой приложения \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 68ee6608b19..490bd2143e3 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -662,4 +662,5 @@ Açıklamayı göster Birlikte aç Aygıtınızdaki hiçbir uygulama bunu açamıyor + Uygulamayı çöktür \ No newline at end of file From 553b80164b4f951e2a967ff26855b408d0ddf070 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 9 Dec 2020 12:42:01 +0100 Subject: [PATCH 10/57] Move all error-related classes into error package --- .../{report => error}/ErrorInfoTest.java | 2 +- app/src/main/AndroidManifest.xml | 4 ++-- app/src/main/java/org/schabi/newpipe/App.java | 7 ++++--- .../schabi/newpipe/CheckForNewAppVersion.java | 19 ++++++++++++------- .../org/schabi/newpipe/DownloaderImpl.java | 1 + .../java/org/schabi/newpipe/MainActivity.java | 2 +- .../org/schabi/newpipe/RouterActivity.java | 2 +- .../newpipe/download/DownloadDialog.java | 6 +++--- .../{report => error}/AcraReportSender.java | 2 +- .../AcraReportSenderFactory.java | 2 +- .../{report => error}/ErrorActivity.java | 2 +- .../newpipe/{report => error}/ErrorInfo.kt | 2 +- .../{ => error}/ReCaptchaActivity.java | 5 ++++- .../newpipe/{report => error}/UserAction.java | 2 +- .../newpipe/fragments/BaseStateFragment.java | 8 ++++---- .../newpipe/fragments/MainFragment.java | 6 +++--- .../fragments/detail/VideoDetailFragment.java | 8 ++++---- .../fragments/list/BaseListFragment.java | 2 +- .../list/channel/ChannelFragment.java | 8 +++----- .../list/comments/CommentsFragment.java | 2 +- .../list/kiosk/DefaultKioskFragment.java | 2 +- .../fragments/list/kiosk/KioskFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 4 ++-- .../fragments/list/search/SearchFragment.java | 8 ++++---- .../list/videos/RelatedVideosFragment.java | 2 +- .../holder/CommentsMiniInfoItemHolder.java | 2 +- .../local/bookmark/BookmarkFragment.java | 2 +- .../schabi/newpipe/local/feed/FeedFragment.kt | 2 +- .../history/StatisticsPlaylistFragment.java | 6 +++--- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../subscription/SubscriptionFragment.kt | 2 +- .../SubscriptionsImportFragment.java | 6 +++--- .../services/BaseImportExportService.java | 6 +++--- .../settings/ContentSettingsFragment.java | 8 ++++---- .../settings/HistorySettingsFragment.java | 6 +++--- .../settings/SelectChannelFragment.java | 6 +++--- .../newpipe/settings/SelectKioskFragment.java | 6 +++--- .../settings/SelectPlaylistFragment.java | 6 +++--- .../newpipe/settings/SettingMigrations.java | 6 +++--- .../settings/tabs/ChooseTabsFragment.java | 6 +++--- .../org/schabi/newpipe/settings/tabs/Tab.java | 6 +++--- .../schabi/newpipe/util/ExtractorHelper.java | 8 ++++---- .../giga/ui/adapter/MissionAdapter.java | 6 +++--- app/src/main/res/layout/activity_error.xml | 2 +- .../{report => error}/ErrorActivityTest.java | 2 +- .../{ => error}/ReCaptchaActivityTest.kt | 9 ++++----- 46 files changed, 111 insertions(+), 104 deletions(-) rename app/src/androidTest/java/org/schabi/newpipe/{report => error}/ErrorInfoTest.java (97%) rename app/src/main/java/org/schabi/newpipe/{report => error}/AcraReportSender.java (97%) rename app/src/main/java/org/schabi/newpipe/{report => error}/AcraReportSenderFactory.java (97%) rename app/src/main/java/org/schabi/newpipe/{report => error}/ErrorActivity.java (99%) rename app/src/main/java/org/schabi/newpipe/{report => error}/ErrorInfo.kt (94%) rename app/src/main/java/org/schabi/newpipe/{ => error}/ReCaptchaActivity.java (98%) rename app/src/main/java/org/schabi/newpipe/{report => error}/UserAction.java (96%) rename app/src/test/java/org/schabi/newpipe/{report => error}/ErrorActivityTest.java (97%) rename app/src/test/java/org/schabi/newpipe/{ => error}/ReCaptchaActivityTest.kt (83%) diff --git a/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java similarity index 97% rename from app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java rename to app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java index 09ae5648d56..ef2ff7ec90c 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java +++ b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.report; +package org.schabi.newpipe.error; import android.os.Parcel; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1f92548b400..e7fa95759ea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -85,7 +85,7 @@ android:name=".ExitActivity" android:label="@string/general_error" android:theme="@android:style/Theme.NoDisplay" /> - + + tools:context=".error.ErrorActivity"> Date: Mon, 8 Mar 2021 09:46:05 +0100 Subject: [PATCH 11/57] Fix last resize mode not being restored correctly I think the settings key "last_resize_mode" is ambiguous. While it is used to get the recently used resize mode, someone thought while working on the resize mode switcher, that the old (to be replaced) resize mode should be stored. Fixes #5613 --- .../java/org/schabi/newpipe/player/helper/PlayerHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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..e07be0c21f1 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; } From 70f421b787373b1278ddaa0136f08e44a97e070c Mon Sep 17 00:00:00 2001 From: Tobi Date: Mon, 8 Mar 2021 10:43:05 +0100 Subject: [PATCH 12/57] Add links to Matrix and IRC chat when opening new issues (#5764) --- .github/ISSUE_TEMPLATE/config.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0cec6..5a97b366252 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,8 @@ 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 From eeaf3496d5ed8e5aa7624b86cfe0cc1b94f48dd0 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Mon, 8 Mar 2021 13:15:22 +0100 Subject: [PATCH 13/57] Fix: CI only on master --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f8960bdc4c..16d0f5a3c78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,12 @@ name: CI -on: [push, pull_request] +on: + pull_request: + branches: + - master + push: + branches: + - master jobs: build-and-test: From 1db3c57ef00131050ef1b59c77819a3327996562 Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Mon, 8 Mar 2021 13:19:18 +0100 Subject: [PATCH 14/57] change master to dev --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16d0f5a3c78..d171d5ad37e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: CI on: pull_request: branches: - - master + - dev push: branches: - - master + - dev jobs: build-and-test: From 3a61ab59f2e086df4978c0a1f4563e6eb32f92ab Mon Sep 17 00:00:00 2001 From: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Date: Tue, 9 Mar 2021 11:21:40 +0100 Subject: [PATCH 15/57] adding master to push trigger Co-authored-by: Tobi --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d171d5ad37e..25e16ddfef9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: push: branches: - dev + - master jobs: build-and-test: From c43bca6007584c2cca28ddf984ad090baf370c68 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 11 Dec 2020 14:55:47 +0100 Subject: [PATCH 16/57] Add report/solve-recaptcha button in error panel It will be shown even when nothing could be loaded not due to a network error, and the user can choose to ignore or report it. Also improve error reporting arguments Also completely refactor error activity Also improve some code here and there --- app/build.gradle | 2 +- .../schabi/newpipe/error/ErrorInfoTest.java | 2 +- .../schabi/newpipe/ActivityCommunicator.java | 47 ---- app/src/main/java/org/schabi/newpipe/App.java | 12 +- .../schabi/newpipe/CheckForNewAppVersion.java | 15 +- .../java/org/schabi/newpipe/MainActivity.java | 10 +- .../org/schabi/newpipe/RouterActivity.java | 94 ++++++-- .../newpipe/download/DownloadDialog.java | 14 +- .../newpipe/error/AcraReportSender.java | 11 +- .../schabi/newpipe/error/ErrorActivity.java | 177 +++++---------- .../org/schabi/newpipe/error/ErrorInfo.kt | 110 +++++++++- .../schabi/newpipe/error/ErrorPanelHelper.kt | 136 ++++++++++++ .../org/schabi/newpipe/error/UserAction.java | 15 +- .../newpipe/fragments/BaseStateFragment.java | 203 ++++-------------- .../newpipe/fragments/MainFragment.java | 30 ++- .../newpipe/fragments/ViewContract.java | 4 +- .../fragments/detail/VideoDetailFragment.java | 69 ++---- .../fragments/list/BaseListFragment.java | 19 +- .../fragments/list/BaseListInfoFragment.java | 49 ++++- .../list/channel/ChannelFragment.java | 83 ++----- .../list/comments/CommentsFragment.java | 48 +---- .../list/kiosk/DefaultKioskFragment.java | 6 +- .../fragments/list/kiosk/KioskFragment.java | 26 +-- .../list/playlist/PlaylistFragment.java | 58 ++--- .../fragments/list/search/SearchFragment.java | 110 ++++------ .../list/videos/RelatedVideosFragment.java | 38 +--- .../holder/CommentsMiniInfoItemHolder.java | 4 +- .../newpipe/local/BaseLocalListFragment.java | 26 +-- .../local/bookmark/BookmarkFragment.java | 26 +-- .../schabi/newpipe/local/feed/FeedFragment.kt | 72 ++----- .../history/StatisticsPlaylistFragment.java | 79 ++----- .../local/playlist/LocalPlaylistFragment.java | 82 ++++--- .../subscription/SubscriptionFragment.kt | 20 +- .../SubscriptionsImportFragment.java | 8 +- .../services/BaseImportExportService.java | 10 +- .../settings/ContentSettingsFragment.java | 21 +- .../settings/HistorySettingsFragment.java | 199 ++++++++--------- .../settings/SelectChannelFragment.java | 28 +-- .../newpipe/settings/SelectKioskFragment.java | 15 +- .../settings/SelectPlaylistFragment.java | 4 +- .../newpipe/settings/SettingMigrations.java | 10 +- .../settings/tabs/ChooseTabsFragment.java | 7 +- .../org/schabi/newpipe/settings/tabs/Tab.java | 5 +- .../schabi/newpipe/util/ExtractorHelper.java | 78 ------- .../giga/ui/adapter/MissionAdapter.java | 12 +- .../fragment_video_detail.xml | 2 +- .../{error_retry.xml => error_panel.xml} | 17 +- app/src/main/res/layout/fragment_blank.xml | 2 +- .../main/res/layout/fragment_bookmarks.xml | 2 +- app/src/main/res/layout/fragment_channel.xml | 2 +- app/src/main/res/layout/fragment_comments.xml | 6 +- app/src/main/res/layout/fragment_feed.xml | 2 +- app/src/main/res/layout/fragment_kiosk.xml | 2 +- app/src/main/res/layout/fragment_playlist.xml | 2 +- .../res/layout/fragment_related_streams.xml | 2 +- app/src/main/res/layout/fragment_search.xml | 2 +- .../main/res/layout/fragment_subscription.xml | 2 +- .../main/res/layout/fragment_video_detail.xml | 2 +- app/src/main/res/values/strings.xml | 1 + 59 files changed, 882 insertions(+), 1258 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java create mode 100644 app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt rename app/src/main/res/layout/{error_retry.xml => error_panel.xml} (64%) diff --git a/app/build.gradle b/app/build.gradle index 7c504e96895..8d0db17b261 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -155,7 +155,7 @@ task formatKtlint(type: JavaExec) { } afterEvaluate { - preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint + //preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint } dependencies { diff --git a/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java index ef2ff7ec90c..d491059e571 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java +++ b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java @@ -31,7 +31,7 @@ public void errorInfoTestParcelable() { assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction()); assertEquals("youtube", infoFromParcel.getServiceName()); assertEquals("request", infoFromParcel.getRequest()); - assertEquals(R.string.general_error, infoFromParcel.getMessage()); + assertEquals(R.string.general_error, infoFromParcel.getMessageStringId()); parcel.recycle(); } diff --git a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java deleted file mode 100644 index 9321b307196..00000000000 --- a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.schabi.newpipe; - -/* - * Created by Christian Schabesberger on 24.12.15. - * - * Copyright (C) Christian Schabesberger 2015 - * 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 . - */ - -/** - * 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; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 05b88148fa7..d8519d0cb57 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -225,14 +225,10 @@ protected void initACRA() { .setBuildConfigClass(BuildConfig.class) .build(); ACRA.init(this, acraConfig); - } catch (final ACRAConfigurationException ace) { - ace.printStackTrace(); - ErrorActivity.reportError(this, - ace, - null, - null, - ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", - "Could not initialize ACRA crash report", R.string.app_ui_crash)); + } catch (final ACRAConfigurationException exception) { + exception.printStackTrace(); + ErrorActivity.reportError(this, null, null, new ErrorInfo(exception, + UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report")); } } diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java index f4c51144dab..ad8aae93db8 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java @@ -63,9 +63,8 @@ private static String getCertificateSHA1Fingerprint(@NonNull final Application a packageInfo = application.getPackageManager().getPackageInfo( application.getPackageName(), PackageManager.GET_SIGNATURES); } catch (final PackageManager.NameNotFoundException e) { - ErrorActivity.reportError(application, e, null, null, - ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", - "Could not find package info", R.string.app_ui_crash)); + ErrorActivity.reportError(application, null, null, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info")); return ""; } @@ -77,9 +76,8 @@ private static String getCertificateSHA1Fingerprint(@NonNull final Application a final CertificateFactory cf = CertificateFactory.getInstance("X509"); c = (X509Certificate) cf.generateCertificate(input); } catch (final CertificateException e) { - ErrorActivity.reportError(application, e, null, null, - ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", - "Certificate error", R.string.app_ui_crash)); + ErrorActivity.reportError(application, null, null, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error")); return ""; } @@ -88,9 +86,8 @@ private static String getCertificateSHA1Fingerprint(@NonNull final Application a final byte[] publicKey = md.digest(c.getEncoded()); return byte2HexFormatted(publicKey); } catch (NoSuchAlgorithmException | CertificateEncodingException e) { - ErrorActivity.reportError(application, e, null, null, - ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", - "Could not retrieve SHA1 key", R.string.app_ui_crash)); + ErrorActivity.reportError(application, null, null, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key")); return ""; } } diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index abcf3b04de5..211441ba70a 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -153,7 +153,7 @@ protected void onCreate(final Bundle savedInstanceState) { try { setupDrawer(); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Setting up drawer", e); } if (DeviceUtils.isTv(this)) { @@ -238,7 +238,7 @@ private boolean drawerItemSelected(final MenuItem item) { try { tabSelected(item); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Selecting main page tab", e); } break; case R.id.menu_options_about_group: @@ -340,7 +340,7 @@ private void toggleServices() { try { showTabs(); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Showing main page tabs", e); } } } @@ -487,7 +487,7 @@ protected void onResume() { drawerHeaderBinding.drawerHeaderActionButton.setContentDescription( getString(R.string.drawer_header_description) + selectedServiceName); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Setting up service toggle", e); } final SharedPreferences sharedPreferences @@ -799,7 +799,7 @@ private void handleIntent(final Intent intent) { NavigationHelper.gotoMainFragment(getSupportFragmentManager()); } } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Handling intent", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 101a535f765..59b87638688 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -33,15 +33,29 @@ import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; 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.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService.LinkType; 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.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.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; @@ -49,7 +63,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -84,13 +97,6 @@ * Get the url from the intent and open it in the chosen preferred player. */ 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(); @State protected int currentServiceId = -1; @@ -100,7 +106,6 @@ public class RouterActivity extends AppCompatActivity { protected int selectedRadioPosition = -1; protected int selectedPreviously = -1; protected String currentUrl; - protected boolean internalRoute = false; private StreamingService currentService; private boolean selectionIsDownload = false; @@ -123,7 +128,7 @@ protected void onCreate(final Bundle savedInstanceState) { } @Override - protected void onSaveInstanceState(final Bundle outState) { + protected void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } @@ -164,18 +169,61 @@ private void handleUrl(final String url) { } else { showUnsupportedUrlDialog(url); } - }, throwable -> handleError(throwable, url))); + }, throwable -> handleError(this, + new ErrorInfo(throwable, UserAction.SHARE_TO_NEWPIPE, url)))); } - private void handleError(final Throwable throwable, final String url) { - throwable.printStackTrace(); + /** + * @param context the context. If instance of {@link RouterActivity} it will be finished at the + * end, and if needed {@link #showUnsupportedUrlDialog(String)} will be called + * on it. + * @param errorInfo The error information. The field {@link ErrorInfo#getRequest()} has to + * contain the url, if context is instance of {@link RouterActivity}, since it + * could be used to call {@link #showUnsupportedUrlDialog(String)}. + */ + private static void handleError(final Context context, final ErrorInfo errorInfo) { + if (errorInfo.getThrowable() != null) { + errorInfo.getThrowable().printStackTrace(); + } - if (throwable instanceof ExtractionException) { - showUnsupportedUrlDialog(url); + if (errorInfo.getThrowable() 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 (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 if (errorInfo.getThrowable() instanceof ExtractionException + && context instanceof RouterActivity) { + // unfortunately we cannot tell if the error is really caused by an unsupported url + ((RouterActivity) context).showUnsupportedUrlDialog(errorInfo.getRequest()); } else { - ExtractorHelper.handleGeneralException(this, -1, url, throwable, - UserAction.SOMETHING_ELSE, null); - finish(); + ErrorActivity.reportError(context, MainActivity.class, null, errorInfo); + } + + if (context instanceof RouterActivity) { + ((RouterActivity) context).finish(); } } @@ -500,7 +548,8 @@ private void handleChoice(final String selectedChoiceKey) { .subscribe(intent -> { startActivity(intent); finish(); - }, throwable -> handleError(throwable, currentUrl)) + }, throwable -> handleError(this, + new ErrorInfo(throwable, UserAction.SHARE_TO_NEWPIPE, currentUrl))) ); return; } @@ -580,6 +629,7 @@ private static class Choice implements Serializable { this.playerChoice = playerChoice; } + @NonNull @Override public String toString() { return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice; @@ -646,9 +696,9 @@ public void handleChoice(final Choice choice) { if (fetcher != null) { fetcher.dispose(); } - }, throwable -> ExtractorHelper.handleGeneralException(this, - choice.serviceId, choice.url, throwable, finalUserAction, - ", opened with " + choice.playerChoice)); + }, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction, + choice.url + " opened with " + choice.playerChoice, + choice.serviceId))); } } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index f845969c19c..e21b27dd5e9 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -591,17 +591,6 @@ private void showFailedDialog(@StringRes final int msg) { .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() { final StoredDirectoryHelper mainStorage; final MediaFormat format; @@ -705,7 +694,8 @@ private void checkSelectedDownload(final StoredDirectoryHelper mainStorage, mainStorage.getTag()); } } catch (final Exception e) { - showErrorActivity(e); + ErrorActivity.reportError(context, null, null, + new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage")); return; } diff --git a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java index 8a90f12a5a5..cc791141798 100644 --- a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java +++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; +import org.acra.ReportField; import org.acra.data.CrashReportData; import org.acra.sender.ReportSender; import org.schabi.newpipe.R; @@ -32,8 +33,12 @@ public class AcraReportSender implements ReportSender { @Override public void send(@NonNull final Context context, @NonNull final CrashReportData report) { - ErrorActivity.reportError(context, report, - ErrorInfo.make(UserAction.UI_ERROR, "none", - "App crash, UI failure", R.string.app_ui_crash)); + ErrorActivity.reportError(context, null, null, new ErrorInfo( + new String[]{report.getString(ReportField.STACK_TRACE)}, + UserAction.UI_ERROR, + ErrorInfo.SERVICE_NONE, + "ACRA report", + R.string.app_ui_crash, + null)); } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index 7ed92441bf7..19e764d2927 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -23,9 +23,6 @@ import com.google.android.material.snackbar.Snackbar; 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.MainActivity; import org.schabi.newpipe.R; @@ -34,14 +31,9 @@ import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; 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; @@ -70,7 +62,6 @@ public class ErrorActivity extends AppCompatActivity { public static final String TAG = ErrorActivity.class.toString(); // BUNDLE TAGS 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_SUBJECT @@ -79,100 +70,68 @@ public class ErrorActivity extends AppCompatActivity { public static final String ERROR_GITHUB_ISSUE_URL = "https://github.com/TeamNewPipe/NewPipe/issues"; - private String[] errorList; + public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER + = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + + /** + * 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. + **/ + @Nullable private static Class savedReturnActivity = null; + private ErrorInfo errorInfo; - private Class returnActivity; private String currentTimeStamp; private ActivityErrorBinding activityErrorBinding; - public static void reportUiError(final AppCompatActivity activity, final Throwable el) { - reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); + public static void reportUiError(final Context context, + @Nullable final View rootView, + final String request, + final Throwable throwable) { + reportError(context, (context instanceof Activity ? context.getClass() : null), rootView, + new ErrorInfo(throwable, UserAction.UI_ERROR, request)); } - public static void reportError(final Context context, final List el, - final Class returnActivity, final View rootView, + public static void reportError(final Context context, + final Class returnActivity, + @Nullable final View rootView, final ErrorInfo errorInfo) { 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) .setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v -> - startErrorActivity(returnActivity, context, errorInfo, el)).show(); + startErrorActivity(returnActivity, context, errorInfo)).show(); } else { - startErrorActivity(returnActivity, context, errorInfo, el); - } - } - - private static void startErrorActivity(final Class returnActivity, final Context context, - final ErrorInfo errorInfo, final List 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, - final ErrorInfo errorInfo) { - List el = null; - if (e != null) { - el = new Vector<>(); - el.add(e); + startErrorActivity(returnActivity, context, errorInfo); } - 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 el = null; - if (e != null) { - el = new Vector<>(); - el.add(e); - } - reportError(handler, context, el, returnActivity, rootView, errorInfo); + public static void reportError(final Handler handler, + final Context context, + final Class returnActivity, + final View rootView, + final ErrorInfo errorInfo) { + handler.post(() -> reportError(context, returnActivity, rootView, errorInfo)); } - // async call - public static void reportError(final Handler handler, final Context context, - final List 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)}; + //////////////////////////////////////////////////////////////////////// + // UTILS + //////////////////////////////////////////////////////////////////////// + private static void startErrorActivity(@Nullable final Class returnActivity, + final Context context, + final ErrorInfo errorInfo) { + savedReturnActivity = returnActivity; 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 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 protected void onCreate(final Bundle savedInstanceState) { assureCorrectAppLanguage(this); @@ -193,38 +152,28 @@ protected void onCreate(final Bundle savedInstanceState) { actionBar.setDisplayShowTitleEnabled(true); } - final ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); - returnActivity = ac.getReturnActivity(); errorInfo = intent.getParcelableExtra(ERROR_INFO); - errorList = intent.getStringArrayExtra(ERROR_LIST); // important add guru meditation addGuruMeditation(); - currentTimeStamp = getCurrentTimeStamp(); + currentTimeStamp = CURRENT_TIMESTAMP_FORMATTER.format(LocalDateTime.now()); activityErrorBinding.errorReportEmailButton.setOnClickListener(v -> openPrivacyPolicyDialog(this, "EMAIL")); - activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> { - ShareUtils.copyToClipboard(this, buildMarkdown()); - }); + activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> + ShareUtils.copyToClipboard(this, buildMarkdown())); activityErrorBinding.errorReportGitHubButton.setOnClickListener(v -> openPrivacyPolicyDialog(this, "GITHUB")); // normal bugreport buildInfo(errorInfo); - if (errorInfo.getMessage() != 0) { - activityErrorBinding.errorMessageView.setText(errorInfo.getMessage()); - } else { - activityErrorBinding.errorMessageView.setVisibility(View.GONE); - activityErrorBinding.messageWhatHappenedView.setVisibility(View.GONE); - } - - activityErrorBinding.errorView.setText(formErrorText(errorList)); + activityErrorBinding.errorMessageView.setText(errorInfo.getMessageStringId()); + activityErrorBinding.errorView.setText(formErrorText(errorInfo.getStackTraces())); // print stack trace once again for debugging: - for (final String e : errorList) { + for (final String e : errorInfo.getStackTraces()) { Log.e(TAG, e); } } @@ -239,15 +188,14 @@ public boolean onCreateOptionsMenu(final Menu menu) { @Override public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); - switch (id) { - case android.R.id.home: - goToReturnActivity(); - break; - case R.id.menu_item_share_error: - ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson()); - break; + if (id == android.R.id.home) { + goToReturnActivity(); + } else if (id == R.id.menu_item_share_error) { + ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson()); + } else { + return false; } - return false; + return true; } private void openPrivacyPolicyDialog(final Context context, final String action) { @@ -311,7 +259,7 @@ static Class getReturnActivity(final Class returnActivity } private void goToReturnActivity() { - final Class checkedReturnActivity = getReturnActivity(returnActivity); + final Class checkedReturnActivity = getReturnActivity(savedReturnActivity); if (checkedReturnActivity == null) { super.onBackPressed(); } else { @@ -355,7 +303,7 @@ private String buildJson() { .value("version", BuildConfig.VERSION_NAME) .value("os", getOsString()) .value("time", currentTimeStamp) - .array("exceptions", Arrays.asList(errorList)) + .array("exceptions", Arrays.asList(errorInfo.getStackTraces())) .value("user_comment", activityErrorBinding.errorCommentBox.getText() .toString()) .end() @@ -393,27 +341,27 @@ private String buildMarkdown() { // Collapse all logs to a single paragraph when there are more than one // to keep the GitHub issue clean. - if (errorList.length > 1) { + if (errorInfo.getStackTraces().length > 1) { htmlErrorReport .append("
Exceptions (") - .append(errorList.length) + .append(errorInfo.getStackTraces().length) .append(")

\n"); } // add the logs - for (int i = 0; i < errorList.length; i++) { + for (int i = 0; i < errorInfo.getStackTraces().length; i++) { htmlErrorReport.append("

Crash log "); - if (errorList.length > 1) { + if (errorInfo.getStackTraces().length > 1) { htmlErrorReport.append(i + 1); } htmlErrorReport.append("") .append("

\n") - .append("\n```\n").append(errorList[i]).append("\n```\n") + .append("\n```\n").append(errorInfo.getStackTraces()[i]).append("\n```\n") .append("

\n"); } // make sure to close everything - if (errorList.length > 1) { + if (errorInfo.getStackTraces().length > 1) { htmlErrorReport.append("

\n"); } htmlErrorReport.append("
\n"); @@ -466,11 +414,4 @@ 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()); - } - } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index bcf8be2207b..ffbc2cafca0 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -3,21 +3,109 @@ 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 userAction: UserAction?, - val serviceName: String, - val request: String, - @field:StringRes @param:StringRes val message: Int + val stackTraces: Array, + 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 { - companion object { - @JvmStatic - fun make( - userAction: UserAction?, + + private constructor( + throwable: Throwable, + userAction: UserAction, + serviceName: String, + request: String + ) : this( + throwableToStringList(throwable), + userAction, + serviceName, + request, + getMessageStringId(throwable, userAction), + throwable + ) + + private constructor( + throwable: List, + userAction: UserAction, serviceName: String, - request: String, - @StringRes message: Int - ) = ErrorInfo(userAction, serviceName, request, message) + 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, userAction: UserAction, request: String) + : this(throwable, userAction, SERVICE_NONE, request) + constructor(throwable: List, userAction: UserAction, request: String, serviceId: Int) + : this(throwable, userAction, NewPipe.getNameOfService(serviceId), request) + constructor(throwable: List, 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) = + 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 + } + } } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt new file mode 100644 index 00000000000..82e08d8deab --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -0,0 +1,136 @@ +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, + MainActivity::class.java, + null, + 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 + } +} diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java index e9d427b01ff..e8dec9556b2 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -6,9 +6,12 @@ public enum UserAction { USER_REPORT("user report"), 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"), - SOMETHING_ELSE("something"), + SOMETHING_ELSE("something else"), SEARCHED("searched"), GET_SUGGESTIONS("get suggestions"), REQUESTED_STREAM("requested stream"), @@ -17,11 +20,15 @@ public enum UserAction { REQUESTED_KIOSK("requested kiosk"), REQUESTED_COMMENTS("requested comments"), REQUESTED_FEED("requested feed"), + REQUESTED_BOOKMARK("bookmark"), 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_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; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 7566ec2a037..0d244231d83 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -1,48 +1,25 @@ package org.schabi.newpipe.fragments; import android.content.Context; -import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; -import android.widget.Button; import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -import com.jakewharton.rxbinding4.view.RxView; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; 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.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.ExceptionUtils; +import org.schabi.newpipe.error.ErrorPanelHelper; 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 icepick.State; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.disposables.Disposable; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -55,12 +32,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC private View emptyStateView; @Nullable private ProgressBar loadingProgressBar; - - private Disposable errorDisposable; - - protected View errorPanelRoot; - private Button errorButtonRetry; - private TextView errorTextView; + private ErrorPanelHelper errorPanelHelper; @Override public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { @@ -77,9 +49,7 @@ public void onPause() { @Override public void onDestroy() { super.onDestroy(); - if (errorDisposable != null) { - errorDisposable.dispose(); - } + errorPanelHelper.dispose(); } /*////////////////////////////////////////////////////////////////////////// @@ -89,22 +59,9 @@ public void onDestroy() { @Override protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - emptyStateView = rootView.findViewById(R.id.empty_state_view); loadingProgressBar = rootView.findViewById(R.id.loading_progress_bar); - - errorPanelRoot = rootView.findViewById(R.id.error_panel); - errorButtonRetry = rootView.findViewById(R.id.error_button_retry); - errorTextView = rootView.findViewById(R.id.error_message_view); - } - - @Override - protected void initListeners() { - super.initListeners(); - errorDisposable = RxView.clicks(errorButtonRetry) - .debounce(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(o -> onRetryButtonClicked()); + errorPanelHelper = new ErrorPanelHelper(this, rootView, this::onRetryButtonClicked); } protected void onRetryButtonClicked() { @@ -143,7 +100,7 @@ public void showLoading() { if (loadingProgressBar != null) { animate(loadingProgressBar, true, 400); } - animate(errorPanelRoot, false, 150); + errorPanelHelper.hide(); } @Override @@ -154,10 +111,9 @@ public void hideLoading() { if (loadingProgressBar != null) { animate(loadingProgressBar, false, 0); } - animate(errorPanelRoot, false, 150); + errorPanelHelper.hide(); } - @Override public void showEmptyState() { isLoading.set(false); if (emptyStateView != null) { @@ -166,33 +122,21 @@ public void showEmptyState() { if (loadingProgressBar != null) { animate(loadingProgressBar, false, 0); } - animate(errorPanelRoot, false, 150); + errorPanelHelper.hide(); } @Override - public void showError(final String message, final boolean showRetryButton) { + public void handleResult(final I result) { if (DEBUG) { - Log.d(TAG, "showError() called with: " - + "message = [" + message + "], showRetryButton = [" + showRetryButton + "]"); + Log.d(TAG, "handleResult() called with: result = [" + result + "]"); } - 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 - public void handleResult(final I result) { - if (DEBUG) { - Log.d(TAG, "handleResult() called with: result = [" + result + "]"); - } + public void handleError() { + isLoading.set(false); + InfoCache.getInstance().clearCache(); hideLoading(); } @@ -200,134 +144,59 @@ public void handleResult(final I result) { // Error handling //////////////////////////////////////////////////////////////////////////*/ - /** - * Default implementation handles some general exceptions. - * - * @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); + public final void showError(final ErrorInfo errorInfo) { + handleError(); if (isDetached() || isRemoving()) { if (DEBUG) { - Log.w(TAG, "onError() is detached or removing = [" + exception + "]"); - } - return true; - } - - if (ExceptionUtils.isInterruptedCaused(exception)) { - if (DEBUG) { - Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]"); + Log.w(TAG, "showError() is detached or removing = [" + errorInfo + "]"); } - return true; - } - - if (exception instanceof ReCaptchaException) { - onReCaptchaException((ReCaptchaException) exception); - return true; - } else if (ExceptionUtils.isNetworkRelated(exception)) { - showError(getString(R.string.network_error), true); - return true; - } else if (exception instanceof AgeRestrictedContentException) { - showError(getString(R.string.restricted_video_no_stream), false); - return true; - } else if (exception instanceof GeographicRestrictionException) { - showError(getString(R.string.georestricted_content), false); - return true; - } else if (exception instanceof PaidContentException) { - showError(getString(R.string.paid_content), false); - return true; - } else if (exception instanceof PrivateContentException) { - showError(getString(R.string.private_content), false); - return true; - } else if (exception instanceof SoundCloudGoPlusContentException) { - showError(getString(R.string.soundcloud_go_plus_content), false); - return true; - } else if (exception instanceof YoutubeMusicPremiumContentException) { - showError(getString(R.string.youtube_music_premium_content), false); - return true; - } else if (exception instanceof ContentNotAvailableException) { - showError(getString(R.string.content_not_available), false); - return true; - } else if (exception instanceof ContentNotSupportedException) { - showError(getString(R.string.content_not_supported), false); - return true; + return; } - return false; + errorPanelHelper.showError(errorInfo); } - public void onReCaptchaException(final ReCaptchaException exception) { - if (DEBUG) { - Log.d(TAG, "onReCaptchaException() called"); - } - 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); + public final void showTextError(final @NonNull String errorString) { + handleError(); - showError(getString(R.string.recaptcha_request_toast), false); - } + if (isDetached() || isRemoving()) { + if (DEBUG) { + Log.w(TAG, "showTextError() is detached or removing = [" + errorString + "]"); + } + return; + } - public void onUnrecoverableError(final Throwable exception, final UserAction userAction, - final String serviceName, final String request, - @StringRes final int errorId) { - onUnrecoverableError(Collections.singletonList(exception), userAction, serviceName, - request, errorId); + errorPanelHelper.showTextError(errorString); } - public void onUnrecoverableError(final List 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 final void hideErrorPanel() { + errorPanelHelper.hide(); } - 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); + public final boolean isErrorPanelVisible() { + return errorPanelHelper.isVisible(); } /** * Show a SnackBar and only call - * {@link ErrorActivity#reportError(Context, List, Class, View, ErrorInfo)} + * {@link ErrorActivity#reportError(Context, Class, View, ErrorInfo)} * IF we a find a valid view (otherwise the error screen appears). * - * @param exception List of the exceptions to show - * @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 + * @param errorInfo The error information */ - public void showSnackBarError(final List exception, final UserAction userAction, - final String serviceName, final String request, - @StringRes final int errorId) { + public void showSnackBarError(final ErrorInfo errorInfo) { if (DEBUG) { - Log.d(TAG, "showSnackBarError() called with: " - + "exception = [" + exception + "], userAction = [" + userAction + "], " - + "request = [" + request + "], errorId = [" + errorId + "]"); + Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]"); } View rootView = activity != null ? activity.findViewById(android.R.id.content) : null; - if (rootView == null && getView() != null) { + if (rootView == null) { rootView = getView(); } if (rootView == null) { return; } - ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, - ErrorInfo.make(userAction, serviceName, request, errorId)); + ErrorActivity.reportError(requireContext(), MainActivity.class, rootView, errorInfo); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 649f2d5b13a..9443bac05ad 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -14,7 +14,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround; @@ -25,10 +24,8 @@ import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.FragmentMainBinding; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.util.NavigationHelper; @@ -128,7 +125,8 @@ public void onDestroy() { //////////////////////////////////////////////////////////////////////////*/ @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); if (DEBUG) { Log.d(TAG, "onCreateOptionsMenu() called with: " @@ -144,15 +142,14 @@ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_search: - try { - NavigationHelper.openSearchFragment(getFM(), - ServiceHelper.getSelectedServiceId(activity), ""); - } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); - } - return true; + if (item.getItemId() == R.id.action_search) { + try { + NavigationHelper.openSearchFragment(getFM(), + ServiceHelper.getSelectedServiceId(activity), ""); + } catch (final Exception e) { + ErrorActivity.reportUiError(getActivity(), null, "Opening search fragment", e); + } + return true; } return super.onOptionsItemSelected(item); } @@ -241,8 +238,7 @@ public Fragment getItem(final int position) { } if (throwable != null) { - ErrorActivity.reportError(context, throwable, null, null, ErrorInfo - .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + ErrorActivity.reportUiError(context, null, "Getting fragment item", throwable); return new BlankFragment(); } @@ -254,7 +250,7 @@ public Fragment getItem(final int position) { } @Override - public int getItemPosition(final Object object) { + public int getItemPosition(@NonNull final Object object) { // Causes adapter to reload all Fragments when // notifyDataSetChanged is called return POSITION_NONE; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java b/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java index bb980ac64e0..78f644ffbe8 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/ViewContract.java @@ -7,7 +7,7 @@ public interface ViewContract { void showEmptyState(); - void showError(String message, boolean showRetryButton); - void handleResult(I result); + + void handleError(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 505111da5be..c6789804c3a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -37,7 +37,6 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; @@ -64,9 +63,7 @@ import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.ServiceList; 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.Stream; import org.schabi.newpipe.extractor.stream.StreamInfo; @@ -526,7 +523,7 @@ private void openChannel(final String subChannelUrl, final String subChannelName NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(), subChannelUrl, subChannelName); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e); } } @@ -684,13 +681,12 @@ private void initThumbnailViews(@NonNull final StreamInfo info) { binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); if (!isEmpty(info.getThumbnailUrl())) { - final String infoServiceName = NewPipe.getNameOfService(info.getServiceId()); final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() { @Override public void onLoadingFailed(final String imageUri, final View view, final FailReason failReason) { - showSnackBarError(failReason.getCause(), UserAction.LOAD_IMAGE, - infoServiceName, imageUri, R.string.could_not_load_thumbnails); + showSnackBarError(new ErrorInfo(failReason.getCause(), UserAction.LOAD_IMAGE, + imageUri, info)); } }; @@ -906,10 +902,8 @@ private void runWorker(final boolean forceLoad, final boolean addToBackStack) { openVideoPlayer(); } } - }, throwable -> { - isLoading.set(false); - onError(throwable); - }); + }, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM, + url == null ? "no url" : url, serviceId))); } /*////////////////////////////////////////////////////////////////////////// @@ -1327,8 +1321,8 @@ private void setErrorImage(final int imageResource) { } @Override - public void showError(final String message, final boolean showRetryButton) { - super.showError(message, showRetryButton); + public void handleError() { + super.handleError(); setErrorImage(R.drawable.not_available_monkey); if (binding.relatedStreamsLayout != null) { // hide related streams for tablets @@ -1341,8 +1335,8 @@ public void showError(final String message, final boolean showRetryButton) { } private void hideAgeRestrictedContent() { - showError(getString(R.string.restricted_video, - getString(R.string.show_age_restricted_content_title)), false); + showTextError(getString(R.string.restricted_video, + getString(R.string.show_age_restricted_content_title))); } private void setupBroadcastReceiver() { @@ -1548,11 +1542,8 @@ public void handleResult(@NonNull final StreamInfo info) { } if (!info.getErrors().isEmpty()) { - showSnackBarError(info.getErrors(), - UserAction.REQUESTED_STREAM, - NewPipe.getNameOfService(info.getServiceId()), - info.getUrl(), - 0); + showSnackBarError(new ErrorInfo(info.getErrors(), + UserAction.REQUESTED_STREAM, info.getUrl(), info)); } binding.detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM @@ -1592,6 +1583,10 @@ private void displayBothUploaderAndSubChannel(final StreamInfo info) { } public void openDownloadDialog() { + if (currentInfo == null) { + return; + } + try { final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); downloadDialog.setVideoStreams(sortedVideoStreams); @@ -1601,18 +1596,10 @@ public void openDownloadDialog() { downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); } catch (final Exception e) { - final ErrorInfo info = ErrorInfo.make(UserAction.UI_ERROR, - ServiceList.all() - .get(currentInfo - .getServiceId()) - .getServiceInfo() - .getName(), "", - R.string.could_not_setup_download_menu); - - ErrorActivity.reportError(activity, - e, - activity.getClass(), - activity.findViewById(android.R.id.content), info); + ErrorActivity.reportError(activity, activity.getClass(), + activity.findViewById(android.R.id.content), + new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog", + currentInfo)); } } @@ -1620,24 +1607,6 @@ public void openDownloadDialog() { // 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) { if (positionSubscriber != null) { positionSubscriber.dispose(); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index b8c9af05b5c..d9c18fe4468 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -14,7 +14,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -292,7 +291,7 @@ public void selected(final ChannelInfoItem selectedItem) { selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e); } } }); @@ -307,7 +306,7 @@ public void selected(final PlaylistInfoItem selectedItem) { selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening playlist fragment", e); } } }); @@ -412,13 +411,6 @@ public void hideLoading() { 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 public void showEmptyState() { super.showEmptyState(); @@ -439,6 +431,13 @@ public void handleNextItems(final N result) { isLoading.set(false); } + @Override + public void handleError() { + super.handleError(); + showListFooter(false); + animate(itemsList, false, 200); + } + @Override public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 006072e9322..6874f80d5e6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -7,12 +7,17 @@ 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.ListInfo; import org.schabi.newpipe.extractor.Page; +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.views.NewPipeRecyclerView; +import java.util.ArrayList; +import java.util.List; import java.util.Queue; import icepick.State; @@ -30,10 +35,15 @@ public abstract class BaseListInfoFragment @State protected String url; + private final UserAction errorUserAction; protected I currentInfo; protected Page currentNextPage; protected Disposable currentWorker; + protected BaseListInfoFragment(final UserAction errorUserAction) { + this.errorUserAction = errorUserAction; + } + @Override protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); @@ -133,7 +143,9 @@ public void startLoading(final boolean forceLoad) { currentInfo = result; currentNextPage = result.getNextPage(); handleResult(result); - }, this::onError); + }, throwable -> + showError(new ErrorInfo(throwable, errorUserAction, + "Start loading: " + url, serviceId))); } /** @@ -161,10 +173,9 @@ protected void loadMoreItems() { .subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> { isLoading.set(false); handleNextItems(InfoItemsPage); - }, (@NonNull Throwable throwable) -> { - isLoading.set(false); - onError(throwable); - }); + }, (@NonNull Throwable throwable) -> + dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(throwable, + errorUserAction, "Loading more items: " + url, serviceId))); } private void forbidDownwardFocusScroll() { @@ -182,10 +193,16 @@ private void allowDownwardFocusScroll() { @Override public void handleNextItems(final ListExtractor.InfoItemsPage result) { super.handleNextItems(result); + currentNextPage = result.getNextPage(); infoListAdapter.addInfoItemList(result.getItems()); showListFooter(hasMoreItems()); + + if (!result.getErrors().isEmpty()) { + dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), errorUserAction, + "Get next items of: " + url, serviceId)); + } } @Override @@ -213,6 +230,18 @@ public void handleResult(@NonNull final I result) { showEmptyState(); } } + + if (!result.getErrors().isEmpty()) { + final List 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 @@ protected void setInitialData(final int sid, final String u, final String title) this.url = u; 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); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index ca95c272a14..1d5bcce3238 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -16,7 +16,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.viewbinding.ViewBinding; @@ -27,20 +26,19 @@ import org.schabi.newpipe.databinding.ChannelHeaderBinding; import org.schabi.newpipe.databinding.FragmentChannelBinding; 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.ListExtractor; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; 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.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; @@ -91,6 +89,10 @@ public static ChannelFragment getInstance(final int serviceId, final String url, return instance; } + public ChannelFragment() { + super(UserAction.REQUESTED_CHANNEL); + } + @Override public void setUserVisibleHint(final boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); @@ -217,9 +219,8 @@ public boolean onOptionsItemSelected(final MenuItem item) { private void monitorSubscription(final ChannelInfo info) { final Consumer onError = (Throwable throwable) -> { animate(headerBinding.channelSubscribeButton, false, 100); - showSnackBarError(throwable, UserAction.SUBSCRIPTION, - NewPipe.getNameOfService(currentInfo.getServiceId()), - "Get subscription status", 0); + showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_GET, + "Get subscription status", currentInfo)); }; final Observable> observable = subscriptionManager @@ -269,11 +270,8 @@ private void updateSubscription(final ChannelInfo info) { }; final Consumer onError = (@NonNull Throwable throwable) -> - onUnrecoverableError(throwable, - UserAction.SUBSCRIPTION, - NewPipe.getNameOfService(info.getServiceId()), - "Updating Subscription for " + info.getUrl(), - R.string.subscription_update_failed); + showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_UPDATE, + "Updating subscription for " + info.getUrl(), info)); disposables.add(subscriptionManager.updateChannelInfo(info) .subscribeOn(Schedulers.io()) @@ -290,11 +288,8 @@ private Disposable monitorSubscribeButton(final Button subscribeButton, }; final Consumer onError = (@NonNull Throwable throwable) -> - onUnrecoverableError(throwable, - UserAction.SUBSCRIPTION, - NewPipe.getNameOfService(currentInfo.getServiceId()), - "Subscription Change", - R.string.subscription_change_failed); + showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_CHANGE, + "Changing subscription for " + currentInfo.getUrl(), currentInfo)); /* Emit clicks from main thread unto io thread */ return RxView.clicks(subscribeButton) @@ -408,7 +403,7 @@ public void onClick(final View v) { currentInfo.getParentChannelUrl(), currentInfo.getParentChannelName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e); } } else if (DEBUG) { Log.i(TAG, "Can't open parent channel because we got no channel URL"); @@ -469,21 +464,9 @@ public void handleResult(@NonNull final ChannelInfo result) { playlistControlBinding.getRoot().setVisibility(View.VISIBLE); - final List errors = new ArrayList<>(result.getErrors()); - if (!errors.isEmpty()) { - - // 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); + for (final Throwable throwable : result.getErrors()) { + if (throwable instanceof ContentNotSupportedException) { + showContentNotSupported(); } } @@ -537,38 +520,6 @@ private PlayQueue getPlayQueue(final int 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 //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java index 797b92c63ed..35ab663a626 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java @@ -11,12 +11,11 @@ import androidx.annotation.Nullable; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.ListExtractor; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.ViewUtils; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import io.reactivex.rxjava3.core.Single; @@ -25,13 +24,17 @@ public class CommentsFragment extends BaseListInfoFragment { 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 CommentsFragment instance = new CommentsFragment(); instance.setInitialData(serviceId, url, name); return instance; } + public CommentsFragment() { + super(UserAction.REQUESTED_COMMENTS); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -67,52 +70,13 @@ protected Single loadResult(final boolean forceLoad) { // Contract //////////////////////////////////////////////////////////////////////////*/ - @Override - public void showLoading() { - super.showLoading(); - } - @Override public void handleResult(@NonNull final CommentsInfo result) { super.handleResult(result); - 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(); } - @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 //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java index d83dfc63bfa..bdca25558d0 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java @@ -2,6 +2,7 @@ import android.os.Bundle; +import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.kiosk.KioskList; @@ -10,6 +11,7 @@ import org.schabi.newpipe.util.ServiceHelper; public class DefaultKioskFragment extends KioskFragment { + @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -46,8 +48,8 @@ private void updateSelectedDefaultKiosk() { currentInfo = null; currentNextPage = null; } catch (final ExtractionException e) { - onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none", - "Loading default kiosk from selected service", 0); + showError(new ErrorInfo(e, UserAction.REQUESTED_KIOSK, + "Loading default kiosk for selected service")); } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java index 849e0771655..77aa8e0019e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -12,6 +12,7 @@ import androidx.appcompat.app.ActionBar; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; @@ -82,6 +83,10 @@ public static KioskFragment getInstance(final int serviceId, final String kioskI return instance; } + public KioskFragment() { + super(UserAction.REQUESTED_KIOSK); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -102,9 +107,7 @@ public void setUserVisibleHint(final boolean isVisibleToUser) { try { setTitle(kioskTranslatedName); } catch (final Exception e) { - onUnrecoverableError(e, UserAction.UI_ERROR, - "none", - "none", R.string.app_ui_crash); + showSnackBarError(new ErrorInfo(e, UserAction.UI_ERROR, "Setting kiosk title")); } } } @@ -169,22 +172,5 @@ public void handleResult(@NonNull final KioskInfo result) { name = 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); - } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 6d49953037f..b82408b031d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -14,7 +14,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; import androidx.viewbinding.ViewBinding; @@ -25,11 +24,12 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; import org.schabi.newpipe.databinding.PlaylistControlBinding; 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.ListExtractor; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.ServiceList; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper; import org.schabi.newpipe.extractor.stream.StreamInfoItem; @@ -40,8 +40,6 @@ import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; -import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.KoreUtil; @@ -87,6 +85,10 @@ public static PlaylistFragment getInstance(final int serviceId, final String url return instance; } + public PlaylistFragment() { + super(UserAction.REQUESTED_PLAYLIST); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -284,7 +286,7 @@ public void handleResult(@NonNull final PlaylistInfo result) { NavigationHelper.openChannelFragment(getFM(), result.getServiceId(), result.getUploaderUrl(), result.getUploaderName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e); } }); } @@ -315,8 +317,8 @@ public void handleResult(@NonNull final PlaylistInfo result) { .localizeStreamCount(getContext(), result.getStreamCount())); if (!result.getErrors().isEmpty()) { - showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, - NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.REQUESTED_PLAYLIST, + result.getUrl(), result)); } remotePlaylistManager.getPlaylist(result) @@ -363,33 +365,6 @@ private PlayQueue getPlayQueue(final int index) { ); } - @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 //////////////////////////////////////////////////////////////////////////*/ @@ -434,8 +409,9 @@ public void onNext(final List playlist) { } @Override - public void onError(final Throwable t) { - PlaylistFragment.this.onError(t); + public void onError(final Throwable throwable) { + showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK, + "Get playlist bookmarks")); } @Override @@ -460,12 +436,16 @@ private void onBookmarkClicked() { if (currentInfo != null && playlistEntity == null) { action = remotePlaylistManager.onBookmark(currentInfo) .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) { action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid()) .observeOn(AndroidSchedulers.mainThread()) .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 { action = Disposable.empty(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 0fca2e5a686..e68ebbf6aa6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -47,7 +47,6 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.Page; 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.SearchInfo; import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory; @@ -258,11 +257,9 @@ public void onResume() { try { service = NewPipe.getService(serviceId); } catch (final Exception e) { - ErrorActivity.reportError(getActivity(), e, requireActivity().getClass(), + ErrorActivity.reportUiError(getActivity(), requireActivity().findViewById(android.R.id.content), - ErrorInfo.make(UserAction.UI_ERROR, - "", - "", R.string.general_error)); + "Getting service for id " + serviceId, e); } if (!TextUtils.isEmpty(searchString)) { @@ -413,7 +410,7 @@ public void reloadContent() { searchEditText.setText(""); showKeyboardSearch(); } - animate(errorPanelRoot, false, 200); + hideErrorPanel(); } } @@ -540,7 +537,7 @@ private void initSearchListeners() { if (DEBUG) { Log.d(TAG, "onClick() called with: v = [" + v + "]"); } - if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) { + if (isSuggestionsEnabled && !isErrorPanelVisible()) { showSuggestionsPanel(); } if (DeviceUtils.isTv(getContext())) { @@ -553,8 +550,7 @@ private void initSearchListeners() { Log.d(TAG, "onFocusChange() called with: " + "v = [" + v + "], hasFocus = [" + hasFocus + "]"); } - if (isSuggestionsEnabled && hasFocus - && errorPanelRoot.getVisibility() != View.VISIBLE) { + if (isSuggestionsEnabled && hasFocus && !isErrorPanelVisible()) { showSuggestionsPanel(); } }); @@ -704,9 +700,9 @@ private void showDeleteSuggestionDialog(final SuggestionItem item) { .subscribe( howManyDeleted -> suggestionPublisher .onNext(searchEditText.getText().toString()), - throwable -> showSnackBarError(throwable, - UserAction.DELETE_FROM_HISTORY, "none", - "Deleting item failed", R.string.general_error)); + throwable -> showSnackBarError(new ErrorInfo(throwable, + UserAction.DELETE_FROM_HISTORY, + "Deleting item failed"))); disposables.add(onDelete); }) .show(); @@ -763,8 +759,8 @@ private void initSuggestionObserver() { .suggestionsFor(serviceId, query) .onErrorReturn(throwable -> { if (!ExceptionUtils.isNetworkRelated(throwable)) { - showSnackBarError(throwable, UserAction.GET_SUGGESTIONS, - NewPipe.getNameOfService(serviceId), searchString, 0); + showSnackBarError(new ErrorInfo(throwable, + UserAction.GET_SUGGESTIONS, searchString, serviceId)); } return new ArrayList<>(); }) @@ -800,7 +796,8 @@ private void initSuggestionObserver() { if (listNotification.isOnNext()) { handleSuggestions(listNotification.getValue()); } else if (listNotification.isOnError()) { - onSuggestionError(listNotification.getError()); + showError(new ErrorInfo(listNotification.getError(), + UserAction.GET_SUGGESTIONS, searchString, serviceId)); } }); } @@ -832,8 +829,7 @@ private void search(final String theSearchString, .subscribe(intent -> { getFM().popBackStackImmediate(); activity.startActivity(intent); - }, throwable -> - showError(getString(R.string.unsupported_url), false))); + }, throwable -> showTextError(getString(R.string.unsupported_url)))); return; } } catch (final Exception ignored) { @@ -849,10 +845,9 @@ private void search(final String theSearchString, disposables.add(historyRecordManager.onSearched(serviceId, theSearchString) .observeOn(AndroidSchedulers.mainThread()) .subscribe( - ignored -> { - }, - error -> showSnackBarError(error, UserAction.SEARCHED, - NewPipe.getNameOfService(serviceId), theSearchString, 0) + ignored -> {}, + throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED, + theSearchString, serviceId)) )); suggestionPublisher.onNext(theSearchString); startLoading(false); @@ -872,7 +867,7 @@ public void startLoading(final boolean forceLoad) { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnEvent((searchResult, throwable) -> isLoading.set(false)) - .subscribe(this::handleResult, this::onError); + .subscribe(this::handleResult, this::onItemError); } @@ -895,7 +890,7 @@ protected void loadMoreItems() { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnEvent((nextItemsResult, throwable) -> isLoading.set(false)) - .subscribe(this::handleNextItems, this::onError); + .subscribe(this::handleNextItems, this::onItemError); } @Override @@ -909,6 +904,15 @@ protected void onItemSelected(final InfoItem selectedItem) { 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 //////////////////////////////////////////////////////////////////////////*/ @@ -945,26 +949,11 @@ public void handleSuggestions(@NonNull final List suggestions) { searchBinding.suggestionsList.smoothScrollToPosition(0); searchBinding.suggestionsList.post(() -> suggestionListAdapter.setItems(suggestions)); - if (suggestionsPanelVisible && errorPanelRoot.getVisibility() == View.VISIBLE) { + if (suggestionsPanelVisible && isErrorPanelVisible()) { 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 //////////////////////////////////////////////////////////////////////////*/ @@ -975,13 +964,6 @@ public void hideLoading() { showListFooter(false); } - @Override - public void showError(final String message, final boolean showRetryButton) { - super.showError(message, showRetryButton); - hideSuggestionsPanel(); - hideKeyboardSearch(); - } - /*////////////////////////////////////////////////////////////////////////// // Search Results //////////////////////////////////////////////////////////////////////////*/ @@ -992,8 +974,8 @@ public void handleResult(@NonNull final SearchInfo result) { if (!exceptions.isEmpty() && !(exceptions.size() == 1 && exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) { - showSnackBarError(result.getErrors(), UserAction.SEARCHED, - NewPipe.getNameOfService(serviceId), searchString, 0); + showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED, + searchString, serviceId)); } searchSuggestion = result.getSearchSuggestion(); @@ -1061,33 +1043,20 @@ public void handleNextItems(final ListExtractor.InfoItemsPage result) { nextPage = result.getNextPage(); if (!result.getErrors().isEmpty()) { - showSnackBarError(result.getErrors(), UserAction.SEARCHED, - NewPipe.getNameOfService(serviceId), + showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED, "\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", " + "pageIds: " + nextPage.getIds() + ", " - + "pageCookies: " + nextPage.getCookies(), 0); + + "pageCookies: " + nextPage.getCookies(), + serviceId)); } super.handleNextItems(result); } @Override - protected boolean onError(final Throwable exception) { - if (super.onError(exception)) { - return true; - } - - 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; + public void handleError() { + super.handleError(); + hideSuggestionsPanel(); + hideKeyboardSearch(); } /*////////////////////////////////////////////////////////////////////////// @@ -1113,9 +1082,8 @@ public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHo .subscribe( howManyDeleted -> suggestionPublisher .onNext(searchEditText.getText().toString()), - throwable -> showSnackBarError(throwable, - UserAction.DELETE_FROM_HISTORY, "none", - "Deleting item failed", R.string.general_error)); + throwable -> showSnackBarError(new ErrorInfo(throwable, + UserAction.DELETE_FROM_HISTORY, "Deleting item failed"))); disposables.add(onDelete); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java index 7f8410012ae..758cddc5568 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java @@ -47,6 +47,10 @@ public static RelatedVideosFragment getInstance(final StreamInfo info) { return instance; } + public RelatedVideosFragment() { + super(UserAction.REQUESTED_STREAM); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -125,43 +129,9 @@ public void handleResult(@NonNull final RelatedStreamInfo result) { } 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(); } - @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 //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 8bad3b6adad..a077169ccb4 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -171,15 +171,15 @@ private void openCommentAuthor(final CommentsInfoItem item) { if (TextUtils.isEmpty(item.getUploaderUrl())) { return; } + final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); try { - final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); NavigationHelper.openChannelFragment( activity.getSupportFragmentManager(), item.getServiceId(), item.getUploaderUrl(), item.getUploaderName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e); + ErrorActivity.reportUiError(activity, null, "Opening channel fragment", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 84908e41a05..1e4260ba345 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -202,19 +202,6 @@ public void hideLoading() { } } - @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 public void showEmptyState() { super.showEmptyState(); @@ -249,9 +236,18 @@ protected void resetFragment() { } @Override - protected boolean onError(final Throwable exception) { + public void handleError() { + super.handleError(); resetFragment(); - return super.onError(exception); + + showListFooter(false); + + if (itemsList != null) { + animate(itemsList, false, 200); + } + if (headerRootBinding != null) { + animate(headerRootBinding.getRoot(), false, 200); + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index b96f23a9ea9..e043b140e26 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -23,6 +23,7 @@ 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.ErrorInfo; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; @@ -206,7 +207,8 @@ public void onNext(final List subscriptions) { @Override public void onError(final Throwable exception) { - BookmarkFragment.this.onError(exception); + showError(new ErrorInfo(exception, + UserAction.REQUESTED_BOOKMARK, "Loading playlists")); } @Override @@ -237,17 +239,6 @@ public void handleResult(@NonNull final List result) { // 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 protected void resetFragment() { super.resetFragment(); @@ -295,8 +286,10 @@ private void showDeleteDialog(final String name, final Single deleteRea .setPositiveButton(R.string.delete, (dialog, i) -> disposables.add(deleteReactor .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) .show(); } @@ -314,7 +307,10 @@ private void changeLocalPlaylistName(final long id, final String name) { localPlaylistManager.renamePlaylist(id, name); final Disposable disposable = localPlaylistManager.renamePlaylist(id, name) .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); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index efe64d2f267..adbff1e543b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -38,6 +38,7 @@ import icepick.State import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity 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.ktx.animate @@ -48,7 +49,6 @@ import java.util.Calendar class FeedFragment : BaseListFragment() { private var _feedBinding: FragmentFeedBinding? = null private val feedBinding get() = _feedBinding!! - private val errorBinding get() = _feedBinding!!.errorPanel private lateinit var viewModel: FeedViewModel @State @@ -171,50 +171,24 @@ class FeedFragment : BaseListFragment() { // ///////////////////////////////////////////////////////////////////////// override fun showLoading() { + super.showLoading() feedBinding.refreshRootView.animate(false, 0) feedBinding.itemsList.animate(false, 0) - - feedBinding.loadingProgressBar.animate(true, 200) feedBinding.loadingProgressText.animate(true, 200) - - feedBinding.emptyStateView.root.animate(false, 0) - errorBinding.root.animate(false, 0) } override fun hideLoading() { + super.hideLoading() feedBinding.refreshRootView.animate(true, 200) - feedBinding.itemsList.animate(true, 300) - - feedBinding.loadingProgressBar.animate(false, 0) feedBinding.loadingProgressText.animate(false, 0) - - feedBinding.emptyStateView.root.animate(false, 0) - errorBinding.root.animate(false, 0) feedBinding.swiperefresh.isRefreshing = false } override fun showEmptyState() { + super.showEmptyState() feedBinding.refreshRootView.animate(true, 200) feedBinding.itemsList.animate(false, 0) - - feedBinding.loadingProgressBar.animate(false, 0) feedBinding.loadingProgressText.animate(false, 0) - - 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) { @@ -227,6 +201,14 @@ class FeedFragment : BaseListFragment() { updateRefreshViewState() } + override fun handleError() { + super.handleError() + infoListAdapter.clearStreamItemList() + feedBinding.refreshRootView.animate(false, 200) + feedBinding.itemsList.animate(false, 200) + feedBinding.loadingProgressText.animate(false, 200) + } + private fun handleProgressState(progressState: FeedState.ProgressState) { showLoading() @@ -266,13 +248,6 @@ class FeedFragment : BaseListFragment() { ) } - if (loadedState.itemsErrors.isNotEmpty()) { - showSnackBarError( - loadedState.itemsErrors, UserAction.REQUESTED_FEED, - "none", "Loading feed", R.string.general_error - ) - } - if (loadedState.items.isEmpty()) { showEmptyState() } else { @@ -281,12 +256,13 @@ class FeedFragment : BaseListFragment() { } private fun handleErrorState(errorState: FeedState.ErrorState): Boolean { - hideLoading() - errorState.error?.let { - onError(errorState.error) - return true + return if (errorState.error == null) { + hideLoading() + false + } else { + showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed")) + true } - return false } private fun updateRelativeTimeViews() { @@ -320,18 +296,6 @@ class FeedFragment : BaseListFragment() { 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 { const val KEY_GROUP_ID = "ARG_GROUP_ID" const val KEY_GROUP_NAME = "ARG_GROUP_NAME" diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 515b623ec82..1bece369b73 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -14,7 +14,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; import androidx.viewbinding.ViewBinding; import com.google.android.material.snackbar.Snackbar; @@ -27,6 +26,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlaylistControlBinding; 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.StreamType; import org.schabi.newpipe.info_list.InfoItemDialog; @@ -34,10 +35,7 @@ import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.settings.SettingsActivity; +import org.schabi.newpipe.settings.HistorySettingsFragment; import org.schabi.newpipe.util.KoreUtil; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; @@ -49,6 +47,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Objects; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -163,48 +162,11 @@ public void held(final LocalItem selectedItem) { @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_history_clear: - new AlertDialog.Builder(activity) - .setTitle(R.string.delete_view_history_alert) - .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss())) - .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); + if (item.getItemId() == R.id.action_history_clear) { + HistorySettingsFragment + .openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables); + } else { + return super.onOptionsItemSelected(item); } return true; } @@ -228,7 +190,7 @@ public void startLoading(final boolean forceLoad) { @Override public void onPause() { super.onPause(); - itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); + itemsListState = Objects.requireNonNull(itemsList.getLayoutManager()).onSaveInstanceState(); } @Override @@ -287,7 +249,8 @@ public void onNext(final List streams) { @Override public void onError(final Throwable exception) { - StatisticsPlaylistFragment.this.onError(exception); + showError( + new ErrorInfo(exception, UserAction.SOMETHING_ELSE, "History Statistics")); } @Override @@ -313,7 +276,7 @@ public void handleResult(@NonNull final List result) { } itemListAdapter.addItems(processResult(result)); - if (itemsListState != null) { + if (itemsListState != null && itemsList.getLayoutManager() != null) { itemsList.getLayoutManager().onRestoreInstanceState(itemsListState); itemsListState = null; } @@ -341,17 +304,6 @@ protected void resetFragment() { } } - @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 //////////////////////////////////////////////////////////////////////////*/ @@ -439,9 +391,8 @@ private void deleteEntry(final int index) { Toast.LENGTH_SHORT).show(); } }, - throwable -> showSnackBarError(throwable, - UserAction.DELETE_FROM_HISTORY, "none", - "Deleting item failed", R.string.general_error)); + throwable -> showSnackBarError(new ErrorInfo(throwable, + UserAction.DELETE_FROM_HISTORY, "Deleting item"))); disposables.add(onDelete); } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index c2d6698a9ce..33cc9f636c6 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -34,6 +34,7 @@ import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding; 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.StreamType; @@ -110,7 +111,7 @@ public static LocalPlaylistFragment getInstance(final long playlistId, final Str @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); + playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext())); debouncedSaveSignal = PublishSubject.create(); disposables = new CompositeDisposable(); @@ -334,7 +335,8 @@ public void onNext(final List streams) { @Override public void onError(final Throwable exception) { - LocalPlaylistFragment.this.onError(exception); + showError(new ErrorInfo(exception, UserAction.REQUESTED_BOOKMARK, + "Loading local playlist")); } @Override @@ -344,25 +346,23 @@ public void onComplete() { } @Override public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_item_remove_watched: - if (!isRemovingWatched) { - new AlertDialog.Builder(requireContext()) - .setMessage(R.string.remove_watched_popup_warning) - .setTitle(R.string.remove_watched_popup_title) - .setPositiveButton(R.string.yes, - (DialogInterface d, int id) -> removeWatchedStreams(false)) - .setNeutralButton( - R.string.remove_watched_popup_yes_and_partially_watched_videos, - (DialogInterface d, int id) -> removeWatchedStreams(true)) - .setNegativeButton(R.string.cancel, - (DialogInterface d, int id) -> d.cancel()) - .create() - .show(); - } - break; - default: - return super.onOptionsItemSelected(item); + if (item.getItemId() == R.id.menu_item_remove_watched) { + if (!isRemovingWatched) { + new AlertDialog.Builder(requireContext()) + .setMessage(R.string.remove_watched_popup_warning) + .setTitle(R.string.remove_watched_popup_title) + .setPositiveButton(R.string.yes, + (DialogInterface d, int id) -> removeWatchedStreams(false)) + .setNeutralButton( + R.string.remove_watched_popup_yes_and_partially_watched_videos, + (DialogInterface d, int id) -> removeWatchedStreams(true)) + .setNegativeButton(R.string.cancel, + (DialogInterface d, int id) -> d.cancel()) + .create() + .show(); + } + } else { + return super.onOptionsItemSelected(item); } return true; } @@ -455,7 +455,8 @@ public void removeWatchedStreams(final boolean removePartiallyWatched) { hideLoading(); isRemovingWatched = false; - }, this::onError)); + }, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK, + "Removing watched videos, partially watched=" + removePartiallyWatched)))); } @Override @@ -511,17 +512,6 @@ protected void resetFragment() { } } - @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 //////////////////////////////////////////////////////////////////////////*/ @@ -562,7 +552,9 @@ private void changePlaylistName(final String title) { final Disposable disposable = playlistManager.renamePlaylist(playlistId, title) .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); } @@ -583,7 +575,9 @@ private void changeThumbnailUrl(final String thumbnailUrl) { final Disposable disposable = playlistManager .changePlaylistThumbnail(playlistId, thumbnailUrl) .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); } @@ -632,7 +626,9 @@ private Disposable getDebouncedSaver() { return debouncedSaveSignal .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> saveImmediate(), this::onError); + .subscribe(ignored -> saveImmediate(), throwable -> + showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE, + "Debounced saver"))); } private void saveImmediate() { @@ -669,7 +665,8 @@ private void saveImmediate() { isModified.set(false); } }, - this::onError + throwable -> showError(new ErrorInfo(throwable, + UserAction.REQUESTED_BOOKMARK, "Saving playlist")) ); disposables.add(disposable); } @@ -683,7 +680,7 @@ private ItemTouchHelper.SimpleCallback getItemTouchCallback() { return new ItemTouchHelper.SimpleCallback(directions, ItemTouchHelper.ACTION_STATE_IDLE) { @Override - public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, + public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView, final int viewSize, final int viewSizeOutOfBounds, final int totalSize, @@ -696,9 +693,9 @@ public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, } @Override - public boolean onMove(final RecyclerView recyclerView, - final RecyclerView.ViewHolder source, - final RecyclerView.ViewHolder target) { + public boolean onMove(@NonNull final RecyclerView recyclerView, + @NonNull final RecyclerView.ViewHolder source, + @NonNull final RecyclerView.ViewHolder target) { if (source.getItemViewType() != target.getItemViewType() || itemListAdapter == null) { return false; @@ -724,7 +721,8 @@ public boolean isItemViewSwipeEnabled() { } @Override - public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { } + public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, + final int swipeDir) {} }; } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index cdb8b5db5f8..d60d82cb422 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -34,6 +34,7 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.databinding.DialogTitleBinding import org.schabi.newpipe.databinding.FeedItemCarouselBinding 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.fragments.BaseStateFragment @@ -288,8 +289,8 @@ class SubscriptionFragment : BaseStateFragment() { binding.itemsList.adapter = groupAdapter viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java) - viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(this::handleResult) }) - viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, { it?.let(this::handleFeedGroups) }) + viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) } + viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) { it?.let(this::handleFeedGroups) } } private fun showLongTapDialog(selectedItem: ChannelInfoItem) { @@ -381,7 +382,9 @@ class SubscriptionFragment : BaseStateFragment() { } } 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() { 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 // ///////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java index 7710c2bba80..571a8d9b1c4 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java @@ -84,10 +84,12 @@ public void onCreate(final Bundle savedInstanceState) { setupServiceVariables(); if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) { - ErrorActivity.reportError(activity, Collections.emptyList(), null, null, - ErrorInfo.make(UserAction.SOMETHING_ELSE, + ErrorActivity.reportError(activity, null, null, + new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT, 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(); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index 6ad0761c895..e1a757f3fcc 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -43,7 +43,6 @@ import org.schabi.newpipe.local.subscription.SubscriptionManager; import java.io.FileNotFoundException; -import java.util.Collections; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -152,13 +151,10 @@ protected void stopService() { postErrorResult(null, null); } - protected void stopAndReportError(@Nullable final Throwable error, final String request) { + protected void stopAndReportError(final Throwable throwable, final String request) { stopService(); - - final ErrorInfo errorInfo = ErrorInfo - .make(UserAction.SUBSCRIPTION, "unknown", request, R.string.general_error); - ErrorActivity.reportError(this, error != null ? Collections.singletonList(error) - : Collections.emptyList(), null, null, errorInfo); + ErrorActivity.reportError(this, null, null, new ErrorInfo( + throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request)); } protected void postErrorResult(final String title, final String text) { 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 cd2a18436a2..69791ad31e5 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.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.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.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.reportUiError(getActivity(), null, "Exporting database", e); } } @@ -243,20 +241,7 @@ private void importDatabase(final String filePath) { System.exit(0); } } catch (final Exception e) { - onError(e); + ErrorActivity.reportUiError(getActivity(), null, "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 3e67d93e228..6f99ec1ae32 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java @@ -1,8 +1,10 @@ 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; @@ -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, SettingsActivity.class, + null, 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, SettingsActivity.class, + null, 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, SettingsActivity.class, + null, 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, SettingsActivity.class, + null, 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 5854d0a8378..b7353c8069f 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.local.subscription.SubscriptionManager; import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.local.subscription.SubscriptionManager; 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,16 @@ 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.reportUiError(requireContext(), null, "Loading subscription", exception); } @Override @@ -173,16 +170,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 +184,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 7d220578e77..3ab1ee905fb 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; @@ -19,8 +18,6 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.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.reportUiError(getActivity(), null, "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 6dcfc9179f8..6f18acac0f5 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -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.reportError(activity, activity.getClass(), null, 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 33f83bc6fda..d96bd73531a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -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, SettingMigrations.class, null, 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 963021b69e3..fd290e93499 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 @@ -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.reportError(requireContext(), null, null, + 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 b92a1a3fec8..3f85bc1b475 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 @@ -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.reportError(context, null, null, 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/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 97436dfbc34..a904f360a31 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -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,10 +30,6 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -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.InfoItem; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; @@ -47,26 +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.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.ParsingException; -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.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 java.util.Collections; import java.util.List; @@ -280,65 +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 AgeRestrictedContentException) { - Toast.makeText(context, R.string.restricted_video_no_stream, - Toast.LENGTH_LONG).show(); - } else if (exception instanceof GeographicRestrictionException) { - Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show(); - } else if (exception instanceof PaidContentException) { - Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show(); - } else if (exception instanceof PrivateContentException) { - Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show(); - } else if (exception instanceof SoundCloudGoPlusContentException) { - Toast.makeText(context, R.string.soundcloud_go_plus_content, - Toast.LENGTH_LONG).show(); - } else if (exception instanceof YoutubeMusicPremiumContentException) { - Toast.makeText(context, R.string.youtube_music_premium_content, - 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 diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index a8b9f5269ca..455dd061444 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -583,16 +583,12 @@ private void showError(DownloadMission mission, UserAction action, @StringRes in try { service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName(); } catch (Exception e) { - service = "-"; + service = ErrorInfo.SERVICE_NONE; } - ErrorActivity.reportError( - mContext, - mission.errObject, - null, - null, - ErrorInfo.make(action, service, request.toString(), reason) - ); + ErrorActivity.reportError(mContext, null, null, + new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action, + service, request.toString(), reason, null)); } public void clearFinishedDownloads(boolean delete) { diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 14459b49489..a3d947f6f1a 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -219,7 +219,7 @@ +