diff --git a/app/build.gradle b/app/build.gradle index 7c504e968..8d0db17b2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -155,7 +155,7 @@ task formatKtlint(type: JavaExec) { } afterEvaluate { - preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint + //preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint } dependencies { diff --git a/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java index ef2ff7ec9..d491059e5 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java +++ b/app/src/androidTest/java/org/schabi/newpipe/error/ErrorInfoTest.java @@ -31,7 +31,7 @@ public class ErrorInfoTest { assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction()); assertEquals("youtube", infoFromParcel.getServiceName()); assertEquals("request", infoFromParcel.getRequest()); - assertEquals(R.string.general_error, infoFromParcel.getMessage()); + assertEquals(R.string.general_error, infoFromParcel.getMessageStringId()); parcel.recycle(); } diff --git a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java deleted file mode 100644 index 9321b3071..000000000 --- a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.schabi.newpipe; - -/* - * Created by Christian Schabesberger on 24.12.15. - * - * Copyright (C) Christian Schabesberger 2015 - * ActivityCommunicator.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -/** - * Singleton: - * Used to send data between certain Activity/Services within the same process. - * This can be considered as an ugly hack inside the Android universe. - **/ -public class ActivityCommunicator { - - private static ActivityCommunicator activityCommunicator; - private volatile Class returnActivity; - - public static ActivityCommunicator getCommunicator() { - if (activityCommunicator == null) { - activityCommunicator = new ActivityCommunicator(); - } - return activityCommunicator; - } - - public Class getReturnActivity() { - return returnActivity; - } - - public void setReturnActivity(final Class returnActivity) { - this.returnActivity = returnActivity; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 05b88148f..d8519d0cb 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -225,14 +225,10 @@ 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, null, null, new ErrorInfo(exception, + UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report")); } } diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java index f4c51144d..ad8aae93d 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java @@ -63,9 +63,8 @@ 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, null, null, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info")); return ""; } @@ -77,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, null, null, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error")); return ""; } @@ -88,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, null, null, new ErrorInfo(e, + UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key")); return ""; } } diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index abcf3b04d..211441ba7 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -153,7 +153,7 @@ public class MainActivity extends AppCompatActivity { try { setupDrawer(); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Setting up drawer", e); } if (DeviceUtils.isTv(this)) { @@ -238,7 +238,7 @@ public class MainActivity extends AppCompatActivity { try { tabSelected(item); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Selecting main page tab", e); } break; case R.id.menu_options_about_group: @@ -340,7 +340,7 @@ public class MainActivity extends AppCompatActivity { try { showTabs(); } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "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.reportUiError(this, null, "Setting up service toggle", e); } final SharedPreferences sharedPreferences @@ -799,7 +799,7 @@ public class MainActivity extends AppCompatActivity { NavigationHelper.gotoMainFragment(getSupportFragmentManager()); } } catch (final Exception e) { - ErrorActivity.reportUiError(this, e); + ErrorActivity.reportUiError(this, null, "Handling intent", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 101a535f7..59b876386 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.error.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); } @@ -164,18 +169,61 @@ public class RouterActivity extends AppCompatActivity { } else { showUnsupportedUrlDialog(url); } - }, throwable -> handleError(throwable, url))); + }, throwable -> handleError(this, + new ErrorInfo(throwable, UserAction.SHARE_TO_NEWPIPE, url)))); } - private void handleError(final Throwable throwable, final String url) { - throwable.printStackTrace(); + /** + * @param context the context. If instance of {@link RouterActivity} it will be finished at the + * end, and if needed {@link #showUnsupportedUrlDialog(String)} will be called + * on it. + * @param errorInfo The error information. The field {@link ErrorInfo#getRequest()} has to + * contain the url, if context is instance of {@link RouterActivity}, since it + * could be used to call {@link #showUnsupportedUrlDialog(String)}. + */ + private static void handleError(final Context context, final ErrorInfo errorInfo) { + if (errorInfo.getThrowable() != null) { + errorInfo.getThrowable().printStackTrace(); + } - if (throwable instanceof ExtractionException) { - showUnsupportedUrlDialog(url); + if (errorInfo.getThrowable() instanceof ReCaptchaException) { + Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); + // Starting ReCaptcha Challenge Activity + final Intent intent = new Intent(context, ReCaptchaActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } else if (errorInfo.getThrowable() != null + && ExceptionUtils.isNetworkRelated(errorInfo.getThrowable())) { + Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof AgeRestrictedContentException) { + Toast.makeText(context, R.string.restricted_video_no_stream, + Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof GeographicRestrictionException) { + Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof PaidContentException) { + Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof PrivateContentException) { + Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof SoundCloudGoPlusContentException) { + Toast.makeText(context, R.string.soundcloud_go_plus_content, + Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof YoutubeMusicPremiumContentException) { + Toast.makeText(context, R.string.youtube_music_premium_content, + Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof ContentNotAvailableException) { + Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) { + Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show(); + } else if (errorInfo.getThrowable() instanceof ExtractionException + && context instanceof RouterActivity) { + // unfortunately we cannot tell if the error is really caused by an unsupported url + ((RouterActivity) context).showUnsupportedUrlDialog(errorInfo.getRequest()); } else { - ExtractorHelper.handleGeneralException(this, -1, url, throwable, - UserAction.SOMETHING_ELSE, null); - finish(); + ErrorActivity.reportError(context, MainActivity.class, null, errorInfo); + } + + if (context instanceof RouterActivity) { + ((RouterActivity) context).finish(); } } @@ -500,7 +548,8 @@ public class RouterActivity extends AppCompatActivity { .subscribe(intent -> { startActivity(intent); finish(); - }, throwable -> handleError(throwable, currentUrl)) + }, throwable -> handleError(this, + new ErrorInfo(throwable, UserAction.SHARE_TO_NEWPIPE, currentUrl))) ); return; } @@ -580,6 +629,7 @@ public class RouterActivity extends AppCompatActivity { this.playerChoice = playerChoice; } + @NonNull @Override public String toString() { return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice; @@ -646,9 +696,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/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index f845969c1..e21b27dd5 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -591,17 +591,6 @@ 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 +694,8 @@ public class DownloadDialog extends DialogFragment mainStorage.getTag()); } } catch (final Exception e) { - showErrorActivity(e); + ErrorActivity.reportError(context, null, null, + new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage")); return; } diff --git a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java index 8a90f12a5..cc7911417 100644 --- a/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java +++ b/app/src/main/java/org/schabi/newpipe/error/AcraReportSender.java @@ -4,6 +4,7 @@ import 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, null, null, new ErrorInfo( + new String[]{report.getString(ReportField.STACK_TRACE)}, + UserAction.UI_ERROR, + ErrorInfo.SERVICE_NONE, + "ACRA report", + R.string.app_ui_crash, + null)); } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index 7ed92441b..19e764d29 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -23,9 +23,6 @@ import androidx.core.app.NavUtils; import com.google.android.material.snackbar.Snackbar; import com.grack.nanojson.JsonWriter; -import org.acra.ReportField; -import org.acra.data.CrashReportData; -import org.schabi.newpipe.ActivityCommunicator; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; @@ -34,14 +31,9 @@ import org.schabi.newpipe.util.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,7 +62,6 @@ public class ErrorActivity extends AppCompatActivity { public static final String TAG = ErrorActivity.class.toString(); // BUNDLE TAGS public static final String ERROR_INFO = "error_info"; - public static final String ERROR_LIST = "error_list"; public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; public static final String ERROR_EMAIL_SUBJECT @@ -79,100 +70,68 @@ public class ErrorActivity extends AppCompatActivity { public static final String ERROR_GITHUB_ISSUE_URL = "https://github.com/TeamNewPipe/NewPipe/issues"; - private String[] errorList; + public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER + = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + + /** + * Singleton: + * Used to send data between certain Activity/Services within the same process. + * This can be considered as an ugly hack inside the Android universe. + **/ + @Nullable private static Class savedReturnActivity = null; + private ErrorInfo errorInfo; - private Class returnActivity; private String currentTimeStamp; private ActivityErrorBinding activityErrorBinding; - public static void reportUiError(final AppCompatActivity activity, final Throwable el) { - reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); + public static void reportUiError(final Context context, + @Nullable final View rootView, + final String request, + final Throwable throwable) { + reportError(context, (context instanceof Activity ? context.getClass() : null), rootView, + new ErrorInfo(throwable, UserAction.UI_ERROR, request)); } - public static void reportError(final Context context, final List el, - final Class returnActivity, final View rootView, + public static void reportError(final Context context, + final Class returnActivity, + @Nullable final View rootView, final ErrorInfo errorInfo) { if (rootView != null) { - Snackbar.make(rootView, R.string.error_snackbar_message, 3 * 1000) + Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG) .setActionTextColor(Color.YELLOW) .setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v -> - startErrorActivity(returnActivity, context, errorInfo, el)).show(); + startErrorActivity(returnActivity, context, errorInfo)).show(); } else { - startErrorActivity(returnActivity, context, errorInfo, el); + startErrorActivity(returnActivity, 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, + public static void reportError(final Handler handler, + final Context context, + final Class returnActivity, + final View rootView, final ErrorInfo errorInfo) { - final String[] el = {report.getString(ReportField.STACK_TRACE)}; + handler.post(() -> reportError(context, returnActivity, rootView, errorInfo)); + } + + //////////////////////////////////////////////////////////////////////// + // UTILS + //////////////////////////////////////////////////////////////////////// + + private static void startErrorActivity(@Nullable final Class returnActivity, + final Context context, + final ErrorInfo errorInfo) { + savedReturnActivity = returnActivity; final Intent intent = new Intent(context, ErrorActivity.class); intent.putExtra(ERROR_INFO, errorInfo); - intent.putExtra(ERROR_LIST, el); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } - private static String getStackTrace(final Throwable throwable) { - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw, true); - throwable.printStackTrace(pw); - return sw.getBuffer().toString(); - } - - // errorList to StringList - private static String[] elToSl(final List stackTraces) { - final String[] out = new String[stackTraces.size()]; - for (int i = 0; i < stackTraces.size(); i++) { - out[i] = getStackTrace(stackTraces.get(i)); - } - return out; - } - @Override protected void onCreate(final Bundle savedInstanceState) { assureCorrectAppLanguage(this); @@ -193,38 +152,28 @@ 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 +188,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) { + goToReturnActivity(); + } else if (id == R.id.menu_item_share_error) { + ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson()); + } else { + return false; } - return false; + return true; } private void openPrivacyPolicyDialog(final Context context, final String action) { @@ -311,7 +259,7 @@ public class ErrorActivity extends AppCompatActivity { } private void goToReturnActivity() { - final Class checkedReturnActivity = getReturnActivity(returnActivity); + final Class checkedReturnActivity = getReturnActivity(savedReturnActivity); if (checkedReturnActivity == null) { super.onBackPressed(); } else { @@ -355,7 +303,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 +341,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"); @@ -466,11 +414,4 @@ public class ErrorActivity extends AppCompatActivity { //super.onBackPressed(); goToReturnActivity(); } - - public String getCurrentTimeStamp() { - final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - df.setTimeZone(TimeZone.getTimeZone("GMT")); - return df.format(new Date()); - } - } diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index bcf8be220..ffbc2cafc 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -3,21 +3,109 @@ package org.schabi.newpipe.error import android.os.Parcelable import androidx.annotation.StringRes import kotlinx.android.parcel.Parcelize +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.Info +import org.schabi.newpipe.extractor.NewPipe +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException +import org.schabi.newpipe.extractor.exceptions.ExtractionException +import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException +import org.schabi.newpipe.ktx.isNetworkRelated +import java.io.PrintWriter +import java.io.StringWriter @Parcelize class ErrorInfo( - val userAction: UserAction?, - val serviceName: String, - val request: String, - @field:StringRes @param:StringRes val message: Int + val stackTraces: Array, + val userAction: UserAction, + val serviceName: String, + val request: String, + val messageStringId: Int, + @Transient // no need to store throwable, all data for report is in other variables + var throwable: Throwable? = null ) : Parcelable { - companion object { - @JvmStatic - fun make( - userAction: UserAction?, + + private constructor( + throwable: Throwable, + userAction: UserAction, serviceName: String, - request: String, - @StringRes message: Int - ) = ErrorInfo(userAction, serviceName, request, message) + 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..82e08d8de --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -0,0 +1,136 @@ +package org.schabi.newpipe.error + +import android.content.Context +import android.content.Intent +import android.util.Log +import android.view.View +import android.widget.Button +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import com.jakewharton.rxbinding4.view.clicks +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.Disposable +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException +import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException +import org.schabi.newpipe.extractor.exceptions.PaidContentException +import org.schabi.newpipe.extractor.exceptions.PrivateContentException +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException +import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException +import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.ktx.isInterruptedCaused +import org.schabi.newpipe.ktx.isNetworkRelated +import java.util.concurrent.TimeUnit + +class ErrorPanelHelper( + private val fragment: Fragment, + rootView: View, + onRetry: Runnable +) { + private val context: Context = rootView.context!! + private val errorPanelRoot: View = rootView.findViewById(R.id.error_panel) + private val errorTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_view) + private val errorButtonAction: Button = errorPanelRoot.findViewById(R.id.error_button_action) + private val errorButtonRetry: Button = errorPanelRoot.findViewById(R.id.error_button_retry) + + private var errorDisposable: Disposable? = null + + init { + errorDisposable = errorButtonRetry.clicks() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { onRetry.run() } + } + + fun showError(errorInfo: ErrorInfo) { + + if (errorInfo.throwable != null && errorInfo.throwable!!.isInterruptedCaused) { + if (DEBUG) { + Log.w(TAG, "onError() isInterruptedCaused! = [$errorInfo.throwable]") + } + return + } + + errorButtonAction.isVisible = true + if (errorInfo.throwable is ReCaptchaException) { + errorButtonAction.setText(R.string.recaptcha_solve) + errorButtonAction.setOnClickListener { + // Starting ReCaptcha Challenge Activity + val intent = Intent(context, ReCaptchaActivity::class.java) + intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, + (errorInfo.throwable as ReCaptchaException).url) + fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST) + errorButtonAction.setOnClickListener(null) + } + errorTextView.setText(R.string.recaptcha_request_toast) + errorButtonRetry.isVisible = true + + } else { + errorButtonAction.setText(R.string.error_snackbar_action) + errorButtonAction.setOnClickListener { + ErrorActivity.reportError( + context, + MainActivity::class.java, + null, + errorInfo + ) + } + + // hide retry button by default, then show only if not unavailable/unsupported content + errorButtonRetry.isVisible = false + errorTextView.setText( + when (errorInfo.throwable) { + is AgeRestrictedContentException -> R.string.restricted_video_no_stream + is GeographicRestrictionException -> R.string.georestricted_content + is PaidContentException -> R.string.paid_content + is PrivateContentException -> R.string.private_content + is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content + is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content + is ContentNotAvailableException -> R.string.content_not_available + is ContentNotSupportedException -> R.string.content_not_supported + else -> { + // show retry button only for content which is not unavailable or unsupported + errorButtonRetry.isVisible = true + if (errorInfo.throwable != null && errorInfo.throwable!!.isNetworkRelated) { + R.string.network_error + } else { + R.string.error_snackbar_message + } + } + } + ) + } + errorPanelRoot.animate(true, 300) + } + + fun showTextError(errorString: String) { + errorButtonAction.isVisible = false + errorButtonRetry.isVisible = false + errorTextView.text = errorString + } + + fun hide() { + errorButtonAction.setOnClickListener(null) + errorPanelRoot.animate(false, 150) + } + + fun isVisible(): Boolean { + return errorPanelRoot.isVisible + } + + fun dispose() { + errorButtonAction.setOnClickListener(null) + errorButtonRetry.setOnClickListener(null) + errorDisposable?.dispose() + } + + companion object { + val TAG: String = ErrorPanelHelper::class.simpleName!! + val DEBUG: Boolean = MainActivity.DEBUG + } +} diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java index e9d427b01..e8dec9556 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -6,9 +6,12 @@ package org.schabi.newpipe.error; 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 7566ec2a0..0d244231d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -1,48 +1,25 @@ package org.schabi.newpipe.fragments; import android.content.Context; -import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; -import android.widget.Button; import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -import com.jakewharton.rxbinding4.view.RxView; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.ReCaptchaActivity; -import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException; -import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; -import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; -import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException; -import org.schabi.newpipe.extractor.exceptions.PaidContentException; -import org.schabi.newpipe.extractor.exceptions.PrivateContentException; -import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException; -import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException; -import org.schabi.newpipe.ktx.ExceptionUtils; +import org.schabi.newpipe.error.ErrorPanelHelper; import org.schabi.newpipe.util.InfoCache; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import icepick.State; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.disposables.Disposable; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -55,12 +32,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC private View emptyStateView; @Nullable private ProgressBar loadingProgressBar; - - private Disposable errorDisposable; - - protected View errorPanelRoot; - private Button errorButtonRetry; - private TextView errorTextView; + private ErrorPanelHelper errorPanelHelper; @Override public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { @@ -77,9 +49,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC @Override public void onDestroy() { super.onDestroy(); - if (errorDisposable != null) { - errorDisposable.dispose(); - } + errorPanelHelper.dispose(); } /*////////////////////////////////////////////////////////////////////////// @@ -89,22 +59,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 +100,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC if (loadingProgressBar != null) { animate(loadingProgressBar, true, 400); } - animate(errorPanelRoot, false, 150); + errorPanelHelper.hide(); } @Override @@ -154,10 +111,9 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC if (loadingProgressBar != null) { animate(loadingProgressBar, false, 0); } - animate(errorPanelRoot, false, 150); + errorPanelHelper.hide(); } - @Override public void showEmptyState() { isLoading.set(false); if (emptyStateView != null) { @@ -166,26 +122,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); + errorPanelHelper.hide(); } @Override @@ -196,138 +133,70 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC hideLoading(); } + @Override + public void handleError() { + isLoading.set(false); + InfoCache.getInstance().clearCache(); + hideLoading(); + } + /*////////////////////////////////////////////////////////////////////////// // 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); + } + + public final void showTextError(final @NonNull 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(); } - 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#reportError(Context, Class, View, ErrorInfo)} * IF we a find a valid view (otherwise the error screen appears). * - * @param exception List of the exceptions to show - * @param userAction The user action that caused the exception - * @param serviceName The service where the exception happened - * @param request The page that was requested - * @param errorId The ID of the error + * @param errorInfo The error information */ - public void showSnackBarError(final List exception, final UserAction userAction, - final String serviceName, final String request, - @StringRes final int errorId) { + public void showSnackBarError(final ErrorInfo errorInfo) { if (DEBUG) { - Log.d(TAG, "showSnackBarError() called with: " - + "exception = [" + exception + "], userAction = [" + userAction + "], " - + "request = [" + request + "], errorId = [" + errorId + "]"); + Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]"); } View rootView = activity != null ? activity.findViewById(android.R.id.content) : null; - if (rootView == null && getView() != null) { + if (rootView == null) { rootView = getView(); } if (rootView == null) { return; } - ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, - ErrorInfo.make(userAction, serviceName, request, errorId)); + ErrorActivity.reportError(requireContext(), MainActivity.class, rootView, errorInfo); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 649f2d5b1..9443bac05 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.extractor.exceptions.ExtractionException; import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.util.NavigationHelper; @@ -128,7 +125,8 @@ public 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.reportUiError(getActivity(), null, "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.reportUiError(context, null, "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 505111da5..c6789804c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -37,7 +37,6 @@ import androidx.annotation.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; @@ -64,9 +63,7 @@ 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; @@ -526,7 +523,7 @@ public final class VideoDetailFragment NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(), subChannelUrl, subChannelName); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e); } } @@ -684,13 +681,12 @@ 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 +902,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))); } /*////////////////////////////////////////////////////////////////////////// @@ -1327,8 +1321,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 +1335,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 +1542,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 +1583,10 @@ public final class VideoDetailFragment } public void openDownloadDialog() { + if (currentInfo == null) { + return; + } + try { final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); downloadDialog.setVideoStreams(sortedVideoStreams); @@ -1601,18 +1596,10 @@ 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.reportError(activity, activity.getClass(), + activity.findViewById(android.R.id.content), + new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog", + currentInfo)); } } @@ -1620,24 +1607,6 @@ public 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 b8c9af05b..d9c18fe44 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; @@ -292,7 +291,7 @@ public abstract class BaseListFragment extends BaseStateFragment selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e); } } }); @@ -307,7 +306,7 @@ public abstract class BaseListFragment extends BaseStateFragment selectedItem.getUrl(), selectedItem.getName()); } catch (final Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + ErrorActivity.reportUiError(getActivity(), null, "Opening playlist fragment", e); } } }); @@ -412,13 +411,6 @@ public abstract class BaseListFragment extends BaseStateFragment animate(itemsList, true, 300); } - @Override - public void showError(final String message, final boolean showRetryButton) { - super.showError(message, showRetryButton); - showListFooter(false); - animate(itemsList, false, 200); - } - @Override public void showEmptyState() { super.showEmptyState(); @@ -439,6 +431,13 @@ public abstract class BaseListFragment extends BaseStateFragment isLoading.set(false); } + @Override + public void handleError() { + super.handleError(); + showListFooter(false); + animate(itemsList, false, 200); + } + @Override public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 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 ca95c272a..1d5bcce32 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.error.ErrorActivity; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; @@ -91,6 +89,10 @@ public 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.reportUiError(getActivity(), null, "Opening channel fragment", e); } } else if (DEBUG) { Log.i(TAG, "Can't open parent channel because we got no channel URL"); @@ -469,21 +464,9 @@ public 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(); } } @@ -537,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 797b92c63..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.error.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 d83dfc63b..bdca25558 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/DefaultKioskFragment.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments.list.kiosk; import android.os.Bundle; +import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.kiosk.KioskList; @@ -10,6 +11,7 @@ import org.schabi.newpipe.util.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 849e07716..77aa8e001 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; @@ -82,6 +83,10 @@ public class KioskFragment extends BaseListInfoFragment { return instance; } + public KioskFragment() { + super(UserAction.REQUESTED_KIOSK); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -102,9 +107,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")); } } } @@ -169,22 +172,5 @@ public class KioskFragment extends BaseListInfoFragment { 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 6d4995303..b82408b03 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.error.ErrorActivity; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.KoreUtil; @@ -87,6 +85,10 @@ public class PlaylistFragment extends BaseListInfoFragment { return instance; } + public PlaylistFragment() { + super(UserAction.REQUESTED_PLAYLIST); + } + /*////////////////////////////////////////////////////////////////////////// // LifeCycle //////////////////////////////////////////////////////////////////////////*/ @@ -284,7 +286,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.reportUiError(getActivity(), null, "Opening channel fragment", e); } }); } @@ -315,8 +317,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 +365,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 +409,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 +436,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 0fca2e5a6..e68ebbf6a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -47,7 +47,6 @@ import org.schabi.newpipe.extractor.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; @@ -258,11 +257,9 @@ 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(); @@ -763,8 +759,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 +796,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) { @@ -849,10 +845,9 @@ 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 +867,7 @@ public class SearchFragment extends BaseListFragment isLoading.set(false)) - .subscribe(this::handleResult, this::onError); + .subscribe(this::handleResult, this::onItemError); } @@ -895,7 +890,7 @@ public class SearchFragment extends BaseListFragment isLoading.set(false)) - .subscribe(this::handleNextItems, this::onError); + .subscribe(this::handleNextItems, this::onItemError); } @Override @@ -909,6 +904,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 +964,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); } } 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 7f8410012..758cddc55 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedVideosFragment.java @@ -47,6 +47,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment 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 +236,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) { + animate(itemsList, false, 200); + } + if (headerRootBinding != null) { + animate(headerRootBinding.getRoot(), false, 200); + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index b96f23a9e..e043b140e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -23,6 +23,7 @@ import org.schabi.newpipe.database.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.local.BaseLocalListFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; @@ -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 efe64d2f2..adbff1e54 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -38,6 +38,7 @@ import icepick.State import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.databinding.FragmentFeedBinding +import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.UserAction import org.schabi.newpipe.fragments.list.BaseListFragment import org.schabi.newpipe.ktx.animate @@ -48,7 +49,6 @@ import java.util.Calendar class FeedFragment : BaseListFragment() { private var _feedBinding: FragmentFeedBinding? = null private val feedBinding get() = _feedBinding!! - private val errorBinding get() = _feedBinding!!.errorPanel private lateinit var viewModel: FeedViewModel @State @@ -171,50 +171,24 @@ class FeedFragment : BaseListFragment() { // ///////////////////////////////////////////////////////////////////////// override fun showLoading() { + super.showLoading() feedBinding.refreshRootView.animate(false, 0) feedBinding.itemsList.animate(false, 0) - - feedBinding.loadingProgressBar.animate(true, 200) feedBinding.loadingProgressText.animate(true, 200) - - feedBinding.emptyStateView.root.animate(false, 0) - errorBinding.root.animate(false, 0) } override fun hideLoading() { + super.hideLoading() feedBinding.refreshRootView.animate(true, 200) - feedBinding.itemsList.animate(true, 300) - - feedBinding.loadingProgressBar.animate(false, 0) feedBinding.loadingProgressText.animate(false, 0) - - feedBinding.emptyStateView.root.animate(false, 0) - errorBinding.root.animate(false, 0) feedBinding.swiperefresh.isRefreshing = false } override fun showEmptyState() { + super.showEmptyState() feedBinding.refreshRootView.animate(true, 200) feedBinding.itemsList.animate(false, 0) - - feedBinding.loadingProgressBar.animate(false, 0) feedBinding.loadingProgressText.animate(false, 0) - - feedBinding.emptyStateView.root.animate(true, 800) - errorBinding.root.animate(false, 0) - } - - override fun showError(message: String, showRetryButton: Boolean) { - infoListAdapter.clearStreamItemList() - feedBinding.refreshRootView.animate(false, 120) - feedBinding.itemsList.animate(false, 120) - - feedBinding.loadingProgressBar.animate(false, 120) - feedBinding.loadingProgressText.animate(false, 120) - - errorBinding.errorMessageView.text = message - errorBinding.errorButtonRetry.animate(showRetryButton, if (showRetryButton) 600 else 0) - errorBinding.root.animate(true, 300) } override fun handleResult(result: FeedState) { @@ -227,6 +201,14 @@ class FeedFragment : BaseListFragment() { updateRefreshViewState() } + override fun handleError() { + super.handleError() + infoListAdapter.clearStreamItemList() + feedBinding.refreshRootView.animate(false, 200) + feedBinding.itemsList.animate(false, 200) + feedBinding.loadingProgressText.animate(false, 200) + } + private fun handleProgressState(progressState: FeedState.ProgressState) { showLoading() @@ -266,13 +248,6 @@ class FeedFragment : BaseListFragment() { ) } - if (loadedState.itemsErrors.isNotEmpty()) { - showSnackBarError( - loadedState.itemsErrors, UserAction.REQUESTED_FEED, - "none", "Loading feed", R.string.general_error - ) - } - if (loadedState.items.isEmpty()) { showEmptyState() } else { @@ -281,12 +256,13 @@ class FeedFragment : BaseListFragment() { } private fun handleErrorState(errorState: FeedState.ErrorState): Boolean { - hideLoading() - errorState.error?.let { - onError(errorState.error) - return true + return if (errorState.error == null) { + hideLoading() + false + } else { + showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed")) + true } - return false } private fun updateRelativeTimeViews() { @@ -320,18 +296,6 @@ class FeedFragment : BaseListFragment() { listState = null } - override fun onError(exception: Throwable): Boolean { - if (super.onError(exception)) return true - - if (useAsFrontPage) { - showSnackBarError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0) - return true - } - - onUnrecoverableError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0) - return true - } - companion object { const val KEY_GROUP_ID = "ARG_GROUP_ID" const val KEY_GROUP_NAME = "ARG_GROUP_NAME" diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 515b623ec..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.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.settings.SettingsActivity; +import org.schabi.newpipe.settings.HistorySettingsFragment; import org.schabi.newpipe.util.KoreUtil; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; @@ -49,6 +47,7 @@ import java.util.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 c2d6698a9..33cc9f636 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -34,6 +34,7 @@ import org.schabi.newpipe.database.stream.model.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; @@ -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 7710c2bba..571a8d9b1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionsImportFragment.java @@ -84,10 +84,12 @@ public 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.reportError(activity, null, null, + new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT, NewPipe.getNameOfService(currentServiceId), - "Service don't support importing", R.string.general_error)); + "Service does not support importing subscriptions", + R.string.general_error, + null)); activity.finish(); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index 6ad0761c8..e1a757f3f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -43,7 +43,6 @@ import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.subscription.SubscriptionManager; import java.io.FileNotFoundException; -import java.util.Collections; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -152,13 +151,10 @@ 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, null, null, new ErrorInfo( + throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request)); } protected void postErrorResult(final String title, final String text) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index cd2a18436..69791ad31 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.error.ErrorActivity; import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.Localization; -import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ZipHelper; @@ -198,7 +196,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show(); } catch (final Exception e) { - onError(e); + ErrorActivity.reportUiError(getActivity(), null, "Exporting database", e); } } @@ -243,20 +241,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { System.exit(0); } } catch (final Exception e) { - onError(e); + ErrorActivity.reportUiError(getActivity(), null, "Importing database", e); } } - - /*////////////////////////////////////////////////////////////////////////// - // Error - //////////////////////////////////////////////////////////////////////////*/ - - protected void onError(final Throwable e) { - final Activity activity = getActivity(); - ErrorActivity.reportError(activity, e, - activity.getClass(), - null, - ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); - } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java index 3e67d93e2..6f99ec1ae 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java @@ -1,8 +1,10 @@ package org.schabi.newpipe.settings; +import android.content.Context; import android.os.Bundle; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; @@ -46,120 +48,103 @@ public 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, SettingsActivity.class, + null, 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, SettingsActivity.class, + null, 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, SettingsActivity.class, + null, 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, SettingsActivity.class, + null, 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 5854d0a83..b7353c806 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.local.subscription.SubscriptionManager; import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.util.ThemeHelper; import java.util.List; @@ -108,7 +105,7 @@ public 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,16 @@ 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.reportUiError(requireContext(), null, "Loading subscription", exception); } @Override @@ -173,16 +170,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 +184,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 7d220578e..3ab1ee905 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.settings; -import android.app.Activity; import android.content.DialogInterface; import android.os.Bundle; import android.view.LayoutInflater; @@ -19,8 +18,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -83,7 +80,7 @@ public class SelectKioskFragment extends DialogFragment { try { selectKioskAdapter = new SelectKioskAdapter(); } catch (final Exception e) { - onError(e); + ErrorActivity.reportUiError(getActivity(), null, "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 6dcfc9179..6f18acac0 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -115,8 +115,8 @@ 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.reportError(activity, activity.getClass(), null, new ErrorInfo(e, + UserAction.UI_ERROR, "Loading playlists")); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index 33f83bc6f..d96bd7353 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -95,15 +95,13 @@ public 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, SettingMigrations.class, null, new ErrorInfo( + e, UserAction.PREFERENCES_MIGRATION, - "none", "Migrating preferences from version " + lastPrefVersion + " to " + VERSION + ". " - + "Error at " + currentVersion + " => " + ++currentVersion, - 0 - ); - ErrorActivity.reportError(context, e, SettingMigrations.class, null, errorInfo); + + "Error at " + currentVersion + " => " + ++currentVersion + )); return; } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 963021b69..fd290e934 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -183,10 +183,9 @@ 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.reportError(requireContext(), null, null, + new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId), + UserAction.SOMETHING_ELSE, "Choosing tabs on settings")); return; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index b92a1a3fe..3f85bc1b4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -483,9 +483,8 @@ 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.reportError(context, null, null, new ErrorInfo(e, + UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service")); } return kioskId; } diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 97436dfbc..a904f360a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -20,12 +20,9 @@ package org.schabi.newpipe.util; import android.content.Context; -import android.content.Intent; -import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.Nullable; import androidx.core.text.HtmlCompat; @@ -33,10 +30,6 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorActivity; -import org.schabi.newpipe.error.ErrorInfo; -import org.schabi.newpipe.error.ReCaptchaActivity; -import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; @@ -47,26 +40,14 @@ import org.schabi.newpipe.extractor.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 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 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 a8b9f5269..455dd0614 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -583,16 +583,12 @@ 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, null, null, + new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action, + service, request.toString(), reason, null)); } public void clearFinishedDownloads(boolean delete) { diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 14459b494..a3d947f6f 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 @@ +