diff --git a/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java new file mode 100644 index 000000000..891824a55 --- /dev/null +++ b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java @@ -0,0 +1,46 @@ +package org.schabi.newpipe.error; + +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.ServiceList; +import org.schabi.newpipe.extractor.exceptions.ParsingException; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Instrumented tests for {@link ErrorInfo}. + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class ErrorInfoTest { + + @Test + public void errorInfoTestParcelable() { + final ErrorInfo info = new ErrorInfo(new ParsingException("Hello"), + UserAction.USER_REPORT, "request", ServiceList.YouTube.getServiceId()); + // Obtain a Parcel object and write the parcelable object to it: + final Parcel parcel = Parcel.obtain(); + info.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + final ErrorInfo infoFromParcel = (ErrorInfo) ErrorInfo.CREATOR.createFromParcel(parcel); + + assertTrue(Arrays.toString(infoFromParcel.getStackTraces()) + .contains(ErrorInfoTest.class.getSimpleName())); + assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction()); + assertEquals(ServiceList.YouTube.getServiceInfo().getName(), + infoFromParcel.getServiceName()); + assertEquals("request", infoFromParcel.getRequest()); + assertEquals(R.string.parsing_error, infoFromParcel.getMessageStringId()); + + parcel.recycle(); + } +} diff --git a/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java b/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java deleted file mode 100644 index 09ae5648d..000000000 --- a/app/src/androidTest/java/org/schabi/newpipe/report/ErrorInfoTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.schabi.newpipe.report; - -import android.os.Parcel; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.schabi.newpipe.R; - -import static org.junit.Assert.assertEquals; - -/** - * Instrumented tests for {@link ErrorInfo}. - */ -@RunWith(AndroidJUnit4.class) -@LargeTest -public class ErrorInfoTest { - - @Test - public void errorInfoTestParcelable() { - final ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request", - R.string.general_error); - // Obtain a Parcel object and write the parcelable object to it: - final Parcel parcel = Parcel.obtain(); - info.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel); - - assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction()); - assertEquals("youtube", infoFromParcel.getServiceName()); - assertEquals("request", infoFromParcel.getRequest()); - assertEquals(R.string.general_error, infoFromParcel.getMessage()); - - parcel.recycle(); - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1f92548b4..e7fa95759 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" /> - + - * 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 cd4a04d9a..e3e9c3e4e 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -20,12 +20,13 @@ import org.acra.ACRA; import org.acra.config.ACRAConfigurationException; import org.acra.config.CoreConfiguration; import org.acra.config.CoreConfigurationBuilder; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ReCaptchaActivity; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.ktx.ExceptionUtils; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.ServiceHelper; @@ -224,14 +225,10 @@ public class App extends MultiDexApplication { .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, 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 63baef547..f84d986aa 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java @@ -10,19 +10,22 @@ import android.content.pm.Signature; import android.net.ConnectivityManager; import android.net.Uri; import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; + import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParserException; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Maybe; -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.schedulers.Schedulers; + +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.security.MessageDigest; @@ -31,9 +34,11 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Maybe; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; public final class CheckForNewAppVersion { private CheckForNewAppVersion() { } @@ -58,9 +63,8 @@ public final class CheckForNewAppVersion { 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, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info")); return ""; } @@ -72,9 +76,8 @@ public final class CheckForNewAppVersion { 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, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error")); return ""; } @@ -83,9 +86,8 @@ public final class CheckForNewAppVersion { 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, 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/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 50972fb2f..3adc71ff2 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; +import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Request; import org.schabi.newpipe.extractor.downloader.Response; diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 277209211..1b8f3190e 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -60,6 +60,7 @@ import org.schabi.newpipe.databinding.DrawerHeaderBinding; import org.schabi.newpipe.databinding.DrawerLayoutBinding; import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding; import org.schabi.newpipe.databinding.ToolbarLayoutBinding; +import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; @@ -72,7 +73,6 @@ import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.KioskTranslator; @@ -153,7 +153,7 @@ public class MainActivity extends AppCompatActivity { try { setupDrawer(); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiErrorInSnackbar(this, "Setting up drawer", e); } if (DeviceUtils.isTv(this)) { @@ -238,7 +238,7 @@ public class MainActivity extends AppCompatActivity { try { tabSelected(item); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiErrorInSnackbar(this, "Selecting main page tab", e); } break; case R.id.menu_options_about_group: @@ -340,7 +340,7 @@ public class MainActivity extends AppCompatActivity { try { showTabs(); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e); } } } @@ -487,7 +487,7 @@ public class MainActivity extends AppCompatActivity { drawerHeaderBinding.drawerHeaderActionButton.setContentDescription( getString(R.string.drawer_header_description) + selectedServiceName); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiErrorInSnackbar(this, "Setting up service toggle", e); } final SharedPreferences sharedPreferences @@ -679,19 +679,16 @@ public class MainActivity extends AppCompatActivity { } @Override - public boolean onOptionsItemSelected(final MenuItem item) { + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { if (DEBUG) { Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]"); } - final int id = item.getItemId(); - switch (id) { - case android.R.id.home: - onHomeButtonPressed(); - return true; - default: - return super.onOptionsItemSelected(item); + if (item.getItemId() == android.R.id.home) { + onHomeButtonPressed(); + return true; } + return super.onOptionsItemSelected(item); } /*////////////////////////////////////////////////////////////////////////// @@ -799,7 +796,7 @@ public class MainActivity extends AppCompatActivity { NavigationHelper.gotoMainFragment(getSupportFragmentManager()); } } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiErrorInSnackbar(this, "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 7f935d007..179fab8dc 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 androidx.preference.PreferenceManager; 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.ChannelPlayQueue; 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.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -84,13 +97,6 @@ import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr; * Get the url from the intent and open it in the chosen preferred player. */ 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 @@ public class RouterActivity extends AppCompatActivity { } @Override - protected void onSaveInstanceState(final Bundle outState) { + protected void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } @@ -145,37 +150,79 @@ public class RouterActivity extends AppCompatActivity { private void handleUrl(final String url) { disposables.add(Observable .fromCallable(() -> { - if (currentServiceId == -1) { - currentService = NewPipe.getServiceByUrl(url); - currentServiceId = currentService.getServiceId(); - currentLinkType = currentService.getLinkTypeByUrl(url); - currentUrl = url; - } else { - currentService = NewPipe.getService(currentServiceId); - } + try { + if (currentServiceId == -1) { + currentService = NewPipe.getServiceByUrl(url); + currentServiceId = currentService.getServiceId(); + currentLinkType = currentService.getLinkTypeByUrl(url); + currentUrl = url; + } else { + currentService = NewPipe.getService(currentServiceId); + } - return currentLinkType != LinkType.NONE; + // return whether the url was found to be supported or not + return currentLinkType != LinkType.NONE; + } catch (final ExtractionException e) { + // this can be reached only when the url is completely unsupported + return false; + } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - if (result) { + .subscribe(isUrlSupported -> { + if (isUrlSupported) { onSuccess(); } else { showUnsupportedUrlDialog(url); } - }, throwable -> handleError(throwable, url))); + }, throwable -> handleError(this, new ErrorInfo(throwable, + UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url)))); } - private void handleError(final Throwable throwable, final String url) { - throwable.printStackTrace(); + /** + * @param context the context. It will be {@code finish()}ed at the end of the handling if it is + * an instance of {@link RouterActivity}. + * @param errorInfo the error information + */ + private static void handleError(final Context context, final ErrorInfo errorInfo) { + if (errorInfo.getThrowable() != null) { + errorInfo.getThrowable().printStackTrace(); + } - if (throwable instanceof ExtractionException) { - 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 { - ExtractorHelper.handleGeneralException(this, -1, url, throwable, - UserAction.SOMETHING_ELSE, null); - finish(); + ErrorActivity.reportError(context, errorInfo); + } + + if (context instanceof RouterActivity) { + ((RouterActivity) context).finish(); } } @@ -500,7 +547,8 @@ public class RouterActivity extends AppCompatActivity { .subscribe(intent -> { startActivity(intent); finish(); - }, throwable -> handleError(throwable, currentUrl)) + }, throwable -> handleError(this, new ErrorInfo(throwable, + UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl))) ); return; } @@ -580,6 +628,7 @@ public class RouterActivity extends AppCompatActivity { this.playerChoice = playerChoice; } + @NonNull @Override public String toString() { return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice; @@ -646,9 +695,9 @@ public class RouterActivity extends AppCompatActivity { 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/database/playlist/PlaylistStreamEntry.kt b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt index aff6205f2..0d46f20bf 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/PlaylistStreamEntry.kt @@ -7,7 +7,6 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamStateEntity import org.schabi.newpipe.extractor.stream.StreamInfoItem -import kotlin.jvm.Throws data class PlaylistStreamEntry( @Embedded 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 68bcf3cc1..e573879c0 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -37,6 +37,9 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.RouterActivity; import org.schabi.newpipe.databinding.DownloadDialogBinding; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.localization.Localization; @@ -45,9 +48,6 @@ import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.FilenameUtils; @@ -61,7 +61,6 @@ import org.schabi.newpipe.util.ThemeHelper; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Locale; @@ -591,17 +590,6 @@ public class DownloadDialog extends DialogFragment .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 +693,8 @@ public class DownloadDialog extends DialogFragment mainStorage.getTag()); } } catch (final Exception e) { - showErrorActivity(e); + ErrorActivity.reportErrorInSnackbar(this, + new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage")); return; } diff --git a/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java similarity index 76% rename from app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java rename to app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java index 311cb8a80..60d4908eb 100644 --- a/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java +++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java @@ -1,9 +1,10 @@ -package org.schabi.newpipe.report; +package org.schabi.newpipe.error; import android.content.Context; 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, 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/report/AcraReportSenderFactory.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.java similarity index 97% rename from app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java rename to app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.java index 2655ea672..e63d55063 100644 --- a/app/src/main/java/org/schabi/newpipe/report/AcraReportSenderFactory.java +++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSenderFactory.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.report; +package org.schabi.newpipe.error; import android.content.Context; diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java similarity index 68% rename from app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java rename to app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index e76af3944..c39d616e6 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.report; +package org.schabi.newpipe.error; import android.app.Activity; import android.app.AlertDialog; @@ -8,7 +8,6 @@ import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -18,14 +17,11 @@ import android.view.View; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.NavUtils; +import androidx.fragment.app.Fragment; 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 +30,9 @@ import org.schabi.newpipe.util.Localization; 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,108 +61,77 @@ 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 - = "Exception in NewPipe " + BuildConfig.VERSION_NAME; + public static final String ERROR_EMAIL_SUBJECT = "Exception in "; 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"); + + 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 reportError(final Context context, final ErrorInfo errorInfo) { + final Intent intent = new Intent(context, ErrorActivity.class); + intent.putExtra(ERROR_INFO, errorInfo); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); } - public static void reportError(final Context context, final List el, - final Class returnActivity, final View rootView, - final ErrorInfo errorInfo) { + public static void reportErrorInSnackbar(final Context context, final ErrorInfo errorInfo) { + final View rootView = context instanceof Activity + ? ((Activity) context).findViewById(android.R.id.content) : null; + reportErrorInSnackbar(context, rootView, errorInfo); + } + + public static void reportErrorInSnackbar(final Fragment fragment, final ErrorInfo errorInfo) { + View rootView = fragment.getView(); + if (rootView == null && fragment.getActivity() != null) { + rootView = fragment.getActivity().findViewById(android.R.id.content); + } + reportErrorInSnackbar(fragment.requireContext(), rootView, errorInfo); + } + + public static void reportUiErrorInSnackbar(final Context context, + final String request, + final Throwable throwable) { + reportErrorInSnackbar(context, new ErrorInfo(throwable, UserAction.UI_ERROR, request)); + } + + public static void reportUiErrorInSnackbar(final Fragment fragment, + final String request, + final Throwable throwable) { + reportErrorInSnackbar(fragment, new ErrorInfo(throwable, UserAction.UI_ERROR, request)); + } + + + //////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////// + + private static void reportErrorInSnackbar(final Context context, + @Nullable final View rootView, + final ErrorInfo errorInfo) { if (rootView != null) { - 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(); + reportError(context, errorInfo)).show(); } else { - startErrorActivity(returnActivity, context, errorInfo, el); + reportError(context, errorInfo); } } - 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); - } - 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); - } - - // 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)}; - - 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; - } + //////////////////////////////////////////////////////////////////////// + // Activity lifecycle + //////////////////////////////////////////////////////////////////////// @Override protected void onCreate(final Bundle savedInstanceState) { @@ -193,38 +153,28 @@ public class ErrorActivity extends AppCompatActivity { 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 +189,14 @@ public class ErrorActivity extends AppCompatActivity { @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) { + onBackPressed(); + } 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) { @@ -264,7 +213,9 @@ public class ErrorActivity extends AppCompatActivity { final Intent i = new Intent(Intent.ACTION_SENDTO) .setData(Uri.parse("mailto:")) // only email apps should handle this .putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS}) - .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT) + .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT + + getString(R.string.app_name) + " " + + BuildConfig.VERSION_NAME) .putExtra(Intent.EXTRA_TEXT, buildJson()); if (i.resolveActivity(getPackageManager()) != null) { ShareUtils.openIntentInApp(context, i); @@ -310,17 +261,6 @@ public class ErrorActivity extends AppCompatActivity { return checkedReturnActivity; } - private void goToReturnActivity() { - final Class checkedReturnActivity = getReturnActivity(returnActivity); - if (checkedReturnActivity == null) { - super.onBackPressed(); - } else { - final Intent intent = new Intent(this, checkedReturnActivity); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - NavUtils.navigateUpTo(this, intent); - } - } - private void buildInfo(final ErrorInfo info) { String text = ""; @@ -355,7 +295,7 @@ public class ErrorActivity extends AppCompatActivity { .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 +333,27 @@ public class ErrorActivity extends AppCompatActivity { // 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"); @@ -460,17 +400,4 @@ public class ErrorActivity extends AppCompatActivity { text += "\n" + getString(R.string.guru_meditation); activityErrorBinding.errorSorryView.setText(text); } - - @Override - public void onBackPressed() { - //super.onBackPressed(); - goToReturnActivity(); - } - - public String getCurrentTimeStamp() { - final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - df.setTimeZone(TimeZone.getTimeZone("GMT")); - return df.format(new Date()); - } - } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt new file mode 100644 index 000000000..e1249bc83 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -0,0 +1,113 @@ +package org.schabi.newpipe.error + +import android.os.Parcelable +import androidx.annotation.StringRes +import kotlinx.android.parcel.Parcelize +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.Info +import org.schabi.newpipe.extractor.NewPipe +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException +import org.schabi.newpipe.extractor.exceptions.ExtractionException +import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException +import org.schabi.newpipe.ktx.isNetworkRelated +import java.io.PrintWriter +import java.io.StringWriter + +@Parcelize +class ErrorInfo( + val stackTraces: Array, + val userAction: UserAction, + val serviceName: String, + val request: String, + val messageStringId: Int, + @Transient // no need to store throwable, all data for report is in other variables + var throwable: Throwable? = null +) : Parcelable { + + private constructor( + throwable: Throwable, + userAction: UserAction, + serviceName: String, + request: String + ) : this( + throwableToStringList(throwable), + userAction, + serviceName, + request, + getMessageStringId(throwable, userAction), + throwable + ) + + private constructor( + throwable: List, + userAction: UserAction, + serviceName: String, + request: String + ) : this( + throwableListToStringList(throwable), + userAction, + serviceName, + request, + getMessageStringId(throwable.firstOrNull(), userAction), + throwable.firstOrNull() + ) + + // constructors with single throwable + constructor(throwable: Throwable, userAction: UserAction, request: String) : + this(throwable, userAction, SERVICE_NONE, request) + constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) : + this(throwable, userAction, NewPipe.getNameOfService(serviceId), request) + constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) : + this(throwable, userAction, getInfoServiceName(info), request) + + // constructors with list of throwables + constructor(throwable: List, 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 000000000..49bcfa926 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -0,0 +1,132 @@ +package org.schabi.newpipe.error + +import android.content.Context +import android.content.Intent +import android.util.Log +import android.view.View +import android.widget.Button +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import com.jakewharton.rxbinding4.view.clicks +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.Disposable +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException +import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException +import org.schabi.newpipe.extractor.exceptions.PaidContentException +import org.schabi.newpipe.extractor.exceptions.PrivateContentException +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException +import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException +import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.ktx.isInterruptedCaused +import org.schabi.newpipe.ktx.isNetworkRelated +import java.util.concurrent.TimeUnit + +class ErrorPanelHelper( + private val fragment: Fragment, + rootView: View, + onRetry: Runnable +) { + private val context: Context = rootView.context!! + private val errorPanelRoot: View = rootView.findViewById(R.id.error_panel) + private val errorTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_view) + private val errorButtonAction: Button = errorPanelRoot.findViewById(R.id.error_button_action) + private val errorButtonRetry: Button = errorPanelRoot.findViewById(R.id.error_button_retry) + + private var errorDisposable: Disposable? = null + + init { + errorDisposable = errorButtonRetry.clicks() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { onRetry.run() } + } + + fun showError(errorInfo: ErrorInfo) { + + if (errorInfo.throwable != null && errorInfo.throwable!!.isInterruptedCaused) { + if (DEBUG) { + Log.w(TAG, "onError() isInterruptedCaused! = [$errorInfo.throwable]") + } + return + } + + errorButtonAction.isVisible = true + if (errorInfo.throwable is ReCaptchaException) { + errorButtonAction.setText(R.string.recaptcha_solve) + errorButtonAction.setOnClickListener { + // Starting ReCaptcha Challenge Activity + val intent = Intent(context, ReCaptchaActivity::class.java) + intent.putExtra( + ReCaptchaActivity.RECAPTCHA_URL_EXTRA, + (errorInfo.throwable as ReCaptchaException).url + ) + fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST) + errorButtonAction.setOnClickListener(null) + } + errorTextView.setText(R.string.recaptcha_request_toast) + errorButtonRetry.isVisible = true + } else { + errorButtonAction.setText(R.string.error_snackbar_action) + errorButtonAction.setOnClickListener { + ErrorActivity.reportError(context, errorInfo) + } + + // hide retry button by default, then show only if not unavailable/unsupported content + errorButtonRetry.isVisible = false + errorTextView.setText( + when (errorInfo.throwable) { + is AgeRestrictedContentException -> R.string.restricted_video_no_stream + is GeographicRestrictionException -> R.string.georestricted_content + is PaidContentException -> R.string.paid_content + is PrivateContentException -> R.string.private_content + is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content + is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content + is ContentNotAvailableException -> R.string.content_not_available + is ContentNotSupportedException -> R.string.content_not_supported + else -> { + // show retry button only for content which is not unavailable or unsupported + errorButtonRetry.isVisible = true + if (errorInfo.throwable != null && errorInfo.throwable!!.isNetworkRelated) { + R.string.network_error + } else { + R.string.error_snackbar_message + } + } + } + ) + } + errorPanelRoot.animate(true, 300) + } + + fun showTextError(errorString: String) { + errorButtonAction.isVisible = false + errorButtonRetry.isVisible = false + errorTextView.text = errorString + } + + fun hide() { + errorButtonAction.setOnClickListener(null) + errorPanelRoot.animate(false, 150) + } + + fun isVisible(): Boolean { + return errorPanelRoot.isVisible + } + + fun dispose() { + errorButtonAction.setOnClickListener(null) + errorButtonRetry.setOnClickListener(null) + errorDisposable?.dispose() + } + + companion object { + val TAG: String = ErrorPanelHelper::class.simpleName!! + val DEBUG: Boolean = MainActivity.DEBUG + } +} diff --git a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java similarity index 98% rename from app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java rename to app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java index 463fc24ac..23df7ed95 100644 --- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe; +package org.schabi.newpipe.error; import android.content.Intent; import android.content.SharedPreferences; @@ -20,6 +20,9 @@ import androidx.preference.PreferenceManager; import androidx.webkit.WebViewClientCompat; import org.schabi.newpipe.databinding.ActivityRecaptchaBinding; +import org.schabi.newpipe.DownloaderImpl; +import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; import org.schabi.newpipe.util.ThemeHelper; import java.io.UnsupportedEncodingException; diff --git a/app/src/main/java/org/schabi/newpipe/report/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java similarity index 59% rename from app/src/main/java/org/schabi/newpipe/report/UserAction.java rename to app/src/main/java/org/schabi/newpipe/error/UserAction.java index 6fa697f71..e8dec9556 100644 --- a/app/src/main/java/org/schabi/newpipe/report/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.report; +package org.schabi.newpipe.error; /** * The user actions that can cause an error. @@ -6,9 +6,12 @@ package org.schabi.newpipe.report; 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 cf02af4a9..0de3ea012 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,23 @@ 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.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; -import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +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; @@ -56,11 +31,10 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC @Nullable private ProgressBar loadingProgressBar; - private Disposable errorDisposable; - - protected View errorPanelRoot; - private Button errorButtonRetry; - private TextView errorTextView; + private ErrorPanelHelper errorPanelHelper; + @Nullable + @State + protected ErrorInfo lastPanelError = null; @Override public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { @@ -74,12 +48,18 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC wasLoading.set(isLoading.get()); } + @Override + public void onResume() { + super.onResume(); + if (lastPanelError != null) { + showError(lastPanelError); + } + } + @Override public void onDestroy() { super.onDestroy(); - if (errorDisposable != null) { - errorDisposable.dispose(); - } + errorPanelHelper.dispose(); } /*////////////////////////////////////////////////////////////////////////// @@ -89,22 +69,9 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC @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 +110,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC if (loadingProgressBar != null) { animate(loadingProgressBar, true, 400); } - animate(errorPanelRoot, false, 150); + hideErrorPanel(); } @Override @@ -154,10 +121,9 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC if (loadingProgressBar != null) { animate(loadingProgressBar, false, 0); } - animate(errorPanelRoot, false, 150); + hideErrorPanel(); } - @Override public void showEmptyState() { isLoading.set(false); if (emptyStateView != null) { @@ -166,26 +132,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC if (loadingProgressBar != null) { animate(loadingProgressBar, false, 0); } - animate(errorPanelRoot, false, 150); - } - - @Override - public void showError(final String message, final boolean showRetryButton) { - if (DEBUG) { - Log.d(TAG, "showError() called with: " - + "message = [" + message + "], showRetryButton = [" + showRetryButton + "]"); - } - isLoading.set(false); - InfoCache.getInstance().clearCache(); - hideLoading(); - - errorTextView.setText(message); - if (showRetryButton) { - animate(errorButtonRetry, true, 600); - } else { - animate(errorButtonRetry, false, 0); - } - animate(errorPanelRoot, true, 300); + hideErrorPanel(); } @Override @@ -196,138 +143,69 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC hideLoading(); } + @Override + public void handleError() { + isLoading.set(false); + InfoCache.getInstance().clearCache(); + if (emptyStateView != null) { + animate(emptyStateView, false, 150); + } + if (loadingProgressBar != null) { + animate(loadingProgressBar, false, 0); + } + } + /*////////////////////////////////////////////////////////////////////////// // Error handling //////////////////////////////////////////////////////////////////////////*/ - /** - * 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 + "]"); + Log.w(TAG, "showError() is detached or removing = [" + errorInfo + "]"); } - return true; + return; } - if (ExceptionUtils.isInterruptedCaused(exception)) { + errorPanelHelper.showError(errorInfo); + lastPanelError = errorInfo; + } + + public final void showTextError(@NonNull final String errorString) { + handleError(); + + if (isDetached() || isRemoving()) { if (DEBUG) { - Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]"); + Log.w(TAG, "showTextError() is detached or removing = [" + errorString + "]"); } - return true; + return; } - 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 false; + errorPanelHelper.showTextError(errorString); } - 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); - - showError(getString(R.string.recaptcha_request_toast), false); + public final void hideErrorPanel() { + errorPanelHelper.hide(); + lastPanelError = null; } - 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); - } - - 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 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#reportErrorInSnackbar(androidx.fragment.app.Fragment, 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) { - rootView = getView(); - } - if (rootView == null) { - return; - } - - ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, - ErrorInfo.make(userAction, serviceName, request, errorId)); + ErrorActivity.reportErrorInSnackbar(this, errorInfo); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java index 62f823c73..fbf2711bc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java @@ -11,9 +11,18 @@ import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; public class EmptyFragment extends BaseFragment { + final boolean showMessage; + + public EmptyFragment(final boolean showMessage) { + this.showMessage = showMessage; + } + @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, final Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_empty, container, false); + final View view = inflater.inflate(R.layout.fragment_empty, container, false); + view.findViewById(R.id.empty_state_view).setVisibility( + showMessage ? View.VISIBLE : View.GONE); + return view; } } 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 4765e6265..5fb68ba30 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 android.view.ViewGroup; 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 com.google.android.material.tabs.TabLayout; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.FragmentMainBinding; +import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.util.NavigationHelper; @@ -128,7 +125,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte //////////////////////////////////////////////////////////////////////////*/ @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 class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @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.reportUiErrorInSnackbar(this, "Opening search fragment", e); + } + return true; } return super.onOptionsItemSelected(item); } @@ -241,8 +238,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte } if (throwable != null) { - ErrorActivity.reportError(context, throwable, null, null, ErrorInfo - .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + ErrorActivity.reportUiErrorInSnackbar(context, "Getting fragment item", throwable); return new BlankFragment(); } @@ -254,7 +250,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte } @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 bb980ac64..78f644ffb 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 8715f357e..08ec9a5fc 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,12 +37,10 @@ import androidx.annotation.NonNull; 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; import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.ExoPlaybackException; @@ -56,14 +54,15 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene import org.schabi.newpipe.App; import org.schabi.newpipe.R; -import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.databinding.FragmentVideoDetailBinding; import org.schabi.newpipe.download.DownloadDialog; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ReCaptchaActivity; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.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; @@ -71,6 +70,7 @@ import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BaseStateFragment; +import org.schabi.newpipe.fragments.EmptyFragment; import org.schabi.newpipe.fragments.list.comments.CommentsFragment; import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment; import org.schabi.newpipe.ktx.AnimationType; @@ -86,9 +86,6 @@ import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -151,6 +148,7 @@ public final class VideoDetailFragment private static final String COMMENTS_TAB_TAG = "COMMENTS"; private static final String RELATED_TAB_TAG = "NEXT VIDEO"; private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"; + private static final String EMPTY_TAB_TAG = "EMPTY TAB"; // tabs private boolean showComments; @@ -526,7 +524,7 @@ public final class VideoDetailFragment NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(), subChannelUrl, subChannelName); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e); } } @@ -684,13 +682,12 @@ public final class VideoDetailFragment 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 +903,8 @@ public final class VideoDetailFragment openVideoPlayer(); } } - }, throwable -> { - isLoading.set(false); - onError(throwable); - }); + }, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM, + url == null ? "no url" : url, serviceId))); } /*////////////////////////////////////////////////////////////////////////// @@ -932,18 +927,22 @@ public final class VideoDetailFragment } if (showRelatedStreams && binding.relatedStreamsLayout == null) { - //temp empty fragment. will be updated in handleResult - pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG); + // temp empty fragment. will be updated in handleResult + pageAdapter.addFragment(new EmptyFragment(false), RELATED_TAB_TAG); tabIcons.add(R.drawable.ic_art_track_white_24dp); tabContentDescriptions.add(R.string.related_streams_tab_description); } if (showDescription) { // temp empty fragment. will be updated in handleResult - pageAdapter.addFragment(new Fragment(), DESCRIPTION_TAB_TAG); + pageAdapter.addFragment(new EmptyFragment(false), DESCRIPTION_TAB_TAG); tabIcons.add(R.drawable.ic_description_white_24dp); tabContentDescriptions.add(R.string.description_tab_description); } + + if (pageAdapter.getCount() == 0) { + pageAdapter.addFragment(new EmptyFragment(true), EMPTY_TAB_TAG); + } pageAdapter.notifyDataSetUpdate(); if (pageAdapter.getCount() >= 2) { @@ -1327,8 +1326,8 @@ public final class VideoDetailFragment } @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 +1340,8 @@ public final class VideoDetailFragment } 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 +1547,8 @@ public final class VideoDetailFragment } 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 +1588,10 @@ public final class VideoDetailFragment } public void openDownloadDialog() { + if (currentInfo == null) { + return; + } + try { final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); downloadDialog.setVideoStreams(sortedVideoStreams); @@ -1601,18 +1601,9 @@ public final class VideoDetailFragment 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.reportErrorInSnackbar(activity, + new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog", + currentInfo)); } } @@ -1620,24 +1611,6 @@ public final class VideoDetailFragment // 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 d42b0a088..3c37bd128 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 android.view.View; 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; @@ -22,6 +21,7 @@ import androidx.viewbinding.ViewBinding; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.PignateFooterBinding; +import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; @@ -33,7 +33,6 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoListAdapter; import org.schabi.newpipe.player.helper.PlayerHolder; -import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.KoreUtil; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; @@ -47,6 +46,7 @@ import java.util.List; import java.util.Queue; import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; public abstract class BaseListFragment extends BaseStateFragment implements ListViewContract, StateSaver.WriteRead, @@ -292,7 +292,8 @@ public abstract class BaseListFragment extends BaseStateFragment selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiErrorInSnackbar( + BaseListFragment.this, "Opening channel fragment", e); } } }); @@ -307,7 +308,8 @@ public abstract class BaseListFragment extends BaseStateFragment selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiErrorInSnackbar(BaseListFragment.this, + "Opening playlist fragment", e); } } }); @@ -406,23 +408,23 @@ public abstract class BaseListFragment extends BaseStateFragment // Contract //////////////////////////////////////////////////////////////////////////*/ + @Override + public void showLoading() { + super.showLoading(); + animateHideRecyclerViewAllowingScrolling(itemsList); + } + @Override public void hideLoading() { super.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(); showListFooter(false); + animateHideRecyclerViewAllowingScrolling(itemsList); } @Override @@ -439,6 +441,13 @@ public abstract class BaseListFragment extends BaseStateFragment isLoading.set(false); } + @Override + public void handleError() { + super.handleError(); + showListFooter(false); + animateHideRecyclerViewAllowingScrolling(itemsList); + } + @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 006072e93..6874f80d5 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 android.view.View; 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 abstract class BaseListInfoFragment currentInfo = result; currentNextPage = result.getNextPage(); handleResult(result); - }, this::onError); + }, throwable -> + showError(new ErrorInfo(throwable, errorUserAction, + "Start loading: " + url, serviceId))); } /** @@ -161,10 +173,9 @@ public abstract class BaseListInfoFragment .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 @@ public abstract class BaseListInfoFragment @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 abstract class BaseListInfoFragment 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 @@ public abstract class BaseListInfoFragment 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 486777ff1..a94581cfd 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 android.widget.Button; 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.database.subscription.SubscriptionEntity; 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.report.ErrorActivity; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; @@ -91,6 +89,10 @@ public class ChannelFragment extends BaseListInfoFragment return instance; } + public ChannelFragment() { + super(UserAction.REQUESTED_CHANNEL); + } + @Override public void setUserVisibleHint(final boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); @@ -217,9 +219,8 @@ public class ChannelFragment extends BaseListInfoFragment 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 @@ public class ChannelFragment extends BaseListInfoFragment }; 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 @@ public class ChannelFragment extends BaseListInfoFragment }; 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 class ChannelFragment extends BaseListInfoFragment currentInfo.getParentChannelUrl(), currentInfo.getParentChannelName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e); } } else if (DEBUG) { Log.i(TAG, "Can't open parent channel because we got no channel URL"); @@ -469,27 +464,13 @@ public class ChannelFragment extends BaseListInfoFragment 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(); } } - if (disposables != null) { - disposables.clear(); - } + disposables.clear(); if (subscribeButtonMonitor != null) { subscribeButtonMonitor.dispose(); } @@ -539,38 +520,6 @@ public class ChannelFragment extends BaseListInfoFragment 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 3682fe13b..35ab663a6 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.NonNull; 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.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import io.reactivex.rxjava3.core.Single; @@ -25,13 +24,17 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; 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 @@ public class CommentsFragment extends BaseListInfoFragment { // 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 1797191b6..d0b9e3a3d 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,14 +2,16 @@ package org.schabi.newpipe.fragments.list.kiosk; import android.os.Bundle; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.kiosk.KioskList; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.KioskTranslator; 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 @@ public class DefaultKioskFragment extends KioskFragment { 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 2e5e64539..882bb021d 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,8 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; @@ -20,7 +22,6 @@ import org.schabi.newpipe.extractor.kiosk.KioskInfo; import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.Localization; @@ -28,8 +29,6 @@ import org.schabi.newpipe.util.Localization; import icepick.State; import io.reactivex.rxjava3.core.Single; -import static org.schabi.newpipe.ktx.ViewUtils.animate; - /** * Created by Christian Schabesberger on 23.09.17. *

@@ -82,6 +81,10 @@ public class KioskFragment extends BaseListInfoFragment { return instance; } + public KioskFragment() { + super(UserAction.REQUESTED_KIOSK); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -102,9 +105,7 @@ public class KioskFragment extends BaseListInfoFragment { 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")); } } } @@ -157,34 +158,11 @@ public class KioskFragment extends BaseListInfoFragment { // Contract //////////////////////////////////////////////////////////////////////////*/ - @Override - public void showLoading() { - super.showLoading(); - animate(itemsList, false, 100); - } - @Override public void handleResult(@NonNull final KioskInfo result) { super.handleResult(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 85dea83dc..114947923 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 android.view.ViewGroup; 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.R; 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.local.playlist.RemotePlaylistManager; 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.report.ErrorActivity; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.KoreUtil; @@ -62,6 +60,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr; public class PlaylistFragment extends BaseListInfoFragment { @@ -87,6 +86,10 @@ public class PlaylistFragment extends BaseListInfoFragment { return instance; } + public PlaylistFragment() { + super(UserAction.REQUESTED_PLAYLIST); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -262,7 +265,7 @@ public class PlaylistFragment extends BaseListInfoFragment { public void showLoading() { super.showLoading(); animate(headerBinding.getRoot(), false, 200); - animate(itemsList, false, 100); + animateHideRecyclerViewAllowingScrolling(itemsList); IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView); animate(headerBinding.uploaderLayout, false, 200); @@ -284,7 +287,7 @@ public class PlaylistFragment extends BaseListInfoFragment { NavigationHelper.openChannelFragment(getFM(), result.getServiceId(), result.getUploaderUrl(), result.getUploaderName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiErrorInSnackbar(this, "Opening channel fragment", e); } }); } @@ -315,8 +318,8 @@ public class PlaylistFragment extends BaseListInfoFragment { .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 +366,6 @@ public class PlaylistFragment extends BaseListInfoFragment { ); } - @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 +410,9 @@ public class PlaylistFragment extends BaseListInfoFragment { } @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 +437,16 @@ public class PlaylistFragment extends BaseListInfoFragment { 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 5273fd396..26360137e 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 @@ -35,16 +35,18 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; -import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import org.schabi.newpipe.databinding.FragmentSearchBinding; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ReCaptchaActivity; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.MetaInfo; 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; @@ -54,9 +56,6 @@ import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -162,11 +161,6 @@ public class SearchFragment extends BaseListFragment 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(); @@ -733,14 +721,12 @@ public class SearchFragment extends BaseListFragment observable = suggestionPublisher + suggestionDisposable = suggestionPublisher .debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS) .startWithItem(searchString != null ? searchString : "") - .filter(ss -> isSuggestionsEnabled); - - suggestionDisposable = observable + .filter(ss -> isSuggestionsEnabled) .switchMap(query -> { final Flowable> flowable = historyRecordManager .getRelatedSearches(query, 3, 25); @@ -763,8 +749,8 @@ public class SearchFragment extends BaseListFragment { 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 +786,8 @@ public class SearchFragment extends BaseListFragment { 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) { @@ -844,15 +830,16 @@ public class SearchFragment extends BaseListFragment { - }, - 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 +859,7 @@ public class SearchFragment extends BaseListFragment isLoading.set(false)) - .subscribe(this::handleResult, this::onError); + .subscribe(this::handleResult, this::onItemError); } @@ -895,7 +882,7 @@ public class SearchFragment extends BaseListFragment isLoading.set(false)) - .subscribe(this::handleNextItems, this::onError); + .subscribe(this::handleNextItems, this::onItemError); } @Override @@ -909,6 +896,15 @@ public class SearchFragment extends BaseListFragment 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 +956,6 @@ public class SearchFragment extends BaseListFragment cannot be bundled without creating some containers metaInfo = new MetaInfo[result.getMetaInfo().size()]; metaInfo = result.getMetaInfo().toArray(metaInfo); - disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, - metaInfoSeparator)); + disposables.add(showMetaInfoInTextView(result.getMetaInfo(), + searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator)); handleSearchSuggestion(); @@ -1061,33 +1035,20 @@ public class SearchFragment extends BaseListFragment 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 7afe69716..902df94bc 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 @@ -16,12 +16,11 @@ import androidx.viewbinding.ViewBinding; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.RelatedStreamsHeaderBinding; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.ListExtractor; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.ViewUtils; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.RelatedStreamInfo; import java.io.Serializable; @@ -47,6 +46,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment extends BaseStateFragment public void showLoading() { super.showLoading(); if (itemsList != null) { - animate(itemsList, false, 200); + animateHideRecyclerViewAllowingScrolling(itemsList); } if (headerRootBinding != null) { animate(headerRootBinding.getRoot(), false, 200); @@ -202,19 +203,6 @@ public abstract class BaseLocalListFragment extends BaseStateFragment } } - @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 +237,18 @@ public abstract class BaseLocalListFragment extends BaseStateFragment } @Override - protected boolean onError(final Throwable exception) { + public void handleError() { + super.handleError(); resetFragment(); - return super.onError(exception); + + showListFooter(false); + + if (itemsList != null) { + animateHideRecyclerViewAllowingScrolling(itemsList); + } + 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 ee77db89f..e9032a1c6 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,10 +23,11 @@ import org.schabi.newpipe.database.LocalItem; 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.error.UserAction; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; @@ -206,7 +207,8 @@ public final class BookmarkFragment extends BaseLocalListFragment 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 @@ public final class BookmarkFragment extends BaseLocalListFragment { /*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 04090abc6..33bed7d7e 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,17 +38,18 @@ 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 +import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling import org.schabi.newpipe.local.feed.service.FeedLoadService -import org.schabi.newpipe.report.UserAction import org.schabi.newpipe.util.Localization 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 @@ -106,7 +107,7 @@ class FeedFragment : BaseListFragment() { override fun initListeners() { super.initListeners() feedBinding.refreshRootView.setOnClickListener { reloadContent() } - feedBinding.swiperefresh.setOnRefreshListener { reloadContent() } + feedBinding.swipeRefreshLayout.setOnRefreshListener { reloadContent() } } // ///////////////////////////////////////////////////////////////////////// @@ -171,50 +172,26 @@ class FeedFragment : BaseListFragment() { // ///////////////////////////////////////////////////////////////////////// override fun showLoading() { + super.showLoading() + feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling() 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) + feedBinding.swipeRefreshLayout.isRefreshing = true } 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 + feedBinding.swipeRefreshLayout.isRefreshing = false } override fun showEmptyState() { + super.showEmptyState() + feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling() 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) + feedBinding.swipeRefreshLayout.isRefreshing = false } override fun handleResult(result: FeedState) { @@ -227,6 +204,15 @@ class FeedFragment : BaseListFragment() { updateRefreshViewState() } + override fun handleError() { + super.handleError() + infoListAdapter.clearStreamItemList() + feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling() + feedBinding.refreshRootView.animate(false, 0) + feedBinding.loadingProgressText.animate(false, 0) + feedBinding.swipeRefreshLayout.isRefreshing = false + } + private fun handleProgressState(progressState: FeedState.ProgressState) { showLoading() @@ -266,13 +252,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 +260,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 +300,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 f9aa38054..1bece369b 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 android.widget.Toast; 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.StreamStatisticsEntry; 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.local.BaseLocalListFragment; 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.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.settings.SettingsActivity; +import org.schabi.newpipe.settings.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.Arrays; 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 class StatisticsPlaylistFragment @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 class StatisticsPlaylistFragment @Override public void onPause() { super.onPause(); - itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); + itemsListState = Objects.requireNonNull(itemsList.getLayoutManager()).onSaveInstanceState(); } @Override @@ -287,7 +249,8 @@ public class StatisticsPlaylistFragment @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 class StatisticsPlaylistFragment } itemListAdapter.addItems(processResult(result)); - if (itemsListState != null) { + if (itemsListState != null && itemsList.getLayoutManager() != null) { itemsList.getLayoutManager().onRestoreInstanceState(itemsListState); itemsListState = null; } @@ -341,17 +304,6 @@ public class StatisticsPlaylistFragment } } - @Override - protected boolean onError(final Throwable exception) { - if (super.onError(exception)) { - return true; - } - - onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, - "none", "History Statistics", R.string.general_error); - return true; - } - /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -439,9 +391,8 @@ public class StatisticsPlaylistFragment 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 3137de9e6..5dbb67cd1 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,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity; 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; import org.schabi.newpipe.info_list.InfoItemDialog; @@ -42,7 +44,6 @@ import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.KoreUtil; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -110,7 +111,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment 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 class LocalPlaylistFragment extends BaseLocalListFragment showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK, + "Removing watched videos, partially watched=" + removePartiallyWatched)))); } @Override @@ -511,17 +512,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { /*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 @@ public class LocalPlaylistFragment extends BaseLocalListFragment 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 @@ public class LocalPlaylistFragment extends BaseLocalListFragment saveImmediate(), this::onError); + .subscribe(ignored -> saveImmediate(), throwable -> + showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE, + "Debounced saver"))); } private void saveImmediate() { @@ -669,7 +665,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment showError(new ErrorInfo(throwable, + UserAction.REQUESTED_BOOKMARK, "Saving playlist")) ); disposables.add(disposable); } @@ -683,7 +680,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment() { 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 d7a1051e6..f0675da1b 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 @@ -22,13 +22,13 @@ import com.nononsenseapps.filepicker.Utils; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ServiceHelper; @@ -84,10 +84,12 @@ public class SubscriptionsImportFragment extends BaseFragment { setupServiceVariables(); if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) { - ErrorActivity.reportError(activity, Collections.emptyList(), null, null, - ErrorInfo.make(UserAction.SOMETHING_ELSE, + ErrorActivity.reportErrorInSnackbar(activity, + 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/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt index 6f821f432..5bd13356d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt @@ -42,7 +42,6 @@ import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem import org.schabi.newpipe.util.DeviceUtils import org.schabi.newpipe.util.ThemeHelper import java.io.Serializable -import kotlin.collections.contains class FeedGroupDialog : DialogFragment(), BackPressable { private var _feedGroupCreateBinding: DialogFeedGroupCreateBinding? = null diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt index 2b09a3b3b..57815ea90 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupReorderDialog.kt @@ -24,6 +24,10 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem import org.schabi.newpipe.util.ThemeHelper import java.util.Collections +import kotlin.collections.ArrayList +import kotlin.collections.List +import kotlin.collections.map +import kotlin.collections.sortedBy class FeedGroupReorderDialog : DialogFragment() { private var _binding: DialogFeedGroupReorderBinding? = null 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 f573f4679..901eabf44 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 @@ -35,15 +35,14 @@ import androidx.core.app.ServiceCompat; import org.reactivestreams.Publisher; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.subscription.SubscriptionManager; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import java.io.FileNotFoundException; -import java.util.Collections; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -152,13 +151,10 @@ public abstract class BaseImportExportService extends Service { 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, 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/report/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/report/ErrorInfo.kt deleted file mode 100644 index 4947d7950..000000000 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorInfo.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.schabi.newpipe.report - -import android.os.Parcelable -import androidx.annotation.StringRes -import kotlinx.android.parcel.Parcelize - -@Parcelize -class ErrorInfo( - val userAction: UserAction?, - val serviceName: String, - val request: String, - @field:StringRes @param:StringRes val message: Int -) : Parcelable { - companion object { - @JvmStatic - fun make( - userAction: UserAction?, - serviceName: String, - request: String, - @StringRes message: Int - ) = ErrorInfo(userAction, serviceName, request, message) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index c0639131c..dbe05bbd2 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 com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; -import org.schabi.newpipe.ReCaptchaActivity; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ZipHelper; @@ -198,7 +196,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show(); } catch (final Exception e) { - onError(e); + ErrorActivity.reportUiErrorInSnackbar(this, "Exporting database", e); } } @@ -243,20 +241,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { System.exit(0); } } catch (final Exception e) { - onError(e); + ErrorActivity.reportUiErrorInSnackbar(this, "Importing database", e); } } - - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ - - protected void onError(final Throwable e) { - final Activity activity = getActivity(); - ErrorActivity.reportError(activity, e, - activity.getClass(), - null, - ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); - } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java index 98c1ffc30..89fabbdde 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java @@ -1,17 +1,19 @@ package org.schabi.newpipe.settings; +import android.content.Context; import android.os.Bundle; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.InfoCache; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -46,120 +48,103 @@ public class HistorySettingsFragment extends BasePreferenceFragment { 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))); + private static Disposable getDeletePlaybackStatesDisposable( + @NonNull final Context context, final HistoryRecordManager recordManager) { + return recordManager.deleteCompleteStreamStateHistory() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> Toast.makeText(context, + R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(), + throwable -> ErrorActivity.reportError(context, + new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, + "Delete playback states"))); + } - final Disposable 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 getWholeStreamHistoryDisposable( + @NonNull final Context context, final HistoryRecordManager recordManager) { + return recordManager.deleteWholeStreamHistory() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> Toast.makeText(context, + R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(), + throwable -> ErrorActivity.reportError(context, + new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, + "Delete from history"))); + } - 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 getRemoveOrphanedRecordsDisposable( + @NonNull final Context context, final HistoryRecordManager recordManager) { + return recordManager.removeOrphanedRecords() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> { }, + throwable -> ErrorActivity.reportError(context, + new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, + "Clear orphaned records"))); + } - 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 getDeleteSearchHistoryDisposable( + @NonNull final Context context, final HistoryRecordManager recordManager) { + return recordManager.deleteCompleteSearchHistory() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + howManyDeleted -> Toast.makeText(context, + R.string.search_history_deleted, Toast.LENGTH_SHORT).show(), + throwable -> ErrorActivity.reportError(context, + new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY, + "Delete search history"))); + } - 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))); + 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(); + } - disposables.add(onDeletePlaybackStates); - })) - .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(); + } - 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(); - } - - 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 afe42d5d8..7f706be77 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 com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; import org.schabi.newpipe.database.subscription.SubscriptionEntity; +import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.local.subscription.SubscriptionManager; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ThemeHelper; import java.util.List; @@ -108,7 +105,7 @@ public class SelectChannelFragment extends DialogFragment { 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 class SelectChannelFragment extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCancel(final DialogInterface dialogInterface) { + public void onCancel(@NonNull final DialogInterface dialogInterface) { super.onCancel(dialogInterface); if (onCancelListener != null) { onCancelListener.onCancel(); @@ -156,16 +153,17 @@ public class SelectChannelFragment extends DialogFragment { private Observer> getSubscriptionObserver() { return new Observer>() { @Override - public void onSubscribe(final Disposable d) { } + public void onSubscribe(@NonNull final Disposable disposable) { } @Override - public void onNext(final List newSubscriptions) { + public void onNext(@NonNull final List newSubscriptions) { displayChannels(newSubscriptions); } @Override - public void onError(final Throwable exception) { - SelectChannelFragment.this.onError(exception); + public void onError(@NonNull final Throwable exception) { + ErrorActivity.reportUiErrorInSnackbar(SelectChannelFragment.this, + "Loading subscription", exception); } @Override @@ -173,16 +171,6 @@ public class SelectChannelFragment extends DialogFragment { }; } - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ - - protected void onError(final Throwable e) { - final Activity activity = getActivity(); - ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo - .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); - } - /*////////////////////////////////////////////////////////////////////////// // Interfaces //////////////////////////////////////////////////////////////////////////*/ @@ -197,6 +185,7 @@ public class SelectChannelFragment extends DialogFragment { 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 fc974607b..5c20b752c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.settings; -import android.app.Activity; import android.content.DialogInterface; import android.os.Bundle; import android.view.LayoutInflater; @@ -16,11 +15,9 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -83,7 +80,7 @@ public class SelectKioskFragment extends DialogFragment { try { selectKioskAdapter = new SelectKioskAdapter(); } catch (final Exception e) { - onError(e); + ErrorActivity.reportUiErrorInSnackbar(this, "Selecting kiosk", e); } recyclerView.setAdapter(selectKioskAdapter); @@ -109,16 +106,6 @@ public class SelectKioskFragment extends DialogFragment { 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 16ccd0953..63da3274f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -24,11 +24,11 @@ import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import java.util.List; import java.util.Vector; @@ -115,8 +115,8 @@ public class SelectPlaylistFragment extends DialogFragment { protected void onError(final Throwable e) { final Activity activity = requireActivity(); - ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo - .make(UserAction.UI_ERROR, "none", "load_playlists", R.string.app_ui_crash)); + ErrorActivity.reportErrorInSnackbar(activity, new ErrorInfo(e, + UserAction.UI_ERROR, "Loading playlists")); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index 9042559c9..c59746428 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -7,9 +7,9 @@ import android.util.Log; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import static org.schabi.newpipe.MainActivity.DEBUG; @@ -95,15 +95,13 @@ public final class SettingMigrations { } catch (final Exception e) { // save the version with the last successful migration and report the error sp.edit().putInt(lastPrefVersionKey, currentVersion).apply(); - final ErrorInfo errorInfo = ErrorInfo.make( + ErrorActivity.reportError(context, new ErrorInfo( + e, UserAction.PREFERENCES_MIGRATION, - "none", "Migrating preferences from version " + lastPrefVersion + " to " + VERSION + ". " - + "Error at " + currentVersion + " => " + ++currentVersion, - 0 - ); - ErrorActivity.reportError(context, e, SettingMigrations.class, null, errorInfo); + + "Error at " + currentVersion + " => " + ++currentVersion + )); return; } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index cbc47392b..572741d03 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -27,10 +27,10 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SelectChannelFragment; import org.schabi.newpipe.settings.SelectKioskFragment; import org.schabi.newpipe.settings.SelectPlaylistFragment; @@ -183,10 +183,9 @@ public class ChooseTabsFragment extends Fragment { final Tab.Type type = typeFrom(tabId); if (type == null) { - ErrorActivity.reportError(requireContext(), - new IllegalStateException("Tab id not found: " + tabId), null, null, - ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", - "Choosing tabs on settings", 0)); + ErrorActivity.reportErrorInSnackbar(this, + new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId), + UserAction.SOMETHING_ELSE, "Choosing tabs on settings")); return; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index ce3874f39..0ffda2261 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -12,6 +12,9 @@ import com.grack.nanojson.JsonSink; import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem.LocalItemType; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; @@ -25,9 +28,6 @@ import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -483,9 +483,8 @@ public abstract class Tab { final StreamingService service = NewPipe.getService(kioskServiceId); kioskId = service.getKioskList().getDefaultKioskId(); } catch (final ExtractionException e) { - ErrorActivity.reportError(context, e, null, null, - ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none", - "Loading default kiosk from selected service", 0)); + ErrorActivity.reportErrorInSnackbar(context, new ErrorInfo(e, + UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service")); } return kioskId; } diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index d6e3a0e80..af7cafc15 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,7 +30,6 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; @@ -44,29 +40,14 @@ import org.schabi.newpipe.extractor.Page; 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 org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; import java.util.Collections; import java.util.List; @@ -280,65 +261,6 @@ public final class ExtractorHelper { 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 @@ -352,10 +274,9 @@ public final class ExtractorHelper { final TextView metaInfoTextView, final View metaInfoSeparator) { final Context context = metaInfoTextView.getContext(); - final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.show_meta_info_key), true); - - if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) { + if (metaInfos == null || metaInfos.isEmpty() + || !PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + context.getString(R.string.show_meta_info_key), true)) { metaInfoTextView.setVisibility(View.GONE); metaInfoSeparator.setVisibility(View.GONE); return Disposable.empty(); diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index bea4b6f94..41a254b49 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 @@ -41,9 +41,9 @@ import com.google.android.material.snackbar.Snackbar; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.ErrorInfo; -import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ShareUtils; @@ -583,16 +583,12 @@ public class MissionAdapter extends Adapter implements Handler.Callb 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, + 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 42bd4bb91..b037ca584 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 @@ + tools:context=".error.ErrorActivity"> +