Add report/solve-recaptcha button in error panel
It will be shown even when nothing could be loaded not due to a network error, and the user can choose to ignore or report it. Also improve error reporting arguments Also completely refactor error activity Also improve some code here and there
This commit is contained in:
parent
553b80164b
commit
c43bca6007
|
@ -155,7 +155,7 @@ task formatKtlint(type: JavaExec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint
|
//preDebugBuild.dependsOn formatKtlint, runCheckstyle, runKtlint
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class ErrorInfoTest {
|
||||||
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
|
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
|
||||||
assertEquals("youtube", infoFromParcel.getServiceName());
|
assertEquals("youtube", infoFromParcel.getServiceName());
|
||||||
assertEquals("request", infoFromParcel.getRequest());
|
assertEquals("request", infoFromParcel.getRequest());
|
||||||
assertEquals(R.string.general_error, infoFromParcel.getMessage());
|
assertEquals(R.string.general_error, infoFromParcel.getMessageStringId());
|
||||||
|
|
||||||
parcel.recycle();
|
parcel.recycle();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
package org.schabi.newpipe;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Created by Christian Schabesberger on 24.12.15.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
|
||||||
* ActivityCommunicator.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton:
|
|
||||||
* Used to send data between certain Activity/Services within the same process.
|
|
||||||
* This can be considered as an ugly hack inside the Android universe.
|
|
||||||
**/
|
|
||||||
public class ActivityCommunicator {
|
|
||||||
|
|
||||||
private static ActivityCommunicator activityCommunicator;
|
|
||||||
private volatile Class returnActivity;
|
|
||||||
|
|
||||||
public static ActivityCommunicator getCommunicator() {
|
|
||||||
if (activityCommunicator == null) {
|
|
||||||
activityCommunicator = new ActivityCommunicator();
|
|
||||||
}
|
|
||||||
return activityCommunicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class getReturnActivity() {
|
|
||||||
return returnActivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReturnActivity(final Class returnActivity) {
|
|
||||||
this.returnActivity = returnActivity;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -225,14 +225,10 @@ public class App extends MultiDexApplication {
|
||||||
.setBuildConfigClass(BuildConfig.class)
|
.setBuildConfigClass(BuildConfig.class)
|
||||||
.build();
|
.build();
|
||||||
ACRA.init(this, acraConfig);
|
ACRA.init(this, acraConfig);
|
||||||
} catch (final ACRAConfigurationException ace) {
|
} catch (final ACRAConfigurationException exception) {
|
||||||
ace.printStackTrace();
|
exception.printStackTrace();
|
||||||
ErrorActivity.reportError(this,
|
ErrorActivity.reportError(this, null, null, new ErrorInfo(exception,
|
||||||
ace,
|
UserAction.SOMETHING_ELSE, "Could not initialize ACRA crash report"));
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
|
||||||
"Could not initialize ACRA crash report", R.string.app_ui_crash));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,9 +63,8 @@ public final class CheckForNewAppVersion {
|
||||||
packageInfo = application.getPackageManager().getPackageInfo(
|
packageInfo = application.getPackageManager().getPackageInfo(
|
||||||
application.getPackageName(), PackageManager.GET_SIGNATURES);
|
application.getPackageName(), PackageManager.GET_SIGNATURES);
|
||||||
} catch (final PackageManager.NameNotFoundException e) {
|
} catch (final PackageManager.NameNotFoundException e) {
|
||||||
ErrorActivity.reportError(application, e, null, null,
|
ErrorActivity.reportError(application, null, null, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
|
||||||
"Could not find package info", R.string.app_ui_crash));
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,9 +76,8 @@ public final class CheckForNewAppVersion {
|
||||||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||||
c = (X509Certificate) cf.generateCertificate(input);
|
c = (X509Certificate) cf.generateCertificate(input);
|
||||||
} catch (final CertificateException e) {
|
} catch (final CertificateException e) {
|
||||||
ErrorActivity.reportError(application, e, null, null,
|
ErrorActivity.reportError(application, null, null, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error"));
|
||||||
"Certificate error", R.string.app_ui_crash));
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,9 +86,8 @@ public final class CheckForNewAppVersion {
|
||||||
final byte[] publicKey = md.digest(c.getEncoded());
|
final byte[] publicKey = md.digest(c.getEncoded());
|
||||||
return byte2HexFormatted(publicKey);
|
return byte2HexFormatted(publicKey);
|
||||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||||
ErrorActivity.reportError(application, e, null, null,
|
ErrorActivity.reportError(application, null, null, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key"));
|
||||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
try {
|
try {
|
||||||
setupDrawer();
|
setupDrawer();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, null, "Setting up drawer", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DeviceUtils.isTv(this)) {
|
if (DeviceUtils.isTv(this)) {
|
||||||
|
@ -238,7 +238,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
try {
|
try {
|
||||||
tabSelected(item);
|
tabSelected(item);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, null, "Selecting main page tab", e);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.menu_options_about_group:
|
case R.id.menu_options_about_group:
|
||||||
|
@ -340,7 +340,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
try {
|
try {
|
||||||
showTabs();
|
showTabs();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, null, "Showing main page tabs", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,7 +487,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
|
drawerHeaderBinding.drawerHeaderActionButton.setContentDescription(
|
||||||
getString(R.string.drawer_header_description) + selectedServiceName);
|
getString(R.string.drawer_header_description) + selectedServiceName);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, null, "Setting up service toggle", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
final SharedPreferences sharedPreferences
|
final SharedPreferences sharedPreferences
|
||||||
|
@ -799,7 +799,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError(this, e);
|
ErrorActivity.reportUiError(this, null, "Handling intent", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,15 +33,29 @@ import androidx.preference.PreferenceManager;
|
||||||
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||||
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||||
import org.schabi.newpipe.download.DownloadDialog;
|
import org.schabi.newpipe.download.DownloadDialog;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.Info;
|
import org.schabi.newpipe.extractor.Info;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.StreamingService.LinkType;
|
import org.schabi.newpipe.extractor.StreamingService.LinkType;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.player.MainPlayer;
|
import org.schabi.newpipe.player.MainPlayer;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
|
@ -49,7 +63,6 @@ import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
@ -84,13 +97,6 @@ import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||||
* Get the url from the intent and open it in the chosen preferred player.
|
* Get the url from the intent and open it in the chosen preferred player.
|
||||||
*/
|
*/
|
||||||
public class RouterActivity extends AppCompatActivity {
|
public class RouterActivity extends AppCompatActivity {
|
||||||
public static final String INTERNAL_ROUTE_KEY = "internalRoute";
|
|
||||||
/**
|
|
||||||
* Removes invisible separators (\p{Z}) and punctuation characters including
|
|
||||||
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
|
|
||||||
* more details.
|
|
||||||
*/
|
|
||||||
private static final String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
|
|
||||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
@State
|
@State
|
||||||
protected int currentServiceId = -1;
|
protected int currentServiceId = -1;
|
||||||
|
@ -100,7 +106,6 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
protected int selectedRadioPosition = -1;
|
protected int selectedRadioPosition = -1;
|
||||||
protected int selectedPreviously = -1;
|
protected int selectedPreviously = -1;
|
||||||
protected String currentUrl;
|
protected String currentUrl;
|
||||||
protected boolean internalRoute = false;
|
|
||||||
private StreamingService currentService;
|
private StreamingService currentService;
|
||||||
private boolean selectionIsDownload = false;
|
private boolean selectionIsDownload = false;
|
||||||
|
|
||||||
|
@ -123,7 +128,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(final Bundle outState) {
|
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
Icepick.saveInstanceState(this, outState);
|
Icepick.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
|
@ -164,18 +169,61 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
} else {
|
} else {
|
||||||
showUnsupportedUrlDialog(url);
|
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) {
|
if (errorInfo.getThrowable() instanceof ReCaptchaException) {
|
||||||
showUnsupportedUrlDialog(url);
|
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
||||||
|
// Starting ReCaptcha Challenge Activity
|
||||||
|
final Intent intent = new Intent(context, ReCaptchaActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
} else if (errorInfo.getThrowable() != null
|
||||||
|
&& ExceptionUtils.isNetworkRelated(errorInfo.getThrowable())) {
|
||||||
|
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof AgeRestrictedContentException) {
|
||||||
|
Toast.makeText(context, R.string.restricted_video_no_stream,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof GeographicRestrictionException) {
|
||||||
|
Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof PaidContentException) {
|
||||||
|
Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof PrivateContentException) {
|
||||||
|
Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof SoundCloudGoPlusContentException) {
|
||||||
|
Toast.makeText(context, R.string.soundcloud_go_plus_content,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof YoutubeMusicPremiumContentException) {
|
||||||
|
Toast.makeText(context, R.string.youtube_music_premium_content,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof ContentNotAvailableException) {
|
||||||
|
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
|
||||||
|
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
|
||||||
|
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
|
||||||
|
} else 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 {
|
} else {
|
||||||
ExtractorHelper.handleGeneralException(this, -1, url, throwable,
|
ErrorActivity.reportError(context, MainActivity.class, null, errorInfo);
|
||||||
UserAction.SOMETHING_ELSE, null);
|
}
|
||||||
finish();
|
|
||||||
|
if (context instanceof RouterActivity) {
|
||||||
|
((RouterActivity) context).finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +548,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
.subscribe(intent -> {
|
.subscribe(intent -> {
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
}, throwable -> handleError(throwable, currentUrl))
|
}, throwable -> handleError(this,
|
||||||
|
new ErrorInfo(throwable, UserAction.SHARE_TO_NEWPIPE, currentUrl)))
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -580,6 +629,7 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
this.playerChoice = playerChoice;
|
this.playerChoice = playerChoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
|
return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice;
|
||||||
|
@ -646,9 +696,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
if (fetcher != null) {
|
if (fetcher != null) {
|
||||||
fetcher.dispose();
|
fetcher.dispose();
|
||||||
}
|
}
|
||||||
}, throwable -> ExtractorHelper.handleGeneralException(this,
|
}, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction,
|
||||||
choice.serviceId, choice.url, throwable, finalUserAction,
|
choice.url + " opened with " + choice.playerChoice,
|
||||||
", opened with " + choice.playerChoice));
|
choice.serviceId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -591,17 +591,6 @@ public class DownloadDialog extends DialogFragment
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showErrorActivity(final Exception e) {
|
|
||||||
ErrorActivity.reportError(
|
|
||||||
context,
|
|
||||||
Collections.singletonList(e),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ErrorInfo
|
|
||||||
.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareSelectedDownload() {
|
private void prepareSelectedDownload() {
|
||||||
final StoredDirectoryHelper mainStorage;
|
final StoredDirectoryHelper mainStorage;
|
||||||
final MediaFormat format;
|
final MediaFormat format;
|
||||||
|
@ -705,7 +694,8 @@ public class DownloadDialog extends DialogFragment
|
||||||
mainStorage.getTag());
|
mainStorage.getTag());
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
showErrorActivity(e);
|
ErrorActivity.reportError(context, null, null,
|
||||||
|
new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.acra.ReportField;
|
||||||
import org.acra.data.CrashReportData;
|
import org.acra.data.CrashReportData;
|
||||||
import org.acra.sender.ReportSender;
|
import org.acra.sender.ReportSender;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -32,8 +33,12 @@ public class AcraReportSender implements ReportSender {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
|
public void send(@NonNull final Context context, @NonNull final CrashReportData report) {
|
||||||
ErrorActivity.reportError(context, report,
|
ErrorActivity.reportError(context, null, null, new ErrorInfo(
|
||||||
ErrorInfo.make(UserAction.UI_ERROR, "none",
|
new String[]{report.getString(ReportField.STACK_TRACE)},
|
||||||
"App crash, UI failure", R.string.app_ui_crash));
|
UserAction.UI_ERROR,
|
||||||
|
ErrorInfo.SERVICE_NONE,
|
||||||
|
"ACRA report",
|
||||||
|
R.string.app_ui_crash,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,6 @@ import androidx.core.app.NavUtils;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
import org.acra.ReportField;
|
|
||||||
import org.acra.data.CrashReportData;
|
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -34,14 +31,9 @@ import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.time.LocalDateTime;
|
||||||
import java.io.StringWriter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
|
@ -70,7 +62,6 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
public static final String TAG = ErrorActivity.class.toString();
|
public static final String TAG = ErrorActivity.class.toString();
|
||||||
// BUNDLE TAGS
|
// BUNDLE TAGS
|
||||||
public static final String ERROR_INFO = "error_info";
|
public static final String ERROR_INFO = "error_info";
|
||||||
public static final String ERROR_LIST = "error_list";
|
|
||||||
|
|
||||||
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
||||||
public static final String ERROR_EMAIL_SUBJECT
|
public static final String ERROR_EMAIL_SUBJECT
|
||||||
|
@ -79,100 +70,68 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
public static final String ERROR_GITHUB_ISSUE_URL
|
public static final String ERROR_GITHUB_ISSUE_URL
|
||||||
= "https://github.com/TeamNewPipe/NewPipe/issues";
|
= "https://github.com/TeamNewPipe/NewPipe/issues";
|
||||||
|
|
||||||
private String[] errorList;
|
public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER
|
||||||
|
= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ErrorInfo errorInfo;
|
||||||
private Class returnActivity;
|
|
||||||
private String currentTimeStamp;
|
private String currentTimeStamp;
|
||||||
|
|
||||||
private ActivityErrorBinding activityErrorBinding;
|
private ActivityErrorBinding activityErrorBinding;
|
||||||
|
|
||||||
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
|
public static void reportUiError(final Context context,
|
||||||
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR,
|
@Nullable final View rootView,
|
||||||
"none", "", R.string.app_ui_crash));
|
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<Throwable> el,
|
public static void reportError(final Context context,
|
||||||
final Class returnActivity, final View rootView,
|
final Class returnActivity,
|
||||||
|
@Nullable final View rootView,
|
||||||
final ErrorInfo errorInfo) {
|
final ErrorInfo errorInfo) {
|
||||||
if (rootView != null) {
|
if (rootView != null) {
|
||||||
Snackbar.make(rootView, R.string.error_snackbar_message, 3 * 1000)
|
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
|
||||||
.setActionTextColor(Color.YELLOW)
|
.setActionTextColor(Color.YELLOW)
|
||||||
.setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v ->
|
.setAction(context.getString(R.string.error_snackbar_action).toUpperCase(), v ->
|
||||||
startErrorActivity(returnActivity, context, errorInfo, el)).show();
|
startErrorActivity(returnActivity, context, errorInfo)).show();
|
||||||
} else {
|
} 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<Throwable> el) {
|
|
||||||
final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
|
||||||
ac.setReturnActivity(returnActivity);
|
|
||||||
final Intent intent = new Intent(context, ErrorActivity.class);
|
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
|
||||||
intent.putExtra(ERROR_LIST, elToSl(el));
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reportError(final Context context, final Throwable e,
|
|
||||||
final Class returnActivity, final View rootView,
|
|
||||||
final ErrorInfo errorInfo) {
|
|
||||||
List<Throwable> el = null;
|
|
||||||
if (e != null) {
|
|
||||||
el = new Vector<>();
|
|
||||||
el.add(e);
|
|
||||||
}
|
|
||||||
reportError(context, el, returnActivity, rootView, errorInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// async call
|
// async call
|
||||||
public static void reportError(final Handler handler, final Context context,
|
public static void reportError(final Handler handler,
|
||||||
final Throwable e, final Class returnActivity,
|
final Context context,
|
||||||
final View rootView, final ErrorInfo errorInfo) {
|
final Class returnActivity,
|
||||||
|
final View rootView,
|
||||||
List<Throwable> el = null;
|
|
||||||
if (e != null) {
|
|
||||||
el = new Vector<>();
|
|
||||||
el.add(e);
|
|
||||||
}
|
|
||||||
reportError(handler, context, el, returnActivity, rootView, errorInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// async call
|
|
||||||
public static void reportError(final Handler handler, final Context context,
|
|
||||||
final List<Throwable> el, final Class returnActivity,
|
|
||||||
final View rootView, final ErrorInfo errorInfo) {
|
|
||||||
handler.post(() -> reportError(context, el, returnActivity, rootView, errorInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reportError(final Context context, final CrashReportData report,
|
|
||||||
final ErrorInfo errorInfo) {
|
final 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);
|
final Intent intent = new Intent(context, ErrorActivity.class);
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
intent.putExtra(ERROR_INFO, errorInfo);
|
||||||
intent.putExtra(ERROR_LIST, el);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getStackTrace(final Throwable throwable) {
|
|
||||||
final StringWriter sw = new StringWriter();
|
|
||||||
final PrintWriter pw = new PrintWriter(sw, true);
|
|
||||||
throwable.printStackTrace(pw);
|
|
||||||
return sw.getBuffer().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorList to StringList
|
|
||||||
private static String[] elToSl(final List<Throwable> stackTraces) {
|
|
||||||
final String[] out = new String[stackTraces.size()];
|
|
||||||
for (int i = 0; i < stackTraces.size(); i++) {
|
|
||||||
out[i] = getStackTrace(stackTraces.get(i));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
assureCorrectAppLanguage(this);
|
assureCorrectAppLanguage(this);
|
||||||
|
@ -193,38 +152,28 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
actionBar.setDisplayShowTitleEnabled(true);
|
actionBar.setDisplayShowTitleEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
|
|
||||||
returnActivity = ac.getReturnActivity();
|
|
||||||
errorInfo = intent.getParcelableExtra(ERROR_INFO);
|
errorInfo = intent.getParcelableExtra(ERROR_INFO);
|
||||||
errorList = intent.getStringArrayExtra(ERROR_LIST);
|
|
||||||
|
|
||||||
// important add guru meditation
|
// important add guru meditation
|
||||||
addGuruMeditation();
|
addGuruMeditation();
|
||||||
currentTimeStamp = getCurrentTimeStamp();
|
currentTimeStamp = CURRENT_TIMESTAMP_FORMATTER.format(LocalDateTime.now());
|
||||||
|
|
||||||
activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
|
activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
|
||||||
openPrivacyPolicyDialog(this, "EMAIL"));
|
openPrivacyPolicyDialog(this, "EMAIL"));
|
||||||
|
|
||||||
activityErrorBinding.errorReportCopyButton.setOnClickListener(v -> {
|
activityErrorBinding.errorReportCopyButton.setOnClickListener(v ->
|
||||||
ShareUtils.copyToClipboard(this, buildMarkdown());
|
ShareUtils.copyToClipboard(this, buildMarkdown()));
|
||||||
});
|
|
||||||
|
|
||||||
activityErrorBinding.errorReportGitHubButton.setOnClickListener(v ->
|
activityErrorBinding.errorReportGitHubButton.setOnClickListener(v ->
|
||||||
openPrivacyPolicyDialog(this, "GITHUB"));
|
openPrivacyPolicyDialog(this, "GITHUB"));
|
||||||
|
|
||||||
// normal bugreport
|
// normal bugreport
|
||||||
buildInfo(errorInfo);
|
buildInfo(errorInfo);
|
||||||
if (errorInfo.getMessage() != 0) {
|
activityErrorBinding.errorMessageView.setText(errorInfo.getMessageStringId());
|
||||||
activityErrorBinding.errorMessageView.setText(errorInfo.getMessage());
|
activityErrorBinding.errorView.setText(formErrorText(errorInfo.getStackTraces()));
|
||||||
} else {
|
|
||||||
activityErrorBinding.errorMessageView.setVisibility(View.GONE);
|
|
||||||
activityErrorBinding.messageWhatHappenedView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
activityErrorBinding.errorView.setText(formErrorText(errorList));
|
|
||||||
|
|
||||||
// print stack trace once again for debugging:
|
// print stack trace once again for debugging:
|
||||||
for (final String e : errorList) {
|
for (final String e : errorInfo.getStackTraces()) {
|
||||||
Log.e(TAG, e);
|
Log.e(TAG, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,16 +188,15 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
final int id = item.getItemId();
|
final int id = item.getItemId();
|
||||||
switch (id) {
|
if (id == android.R.id.home) {
|
||||||
case android.R.id.home:
|
|
||||||
goToReturnActivity();
|
goToReturnActivity();
|
||||||
break;
|
} else if (id == R.id.menu_item_share_error) {
|
||||||
case R.id.menu_item_share_error:
|
|
||||||
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
||||||
break;
|
} else {
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
||||||
new AlertDialog.Builder(context)
|
new AlertDialog.Builder(context)
|
||||||
|
@ -311,7 +259,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void goToReturnActivity() {
|
private void goToReturnActivity() {
|
||||||
final Class<? extends Activity> checkedReturnActivity = getReturnActivity(returnActivity);
|
final Class<? extends Activity> checkedReturnActivity = getReturnActivity(savedReturnActivity);
|
||||||
if (checkedReturnActivity == null) {
|
if (checkedReturnActivity == null) {
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
} else {
|
} else {
|
||||||
|
@ -355,7 +303,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
.value("version", BuildConfig.VERSION_NAME)
|
.value("version", BuildConfig.VERSION_NAME)
|
||||||
.value("os", getOsString())
|
.value("os", getOsString())
|
||||||
.value("time", currentTimeStamp)
|
.value("time", currentTimeStamp)
|
||||||
.array("exceptions", Arrays.asList(errorList))
|
.array("exceptions", Arrays.asList(errorInfo.getStackTraces()))
|
||||||
.value("user_comment", activityErrorBinding.errorCommentBox.getText()
|
.value("user_comment", activityErrorBinding.errorCommentBox.getText()
|
||||||
.toString())
|
.toString())
|
||||||
.end()
|
.end()
|
||||||
|
@ -393,27 +341,27 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
|
|
||||||
// Collapse all logs to a single paragraph when there are more than one
|
// Collapse all logs to a single paragraph when there are more than one
|
||||||
// to keep the GitHub issue clean.
|
// to keep the GitHub issue clean.
|
||||||
if (errorList.length > 1) {
|
if (errorInfo.getStackTraces().length > 1) {
|
||||||
htmlErrorReport
|
htmlErrorReport
|
||||||
.append("<details><summary><b>Exceptions (")
|
.append("<details><summary><b>Exceptions (")
|
||||||
.append(errorList.length)
|
.append(errorInfo.getStackTraces().length)
|
||||||
.append(")</b></summary><p>\n");
|
.append(")</b></summary><p>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the logs
|
// add the logs
|
||||||
for (int i = 0; i < errorList.length; i++) {
|
for (int i = 0; i < errorInfo.getStackTraces().length; i++) {
|
||||||
htmlErrorReport.append("<details><summary><b>Crash log ");
|
htmlErrorReport.append("<details><summary><b>Crash log ");
|
||||||
if (errorList.length > 1) {
|
if (errorInfo.getStackTraces().length > 1) {
|
||||||
htmlErrorReport.append(i + 1);
|
htmlErrorReport.append(i + 1);
|
||||||
}
|
}
|
||||||
htmlErrorReport.append("</b>")
|
htmlErrorReport.append("</b>")
|
||||||
.append("</summary><p>\n")
|
.append("</summary><p>\n")
|
||||||
.append("\n```\n").append(errorList[i]).append("\n```\n")
|
.append("\n```\n").append(errorInfo.getStackTraces()[i]).append("\n```\n")
|
||||||
.append("</details>\n");
|
.append("</details>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure to close everything
|
// make sure to close everything
|
||||||
if (errorList.length > 1) {
|
if (errorInfo.getStackTraces().length > 1) {
|
||||||
htmlErrorReport.append("</p></details>\n");
|
htmlErrorReport.append("</p></details>\n");
|
||||||
}
|
}
|
||||||
htmlErrorReport.append("<hr>\n");
|
htmlErrorReport.append("<hr>\n");
|
||||||
|
@ -466,11 +414,4 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
//super.onBackPressed();
|
//super.onBackPressed();
|
||||||
goToReturnActivity();
|
goToReturnActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrentTimeStamp() {
|
|
||||||
final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
|
||||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
||||||
return df.format(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,109 @@ package org.schabi.newpipe.error
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import kotlinx.android.parcel.Parcelize
|
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
|
@Parcelize
|
||||||
class ErrorInfo(
|
class ErrorInfo(
|
||||||
val userAction: UserAction?,
|
val stackTraces: Array<String>,
|
||||||
|
val userAction: UserAction,
|
||||||
val serviceName: String,
|
val serviceName: String,
|
||||||
val request: String,
|
val request: String,
|
||||||
@field:StringRes @param:StringRes val message: Int
|
val messageStringId: Int,
|
||||||
|
@Transient // no need to store throwable, all data for report is in other variables
|
||||||
|
var throwable: Throwable? = null
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
private constructor(
|
||||||
fun make(
|
throwable: Throwable,
|
||||||
userAction: UserAction?,
|
userAction: UserAction,
|
||||||
serviceName: String,
|
serviceName: String,
|
||||||
request: String,
|
request: String
|
||||||
@StringRes message: Int
|
) : this(
|
||||||
) = ErrorInfo(userAction, serviceName, request, message)
|
throwableToStringList(throwable),
|
||||||
|
userAction,
|
||||||
|
serviceName,
|
||||||
|
request,
|
||||||
|
getMessageStringId(throwable, userAction),
|
||||||
|
throwable
|
||||||
|
)
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
throwable: List<Throwable>,
|
||||||
|
userAction: UserAction,
|
||||||
|
serviceName: String,
|
||||||
|
request: String
|
||||||
|
) : this(
|
||||||
|
throwableListToStringList(throwable),
|
||||||
|
userAction,
|
||||||
|
serviceName,
|
||||||
|
request,
|
||||||
|
getMessageStringId(throwable.firstOrNull(), userAction),
|
||||||
|
throwable.firstOrNull()
|
||||||
|
)
|
||||||
|
|
||||||
|
// constructors with single throwable
|
||||||
|
constructor(throwable: Throwable, userAction: UserAction, request: String)
|
||||||
|
: this(throwable, userAction, SERVICE_NONE, request)
|
||||||
|
constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int)
|
||||||
|
: this(throwable, userAction, NewPipe.getNameOfService(serviceId), request)
|
||||||
|
constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?)
|
||||||
|
: this(throwable, userAction, getInfoServiceName(info), request)
|
||||||
|
|
||||||
|
// constructors with list of throwables
|
||||||
|
constructor(throwable: List<Throwable>, userAction: UserAction, request: String)
|
||||||
|
: this(throwable, userAction, SERVICE_NONE, request)
|
||||||
|
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, serviceId: Int)
|
||||||
|
: this(throwable, userAction, NewPipe.getNameOfService(serviceId), request)
|
||||||
|
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, info: Info?)
|
||||||
|
: this(throwable, userAction, getInfoServiceName(info), request)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SERVICE_NONE = "none"
|
||||||
|
|
||||||
|
private fun getStackTrace(throwable: Throwable): String {
|
||||||
|
StringWriter().use { stringWriter ->
|
||||||
|
PrintWriter(stringWriter, true).use { printWriter ->
|
||||||
|
throwable.printStackTrace(printWriter)
|
||||||
|
return stringWriter.buffer.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun throwableToStringList(throwable: Throwable) = arrayOf(getStackTrace(throwable))
|
||||||
|
|
||||||
|
fun throwableListToStringList(throwable: List<Throwable>) =
|
||||||
|
Array(throwable.size) { i -> getStackTrace(throwable[i]) }
|
||||||
|
|
||||||
|
private fun getInfoServiceName(info: Info?) =
|
||||||
|
if (info == null) SERVICE_NONE else NewPipe.getNameOfService(info.serviceId)
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private fun getMessageStringId(throwable: Throwable?,
|
||||||
|
action: UserAction): Int {
|
||||||
|
return when {
|
||||||
|
throwable is ContentNotAvailableException -> R.string.content_not_available
|
||||||
|
throwable != null && throwable.isNetworkRelated -> R.string.network_error
|
||||||
|
throwable is ContentNotSupportedException -> R.string.content_not_supported
|
||||||
|
throwable is DeobfuscateException -> R.string.youtube_signature_deobfuscation_error
|
||||||
|
throwable is ExtractionException -> R.string.parsing_error
|
||||||
|
action == UserAction.UI_ERROR -> R.string.app_ui_crash
|
||||||
|
action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments
|
||||||
|
action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed
|
||||||
|
action == UserAction.SUBSCRIPTION_UPDATE -> R.string.subscription_update_failed
|
||||||
|
action == UserAction.LOAD_IMAGE -> R.string.could_not_load_thumbnails
|
||||||
|
action == UserAction.DOWNLOAD_OPEN_DIALOG -> R.string.could_not_setup_download_menu
|
||||||
|
else -> R.string.general_error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,12 @@ package org.schabi.newpipe.error;
|
||||||
public enum UserAction {
|
public enum UserAction {
|
||||||
USER_REPORT("user report"),
|
USER_REPORT("user report"),
|
||||||
UI_ERROR("ui error"),
|
UI_ERROR("ui error"),
|
||||||
SUBSCRIPTION("subscription"),
|
SUBSCRIPTION_CHANGE("subscription change"),
|
||||||
|
SUBSCRIPTION_UPDATE("subscription update"),
|
||||||
|
SUBSCRIPTION_GET("get subscription"),
|
||||||
|
SUBSCRIPTION_IMPORT_EXPORT("subscription import or export"),
|
||||||
LOAD_IMAGE("load image"),
|
LOAD_IMAGE("load image"),
|
||||||
SOMETHING_ELSE("something"),
|
SOMETHING_ELSE("something else"),
|
||||||
SEARCHED("searched"),
|
SEARCHED("searched"),
|
||||||
GET_SUGGESTIONS("get suggestions"),
|
GET_SUGGESTIONS("get suggestions"),
|
||||||
REQUESTED_STREAM("requested stream"),
|
REQUESTED_STREAM("requested stream"),
|
||||||
|
@ -17,11 +20,15 @@ public enum UserAction {
|
||||||
REQUESTED_KIOSK("requested kiosk"),
|
REQUESTED_KIOSK("requested kiosk"),
|
||||||
REQUESTED_COMMENTS("requested comments"),
|
REQUESTED_COMMENTS("requested comments"),
|
||||||
REQUESTED_FEED("requested feed"),
|
REQUESTED_FEED("requested feed"),
|
||||||
|
REQUESTED_BOOKMARK("bookmark"),
|
||||||
DELETE_FROM_HISTORY("delete from history"),
|
DELETE_FROM_HISTORY("delete from history"),
|
||||||
PLAY_STREAM("Play stream"),
|
PLAY_STREAM("play stream"),
|
||||||
|
DOWNLOAD_OPEN_DIALOG("download open dialog"),
|
||||||
DOWNLOAD_POSTPROCESSING("download post-processing"),
|
DOWNLOAD_POSTPROCESSING("download post-processing"),
|
||||||
DOWNLOAD_FAILED("download failed"),
|
DOWNLOAD_FAILED("download failed"),
|
||||||
PREFERENCES_MIGRATION("migration of preferences");
|
PREFERENCES_MIGRATION("migration of preferences"),
|
||||||
|
SHARE_TO_NEWPIPE("share to newpipe"),
|
||||||
|
CHECK_FOR_NEW_APP_VERSION("check for new app version");
|
||||||
|
|
||||||
|
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
|
@ -1,48 +1,25 @@
|
||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
|
|
||||||
import com.jakewharton.rxbinding4.view.RxView;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
import org.schabi.newpipe.error.ErrorPanelHelper;
|
||||||
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.util.InfoCache;
|
import org.schabi.newpipe.util.InfoCache;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||||
|
|
||||||
|
@ -55,12 +32,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
private View emptyStateView;
|
private View emptyStateView;
|
||||||
@Nullable
|
@Nullable
|
||||||
private ProgressBar loadingProgressBar;
|
private ProgressBar loadingProgressBar;
|
||||||
|
private ErrorPanelHelper errorPanelHelper;
|
||||||
private Disposable errorDisposable;
|
|
||||||
|
|
||||||
protected View errorPanelRoot;
|
|
||||||
private Button errorButtonRetry;
|
|
||||||
private TextView errorTextView;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
||||||
|
@ -77,9 +49,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (errorDisposable != null) {
|
errorPanelHelper.dispose();
|
||||||
errorDisposable.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -89,22 +59,9 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
|
||||||
emptyStateView = rootView.findViewById(R.id.empty_state_view);
|
emptyStateView = rootView.findViewById(R.id.empty_state_view);
|
||||||
loadingProgressBar = rootView.findViewById(R.id.loading_progress_bar);
|
loadingProgressBar = rootView.findViewById(R.id.loading_progress_bar);
|
||||||
|
errorPanelHelper = new ErrorPanelHelper(this, rootView, this::onRetryButtonClicked);
|
||||||
errorPanelRoot = rootView.findViewById(R.id.error_panel);
|
|
||||||
errorButtonRetry = rootView.findViewById(R.id.error_button_retry);
|
|
||||||
errorTextView = rootView.findViewById(R.id.error_message_view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initListeners() {
|
|
||||||
super.initListeners();
|
|
||||||
errorDisposable = RxView.clicks(errorButtonRetry)
|
|
||||||
.debounce(300, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(o -> onRetryButtonClicked());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRetryButtonClicked() {
|
protected void onRetryButtonClicked() {
|
||||||
|
@ -143,7 +100,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
if (loadingProgressBar != null) {
|
if (loadingProgressBar != null) {
|
||||||
animate(loadingProgressBar, true, 400);
|
animate(loadingProgressBar, true, 400);
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 150);
|
errorPanelHelper.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -154,10 +111,9 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
if (loadingProgressBar != null) {
|
if (loadingProgressBar != null) {
|
||||||
animate(loadingProgressBar, false, 0);
|
animate(loadingProgressBar, false, 0);
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 150);
|
errorPanelHelper.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
if (emptyStateView != null) {
|
if (emptyStateView != null) {
|
||||||
|
@ -166,26 +122,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
if (loadingProgressBar != null) {
|
if (loadingProgressBar != null) {
|
||||||
animate(loadingProgressBar, false, 0);
|
animate(loadingProgressBar, false, 0);
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 150);
|
errorPanelHelper.hide();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "showError() called with: "
|
|
||||||
+ "message = [" + message + "], showRetryButton = [" + showRetryButton + "]");
|
|
||||||
}
|
|
||||||
isLoading.set(false);
|
|
||||||
InfoCache.getInstance().clearCache();
|
|
||||||
hideLoading();
|
|
||||||
|
|
||||||
errorTextView.setText(message);
|
|
||||||
if (showRetryButton) {
|
|
||||||
animate(errorButtonRetry, true, 600);
|
|
||||||
} else {
|
|
||||||
animate(errorButtonRetry, false, 0);
|
|
||||||
}
|
|
||||||
animate(errorPanelRoot, true, 300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -196,138 +133,70 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError() {
|
||||||
|
isLoading.set(false);
|
||||||
|
InfoCache.getInstance().clearCache();
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Error handling
|
// Error handling
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
/**
|
public final void showError(final ErrorInfo errorInfo) {
|
||||||
* Default implementation handles some general exceptions.
|
handleError();
|
||||||
*
|
|
||||||
* @param exception The exception that should be handled
|
|
||||||
* @return If the exception was handled
|
|
||||||
*/
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onError() called with: exception = [" + exception + "]");
|
|
||||||
}
|
|
||||||
isLoading.set(false);
|
|
||||||
|
|
||||||
if (isDetached() || isRemoving()) {
|
if (isDetached() || isRemoving()) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, "onError() is detached or removing = [" + exception + "]");
|
Log.w(TAG, "showError() is detached or removing = [" + errorInfo + "]");
|
||||||
}
|
}
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ExceptionUtils.isInterruptedCaused(exception)) {
|
errorPanelHelper.showError(errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void showTextError(final @NonNull String errorString) {
|
||||||
|
handleError();
|
||||||
|
|
||||||
|
if (isDetached() || isRemoving()) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
|
Log.w(TAG, "showTextError() is detached or removing = [" + errorString + "]");
|
||||||
}
|
}
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception instanceof ReCaptchaException) {
|
errorPanelHelper.showTextError(errorString);
|
||||||
onReCaptchaException((ReCaptchaException) exception);
|
|
||||||
return true;
|
|
||||||
} else if (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;
|
public final void hideErrorPanel() {
|
||||||
|
errorPanelHelper.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onReCaptchaException(final ReCaptchaException exception) {
|
public final boolean isErrorPanelVisible() {
|
||||||
if (DEBUG) {
|
return errorPanelHelper.isVisible();
|
||||||
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 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<Throwable> exception, final UserAction userAction,
|
|
||||||
final String serviceName, final String request,
|
|
||||||
@StringRes final int errorId) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, null,
|
|
||||||
ErrorInfo.make(userAction, serviceName == null ? "none" : serviceName,
|
|
||||||
request == null ? "none" : request, errorId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showSnackBarError(final Throwable exception, final UserAction userAction,
|
|
||||||
final String serviceName, final String request,
|
|
||||||
@StringRes final int errorId) {
|
|
||||||
showSnackBarError(Collections.singletonList(exception), userAction, serviceName, request,
|
|
||||||
errorId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a SnackBar and only call
|
* Show a SnackBar and only call
|
||||||
* {@link ErrorActivity#reportError(Context, List, Class, View, ErrorInfo)}
|
* {@link ErrorActivity#reportError(Context, Class, View, ErrorInfo)}
|
||||||
* IF we a find a valid view (otherwise the error screen appears).
|
* IF we a find a valid view (otherwise the error screen appears).
|
||||||
*
|
*
|
||||||
* @param exception List of the exceptions to show
|
* @param errorInfo The error information
|
||||||
* @param userAction The user action that caused the exception
|
|
||||||
* @param serviceName The service where the exception happened
|
|
||||||
* @param request The page that was requested
|
|
||||||
* @param errorId The ID of the error
|
|
||||||
*/
|
*/
|
||||||
public void showSnackBarError(final List<Throwable> exception, final UserAction userAction,
|
public void showSnackBarError(final ErrorInfo errorInfo) {
|
||||||
final String serviceName, final String request,
|
|
||||||
@StringRes final int errorId) {
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "showSnackBarError() called with: "
|
Log.d(TAG, "showSnackBarError() called with: errorInfo = [" + errorInfo + "]");
|
||||||
+ "exception = [" + exception + "], userAction = [" + userAction + "], "
|
|
||||||
+ "request = [" + request + "], errorId = [" + errorId + "]");
|
|
||||||
}
|
}
|
||||||
View rootView = activity != null ? activity.findViewById(android.R.id.content) : null;
|
View rootView = activity != null ? activity.findViewById(android.R.id.content) : null;
|
||||||
if (rootView == null && getView() != null) {
|
if (rootView == null) {
|
||||||
rootView = getView();
|
rootView = getView();
|
||||||
}
|
}
|
||||||
if (rootView == null) {
|
if (rootView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
ErrorActivity.reportError(requireContext(), MainActivity.class, rootView, errorInfo);
|
||||||
ErrorInfo.make(userAction, serviceName, request, errorId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import android.view.ViewGroup;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
|
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
|
||||||
|
@ -25,10 +24,8 @@ import com.google.android.material.tabs.TabLayout;
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.FragmentMainBinding;
|
import org.schabi.newpipe.databinding.FragmentMainBinding;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.tabs.Tab;
|
import org.schabi.newpipe.settings.tabs.Tab;
|
||||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
@ -128,7 +125,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
public void onCreateOptionsMenu(@NonNull final Menu menu,
|
||||||
|
@NonNull final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
Log.d(TAG, "onCreateOptionsMenu() called with: "
|
||||||
|
@ -144,13 +142,12 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.action_search) {
|
||||||
case R.id.action_search:
|
|
||||||
try {
|
try {
|
||||||
NavigationHelper.openSearchFragment(getFM(),
|
NavigationHelper.openSearchFragment(getFM(),
|
||||||
ServiceHelper.getSelectedServiceId(activity), "");
|
ServiceHelper.getSelectedServiceId(activity), "");
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError(getActivity(), null, "Opening search fragment", e);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -241,8 +238,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
ErrorActivity.reportError(context, throwable, null, null, ErrorInfo
|
ErrorActivity.reportUiError(context, null, "Getting fragment item", throwable);
|
||||||
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
|
||||||
return new BlankFragment();
|
return new BlankFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +250,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemPosition(final Object object) {
|
public int getItemPosition(@NonNull final Object object) {
|
||||||
// Causes adapter to reload all Fragments when
|
// Causes adapter to reload all Fragments when
|
||||||
// notifyDataSetChanged is called
|
// notifyDataSetChanged is called
|
||||||
return POSITION_NONE;
|
return POSITION_NONE;
|
||||||
|
|
|
@ -7,7 +7,7 @@ public interface ViewContract<I> {
|
||||||
|
|
||||||
void showEmptyState();
|
void showEmptyState();
|
||||||
|
|
||||||
void showError(String message, boolean showRetryButton);
|
|
||||||
|
|
||||||
void handleResult(I result);
|
void handleResult(I result);
|
||||||
|
|
||||||
|
void handleError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
@ -64,9 +63,7 @@ import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.Stream;
|
import org.schabi.newpipe.extractor.stream.Stream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
@ -526,7 +523,7 @@ public final class VideoDetailFragment
|
||||||
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
NavigationHelper.openChannelFragment(getFM(), currentInfo.getServiceId(),
|
||||||
subChannelUrl, subChannelName);
|
subChannelUrl, subChannelName);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,13 +681,12 @@ public final class VideoDetailFragment
|
||||||
binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
binding.detailThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
|
||||||
|
|
||||||
if (!isEmpty(info.getThumbnailUrl())) {
|
if (!isEmpty(info.getThumbnailUrl())) {
|
||||||
final String infoServiceName = NewPipe.getNameOfService(info.getServiceId());
|
|
||||||
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
|
final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onLoadingFailed(final String imageUri, final View view,
|
public void onLoadingFailed(final String imageUri, final View view,
|
||||||
final FailReason failReason) {
|
final FailReason failReason) {
|
||||||
showSnackBarError(failReason.getCause(), UserAction.LOAD_IMAGE,
|
showSnackBarError(new ErrorInfo(failReason.getCause(), UserAction.LOAD_IMAGE,
|
||||||
infoServiceName, imageUri, R.string.could_not_load_thumbnails);
|
imageUri, info));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -906,10 +902,8 @@ public final class VideoDetailFragment
|
||||||
openVideoPlayer();
|
openVideoPlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, throwable -> {
|
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
|
||||||
isLoading.set(false);
|
url == null ? "no url" : url, serviceId)));
|
||||||
onError(throwable);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1327,8 +1321,8 @@ public final class VideoDetailFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
public void handleError() {
|
||||||
super.showError(message, showRetryButton);
|
super.handleError();
|
||||||
setErrorImage(R.drawable.not_available_monkey);
|
setErrorImage(R.drawable.not_available_monkey);
|
||||||
|
|
||||||
if (binding.relatedStreamsLayout != null) { // hide related streams for tablets
|
if (binding.relatedStreamsLayout != null) { // hide related streams for tablets
|
||||||
|
@ -1341,8 +1335,8 @@ public final class VideoDetailFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideAgeRestrictedContent() {
|
private void hideAgeRestrictedContent() {
|
||||||
showError(getString(R.string.restricted_video,
|
showTextError(getString(R.string.restricted_video,
|
||||||
getString(R.string.show_age_restricted_content_title)), false);
|
getString(R.string.show_age_restricted_content_title)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupBroadcastReceiver() {
|
private void setupBroadcastReceiver() {
|
||||||
|
@ -1548,11 +1542,8 @@ public final class VideoDetailFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info.getErrors().isEmpty()) {
|
if (!info.getErrors().isEmpty()) {
|
||||||
showSnackBarError(info.getErrors(),
|
showSnackBarError(new ErrorInfo(info.getErrors(),
|
||||||
UserAction.REQUESTED_STREAM,
|
UserAction.REQUESTED_STREAM, info.getUrl(), info));
|
||||||
NewPipe.getNameOfService(info.getServiceId()),
|
|
||||||
info.getUrl(),
|
|
||||||
0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM
|
binding.detailControlsDownload.setVisibility(info.getStreamType() == StreamType.LIVE_STREAM
|
||||||
|
@ -1592,6 +1583,10 @@ public final class VideoDetailFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openDownloadDialog() {
|
public void openDownloadDialog() {
|
||||||
|
if (currentInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
|
final DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
|
||||||
downloadDialog.setVideoStreams(sortedVideoStreams);
|
downloadDialog.setVideoStreams(sortedVideoStreams);
|
||||||
|
@ -1601,18 +1596,10 @@ public final class VideoDetailFragment
|
||||||
|
|
||||||
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
final ErrorInfo info = ErrorInfo.make(UserAction.UI_ERROR,
|
ErrorActivity.reportError(activity, activity.getClass(),
|
||||||
ServiceList.all()
|
activity.findViewById(android.R.id.content),
|
||||||
.get(currentInfo
|
new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog",
|
||||||
.getServiceId())
|
currentInfo));
|
||||||
.getServiceInfo()
|
|
||||||
.getName(), "",
|
|
||||||
R.string.could_not_setup_download_menu);
|
|
||||||
|
|
||||||
ErrorActivity.reportError(activity,
|
|
||||||
e,
|
|
||||||
activity.getClass(),
|
|
||||||
activity.findViewById(android.R.id.content), info);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1620,24 +1607,6 @@ public final class VideoDetailFragment
|
||||||
// Stream Results
|
// Stream Results
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof YoutubeStreamExtractor.DeobfuscateException
|
|
||||||
? R.string.youtube_signature_deobfuscation_error
|
|
||||||
: exception instanceof ExtractionException
|
|
||||||
? R.string.parsing_error
|
|
||||||
: R.string.general_error;
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProgressInfo(@NonNull final StreamInfo info) {
|
private void updateProgressInfo(@NonNull final StreamInfo info) {
|
||||||
if (positionSubscriber != null) {
|
if (positionSubscriber != null) {
|
||||||
positionSubscriber.dispose();
|
positionSubscriber.dispose();
|
||||||
|
|
|
@ -14,7 +14,6 @@ import android.view.View;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -292,7 +291,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
selectedItem.getUrl(),
|
selectedItem.getUrl(),
|
||||||
selectedItem.getName());
|
selectedItem.getName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -307,7 +306,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
selectedItem.getUrl(),
|
selectedItem.getUrl(),
|
||||||
selectedItem.getName());
|
selectedItem.getName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError(getActivity(), null, "Opening playlist fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -412,13 +411,6 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
animate(itemsList, true, 300);
|
animate(itemsList, true, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
super.showError(message, showRetryButton);
|
|
||||||
showListFooter(false);
|
|
||||||
animate(itemsList, false, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
super.showEmptyState();
|
super.showEmptyState();
|
||||||
|
@ -439,6 +431,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError() {
|
||||||
|
super.handleError();
|
||||||
|
showListFooter(false);
|
||||||
|
animate(itemsList, false, 200);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||||
final String key) {
|
final String key) {
|
||||||
|
|
|
@ -7,12 +7,17 @@ import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.ListInfo;
|
import org.schabi.newpipe.extractor.ListInfo;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
@ -30,10 +35,15 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
@State
|
@State
|
||||||
protected String url;
|
protected String url;
|
||||||
|
|
||||||
|
private final UserAction errorUserAction;
|
||||||
protected I currentInfo;
|
protected I currentInfo;
|
||||||
protected Page currentNextPage;
|
protected Page currentNextPage;
|
||||||
protected Disposable currentWorker;
|
protected Disposable currentWorker;
|
||||||
|
|
||||||
|
protected BaseListInfoFragment(final UserAction errorUserAction) {
|
||||||
|
this.errorUserAction = errorUserAction;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
|
@ -133,7 +143,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
currentInfo = result;
|
currentInfo = result;
|
||||||
currentNextPage = result.getNextPage();
|
currentNextPage = result.getNextPage();
|
||||||
handleResult(result);
|
handleResult(result);
|
||||||
}, this::onError);
|
}, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, errorUserAction,
|
||||||
|
"Start loading: " + url, serviceId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,10 +173,9 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
handleNextItems(InfoItemsPage);
|
handleNextItems(InfoItemsPage);
|
||||||
}, (@NonNull Throwable throwable) -> {
|
}, (@NonNull Throwable throwable) ->
|
||||||
isLoading.set(false);
|
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(throwable,
|
||||||
onError(throwable);
|
errorUserAction, "Loading more items: " + url, serviceId)));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forbidDownwardFocusScroll() {
|
private void forbidDownwardFocusScroll() {
|
||||||
|
@ -182,10 +193,16 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
@Override
|
@Override
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
currentNextPage = result.getNextPage();
|
currentNextPage = result.getNextPage();
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
|
|
||||||
showListFooter(hasMoreItems());
|
showListFooter(hasMoreItems());
|
||||||
|
|
||||||
|
if (!result.getErrors().isEmpty()) {
|
||||||
|
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), errorUserAction,
|
||||||
|
"Get next items of: " + url, serviceId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -213,6 +230,18 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
showEmptyState();
|
showEmptyState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!result.getErrors().isEmpty()) {
|
||||||
|
final List<Throwable> errors = new ArrayList<>(result.getErrors());
|
||||||
|
// handling ContentNotSupportedException not to show the error but an appropriate string
|
||||||
|
// so that crashes won't be sent uselessly and the user will understand what happened
|
||||||
|
errors.removeIf(throwable -> throwable instanceof ContentNotSupportedException);
|
||||||
|
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(),
|
||||||
|
errorUserAction, "Start loading: " + url, serviceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -224,4 +253,14 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||||
this.url = u;
|
this.url = u;
|
||||||
this.name = !TextUtils.isEmpty(title) ? title : "";
|
this.name = !TextUtils.isEmpty(title) ? title : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dynamicallyShowErrorPanelOrSnackbar(final ErrorInfo errorInfo) {
|
||||||
|
if (infoListAdapter.getItemCount() == 0) {
|
||||||
|
// show error panel only if no items already visible
|
||||||
|
showError(errorInfo);
|
||||||
|
} else {
|
||||||
|
isLoading.set(false);
|
||||||
|
showSnackBarError(errorInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import android.widget.Button;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.viewbinding.ViewBinding;
|
import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
|
@ -27,20 +26,19 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
|
import org.schabi.newpipe.databinding.ChannelHeaderBinding;
|
||||||
import org.schabi.newpipe.databinding.FragmentChannelBinding;
|
import org.schabi.newpipe.databinding.FragmentChannelBinding;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
@ -91,6 +89,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChannelFragment() {
|
||||||
|
super(UserAction.REQUESTED_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
|
@ -217,9 +219,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
private void monitorSubscription(final ChannelInfo info) {
|
private void monitorSubscription(final ChannelInfo info) {
|
||||||
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
||||||
animate(headerBinding.channelSubscribeButton, false, 100);
|
animate(headerBinding.channelSubscribeButton, false, 100);
|
||||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
|
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_GET,
|
||||||
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
"Get subscription status", currentInfo));
|
||||||
"Get subscription status", 0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final Observable<List<SubscriptionEntity>> observable = subscriptionManager
|
final Observable<List<SubscriptionEntity>> observable = subscriptionManager
|
||||||
|
@ -269,11 +270,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
onUnrecoverableError(throwable,
|
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_UPDATE,
|
||||||
UserAction.SUBSCRIPTION,
|
"Updating subscription for " + info.getUrl(), info));
|
||||||
NewPipe.getNameOfService(info.getServiceId()),
|
|
||||||
"Updating Subscription for " + info.getUrl(),
|
|
||||||
R.string.subscription_update_failed);
|
|
||||||
|
|
||||||
disposables.add(subscriptionManager.updateChannelInfo(info)
|
disposables.add(subscriptionManager.updateChannelInfo(info)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
@ -290,11 +288,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
onUnrecoverableError(throwable,
|
showSnackBarError(new ErrorInfo(throwable, UserAction.SUBSCRIPTION_CHANGE,
|
||||||
UserAction.SUBSCRIPTION,
|
"Changing subscription for " + currentInfo.getUrl(), currentInfo));
|
||||||
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
|
||||||
"Subscription Change",
|
|
||||||
R.string.subscription_change_failed);
|
|
||||||
|
|
||||||
/* Emit clicks from main thread unto io thread */
|
/* Emit clicks from main thread unto io thread */
|
||||||
return RxView.clicks(subscribeButton)
|
return RxView.clicks(subscribeButton)
|
||||||
|
@ -408,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
currentInfo.getParentChannelUrl(),
|
currentInfo.getParentChannelUrl(),
|
||||||
currentInfo.getParentChannelName());
|
currentInfo.getParentChannelName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
} else if (DEBUG) {
|
} else if (DEBUG) {
|
||||||
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
||||||
|
@ -469,22 +464,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
|
|
||||||
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
|
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
final List<Throwable> errors = new ArrayList<>(result.getErrors());
|
for (final Throwable throwable : result.getErrors()) {
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
|
|
||||||
// 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) {
|
if (throwable instanceof ContentNotSupportedException) {
|
||||||
showContentNotSupported();
|
showContentNotSupported();
|
||||||
}
|
}
|
||||||
return throwable instanceof ContentNotSupportedException;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
showSnackBarError(errors, UserAction.REQUESTED_CHANNEL,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
|
@ -537,38 +520,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
currentInfo.getNextPage(), streamItems, index);
|
currentInfo.getNextPage(), streamItems, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_CHANNEL,
|
|
||||||
NewPipe.getNameOfService(serviceId),
|
|
||||||
"Get next page of: " + url,
|
|
||||||
R.string.general_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof ExtractionException
|
|
||||||
? R.string.parsing_error : R.string.general_error;
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -11,12 +11,11 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
import org.schabi.newpipe.ktx.ViewUtils;
|
import org.schabi.newpipe.ktx.ViewUtils;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
@ -32,6 +31,10 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommentsFragment() {
|
||||||
|
super(UserAction.REQUESTED_COMMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -67,52 +70,13 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showLoading() {
|
|
||||||
super.showLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull final CommentsInfo result) {
|
public void handleResult(@NonNull final CommentsInfo result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
ViewUtils.slideUp(requireView(), 120, 150, 0.06f);
|
ViewUtils.slideUp(requireView(), 120, 150, 0.06f);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS,
|
|
||||||
NewPipe.getNameOfService(serviceId), "Get next page of: " + url,
|
|
||||||
R.string.general_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideLoading();
|
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_COMMENTS,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, R.string.error_unable_to_load_comments);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments.list.kiosk;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||||
|
@ -10,6 +11,7 @@ import org.schabi.newpipe.util.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
|
||||||
public class DefaultKioskFragment extends KioskFragment {
|
public class DefaultKioskFragment extends KioskFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -46,8 +48,8 @@ public class DefaultKioskFragment extends KioskFragment {
|
||||||
currentInfo = null;
|
currentInfo = null;
|
||||||
currentNextPage = null;
|
currentNextPage = null;
|
||||||
} catch (final ExtractionException e) {
|
} catch (final ExtractionException e) {
|
||||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
showError(new ErrorInfo(e, UserAction.REQUESTED_KIOSK,
|
||||||
"Loading default kiosk from selected service", 0);
|
"Loading default kiosk for selected service"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
|
@ -82,6 +83,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KioskFragment() {
|
||||||
|
super(UserAction.REQUESTED_KIOSK);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -102,9 +107,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
try {
|
try {
|
||||||
setTitle(kioskTranslatedName);
|
setTitle(kioskTranslatedName);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onUnrecoverableError(e, UserAction.UI_ERROR,
|
showSnackBarError(new ErrorInfo(e, UserAction.UI_ERROR, "Setting kiosk title"));
|
||||||
"none",
|
|
||||||
"none", R.string.app_ui_crash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,22 +172,5 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
|
|
||||||
name = kioskTranslatedName;
|
name = kioskTranslatedName;
|
||||||
setTitle(kioskTranslatedName);
|
setTitle(kioskTranslatedName);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_KIOSK,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId),
|
|
||||||
"Get next page of: " + url, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.viewbinding.ViewBinding;
|
import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
|
@ -25,11 +24,12 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
|
import org.schabi.newpipe.databinding.PlaylistHeaderBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
|
||||||
import org.schabi.newpipe.extractor.ServiceList;
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
@ -40,8 +40,6 @@ import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
|
@ -87,6 +85,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlaylistFragment() {
|
||||||
|
super(UserAction.REQUESTED_PLAYLIST);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -284,7 +286,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
NavigationHelper.openChannelFragment(getFM(), result.getServiceId(),
|
||||||
result.getUploaderUrl(), result.getUploaderName());
|
result.getUploaderUrl(), result.getUploaderName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
ErrorActivity.reportUiError(getActivity(), null, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -315,8 +317,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
.localizeStreamCount(getContext(), result.getStreamCount()));
|
.localizeStreamCount(getContext(), result.getStreamCount()));
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
result.getUrl(), result));
|
||||||
}
|
}
|
||||||
|
|
||||||
remotePlaylistManager.getPlaylist(result)
|
remotePlaylistManager.getPlaylist(result)
|
||||||
|
@ -363,33 +365,6 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,
|
|
||||||
NewPipe.getNameOfService(serviceId), "Get next page of: " + url, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof ExtractionException
|
|
||||||
? R.string.parsing_error : R.string.general_error;
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -434,8 +409,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable t) {
|
public void onError(final Throwable throwable) {
|
||||||
PlaylistFragment.this.onError(t);
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Get playlist bookmarks"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -460,12 +436,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
if (currentInfo != null && playlistEntity == null) {
|
if (currentInfo != null && playlistEntity == null) {
|
||||||
action = remotePlaylistManager.onBookmark(currentInfo)
|
action = remotePlaylistManager.onBookmark(currentInfo)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
|
.subscribe(ignored -> { /* Do nothing */ }, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Adding playlist bookmark")));
|
||||||
} else if (playlistEntity != null) {
|
} else if (playlistEntity != null) {
|
||||||
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
|
action = remotePlaylistManager.deletePlaylist(playlistEntity.getUid())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doFinally(() -> playlistEntity = null)
|
.doFinally(() -> playlistEntity = null)
|
||||||
.subscribe(ignored -> { /* Do nothing */ }, this::onError);
|
.subscribe(ignored -> { /* Do nothing */ }, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Deleting playlist bookmark")));
|
||||||
} else {
|
} else {
|
||||||
action = Disposable.empty();
|
action = Disposable.empty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,6 @@ import org.schabi.newpipe.extractor.MetaInfo;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.Page;
|
import org.schabi.newpipe.extractor.Page;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
|
||||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||||
import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
|
import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
|
||||||
|
@ -258,11 +257,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
try {
|
try {
|
||||||
service = NewPipe.getService(serviceId);
|
service = NewPipe.getService(serviceId);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportError(getActivity(), e, requireActivity().getClass(),
|
ErrorActivity.reportUiError(getActivity(),
|
||||||
requireActivity().findViewById(android.R.id.content),
|
requireActivity().findViewById(android.R.id.content),
|
||||||
ErrorInfo.make(UserAction.UI_ERROR,
|
"Getting service for id " + serviceId, e);
|
||||||
"",
|
|
||||||
"", R.string.general_error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(searchString)) {
|
if (!TextUtils.isEmpty(searchString)) {
|
||||||
|
@ -413,7 +410,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
searchEditText.setText("");
|
searchEditText.setText("");
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 200);
|
hideErrorPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,7 +537,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||||
}
|
}
|
||||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
if (isSuggestionsEnabled && !isErrorPanelVisible()) {
|
||||||
showSuggestionsPanel();
|
showSuggestionsPanel();
|
||||||
}
|
}
|
||||||
if (DeviceUtils.isTv(getContext())) {
|
if (DeviceUtils.isTv(getContext())) {
|
||||||
|
@ -553,8 +550,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
Log.d(TAG, "onFocusChange() called with: "
|
Log.d(TAG, "onFocusChange() called with: "
|
||||||
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
||||||
}
|
}
|
||||||
if (isSuggestionsEnabled && hasFocus
|
if (isSuggestionsEnabled && hasFocus && !isErrorPanelVisible()) {
|
||||||
&& errorPanelRoot.getVisibility() != View.VISIBLE) {
|
|
||||||
showSuggestionsPanel();
|
showSuggestionsPanel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -704,9 +700,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
.subscribe(
|
.subscribe(
|
||||||
howManyDeleted -> suggestionPublisher
|
howManyDeleted -> suggestionPublisher
|
||||||
.onNext(searchEditText.getText().toString()),
|
.onNext(searchEditText.getText().toString()),
|
||||||
throwable -> showSnackBarError(throwable,
|
throwable -> showSnackBarError(new ErrorInfo(throwable,
|
||||||
UserAction.DELETE_FROM_HISTORY, "none",
|
UserAction.DELETE_FROM_HISTORY,
|
||||||
"Deleting item failed", R.string.general_error));
|
"Deleting item failed")));
|
||||||
disposables.add(onDelete);
|
disposables.add(onDelete);
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
|
@ -763,8 +759,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
.suggestionsFor(serviceId, query)
|
.suggestionsFor(serviceId, query)
|
||||||
.onErrorReturn(throwable -> {
|
.onErrorReturn(throwable -> {
|
||||||
if (!ExceptionUtils.isNetworkRelated(throwable)) {
|
if (!ExceptionUtils.isNetworkRelated(throwable)) {
|
||||||
showSnackBarError(throwable, UserAction.GET_SUGGESTIONS,
|
showSnackBarError(new ErrorInfo(throwable,
|
||||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
UserAction.GET_SUGGESTIONS, searchString, serviceId));
|
||||||
}
|
}
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
})
|
})
|
||||||
|
@ -800,7 +796,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
if (listNotification.isOnNext()) {
|
if (listNotification.isOnNext()) {
|
||||||
handleSuggestions(listNotification.getValue());
|
handleSuggestions(listNotification.getValue());
|
||||||
} else if (listNotification.isOnError()) {
|
} else if (listNotification.isOnError()) {
|
||||||
onSuggestionError(listNotification.getError());
|
showError(new ErrorInfo(listNotification.getError(),
|
||||||
|
UserAction.GET_SUGGESTIONS, searchString, serviceId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -832,8 +829,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
.subscribe(intent -> {
|
.subscribe(intent -> {
|
||||||
getFM().popBackStackImmediate();
|
getFM().popBackStackImmediate();
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}, throwable ->
|
}, throwable -> showTextError(getString(R.string.unsupported_url))));
|
||||||
showError(getString(R.string.unsupported_url), false)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (final Exception ignored) {
|
} catch (final Exception ignored) {
|
||||||
|
@ -849,10 +845,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
|
disposables.add(historyRecordManager.onSearched(serviceId, theSearchString)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
ignored -> {
|
ignored -> {},
|
||||||
},
|
throwable -> showSnackBarError(new ErrorInfo(throwable, UserAction.SEARCHED,
|
||||||
error -> showSnackBarError(error, UserAction.SEARCHED,
|
theSearchString, serviceId))
|
||||||
NewPipe.getNameOfService(serviceId), theSearchString, 0)
|
|
||||||
));
|
));
|
||||||
suggestionPublisher.onNext(theSearchString);
|
suggestionPublisher.onNext(theSearchString);
|
||||||
startLoading(false);
|
startLoading(false);
|
||||||
|
@ -872,7 +867,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
||||||
.subscribe(this::handleResult, this::onError);
|
.subscribe(this::handleResult, this::onItemError);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -895,7 +890,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||||
.subscribe(this::handleNextItems, this::onError);
|
.subscribe(this::handleNextItems, this::onItemError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -909,6 +904,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onItemError(final Throwable exception) {
|
||||||
|
if (exception instanceof SearchExtractor.NothingFoundException) {
|
||||||
|
infoListAdapter.clearStreamItemList();
|
||||||
|
showEmptyState();
|
||||||
|
} else {
|
||||||
|
showError(new ErrorInfo(exception, UserAction.SEARCHED, searchString, serviceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -945,26 +949,11 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
searchBinding.suggestionsList.smoothScrollToPosition(0);
|
searchBinding.suggestionsList.smoothScrollToPosition(0);
|
||||||
searchBinding.suggestionsList.post(() -> suggestionListAdapter.setItems(suggestions));
|
searchBinding.suggestionsList.post(() -> suggestionListAdapter.setItems(suggestions));
|
||||||
|
|
||||||
if (suggestionsPanelVisible && errorPanelRoot.getVisibility() == View.VISIBLE) {
|
if (suggestionsPanelVisible && isErrorPanelVisible()) {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSuggestionError(final Throwable exception) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
|
|
||||||
}
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int errorId = exception instanceof ParsingException
|
|
||||||
? R.string.parsing_error
|
|
||||||
: R.string.general_error;
|
|
||||||
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
|
|
||||||
NewPipe.getNameOfService(serviceId), searchString, errorId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -975,13 +964,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
super.showError(message, showRetryButton);
|
|
||||||
hideSuggestionsPanel();
|
|
||||||
hideKeyboardSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Search Results
|
// Search Results
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -992,8 +974,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
if (!exceptions.isEmpty()
|
if (!exceptions.isEmpty()
|
||||||
&& !(exceptions.size() == 1
|
&& !(exceptions.size() == 1
|
||||||
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
|
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
searchString, serviceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
searchSuggestion = result.getSearchSuggestion();
|
searchSuggestion = result.getSearchSuggestion();
|
||||||
|
@ -1061,33 +1043,20 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
nextPage = result.getNextPage();
|
nextPage = result.getNextPage();
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId),
|
|
||||||
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||||
+ "pageIds: " + nextPage.getIds() + ", "
|
+ "pageIds: " + nextPage.getIds() + ", "
|
||||||
+ "pageCookies: " + nextPage.getCookies(), 0);
|
+ "pageCookies: " + nextPage.getCookies(),
|
||||||
|
serviceId));
|
||||||
}
|
}
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(final Throwable exception) {
|
public void handleError() {
|
||||||
if (super.onError(exception)) {
|
super.handleError();
|
||||||
return true;
|
hideSuggestionsPanel();
|
||||||
}
|
hideKeyboardSearch();
|
||||||
|
|
||||||
if (exception instanceof SearchExtractor.NothingFoundException) {
|
|
||||||
infoListAdapter.clearStreamItemList();
|
|
||||||
showEmptyState();
|
|
||||||
} else {
|
|
||||||
final int errorId = exception instanceof ParsingException
|
|
||||||
? R.string.parsing_error
|
|
||||||
: R.string.general_error;
|
|
||||||
onUnrecoverableError(exception, UserAction.SEARCHED,
|
|
||||||
NewPipe.getNameOfService(serviceId), searchString, errorId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1113,9 +1082,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
.subscribe(
|
.subscribe(
|
||||||
howManyDeleted -> suggestionPublisher
|
howManyDeleted -> suggestionPublisher
|
||||||
.onNext(searchEditText.getText().toString()),
|
.onNext(searchEditText.getText().toString()),
|
||||||
throwable -> showSnackBarError(throwable,
|
throwable -> showSnackBarError(new ErrorInfo(throwable,
|
||||||
UserAction.DELETE_FROM_HISTORY, "none",
|
UserAction.DELETE_FROM_HISTORY, "Deleting item failed")));
|
||||||
"Deleting item failed", R.string.general_error));
|
|
||||||
disposables.add(onDelete);
|
disposables.add(onDelete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RelatedVideosFragment() {
|
||||||
|
super(UserAction.REQUESTED_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -125,43 +129,9 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
}
|
}
|
||||||
ViewUtils.slideUp(requireView(), 120, 96, 0.06f);
|
ViewUtils.slideUp(requireView(), 120, 96, 0.06f);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
|
||||||
super.handleNextItems(result);
|
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
|
||||||
showSnackBarError(result.getErrors(),
|
|
||||||
UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(serviceId),
|
|
||||||
"Get next page of: " + url,
|
|
||||||
R.string.general_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// OnError
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideLoading();
|
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_STREAM,
|
|
||||||
NewPipe.getNameOfService(serviceId), url, R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -171,15 +171,15 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
||||||
|
try {
|
||||||
NavigationHelper.openChannelFragment(
|
NavigationHelper.openChannelFragment(
|
||||||
activity.getSupportFragmentManager(),
|
activity.getSupportFragmentManager(),
|
||||||
item.getServiceId(),
|
item.getServiceId(),
|
||||||
item.getUploaderUrl(),
|
item.getUploaderUrl(),
|
||||||
item.getUploaderName());
|
item.getUploaderName());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
ErrorActivity.reportUiError(activity, null, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,19 +202,6 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(final String message, final boolean showRetryButton) {
|
|
||||||
super.showError(message, showRetryButton);
|
|
||||||
showListFooter(false);
|
|
||||||
|
|
||||||
if (itemsList != null) {
|
|
||||||
animate(itemsList, false, 200);
|
|
||||||
}
|
|
||||||
if (headerRootBinding != null) {
|
|
||||||
animate(headerRootBinding.getRoot(), false, 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
super.showEmptyState();
|
super.showEmptyState();
|
||||||
|
@ -249,9 +236,18 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onError(final Throwable exception) {
|
public void handleError() {
|
||||||
|
super.handleError();
|
||||||
resetFragment();
|
resetFragment();
|
||||||
return super.onError(exception);
|
|
||||||
|
showListFooter(false);
|
||||||
|
|
||||||
|
if (itemsList != null) {
|
||||||
|
animate(itemsList, false, 200);
|
||||||
|
}
|
||||||
|
if (headerRootBinding != null) {
|
||||||
|
animate(headerRootBinding.getRoot(), false, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.schabi.newpipe.database.LocalItem;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||||
|
@ -206,7 +207,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
BookmarkFragment.this.onError(exception);
|
showError(new ErrorInfo(exception,
|
||||||
|
UserAction.REQUESTED_BOOKMARK, "Loading playlists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -237,17 +239,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
// Fragment Error Handling
|
// Fragment Error Handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
|
||||||
"none", "Bookmark", R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void resetFragment() {
|
protected void resetFragment() {
|
||||||
super.resetFragment();
|
super.resetFragment();
|
||||||
|
@ -295,8 +286,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
.setPositiveButton(R.string.delete, (dialog, i) ->
|
.setPositiveButton(R.string.delete, (dialog, i) ->
|
||||||
disposables.add(deleteReactor
|
disposables.add(deleteReactor
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> { /*Do nothing on success*/ }, this::onError))
|
.subscribe(ignored -> { /*Do nothing on success*/ }, throwable ->
|
||||||
)
|
showError(new ErrorInfo(throwable,
|
||||||
|
UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Deleting playlist")))))
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
@ -314,7 +307,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
localPlaylistManager.renamePlaylist(id, name);
|
localPlaylistManager.renamePlaylist(id, name);
|
||||||
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
|
final Disposable disposable = localPlaylistManager.renamePlaylist(id, name)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
|
.subscribe(longs -> { /*Do nothing on success*/ }, throwable -> showError(
|
||||||
|
new ErrorInfo(throwable,
|
||||||
|
UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Changing playlist name")));
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import icepick.State
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.databinding.FragmentFeedBinding
|
import org.schabi.newpipe.databinding.FragmentFeedBinding
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo
|
||||||
import org.schabi.newpipe.error.UserAction
|
import org.schabi.newpipe.error.UserAction
|
||||||
import org.schabi.newpipe.fragments.list.BaseListFragment
|
import org.schabi.newpipe.fragments.list.BaseListFragment
|
||||||
import org.schabi.newpipe.ktx.animate
|
import org.schabi.newpipe.ktx.animate
|
||||||
|
@ -48,7 +49,6 @@ import java.util.Calendar
|
||||||
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
private var _feedBinding: FragmentFeedBinding? = null
|
private var _feedBinding: FragmentFeedBinding? = null
|
||||||
private val feedBinding get() = _feedBinding!!
|
private val feedBinding get() = _feedBinding!!
|
||||||
private val errorBinding get() = _feedBinding!!.errorPanel
|
|
||||||
|
|
||||||
private lateinit var viewModel: FeedViewModel
|
private lateinit var viewModel: FeedViewModel
|
||||||
@State
|
@State
|
||||||
|
@ -171,50 +171,24 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun showLoading() {
|
override fun showLoading() {
|
||||||
|
super.showLoading()
|
||||||
feedBinding.refreshRootView.animate(false, 0)
|
feedBinding.refreshRootView.animate(false, 0)
|
||||||
feedBinding.itemsList.animate(false, 0)
|
feedBinding.itemsList.animate(false, 0)
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(true, 200)
|
|
||||||
feedBinding.loadingProgressText.animate(true, 200)
|
feedBinding.loadingProgressText.animate(true, 200)
|
||||||
|
|
||||||
feedBinding.emptyStateView.root.animate(false, 0)
|
|
||||||
errorBinding.root.animate(false, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideLoading() {
|
override fun hideLoading() {
|
||||||
|
super.hideLoading()
|
||||||
feedBinding.refreshRootView.animate(true, 200)
|
feedBinding.refreshRootView.animate(true, 200)
|
||||||
feedBinding.itemsList.animate(true, 300)
|
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(false, 0)
|
|
||||||
feedBinding.loadingProgressText.animate(false, 0)
|
feedBinding.loadingProgressText.animate(false, 0)
|
||||||
|
|
||||||
feedBinding.emptyStateView.root.animate(false, 0)
|
|
||||||
errorBinding.root.animate(false, 0)
|
|
||||||
feedBinding.swiperefresh.isRefreshing = false
|
feedBinding.swiperefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmptyState() {
|
override fun showEmptyState() {
|
||||||
|
super.showEmptyState()
|
||||||
feedBinding.refreshRootView.animate(true, 200)
|
feedBinding.refreshRootView.animate(true, 200)
|
||||||
feedBinding.itemsList.animate(false, 0)
|
feedBinding.itemsList.animate(false, 0)
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(false, 0)
|
|
||||||
feedBinding.loadingProgressText.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) {
|
override fun handleResult(result: FeedState) {
|
||||||
|
@ -227,6 +201,14 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
updateRefreshViewState()
|
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) {
|
private fun handleProgressState(progressState: FeedState.ProgressState) {
|
||||||
showLoading()
|
showLoading()
|
||||||
|
|
||||||
|
@ -266,13 +248,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadedState.itemsErrors.isNotEmpty()) {
|
|
||||||
showSnackBarError(
|
|
||||||
loadedState.itemsErrors, UserAction.REQUESTED_FEED,
|
|
||||||
"none", "Loading feed", R.string.general_error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadedState.items.isEmpty()) {
|
if (loadedState.items.isEmpty()) {
|
||||||
showEmptyState()
|
showEmptyState()
|
||||||
} else {
|
} else {
|
||||||
|
@ -281,12 +256,13 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
||||||
|
return if (errorState.error == null) {
|
||||||
hideLoading()
|
hideLoading()
|
||||||
errorState.error?.let {
|
false
|
||||||
onError(errorState.error)
|
} else {
|
||||||
return true
|
showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed"))
|
||||||
|
true
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRelativeTimeViews() {
|
private fun updateRelativeTimeViews() {
|
||||||
|
@ -320,18 +296,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
listState = null
|
listState = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(exception: Throwable): Boolean {
|
|
||||||
if (super.onError(exception)) return true
|
|
||||||
|
|
||||||
if (useAsFrontPage) {
|
|
||||||
showSnackBarError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.REQUESTED_FEED, "none", "Loading Feed", 0)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KEY_GROUP_ID = "ARG_GROUP_ID"
|
const val KEY_GROUP_ID = "ARG_GROUP_ID"
|
||||||
const val KEY_GROUP_NAME = "ARG_GROUP_NAME"
|
const val KEY_GROUP_NAME = "ARG_GROUP_NAME"
|
||||||
|
|
|
@ -14,7 +14,6 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.viewbinding.ViewBinding;
|
import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
@ -27,6 +26,8 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
|
import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
|
@ -34,10 +35,7 @@ import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
import org.schabi.newpipe.settings.HistorySettingsFragment;
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
@ -49,6 +47,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
@ -163,47 +162,10 @@ public class StatisticsPlaylistFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.action_history_clear) {
|
||||||
case R.id.action_history_clear:
|
HistorySettingsFragment
|
||||||
new AlertDialog.Builder(activity)
|
.openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
|
||||||
.setTitle(R.string.delete_view_history_alert)
|
} else {
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
|
||||||
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
howManyDeleted -> Toast.makeText(getContext(),
|
|
||||||
R.string.watch_history_deleted,
|
|
||||||
Toast.LENGTH_SHORT).show(),
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete view history",
|
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
howManyDeleted -> {
|
|
||||||
},
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete search history",
|
|
||||||
R.string.general_error)));
|
|
||||||
disposables.add(onClearOrphans);
|
|
||||||
disposables.add(onDelete);
|
|
||||||
}))
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -228,7 +190,7 @@ public class StatisticsPlaylistFragment
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
|
itemsListState = Objects.requireNonNull(itemsList.getLayoutManager()).onSaveInstanceState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -287,7 +249,8 @@ public class StatisticsPlaylistFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
StatisticsPlaylistFragment.this.onError(exception);
|
showError(
|
||||||
|
new ErrorInfo(exception, UserAction.SOMETHING_ELSE, "History Statistics"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -313,7 +276,7 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
itemListAdapter.addItems(processResult(result));
|
itemListAdapter.addItems(processResult(result));
|
||||||
if (itemsListState != null) {
|
if (itemsListState != null && itemsList.getLayoutManager() != null) {
|
||||||
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
|
||||||
itemsListState = null;
|
itemsListState = null;
|
||||||
}
|
}
|
||||||
|
@ -341,17 +304,6 @@ public class StatisticsPlaylistFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
|
||||||
"none", "History Statistics", R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -439,9 +391,8 @@ public class StatisticsPlaylistFragment
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
throwable -> showSnackBarError(throwable,
|
throwable -> showSnackBarError(new ErrorInfo(throwable,
|
||||||
UserAction.DELETE_FROM_HISTORY, "none",
|
UserAction.DELETE_FROM_HISTORY, "Deleting item")));
|
||||||
"Deleting item failed", R.string.general_error));
|
|
||||||
|
|
||||||
disposables.add(onDelete);
|
disposables.add(onDelete);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
|
||||||
import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding;
|
import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding;
|
||||||
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
import org.schabi.newpipe.databinding.PlaylistControlBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
|
@ -110,7 +111,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
|
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
|
||||||
debouncedSaveSignal = PublishSubject.create();
|
debouncedSaveSignal = PublishSubject.create();
|
||||||
|
|
||||||
disposables = new CompositeDisposable();
|
disposables = new CompositeDisposable();
|
||||||
|
@ -334,7 +335,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(final Throwable exception) {
|
||||||
LocalPlaylistFragment.this.onError(exception);
|
showError(new ErrorInfo(exception, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Loading local playlist"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -344,8 +346,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.menu_item_remove_watched) {
|
||||||
case R.id.menu_item_remove_watched:
|
|
||||||
if (!isRemovingWatched) {
|
if (!isRemovingWatched) {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setMessage(R.string.remove_watched_popup_warning)
|
.setMessage(R.string.remove_watched_popup_warning)
|
||||||
|
@ -360,8 +361,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
.create()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
break;
|
} else {
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -455,7 +455,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
|
|
||||||
hideLoading();
|
hideLoading();
|
||||||
isRemovingWatched = false;
|
isRemovingWatched = false;
|
||||||
}, this::onError));
|
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Removing watched videos, partially watched=" + removePartiallyWatched))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -511,17 +512,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onError(final Throwable exception) {
|
|
||||||
if (super.onError(exception)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE,
|
|
||||||
"none", "Local Playlist", R.string.general_error);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Playlist Metadata/Streams Manipulation
|
// Playlist Metadata/Streams Manipulation
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -562,7 +552,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
|
|
||||||
final Disposable disposable = playlistManager.renamePlaylist(playlistId, title)
|
final Disposable disposable = playlistManager.renamePlaylist(playlistId, title)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(longs -> { /*Do nothing on success*/ }, this::onError);
|
.subscribe(longs -> { /*Do nothing on success*/ }, throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Renaming playlist")));
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,7 +575,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
final Disposable disposable = playlistManager
|
final Disposable disposable = playlistManager
|
||||||
.changePlaylistThumbnail(playlistId, thumbnailUrl)
|
.changePlaylistThumbnail(playlistId, thumbnailUrl)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignore -> successToast.show(), this::onError);
|
.subscribe(ignore -> successToast.show(), throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
|
||||||
|
"Changing playlist thumbnail")));
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,7 +626,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
return debouncedSaveSignal
|
return debouncedSaveSignal
|
||||||
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(ignored -> saveImmediate(), this::onError);
|
.subscribe(ignored -> saveImmediate(), throwable ->
|
||||||
|
showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
|
||||||
|
"Debounced saver")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveImmediate() {
|
private void saveImmediate() {
|
||||||
|
@ -669,7 +665,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
isModified.set(false);
|
isModified.set(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this::onError
|
throwable -> showError(new ErrorInfo(throwable,
|
||||||
|
UserAction.REQUESTED_BOOKMARK, "Saving playlist"))
|
||||||
);
|
);
|
||||||
disposables.add(disposable);
|
disposables.add(disposable);
|
||||||
}
|
}
|
||||||
|
@ -683,7 +680,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
return new ItemTouchHelper.SimpleCallback(directions,
|
return new ItemTouchHelper.SimpleCallback(directions,
|
||||||
ItemTouchHelper.ACTION_STATE_IDLE) {
|
ItemTouchHelper.ACTION_STATE_IDLE) {
|
||||||
@Override
|
@Override
|
||||||
public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView,
|
public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView,
|
||||||
final int viewSize,
|
final int viewSize,
|
||||||
final int viewSizeOutOfBounds,
|
final int viewSizeOutOfBounds,
|
||||||
final int totalSize,
|
final int totalSize,
|
||||||
|
@ -696,9 +693,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMove(final RecyclerView recyclerView,
|
public boolean onMove(@NonNull final RecyclerView recyclerView,
|
||||||
final RecyclerView.ViewHolder source,
|
@NonNull final RecyclerView.ViewHolder source,
|
||||||
final RecyclerView.ViewHolder target) {
|
@NonNull final RecyclerView.ViewHolder target) {
|
||||||
if (source.getItemViewType() != target.getItemViewType()
|
if (source.getItemViewType() != target.getItemViewType()
|
||||||
|| itemListAdapter == null) {
|
|| itemListAdapter == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -724,7 +721,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) { }
|
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
|
||||||
|
final int swipeDir) {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.databinding.DialogTitleBinding
|
import org.schabi.newpipe.databinding.DialogTitleBinding
|
||||||
import org.schabi.newpipe.databinding.FeedItemCarouselBinding
|
import org.schabi.newpipe.databinding.FeedItemCarouselBinding
|
||||||
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
|
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo
|
||||||
import org.schabi.newpipe.error.UserAction
|
import org.schabi.newpipe.error.UserAction
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||||
import org.schabi.newpipe.fragments.BaseStateFragment
|
import org.schabi.newpipe.fragments.BaseStateFragment
|
||||||
|
@ -288,8 +289,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
binding.itemsList.adapter = groupAdapter
|
binding.itemsList.adapter = groupAdapter
|
||||||
|
|
||||||
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
|
viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java)
|
||||||
viewModel.stateLiveData.observe(viewLifecycleOwner, { it?.let(this::handleResult) })
|
viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) }
|
||||||
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner, { it?.let(this::handleFeedGroups) })
|
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) { it?.let(this::handleFeedGroups) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
|
private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
|
||||||
|
@ -381,7 +382,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SubscriptionState.ErrorState -> {
|
is SubscriptionState.ErrorState -> {
|
||||||
result.error?.let { onError(result.error) }
|
result.error?.let {
|
||||||
|
showError(ErrorInfo(result.error, UserAction.SOMETHING_ELSE, "Subscriptions"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,17 +415,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
binding.itemsList.animate(true, 200)
|
binding.itemsList.animate(true, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
// Fragment Error Handling
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
override fun onError(exception: Throwable): Boolean {
|
|
||||||
if (super.onError(exception)) return true
|
|
||||||
|
|
||||||
onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Subscriptions", R.string.general_error)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Grid Mode
|
// Grid Mode
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -84,10 +84,12 @@ public class SubscriptionsImportFragment extends BaseFragment {
|
||||||
|
|
||||||
setupServiceVariables();
|
setupServiceVariables();
|
||||||
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
|
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
|
||||||
ErrorActivity.reportError(activity, Collections.emptyList(), null, null,
|
ErrorActivity.reportError(activity, null, null,
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE,
|
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
|
||||||
NewPipe.getNameOfService(currentServiceId),
|
NewPipe.getNameOfService(currentServiceId),
|
||||||
"Service don't support importing", R.string.general_error));
|
"Service does not support importing subscriptions",
|
||||||
|
R.string.general_error,
|
||||||
|
null));
|
||||||
activity.finish();
|
activity.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,6 @@ import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@ -152,13 +151,10 @@ public abstract class BaseImportExportService extends Service {
|
||||||
postErrorResult(null, null);
|
postErrorResult(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void stopAndReportError(@Nullable final Throwable error, final String request) {
|
protected void stopAndReportError(final Throwable throwable, final String request) {
|
||||||
stopService();
|
stopService();
|
||||||
|
ErrorActivity.reportError(this, null, null, new ErrorInfo(
|
||||||
final ErrorInfo errorInfo = ErrorInfo
|
throwable, UserAction.SUBSCRIPTION_IMPORT_EXPORT, request));
|
||||||
.make(UserAction.SUBSCRIPTION, "unknown", request, R.string.general_error);
|
|
||||||
ErrorActivity.reportError(this, error != null ? Collections.singletonList(error)
|
|
||||||
: Collections.emptyList(), null, null, errorInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void postErrorResult(final String title, final String text) {
|
protected void postErrorResult(final String title, final String text) {
|
||||||
|
|
|
@ -21,13 +21,11 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import org.schabi.newpipe.DownloaderImpl;
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
import org.schabi.newpipe.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.FilePickerActivityHelper;
|
||||||
import org.schabi.newpipe.util.ZipHelper;
|
import org.schabi.newpipe.util.ZipHelper;
|
||||||
|
|
||||||
|
@ -198,7 +196,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
|
|
||||||
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT).show();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onError(e);
|
ErrorActivity.reportUiError(getActivity(), null, "Exporting database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,20 +241,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onError(e);
|
ErrorActivity.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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
@ -46,120 +48,103 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
||||||
public boolean onPreferenceTreeClick(final Preference preference) {
|
public boolean onPreferenceTreeClick(final Preference preference) {
|
||||||
if (preference.getKey().equals(cacheWipeKey)) {
|
if (preference.getKey().equals(cacheWipeKey)) {
|
||||||
InfoCache.getInstance().clearCache();
|
InfoCache.getInstance().clearCache();
|
||||||
Toast.makeText(preference.getContext(), R.string.metadata_cache_wipe_complete_notice,
|
Toast.makeText(requireContext(),
|
||||||
Toast.LENGTH_SHORT).show();
|
R.string.metadata_cache_wipe_complete_notice, Toast.LENGTH_SHORT).show();
|
||||||
|
} else if (preference.getKey().equals(viewsHistoryClearKey)) {
|
||||||
|
openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
|
||||||
|
} else if (preference.getKey().equals(playbackStatesClearKey)) {
|
||||||
|
openDeletePlaybackStatesDialog(requireContext(), recordManager, disposables);
|
||||||
|
} else if (preference.getKey().equals(searchHistoryClearKey)) {
|
||||||
|
openDeleteSearchHistoryDialog(requireContext(), recordManager, disposables);
|
||||||
|
} else {
|
||||||
|
return super.onPreferenceTreeClick(preference);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preference.getKey().equals(viewsHistoryClearKey)) {
|
private static Disposable getDeletePlaybackStatesDisposable(
|
||||||
new AlertDialog.Builder(getActivity())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
.setTitle(R.string.delete_view_history_alert)
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
||||||
final Disposable onDeletePlaybackStates
|
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager));
|
||||||
= recordManager.deleteCompleteStreamStateHistory()
|
disposables.add(getWholeStreamHistoryDisposable(context, recordManager));
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
disposables.add(getRemoveOrphanedRecordsDisposable(context, recordManager));
|
||||||
.subscribe(
|
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
|
||||||
R.string.watch_history_states_deleted,
|
|
||||||
Toast.LENGTH_SHORT).show(),
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete playback states",
|
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
|
||||||
R.string.watch_history_deleted,
|
|
||||||
Toast.LENGTH_SHORT).show(),
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete view history",
|
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
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()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preference.getKey().equals(playbackStatesClearKey)) {
|
public static void openDeletePlaybackStatesDialog(@NonNull final Context context,
|
||||||
new AlertDialog.Builder(getActivity())
|
final HistoryRecordManager recordManager,
|
||||||
|
final CompositeDisposable disposables) {
|
||||||
|
new AlertDialog.Builder(context)
|
||||||
.setTitle(R.string.delete_playback_states_alert)
|
.setTitle(R.string.delete_playback_states_alert)
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||||
|
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager))))
|
||||||
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)));
|
|
||||||
|
|
||||||
disposables.add(onDeletePlaybackStates);
|
|
||||||
}))
|
|
||||||
.create()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preference.getKey().equals(searchHistoryClearKey)) {
|
public static void openDeleteSearchHistoryDialog(@NonNull final Context context,
|
||||||
new AlertDialog.Builder(getActivity())
|
final HistoryRecordManager recordManager,
|
||||||
|
final CompositeDisposable disposables) {
|
||||||
|
new AlertDialog.Builder(context)
|
||||||
.setTitle(R.string.delete_search_history_alert)
|
.setTitle(R.string.delete_search_history_alert)
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||||
final Disposable onDelete = recordManager.deleteCompleteSearchHistory()
|
disposables.add(getDeleteSearchHistoryDisposable(context, recordManager))))
|
||||||
.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()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onPreferenceTreeClick(preference);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -20,10 +19,8 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -108,7 +105,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||||
emptyView.setVisibility(View.GONE);
|
emptyView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
|
||||||
final SubscriptionManager subscriptionManager = new SubscriptionManager(getContext());
|
final SubscriptionManager subscriptionManager = new SubscriptionManager(requireContext());
|
||||||
subscriptionManager.subscriptions().toObservable()
|
subscriptionManager.subscriptions().toObservable()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -122,7 +119,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel(final DialogInterface dialogInterface) {
|
public void onCancel(@NonNull final DialogInterface dialogInterface) {
|
||||||
super.onCancel(dialogInterface);
|
super.onCancel(dialogInterface);
|
||||||
if (onCancelListener != null) {
|
if (onCancelListener != null) {
|
||||||
onCancelListener.onCancel();
|
onCancelListener.onCancel();
|
||||||
|
@ -156,16 +153,16 @@ public class SelectChannelFragment extends DialogFragment {
|
||||||
private Observer<List<SubscriptionEntity>> getSubscriptionObserver() {
|
private Observer<List<SubscriptionEntity>> getSubscriptionObserver() {
|
||||||
return new Observer<List<SubscriptionEntity>>() {
|
return new Observer<List<SubscriptionEntity>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(final Disposable d) { }
|
public void onSubscribe(@NonNull final Disposable disposable) { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(final List<SubscriptionEntity> newSubscriptions) {
|
public void onNext(@NonNull final List<SubscriptionEntity> newSubscriptions) {
|
||||||
displayChannels(newSubscriptions);
|
displayChannels(newSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(final Throwable exception) {
|
public void onError(@NonNull final Throwable exception) {
|
||||||
SelectChannelFragment.this.onError(exception);
|
ErrorActivity.reportUiError(requireContext(), null, "Loading subscription", exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// Interfaces
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -197,6 +184,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||||
|
|
||||||
private class SelectChannelAdapter
|
private class SelectChannelAdapter
|
||||||
extends RecyclerView.Adapter<SelectChannelAdapter.SelectChannelItemHolder> {
|
extends RecyclerView.Adapter<SelectChannelAdapter.SelectChannelItemHolder> {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SelectChannelItemHolder onCreateViewHolder(final ViewGroup parent,
|
public SelectChannelItemHolder onCreateViewHolder(final ViewGroup parent,
|
||||||
final int viewType) {
|
final int viewType) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -19,8 +18,6 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
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.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
@ -83,7 +80,7 @@ public class SelectKioskFragment extends DialogFragment {
|
||||||
try {
|
try {
|
||||||
selectKioskAdapter = new SelectKioskAdapter();
|
selectKioskAdapter = new SelectKioskAdapter();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
onError(e);
|
ErrorActivity.reportUiError(getActivity(), null, "Selecting kiosk", e);
|
||||||
}
|
}
|
||||||
recyclerView.setAdapter(selectKioskAdapter);
|
recyclerView.setAdapter(selectKioskAdapter);
|
||||||
|
|
||||||
|
@ -109,16 +106,6 @@ public class SelectKioskFragment extends DialogFragment {
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Error
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void onError(final Throwable e) {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo
|
|
||||||
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Interfaces
|
// Interfaces
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -115,8 +115,8 @@ public class SelectPlaylistFragment extends DialogFragment {
|
||||||
|
|
||||||
protected void onError(final Throwable e) {
|
protected void onError(final Throwable e) {
|
||||||
final Activity activity = requireActivity();
|
final Activity activity = requireActivity();
|
||||||
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo
|
ErrorActivity.reportError(activity, activity.getClass(), null, new ErrorInfo(e,
|
||||||
.make(UserAction.UI_ERROR, "none", "load_playlists", R.string.app_ui_crash));
|
UserAction.UI_ERROR, "Loading playlists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -95,15 +95,13 @@ public final class SettingMigrations {
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
// save the version with the last successful migration and report the error
|
// save the version with the last successful migration and report the error
|
||||||
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
|
sp.edit().putInt(lastPrefVersionKey, currentVersion).apply();
|
||||||
final ErrorInfo errorInfo = ErrorInfo.make(
|
ErrorActivity.reportError(context, SettingMigrations.class, null, new ErrorInfo(
|
||||||
|
e,
|
||||||
UserAction.PREFERENCES_MIGRATION,
|
UserAction.PREFERENCES_MIGRATION,
|
||||||
"none",
|
|
||||||
"Migrating preferences from version " + lastPrefVersion + " to "
|
"Migrating preferences from version " + lastPrefVersion + " to "
|
||||||
+ VERSION + ". "
|
+ VERSION + ". "
|
||||||
+ "Error at " + currentVersion + " => " + ++currentVersion,
|
+ "Error at " + currentVersion + " => " + ++currentVersion
|
||||||
0
|
));
|
||||||
);
|
|
||||||
ErrorActivity.reportError(context, e, SettingMigrations.class, null, errorInfo);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,10 +183,9 @@ public class ChooseTabsFragment extends Fragment {
|
||||||
final Tab.Type type = typeFrom(tabId);
|
final Tab.Type type = typeFrom(tabId);
|
||||||
|
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
ErrorActivity.reportError(requireContext(),
|
ErrorActivity.reportError(requireContext(), null, null,
|
||||||
new IllegalStateException("Tab id not found: " + tabId), null, null,
|
new ErrorInfo(new IllegalStateException("Tab id not found: " + tabId),
|
||||||
ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
UserAction.SOMETHING_ELSE, "Choosing tabs on settings"));
|
||||||
"Choosing tabs on settings", 0));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -483,9 +483,8 @@ public abstract class Tab {
|
||||||
final StreamingService service = NewPipe.getService(kioskServiceId);
|
final StreamingService service = NewPipe.getService(kioskServiceId);
|
||||||
kioskId = service.getKioskList().getDefaultKioskId();
|
kioskId = service.getKioskList().getDefaultKioskId();
|
||||||
} catch (final ExtractionException e) {
|
} catch (final ExtractionException e) {
|
||||||
ErrorActivity.reportError(context, e, null, null,
|
ErrorActivity.reportError(context, null, null, new ErrorInfo(e,
|
||||||
ErrorInfo.make(UserAction.REQUESTED_KIOSK, "none",
|
UserAction.REQUESTED_KIOSK, "Loading default kiosk for selected service"));
|
||||||
"Loading default kiosk from selected service", 0));
|
|
||||||
}
|
}
|
||||||
return kioskId;
|
return kioskId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,9 @@
|
||||||
package org.schabi.newpipe.util;
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
|
@ -33,10 +30,6 @@ import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.extractor.Info;
|
import org.schabi.newpipe.extractor.Info;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
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.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
import org.schabi.newpipe.extractor.comments.CommentsInfo;
|
||||||
import org.schabi.newpipe.extractor.exceptions.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.FeedExtractor;
|
||||||
import org.schabi.newpipe.extractor.feed.FeedInfo;
|
import org.schabi.newpipe.extractor.feed.FeedInfo;
|
||||||
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -280,65 +261,6 @@ public final class ExtractorHelper {
|
||||||
return null != loadFromCache(serviceId, url, infoType).blockingGet();
|
return null != loadFromCache(serviceId, url, infoType).blockingGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple and general error handler that show a Toast for known exceptions,
|
|
||||||
* and for others, opens the report error activity with the (optional) error message.
|
|
||||||
*
|
|
||||||
* @param context Android app context
|
|
||||||
* @param serviceId the service the exception happened in
|
|
||||||
* @param url the URL where the exception happened
|
|
||||||
* @param exception the exception to be handled
|
|
||||||
* @param userAction the action of the user that caused the exception
|
|
||||||
* @param optionalErrorMessage the optional error message
|
|
||||||
*/
|
|
||||||
public static void handleGeneralException(final Context context, final int serviceId,
|
|
||||||
final String url, final Throwable exception,
|
|
||||||
final UserAction userAction,
|
|
||||||
final String optionalErrorMessage) {
|
|
||||||
final Handler handler = new Handler(context.getMainLooper());
|
|
||||||
|
|
||||||
handler.post(() -> {
|
|
||||||
if (exception instanceof ReCaptchaException) {
|
|
||||||
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
|
|
||||||
// Starting ReCaptcha Challenge Activity
|
|
||||||
final Intent intent = new Intent(context, ReCaptchaActivity.class);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(intent);
|
|
||||||
} else if (ExceptionUtils.isNetworkRelated(exception)) {
|
|
||||||
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
|
|
||||||
} else if (exception instanceof 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,
|
* Formats the text contained in the meta info list as HTML and puts it into the text view,
|
||||||
* while also making the separator visible. If the list is null or empty, or the user chose not
|
* while also making the separator visible. If the list is null or empty, or the user chose not
|
||||||
|
|
|
@ -583,16 +583,12 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
|
||||||
try {
|
try {
|
||||||
service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName();
|
service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
service = "-";
|
service = ErrorInfo.SERVICE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorActivity.reportError(
|
ErrorActivity.reportError(mContext, null, null,
|
||||||
mContext,
|
new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action,
|
||||||
mission.errObject,
|
service, request.toString(), reason, null));
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ErrorInfo.make(action, service, request.toString(), reason)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearFinishedDownloads(boolean delete) {
|
public void clearFinishedDownloads(boolean delete) {
|
||||||
|
|
|
@ -219,7 +219,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detail_title_root_layout"
|
android:layout_below="@id/detail_title_root_layout"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
@ -17,11 +17,24 @@
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="Network error" />
|
tools:text="Network error" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/error_button_action"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/error_snackbar_action"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:theme="@style/ServiceColoredButton" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/error_button_retry"
|
android:id="@+id/error_button_retry"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:text="@string/retry"
|
android:text="@string/retry"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
|
|
@ -52,11 +52,11 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginTop="50dp"
|
android:layout_marginTop="16dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/items_list"
|
android:layout_below="@id/items_list"
|
||||||
|
|
|
@ -206,7 +206,7 @@
|
||||||
<!--ERROR PANEL-->
|
<!--ERROR PANEL-->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/error_panel"
|
android:id="@+id/error_panel"
|
||||||
layout="@layout/error_retry"
|
layout="@layout/error_panel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/detail_title_root_layout"
|
android:layout_below="@id/detail_title_root_layout"
|
||||||
|
|
|
@ -372,6 +372,7 @@
|
||||||
<string name="title_activity_recaptcha">reCAPTCHA challenge</string>
|
<string name="title_activity_recaptcha">reCAPTCHA challenge</string>
|
||||||
<string name="subtitle_activity_recaptcha">Press \"Done\" when solved</string>
|
<string name="subtitle_activity_recaptcha">Press \"Done\" when solved</string>
|
||||||
<string name="recaptcha_request_toast">reCAPTCHA challenge requested</string>
|
<string name="recaptcha_request_toast">reCAPTCHA challenge requested</string>
|
||||||
|
<string name="recaptcha_solve">Solve</string>
|
||||||
<string name="recaptcha_done_button">Done</string>
|
<string name="recaptcha_done_button">Done</string>
|
||||||
<!-- Downloads -->
|
<!-- Downloads -->
|
||||||
<string name="settings_category_downloads_title">Download</string>
|
<string name="settings_category_downloads_title">Download</string>
|
||||||
|
|
Loading…
Reference in New Issue