Merge pull request #5148 from Stypox/error-panel
Show improved error panel instead of annoying snackbar or crashing
This commit is contained in:
commit
404a6c12a6
|
@ -0,0 +1,46 @@
|
||||||
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented tests for {@link ErrorInfo}.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class ErrorInfoTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void errorInfoTestParcelable() {
|
||||||
|
final ErrorInfo info = new ErrorInfo(new ParsingException("Hello"),
|
||||||
|
UserAction.USER_REPORT, "request", ServiceList.YouTube.getServiceId());
|
||||||
|
// Obtain a Parcel object and write the parcelable object to it:
|
||||||
|
final Parcel parcel = Parcel.obtain();
|
||||||
|
info.writeToParcel(parcel, 0);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
final ErrorInfo infoFromParcel = (ErrorInfo) ErrorInfo.CREATOR.createFromParcel(parcel);
|
||||||
|
|
||||||
|
assertTrue(Arrays.toString(infoFromParcel.getStackTraces())
|
||||||
|
.contains(ErrorInfoTest.class.getSimpleName()));
|
||||||
|
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
|
||||||
|
assertEquals(ServiceList.YouTube.getServiceInfo().getName(),
|
||||||
|
infoFromParcel.getServiceName());
|
||||||
|
assertEquals("request", infoFromParcel.getRequest());
|
||||||
|
assertEquals(R.string.parsing_error, infoFromParcel.getMessageStringId());
|
||||||
|
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
package org.schabi.newpipe.report;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented tests for {@link ErrorInfo}.
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@LargeTest
|
|
||||||
public class ErrorInfoTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void errorInfoTestParcelable() {
|
|
||||||
final ErrorInfo info = ErrorInfo.make(UserAction.USER_REPORT, "youtube", "request",
|
|
||||||
R.string.general_error);
|
|
||||||
// Obtain a Parcel object and write the parcelable object to it:
|
|
||||||
final Parcel parcel = Parcel.obtain();
|
|
||||||
info.writeToParcel(parcel, 0);
|
|
||||||
parcel.setDataPosition(0);
|
|
||||||
final ErrorInfo infoFromParcel = ErrorInfo.CREATOR.createFromParcel(parcel);
|
|
||||||
|
|
||||||
assertEquals(UserAction.USER_REPORT, infoFromParcel.getUserAction());
|
|
||||||
assertEquals("youtube", infoFromParcel.getServiceName());
|
|
||||||
assertEquals("request", infoFromParcel.getRequest());
|
|
||||||
assertEquals(R.string.general_error, infoFromParcel.getMessage());
|
|
||||||
|
|
||||||
parcel.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -85,7 +85,7 @@
|
||||||
android:name=".ExitActivity"
|
android:name=".ExitActivity"
|
||||||
android:label="@string/general_error"
|
android:label="@string/general_error"
|
||||||
android:theme="@android:style/Theme.NoDisplay" />
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity android:name=".report.ErrorActivity" />
|
<activity android:name=".error.ErrorActivity" />
|
||||||
|
|
||||||
<!-- giga get related -->
|
<!-- giga get related -->
|
||||||
<activity
|
<activity
|
||||||
|
@ -106,7 +106,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ReCaptchaActivity"
|
android:name=".error.ReCaptchaActivity"
|
||||||
android:label="@string/recaptcha" />
|
android:label="@string/recaptcha" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,12 +20,13 @@ import org.acra.ACRA;
|
||||||
import org.acra.config.ACRAConfigurationException;
|
import org.acra.config.ACRAConfigurationException;
|
||||||
import org.acra.config.CoreConfiguration;
|
import org.acra.config.CoreConfiguration;
|
||||||
import org.acra.config.CoreConfigurationBuilder;
|
import org.acra.config.CoreConfigurationBuilder;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.SettingsActivity;
|
import org.schabi.newpipe.settings.SettingsActivity;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
@ -224,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, 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,19 +10,22 @@ import android.content.pm.Signature;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.rxjava3.core.Maybe;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
@ -31,9 +34,11 @@ import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import io.reactivex.rxjava3.core.Maybe;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
public final class CheckForNewAppVersion {
|
public final class CheckForNewAppVersion {
|
||||||
private CheckForNewAppVersion() { }
|
private CheckForNewAppVersion() { }
|
||||||
|
@ -58,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, 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 "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,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, 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 "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,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, 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 "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.extractor.downloader.Request;
|
import org.schabi.newpipe.extractor.downloader.Request;
|
||||||
import org.schabi.newpipe.extractor.downloader.Response;
|
import org.schabi.newpipe.extractor.downloader.Response;
|
||||||
|
|
|
@ -60,6 +60,7 @@ import org.schabi.newpipe.databinding.DrawerHeaderBinding;
|
||||||
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
|
import org.schabi.newpipe.databinding.DrawerLayoutBinding;
|
||||||
import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
|
import org.schabi.newpipe.databinding.InstanceSpinnerLayoutBinding;
|
||||||
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
|
import org.schabi.newpipe.databinding.ToolbarLayoutBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
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.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
@ -72,7 +73,6 @@ import org.schabi.newpipe.player.Player;
|
||||||
import org.schabi.newpipe.player.event.OnKeyDownListener;
|
import org.schabi.newpipe.player.event.OnKeyDownListener;
|
||||||
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.report.ErrorActivity;
|
|
||||||
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.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
|
@ -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.reportUiErrorInSnackbar(this, "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.reportUiErrorInSnackbar(this, "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.reportUiErrorInSnackbar(this, "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.reportUiErrorInSnackbar(this, "Setting up service toggle", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
final SharedPreferences sharedPreferences
|
final SharedPreferences sharedPreferences
|
||||||
|
@ -679,19 +679,16 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
|
||||||
}
|
}
|
||||||
final int id = item.getItemId();
|
|
||||||
|
|
||||||
switch (id) {
|
if (item.getItemId() == android.R.id.home) {
|
||||||
case android.R.id.home:
|
onHomeButtonPressed();
|
||||||
onHomeButtonPressed();
|
return true;
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -799,7 +796,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.reportUiErrorInSnackbar(this, "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.report.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);
|
||||||
}
|
}
|
||||||
|
@ -145,37 +150,79 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
private void handleUrl(final String url) {
|
private void handleUrl(final String url) {
|
||||||
disposables.add(Observable
|
disposables.add(Observable
|
||||||
.fromCallable(() -> {
|
.fromCallable(() -> {
|
||||||
if (currentServiceId == -1) {
|
try {
|
||||||
currentService = NewPipe.getServiceByUrl(url);
|
if (currentServiceId == -1) {
|
||||||
currentServiceId = currentService.getServiceId();
|
currentService = NewPipe.getServiceByUrl(url);
|
||||||
currentLinkType = currentService.getLinkTypeByUrl(url);
|
currentServiceId = currentService.getServiceId();
|
||||||
currentUrl = url;
|
currentLinkType = currentService.getLinkTypeByUrl(url);
|
||||||
} else {
|
currentUrl = url;
|
||||||
currentService = NewPipe.getService(currentServiceId);
|
} else {
|
||||||
}
|
currentService = NewPipe.getService(currentServiceId);
|
||||||
|
}
|
||||||
|
|
||||||
return currentLinkType != LinkType.NONE;
|
// return whether the url was found to be supported or not
|
||||||
|
return currentLinkType != LinkType.NONE;
|
||||||
|
} catch (final ExtractionException e) {
|
||||||
|
// this can be reached only when the url is completely unsupported
|
||||||
|
return false;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(result -> {
|
.subscribe(isUrlSupported -> {
|
||||||
if (result) {
|
if (isUrlSupported) {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} else {
|
} else {
|
||||||
showUnsupportedUrlDialog(url);
|
showUnsupportedUrlDialog(url);
|
||||||
}
|
}
|
||||||
}, throwable -> handleError(throwable, url)));
|
}, throwable -> handleError(this, new ErrorInfo(throwable,
|
||||||
|
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleError(final Throwable throwable, final String url) {
|
/**
|
||||||
throwable.printStackTrace();
|
* @param context the context. It will be {@code finish()}ed at the end of the handling if it is
|
||||||
|
* an instance of {@link RouterActivity}.
|
||||||
|
* @param errorInfo the error information
|
||||||
|
*/
|
||||||
|
private static void handleError(final Context context, final ErrorInfo errorInfo) {
|
||||||
|
if (errorInfo.getThrowable() != null) {
|
||||||
|
errorInfo.getThrowable().printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
if (throwable instanceof ExtractionException) {
|
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 {
|
} else {
|
||||||
ExtractorHelper.handleGeneralException(this, -1, url, throwable,
|
ErrorActivity.reportError(context, errorInfo);
|
||||||
UserAction.SOMETHING_ELSE, null);
|
}
|
||||||
finish();
|
|
||||||
|
if (context instanceof RouterActivity) {
|
||||||
|
((RouterActivity) context).finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +547,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, "Starting info activity: " + currentUrl)))
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -580,6 +628,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 +695,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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
import org.schabi.newpipe.database.stream.model.StreamStateEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import kotlin.jvm.Throws
|
|
||||||
|
|
||||||
data class PlaylistStreamEntry(
|
data class PlaylistStreamEntry(
|
||||||
@Embedded
|
@Embedded
|
||||||
|
|
|
@ -37,6 +37,9 @@ import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.RouterActivity;
|
import org.schabi.newpipe.RouterActivity;
|
||||||
import org.schabi.newpipe.databinding.DownloadDialogBinding;
|
import org.schabi.newpipe.databinding.DownloadDialogBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
|
@ -45,9 +48,6 @@ import org.schabi.newpipe.extractor.stream.Stream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||||
import org.schabi.newpipe.util.FilenameUtils;
|
import org.schabi.newpipe.util.FilenameUtils;
|
||||||
|
@ -61,7 +61,6 @@ import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@ -591,17 +590,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 +693,8 @@ public class DownloadDialog extends DialogFragment
|
||||||
mainStorage.getTag());
|
mainStorage.getTag());
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
showErrorActivity(e);
|
ErrorActivity.reportErrorInSnackbar(this,
|
||||||
|
new ErrorInfo(e, UserAction.DOWNLOAD_FAILED, "Getting storage"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.content.Context;
|
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, 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
@ -8,7 +8,6 @@ import android.graphics.Color;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -18,14 +17,11 @@ import android.view.View;
|
||||||
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.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.app.NavUtils;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
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 +30,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,108 +61,77 @@ 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 = "Exception in ";
|
||||||
= "Exception in NewPipe " + BuildConfig.VERSION_NAME;
|
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
|
|
||||||
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 reportError(final Context context, final ErrorInfo errorInfo) {
|
||||||
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UserAction.UI_ERROR,
|
final Intent intent = new Intent(context, ErrorActivity.class);
|
||||||
"none", "", R.string.app_ui_crash));
|
intent.putExtra(ERROR_INFO, errorInfo);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reportError(final Context context, final List<Throwable> el,
|
public static void reportErrorInSnackbar(final Context context, final ErrorInfo errorInfo) {
|
||||||
final Class returnActivity, final View rootView,
|
final View rootView = context instanceof Activity
|
||||||
final ErrorInfo errorInfo) {
|
? ((Activity) context).findViewById(android.R.id.content) : null;
|
||||||
|
reportErrorInSnackbar(context, rootView, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reportErrorInSnackbar(final Fragment fragment, final ErrorInfo errorInfo) {
|
||||||
|
View rootView = fragment.getView();
|
||||||
|
if (rootView == null && fragment.getActivity() != null) {
|
||||||
|
rootView = fragment.getActivity().findViewById(android.R.id.content);
|
||||||
|
}
|
||||||
|
reportErrorInSnackbar(fragment.requireContext(), rootView, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reportUiErrorInSnackbar(final Context context,
|
||||||
|
final String request,
|
||||||
|
final Throwable throwable) {
|
||||||
|
reportErrorInSnackbar(context, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reportUiErrorInSnackbar(final Fragment fragment,
|
||||||
|
final String request,
|
||||||
|
final Throwable throwable) {
|
||||||
|
reportErrorInSnackbar(fragment, new ErrorInfo(throwable, UserAction.UI_ERROR, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private static void reportErrorInSnackbar(final Context context,
|
||||||
|
@Nullable final View rootView,
|
||||||
|
final ErrorInfo errorInfo) {
|
||||||
if (rootView != null) {
|
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();
|
reportError(context, errorInfo)).show();
|
||||||
} else {
|
} else {
|
||||||
startErrorActivity(returnActivity, context, errorInfo, el);
|
reportError(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,
|
// Activity lifecycle
|
||||||
final ErrorInfo errorInfo) {
|
////////////////////////////////////////////////////////////////////////
|
||||||
List<Throwable> el = null;
|
|
||||||
if (e != null) {
|
|
||||||
el = new Vector<>();
|
|
||||||
el.add(e);
|
|
||||||
}
|
|
||||||
reportError(context, el, returnActivity, rootView, errorInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// async call
|
|
||||||
public static void reportError(final Handler handler, final Context context,
|
|
||||||
final Throwable e, final Class returnActivity,
|
|
||||||
final View rootView, final ErrorInfo errorInfo) {
|
|
||||||
|
|
||||||
List<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 String[] el = {report.getString(ReportField.STACK_TRACE)};
|
|
||||||
|
|
||||||
final Intent intent = new Intent(context, ErrorActivity.class);
|
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
|
||||||
intent.putExtra(ERROR_LIST, el);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getStackTrace(final Throwable throwable) {
|
|
||||||
final StringWriter sw = new StringWriter();
|
|
||||||
final PrintWriter pw = new PrintWriter(sw, true);
|
|
||||||
throwable.printStackTrace(pw);
|
|
||||||
return sw.getBuffer().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorList to StringList
|
|
||||||
private static String[] elToSl(final List<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) {
|
||||||
|
@ -193,38 +153,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,15 +189,14 @@ 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:
|
onBackPressed();
|
||||||
goToReturnActivity();
|
} else if (id == R.id.menu_item_share_error) {
|
||||||
break;
|
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
||||||
case R.id.menu_item_share_error:
|
} else {
|
||||||
ShareUtils.shareText(this, getString(R.string.error_report_title), buildJson());
|
return false;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
||||||
|
@ -264,7 +213,9 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
final Intent i = new Intent(Intent.ACTION_SENDTO)
|
final Intent i = new Intent(Intent.ACTION_SENDTO)
|
||||||
.setData(Uri.parse("mailto:")) // only email apps should handle this
|
.setData(Uri.parse("mailto:")) // only email apps should handle this
|
||||||
.putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS})
|
.putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS})
|
||||||
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
|
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT
|
||||||
|
+ getString(R.string.app_name) + " "
|
||||||
|
+ BuildConfig.VERSION_NAME)
|
||||||
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
||||||
if (i.resolveActivity(getPackageManager()) != null) {
|
if (i.resolveActivity(getPackageManager()) != null) {
|
||||||
ShareUtils.openIntentInApp(context, i);
|
ShareUtils.openIntentInApp(context, i);
|
||||||
|
@ -310,17 +261,6 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
return checkedReturnActivity;
|
return checkedReturnActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void goToReturnActivity() {
|
|
||||||
final Class<? extends Activity> checkedReturnActivity = getReturnActivity(returnActivity);
|
|
||||||
if (checkedReturnActivity == null) {
|
|
||||||
super.onBackPressed();
|
|
||||||
} else {
|
|
||||||
final Intent intent = new Intent(this, checkedReturnActivity);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
NavUtils.navigateUpTo(this, intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildInfo(final ErrorInfo info) {
|
private void buildInfo(final ErrorInfo info) {
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
|
@ -355,7 +295,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 +333,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");
|
||||||
|
@ -460,17 +400,4 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
text += "\n" + getString(R.string.guru_meditation);
|
text += "\n" + getString(R.string.guru_meditation);
|
||||||
activityErrorBinding.errorSorryView.setText(text);
|
activityErrorBinding.errorSorryView.setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
//super.onBackPressed();
|
|
||||||
goToReturnActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrentTimeStamp() {
|
|
||||||
final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
|
||||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
||||||
return df.format(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package org.schabi.newpipe.error
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.extractor.Info
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException
|
||||||
|
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException
|
||||||
|
import org.schabi.newpipe.ktx.isNetworkRelated
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class ErrorInfo(
|
||||||
|
val stackTraces: Array<String>,
|
||||||
|
val userAction: UserAction,
|
||||||
|
val serviceName: String,
|
||||||
|
val request: String,
|
||||||
|
val messageStringId: Int,
|
||||||
|
@Transient // no need to store throwable, all data for report is in other variables
|
||||||
|
var throwable: Throwable? = null
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
throwable: Throwable,
|
||||||
|
userAction: UserAction,
|
||||||
|
serviceName: String,
|
||||||
|
request: String
|
||||||
|
) : this(
|
||||||
|
throwableToStringList(throwable),
|
||||||
|
userAction,
|
||||||
|
serviceName,
|
||||||
|
request,
|
||||||
|
getMessageStringId(throwable, userAction),
|
||||||
|
throwable
|
||||||
|
)
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
throwable: List<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,132 @@
|
||||||
|
package org.schabi.newpipe.error
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.jakewharton.rxbinding4.view.clicks
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
|
import org.schabi.newpipe.MainActivity
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PaidContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.PrivateContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException
|
||||||
|
import org.schabi.newpipe.ktx.animate
|
||||||
|
import org.schabi.newpipe.ktx.isInterruptedCaused
|
||||||
|
import org.schabi.newpipe.ktx.isNetworkRelated
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class ErrorPanelHelper(
|
||||||
|
private val fragment: Fragment,
|
||||||
|
rootView: View,
|
||||||
|
onRetry: Runnable
|
||||||
|
) {
|
||||||
|
private val context: Context = rootView.context!!
|
||||||
|
private val errorPanelRoot: View = rootView.findViewById(R.id.error_panel)
|
||||||
|
private val errorTextView: TextView = errorPanelRoot.findViewById(R.id.error_message_view)
|
||||||
|
private val errorButtonAction: Button = errorPanelRoot.findViewById(R.id.error_button_action)
|
||||||
|
private val errorButtonRetry: Button = errorPanelRoot.findViewById(R.id.error_button_retry)
|
||||||
|
|
||||||
|
private var errorDisposable: Disposable? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
errorDisposable = errorButtonRetry.clicks()
|
||||||
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { onRetry.run() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showError(errorInfo: ErrorInfo) {
|
||||||
|
|
||||||
|
if (errorInfo.throwable != null && errorInfo.throwable!!.isInterruptedCaused) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, "onError() isInterruptedCaused! = [$errorInfo.throwable]")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errorButtonAction.isVisible = true
|
||||||
|
if (errorInfo.throwable is ReCaptchaException) {
|
||||||
|
errorButtonAction.setText(R.string.recaptcha_solve)
|
||||||
|
errorButtonAction.setOnClickListener {
|
||||||
|
// Starting ReCaptcha Challenge Activity
|
||||||
|
val intent = Intent(context, ReCaptchaActivity::class.java)
|
||||||
|
intent.putExtra(
|
||||||
|
ReCaptchaActivity.RECAPTCHA_URL_EXTRA,
|
||||||
|
(errorInfo.throwable as ReCaptchaException).url
|
||||||
|
)
|
||||||
|
fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST)
|
||||||
|
errorButtonAction.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
errorTextView.setText(R.string.recaptcha_request_toast)
|
||||||
|
errorButtonRetry.isVisible = true
|
||||||
|
} else {
|
||||||
|
errorButtonAction.setText(R.string.error_snackbar_action)
|
||||||
|
errorButtonAction.setOnClickListener {
|
||||||
|
ErrorActivity.reportError(context, errorInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide retry button by default, then show only if not unavailable/unsupported content
|
||||||
|
errorButtonRetry.isVisible = false
|
||||||
|
errorTextView.setText(
|
||||||
|
when (errorInfo.throwable) {
|
||||||
|
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
|
||||||
|
is GeographicRestrictionException -> R.string.georestricted_content
|
||||||
|
is PaidContentException -> R.string.paid_content
|
||||||
|
is PrivateContentException -> R.string.private_content
|
||||||
|
is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content
|
||||||
|
is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content
|
||||||
|
is ContentNotAvailableException -> R.string.content_not_available
|
||||||
|
is ContentNotSupportedException -> R.string.content_not_supported
|
||||||
|
else -> {
|
||||||
|
// show retry button only for content which is not unavailable or unsupported
|
||||||
|
errorButtonRetry.isVisible = true
|
||||||
|
if (errorInfo.throwable != null && errorInfo.throwable!!.isNetworkRelated) {
|
||||||
|
R.string.network_error
|
||||||
|
} else {
|
||||||
|
R.string.error_snackbar_message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
errorPanelRoot.animate(true, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showTextError(errorString: String) {
|
||||||
|
errorButtonAction.isVisible = false
|
||||||
|
errorButtonRetry.isVisible = false
|
||||||
|
errorTextView.text = errorString
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() {
|
||||||
|
errorButtonAction.setOnClickListener(null)
|
||||||
|
errorPanelRoot.animate(false, 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isVisible(): Boolean {
|
||||||
|
return errorPanelRoot.isVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
errorButtonAction.setOnClickListener(null)
|
||||||
|
errorButtonRetry.setOnClickListener(null)
|
||||||
|
errorDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG: String = ErrorPanelHelper::class.simpleName!!
|
||||||
|
val DEBUG: Boolean = MainActivity.DEBUG
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -20,6 +20,9 @@ import androidx.preference.PreferenceManager;
|
||||||
import androidx.webkit.WebViewClientCompat;
|
import androidx.webkit.WebViewClientCompat;
|
||||||
|
|
||||||
import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
|
import org.schabi.newpipe.databinding.ActivityRecaptchaBinding;
|
||||||
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
|
import org.schabi.newpipe.MainActivity;
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user actions that can cause an error.
|
* The user actions that can cause an error.
|
||||||
|
@ -6,9 +6,12 @@ package org.schabi.newpipe.report;
|
||||||
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,23 @@
|
||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
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.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
import org.schabi.newpipe.error.ErrorPanelHelper;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
|
|
||||||
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
|
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.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;
|
||||||
|
|
||||||
|
@ -56,11 +31,10 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
@Nullable
|
@Nullable
|
||||||
private ProgressBar loadingProgressBar;
|
private ProgressBar loadingProgressBar;
|
||||||
|
|
||||||
private Disposable errorDisposable;
|
private ErrorPanelHelper errorPanelHelper;
|
||||||
|
@Nullable
|
||||||
protected View errorPanelRoot;
|
@State
|
||||||
private Button errorButtonRetry;
|
protected ErrorInfo lastPanelError = null;
|
||||||
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) {
|
||||||
|
@ -74,12 +48,18 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
wasLoading.set(isLoading.get());
|
wasLoading.set(isLoading.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (lastPanelError != null) {
|
||||||
|
showError(lastPanelError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (errorDisposable != null) {
|
errorPanelHelper.dispose();
|
||||||
errorDisposable.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -89,22 +69,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 +110,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);
|
hideErrorPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -154,10 +121,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);
|
hideErrorPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showEmptyState() {
|
public void showEmptyState() {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
if (emptyStateView != null) {
|
if (emptyStateView != null) {
|
||||||
|
@ -166,26 +132,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);
|
hideErrorPanel();
|
||||||
}
|
|
||||||
|
|
||||||
@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 +143,69 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError() {
|
||||||
|
isLoading.set(false);
|
||||||
|
InfoCache.getInstance().clearCache();
|
||||||
|
if (emptyStateView != null) {
|
||||||
|
animate(emptyStateView, false, 150);
|
||||||
|
}
|
||||||
|
if (loadingProgressBar != null) {
|
||||||
|
animate(loadingProgressBar, false, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Error handling
|
// 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);
|
||||||
|
lastPanelError = errorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void showTextError(@NonNull final 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 void onReCaptchaException(final ReCaptchaException exception) {
|
public final void hideErrorPanel() {
|
||||||
if (DEBUG) {
|
errorPanelHelper.hide();
|
||||||
Log.d(TAG, "onReCaptchaException() called");
|
lastPanelError = null;
|
||||||
}
|
|
||||||
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,
|
public final boolean isErrorPanelVisible() {
|
||||||
final String serviceName, final String request,
|
return errorPanelHelper.isVisible();
|
||||||
@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#reportErrorInSnackbar(androidx.fragment.app.Fragment, 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;
|
ErrorActivity.reportErrorInSnackbar(this, errorInfo);
|
||||||
if (rootView == null && getView() != null) {
|
|
||||||
rootView = getView();
|
|
||||||
}
|
|
||||||
if (rootView == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView,
|
|
||||||
ErrorInfo.make(userAction, serviceName, request, errorId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,18 @@ import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
public class EmptyFragment extends BaseFragment {
|
public class EmptyFragment extends BaseFragment {
|
||||||
|
final boolean showMessage;
|
||||||
|
|
||||||
|
public EmptyFragment(final boolean showMessage) {
|
||||||
|
this.showMessage = showMessage;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
final Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_empty, container, false);
|
final View view = inflater.inflate(R.layout.fragment_empty, container, false);
|
||||||
|
view.findViewById(R.id.empty_state_view).setVisibility(
|
||||||
|
showMessage ? View.VISIBLE : View.GONE);
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.tabs.Tab;
|
import org.schabi.newpipe.settings.tabs.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,15 +142,14 @@ 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.reportUiErrorInSnackbar(this, "Opening search fragment", e);
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -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.reportUiErrorInSnackbar(context, "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,12 +37,10 @@ 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;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
@ -56,14 +54,15 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
|
||||||
|
|
||||||
import org.schabi.newpipe.App;
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
||||||
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.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;
|
||||||
|
@ -71,6 +70,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.fragments.BackPressable;
|
import org.schabi.newpipe.fragments.BackPressable;
|
||||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||||
|
import org.schabi.newpipe.fragments.EmptyFragment;
|
||||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
||||||
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
|
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
|
||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
|
@ -86,9 +86,6 @@ import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.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;
|
||||||
|
@ -151,6 +148,7 @@ public final class VideoDetailFragment
|
||||||
private static final String COMMENTS_TAB_TAG = "COMMENTS";
|
private static final String COMMENTS_TAB_TAG = "COMMENTS";
|
||||||
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
|
||||||
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
||||||
|
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
private boolean showComments;
|
private boolean showComments;
|
||||||
|
@ -526,7 +524,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.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,13 +682,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 +903,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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -932,18 +927,22 @@ public final class VideoDetailFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showRelatedStreams && binding.relatedStreamsLayout == null) {
|
if (showRelatedStreams && binding.relatedStreamsLayout == null) {
|
||||||
//temp empty fragment. will be updated in handleResult
|
// temp empty fragment. will be updated in handleResult
|
||||||
pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG);
|
pageAdapter.addFragment(new EmptyFragment(false), RELATED_TAB_TAG);
|
||||||
tabIcons.add(R.drawable.ic_art_track_white_24dp);
|
tabIcons.add(R.drawable.ic_art_track_white_24dp);
|
||||||
tabContentDescriptions.add(R.string.related_streams_tab_description);
|
tabContentDescriptions.add(R.string.related_streams_tab_description);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showDescription) {
|
if (showDescription) {
|
||||||
// temp empty fragment. will be updated in handleResult
|
// temp empty fragment. will be updated in handleResult
|
||||||
pageAdapter.addFragment(new Fragment(), DESCRIPTION_TAB_TAG);
|
pageAdapter.addFragment(new EmptyFragment(false), DESCRIPTION_TAB_TAG);
|
||||||
tabIcons.add(R.drawable.ic_description_white_24dp);
|
tabIcons.add(R.drawable.ic_description_white_24dp);
|
||||||
tabContentDescriptions.add(R.string.description_tab_description);
|
tabContentDescriptions.add(R.string.description_tab_description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pageAdapter.getCount() == 0) {
|
||||||
|
pageAdapter.addFragment(new EmptyFragment(true), EMPTY_TAB_TAG);
|
||||||
|
}
|
||||||
pageAdapter.notifyDataSetUpdate();
|
pageAdapter.notifyDataSetUpdate();
|
||||||
|
|
||||||
if (pageAdapter.getCount() >= 2) {
|
if (pageAdapter.getCount() >= 2) {
|
||||||
|
@ -1327,8 +1326,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 +1340,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 +1547,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 +1588,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 +1601,9 @@ 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.reportErrorInSnackbar(activity,
|
||||||
ServiceList.all()
|
new ErrorInfo(e, UserAction.DOWNLOAD_OPEN_DIALOG, "Showing download dialog",
|
||||||
.get(currentInfo
|
currentInfo));
|
||||||
.getServiceId())
|
|
||||||
.getServiceInfo()
|
|
||||||
.getName(), "",
|
|
||||||
R.string.could_not_setup_download_menu);
|
|
||||||
|
|
||||||
ErrorActivity.reportError(activity,
|
|
||||||
e,
|
|
||||||
activity.getClass(),
|
|
||||||
activity.findViewById(android.R.id.content), info);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1620,24 +1611,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;
|
||||||
|
@ -22,6 +21,7 @@ import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.PignateFooterBinding;
|
import org.schabi.newpipe.databinding.PignateFooterBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||||
|
@ -33,7 +33,6 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
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;
|
||||||
|
@ -47,6 +46,7 @@ import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||||
|
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||||
|
|
||||||
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
implements ListViewContract<I, N>, StateSaver.WriteRead,
|
implements ListViewContract<I, N>, StateSaver.WriteRead,
|
||||||
|
@ -292,7 +292,8 @@ 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.reportUiErrorInSnackbar(
|
||||||
|
BaseListFragment.this, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -307,7 +308,8 @@ 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.reportUiErrorInSnackbar(BaseListFragment.this,
|
||||||
|
"Opening playlist fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -406,23 +408,23 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showLoading() {
|
||||||
|
super.showLoading();
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hideLoading() {
|
public void hideLoading() {
|
||||||
super.hideLoading();
|
super.hideLoading();
|
||||||
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();
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -439,6 +441,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError() {
|
||||||
|
super.handleError();
|
||||||
|
showListFooter(false);
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
}
|
||||||
|
|
||||||
@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.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.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.reportUiErrorInSnackbar(this, "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,27 +464,13 @@ 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()) {
|
if (throwable instanceof ContentNotSupportedException) {
|
||||||
|
showContentNotSupported();
|
||||||
// handling ContentNotSupportedException not to show the error but an appropriate string
|
|
||||||
// so that crashes won't be sent uselessly and the user will understand what happened
|
|
||||||
errors.removeIf(throwable -> {
|
|
||||||
if (throwable instanceof ContentNotSupportedException) {
|
|
||||||
showContentNotSupported();
|
|
||||||
}
|
|
||||||
return throwable instanceof ContentNotSupportedException;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
showSnackBarError(errors, UserAction.REQUESTED_CHANNEL,
|
|
||||||
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disposables != null) {
|
disposables.clear();
|
||||||
disposables.clear();
|
|
||||||
}
|
|
||||||
if (subscribeButtonMonitor != null) {
|
if (subscribeButtonMonitor != null) {
|
||||||
subscribeButtonMonitor.dispose();
|
subscribeButtonMonitor.dispose();
|
||||||
}
|
}
|
||||||
|
@ -539,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.report.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;
|
||||||
|
@ -25,13 +24,17 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
|
||||||
public static CommentsFragment getInstance(final int serviceId, final String url,
|
public static CommentsFragment getInstance(final int serviceId, final String url,
|
||||||
final String name) {
|
final String name) {
|
||||||
final CommentsFragment instance = new CommentsFragment();
|
final CommentsFragment instance = new CommentsFragment();
|
||||||
instance.setInitialData(serviceId, url, name);
|
instance.setInitialData(serviceId, url, name);
|
||||||
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,14 +2,16 @@ 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.error.UserAction;
|
||||||
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;
|
||||||
import org.schabi.newpipe.report.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;
|
||||||
|
|
||||||
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,8 @@ 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.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.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
|
@ -20,7 +22,6 @@ import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
||||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
|
@ -28,8 +29,6 @@ import org.schabi.newpipe.util.Localization;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Christian Schabesberger on 23.09.17.
|
* Created by Christian Schabesberger on 23.09.17.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -82,6 +81,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KioskFragment() {
|
||||||
|
super(UserAction.REQUESTED_KIOSK);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -102,9 +105,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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,34 +158,11 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showLoading() {
|
|
||||||
super.showLoading();
|
|
||||||
animate(itemsList, false, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull final KioskInfo result) {
|
public void handleResult(@NonNull final KioskInfo result) {
|
||||||
super.handleResult(result);
|
super.handleResult(result);
|
||||||
|
|
||||||
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.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.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;
|
||||||
|
@ -62,6 +60,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||||
|
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||||
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr;
|
||||||
|
|
||||||
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
|
@ -87,6 +86,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlaylistFragment() {
|
||||||
|
super(UserAction.REQUESTED_PLAYLIST);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -262,7 +265,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
animate(headerBinding.getRoot(), false, 200);
|
animate(headerBinding.getRoot(), false, 200);
|
||||||
animate(itemsList, false, 100);
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
|
||||||
IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView);
|
IMAGE_LOADER.cancelDisplayTask(headerBinding.uploaderAvatarView);
|
||||||
animate(headerBinding.uploaderLayout, false, 200);
|
animate(headerBinding.uploaderLayout, false, 200);
|
||||||
|
@ -284,7 +287,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.reportUiErrorInSnackbar(this, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -315,8 +318,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 +366,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 +410,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 +437,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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,16 +35,18 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||||
import org.schabi.newpipe.databinding.FragmentSearchBinding;
|
import org.schabi.newpipe.databinding.FragmentSearchBinding;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.ListExtractor;
|
import org.schabi.newpipe.extractor.ListExtractor;
|
||||||
import org.schabi.newpipe.extractor.MetaInfo;
|
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;
|
||||||
|
@ -54,9 +56,6 @@ import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.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;
|
||||||
|
@ -162,11 +161,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
private EditText searchEditText;
|
private EditText searchEditText;
|
||||||
private View searchClear;
|
private View searchClear;
|
||||||
|
|
||||||
private TextView correctSuggestion;
|
|
||||||
private TextView metaInfoTextView;
|
|
||||||
private View metaInfoSeparator;
|
|
||||||
|
|
||||||
private View suggestionsPanel;
|
|
||||||
private boolean suggestionsPanelVisible = false;
|
private boolean suggestionsPanelVisible = false;
|
||||||
|
|
||||||
/*////////////////////////////////////////////////////////////////////////*/
|
/*////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -258,20 +252,23 @@ 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.reportUiErrorInSnackbar(this,
|
||||||
requireActivity().findViewById(android.R.id.content),
|
"Getting service for id " + serviceId, e);
|
||||||
ErrorInfo.make(UserAction.UI_ERROR,
|
}
|
||||||
"",
|
|
||||||
"", R.string.general_error));
|
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||||
|
initSuggestionObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(searchString)) {
|
if (!TextUtils.isEmpty(searchString)) {
|
||||||
if (wasLoading.getAndSet(false)) {
|
if (wasLoading.getAndSet(false)) {
|
||||||
search(searchString, contentFilter, sortFilter);
|
search(searchString, contentFilter, sortFilter);
|
||||||
|
return;
|
||||||
} else if (infoListAdapter.getItemsList().isEmpty()) {
|
} else if (infoListAdapter.getItemsList().isEmpty()) {
|
||||||
if (savedState == null) {
|
if (savedState == null) {
|
||||||
search(searchString, contentFilter, sortFilter);
|
search(searchString, contentFilter, sortFilter);
|
||||||
} else if (!isLoading.get() && !wasSearchFocused) {
|
return;
|
||||||
|
} else if (!isLoading.get() && !wasSearchFocused && lastPanelError == null) {
|
||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
showEmptyState();
|
showEmptyState();
|
||||||
}
|
}
|
||||||
|
@ -281,11 +278,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
handleSearchSuggestion();
|
handleSearchSuggestion();
|
||||||
|
|
||||||
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
|
||||||
metaInfoTextView, metaInfoSeparator));
|
searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator));
|
||||||
|
|
||||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
|
||||||
initSuggestionObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
|
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
|
@ -367,10 +360,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||||
|
|
||||||
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
|
|
||||||
metaInfoTextView = rootView.findViewById(R.id.search_meta_info_text_view);
|
|
||||||
metaInfoSeparator = rootView.findViewById(R.id.search_meta_info_separator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -413,7 +402,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
searchEditText.setText("");
|
searchEditText.setText("");
|
||||||
showKeyboardSearch();
|
showKeyboardSearch();
|
||||||
}
|
}
|
||||||
animate(errorPanelRoot, false, 200);
|
hideErrorPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,7 +529,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 +542,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 +692,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();
|
||||||
|
@ -733,14 +721,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
suggestionDisposable.dispose();
|
suggestionDisposable.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Observable<String> observable = suggestionPublisher
|
suggestionDisposable = suggestionPublisher
|
||||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||||
.startWithItem(searchString != null
|
.startWithItem(searchString != null
|
||||||
? searchString
|
? searchString
|
||||||
: "")
|
: "")
|
||||||
.filter(ss -> isSuggestionsEnabled);
|
.filter(ss -> isSuggestionsEnabled)
|
||||||
|
|
||||||
suggestionDisposable = observable
|
|
||||||
.switchMap(query -> {
|
.switchMap(query -> {
|
||||||
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
|
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
|
||||||
.getRelatedSearches(query, 3, 25);
|
.getRelatedSearches(query, 3, 25);
|
||||||
|
@ -763,8 +749,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 +786,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 +819,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) {
|
||||||
|
@ -844,15 +830,16 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
this.searchString = theSearchString;
|
this.searchString = theSearchString;
|
||||||
infoListAdapter.clearStreamItemList();
|
infoListAdapter.clearStreamItemList();
|
||||||
hideSuggestionsPanel();
|
hideSuggestionsPanel();
|
||||||
|
showMetaInfoInTextView(null, searchBinding.searchMetaInfoTextView,
|
||||||
|
searchBinding.searchMetaInfoSeparator);
|
||||||
hideKeyboardSearch();
|
hideKeyboardSearch();
|
||||||
|
|
||||||
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 +859,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 +882,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 +896,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 +941,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 +956,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 +966,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();
|
||||||
|
@ -1002,8 +976,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||||
// List<MetaInfo> cannot be bundled without creating some containers
|
// List<MetaInfo> cannot be bundled without creating some containers
|
||||||
metaInfo = new MetaInfo[result.getMetaInfo().size()];
|
metaInfo = new MetaInfo[result.getMetaInfo().size()];
|
||||||
metaInfo = result.getMetaInfo().toArray(metaInfo);
|
metaInfo = result.getMetaInfo().toArray(metaInfo);
|
||||||
disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView,
|
disposables.add(showMetaInfoInTextView(result.getMetaInfo(),
|
||||||
metaInfoSeparator));
|
searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator));
|
||||||
|
|
||||||
handleSearchSuggestion();
|
handleSearchSuggestion();
|
||||||
|
|
||||||
|
@ -1061,33 +1035,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 +1074,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,11 @@ import androidx.viewbinding.ViewBinding;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.RelatedStreamsHeaderBinding;
|
import org.schabi.newpipe.databinding.RelatedStreamsHeaderBinding;
|
||||||
|
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.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
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.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.RelatedStreamInfo;
|
import org.schabi.newpipe.util.RelatedStreamInfo;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -47,6 +46,10 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RelatedVideosFragment() {
|
||||||
|
super(UserAction.REQUESTED_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -125,43 +128,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
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -190,11 +159,9 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
|
||||||
super.onRestoreInstanceState(savedState);
|
super.onRestoreInstanceState(savedState);
|
||||||
if (savedState != null) {
|
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
||||||
final Serializable serializable = savedState.getSerializable(INFO_KEY);
|
if (serializable instanceof RelatedStreamInfo) {
|
||||||
if (serializable instanceof RelatedStreamInfo) {
|
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
||||||
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,11 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
|
@ -171,15 +171,15 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
||||||
try {
|
try {
|
||||||
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
|
||||||
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.reportUiErrorInSnackbar(activity, "Opening channel fragment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -319,6 +319,16 @@ fun View.slideUp(duration: Long, delay: Long, @FloatRange(from = 0.0, to = 1.0)
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instead of hiding normally using [animate], which would make
|
||||||
|
* the recycler view unable to capture touches after being hidden, this just animates the alpha
|
||||||
|
* value setting it to `0.0` after `200` milliseconds.
|
||||||
|
*/
|
||||||
|
fun View.animateHideRecyclerViewAllowingScrolling() {
|
||||||
|
// not hiding normally because the view needs to still capture touches and allow scroll
|
||||||
|
animate().alpha(0.0f).setDuration(200).start()
|
||||||
|
}
|
||||||
|
|
||||||
enum class AnimationType {
|
enum class AnimationType {
|
||||||
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
|
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||||
import org.schabi.newpipe.fragments.list.ListViewContract;
|
import org.schabi.newpipe.fragments.list.ListViewContract;
|
||||||
|
|
||||||
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
import static org.schabi.newpipe.ktx.ViewUtils.animate;
|
||||||
|
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This fragment is design to be used with persistent data such as
|
* This fragment is design to be used with persistent data such as
|
||||||
|
@ -184,7 +185,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
||||||
public void showLoading() {
|
public void showLoading() {
|
||||||
super.showLoading();
|
super.showLoading();
|
||||||
if (itemsList != null) {
|
if (itemsList != null) {
|
||||||
animate(itemsList, false, 200);
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
}
|
}
|
||||||
if (headerRootBinding != null) {
|
if (headerRootBinding != null) {
|
||||||
animate(headerRootBinding.getRoot(), false, 200);
|
animate(headerRootBinding.getRoot(), false, 200);
|
||||||
|
@ -202,19 +203,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 +237,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) {
|
||||||
|
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||||
|
}
|
||||||
|
if (headerRootBinding != null) {
|
||||||
|
animate(headerRootBinding.getRoot(), false, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -23,10 +23,11 @@ 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.error.UserAction;
|
||||||
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;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
|
||||||
|
@ -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,17 +38,18 @@ 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.fragments.list.BaseListFragment
|
import org.schabi.newpipe.fragments.list.BaseListFragment
|
||||||
import org.schabi.newpipe.ktx.animate
|
import org.schabi.newpipe.ktx.animate
|
||||||
|
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
|
||||||
import org.schabi.newpipe.local.feed.service.FeedLoadService
|
import org.schabi.newpipe.local.feed.service.FeedLoadService
|
||||||
import org.schabi.newpipe.report.UserAction
|
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import java.util.Calendar
|
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
|
||||||
|
@ -106,7 +107,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
override fun initListeners() {
|
override fun initListeners() {
|
||||||
super.initListeners()
|
super.initListeners()
|
||||||
feedBinding.refreshRootView.setOnClickListener { reloadContent() }
|
feedBinding.refreshRootView.setOnClickListener { reloadContent() }
|
||||||
feedBinding.swiperefresh.setOnRefreshListener { reloadContent() }
|
feedBinding.swipeRefreshLayout.setOnRefreshListener { reloadContent() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -171,50 +172,26 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun showLoading() {
|
override fun showLoading() {
|
||||||
|
super.showLoading()
|
||||||
|
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
|
||||||
feedBinding.refreshRootView.animate(false, 0)
|
feedBinding.refreshRootView.animate(false, 0)
|
||||||
feedBinding.itemsList.animate(false, 0)
|
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(true, 200)
|
|
||||||
feedBinding.loadingProgressText.animate(true, 200)
|
feedBinding.loadingProgressText.animate(true, 200)
|
||||||
|
feedBinding.swipeRefreshLayout.isRefreshing = true
|
||||||
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.swipeRefreshLayout.isRefreshing = false
|
||||||
feedBinding.emptyStateView.root.animate(false, 0)
|
|
||||||
errorBinding.root.animate(false, 0)
|
|
||||||
feedBinding.swiperefresh.isRefreshing = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEmptyState() {
|
override fun showEmptyState() {
|
||||||
|
super.showEmptyState()
|
||||||
|
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
|
||||||
feedBinding.refreshRootView.animate(true, 200)
|
feedBinding.refreshRootView.animate(true, 200)
|
||||||
feedBinding.itemsList.animate(false, 0)
|
|
||||||
|
|
||||||
feedBinding.loadingProgressBar.animate(false, 0)
|
|
||||||
feedBinding.loadingProgressText.animate(false, 0)
|
feedBinding.loadingProgressText.animate(false, 0)
|
||||||
|
feedBinding.swipeRefreshLayout.isRefreshing = false
|
||||||
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 +204,15 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
updateRefreshViewState()
|
updateRefreshViewState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleError() {
|
||||||
|
super.handleError()
|
||||||
|
infoListAdapter.clearStreamItemList()
|
||||||
|
feedBinding.itemsList.animateHideRecyclerViewAllowingScrolling()
|
||||||
|
feedBinding.refreshRootView.animate(false, 0)
|
||||||
|
feedBinding.loadingProgressText.animate(false, 0)
|
||||||
|
feedBinding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleProgressState(progressState: FeedState.ProgressState) {
|
private fun handleProgressState(progressState: FeedState.ProgressState) {
|
||||||
showLoading()
|
showLoading()
|
||||||
|
|
||||||
|
@ -266,13 +252,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 +260,13 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
||||||
hideLoading()
|
return if (errorState.error == null) {
|
||||||
errorState.error?.let {
|
hideLoading()
|
||||||
onError(errorState.error)
|
false
|
||||||
return true
|
} else {
|
||||||
|
showError(ErrorInfo(errorState.error, UserAction.REQUESTED_FEED, "Loading feed"))
|
||||||
|
true
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRelativeTimeViews() {
|
private fun updateRelativeTimeViews() {
|
||||||
|
@ -320,18 +300,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.report.ErrorActivity;
|
import org.schabi.newpipe.settings.HistorySettingsFragment;
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.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,48 +162,11 @@ 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()))
|
return super.onOptionsItemSelected(item);
|
||||||
.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 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,8 @@ 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.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;
|
||||||
|
@ -42,7 +44,6 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
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.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
@ -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,25 +346,23 @@ 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)
|
.setTitle(R.string.remove_watched_popup_title)
|
||||||
.setTitle(R.string.remove_watched_popup_title)
|
.setPositiveButton(R.string.yes,
|
||||||
.setPositiveButton(R.string.yes,
|
(DialogInterface d, int id) -> removeWatchedStreams(false))
|
||||||
(DialogInterface d, int id) -> removeWatchedStreams(false))
|
.setNeutralButton(
|
||||||
.setNeutralButton(
|
R.string.remove_watched_popup_yes_and_partially_watched_videos,
|
||||||
R.string.remove_watched_popup_yes_and_partially_watched_videos,
|
(DialogInterface d, int id) -> removeWatchedStreams(true))
|
||||||
(DialogInterface d, int id) -> removeWatchedStreams(true))
|
.setNegativeButton(R.string.cancel,
|
||||||
.setNegativeButton(R.string.cancel,
|
(DialogInterface d, int id) -> d.cancel())
|
||||||
(DialogInterface d, int id) -> d.cancel())
|
.create()
|
||||||
.create()
|
.show();
|
||||||
.show();
|
}
|
||||||
}
|
} else {
|
||||||
break;
|
return super.onOptionsItemSelected(item);
|
||||||
default:
|
|
||||||
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,8 @@ 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.extractor.channel.ChannelInfoItem
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||||
import org.schabi.newpipe.fragments.BaseStateFragment
|
import org.schabi.newpipe.fragments.BaseStateFragment
|
||||||
import org.schabi.newpipe.ktx.animate
|
import org.schabi.newpipe.ktx.animate
|
||||||
|
@ -56,7 +58,6 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
||||||
import org.schabi.newpipe.report.UserAction
|
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper
|
import org.schabi.newpipe.util.FilePickerActivityHelper
|
||||||
import org.schabi.newpipe.util.NavigationHelper
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import org.schabi.newpipe.util.OnClickGesture
|
import org.schabi.newpipe.util.OnClickGesture
|
||||||
|
@ -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
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -22,13 +22,13 @@ import com.nononsenseapps.filepicker.Utils;
|
||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
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.UserAction;
|
||||||
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.subscription.SubscriptionExtractor;
|
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
@ -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.reportErrorInSnackbar(activity,
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,6 @@ import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||||
import org.schabi.newpipe.util.DeviceUtils
|
import org.schabi.newpipe.util.DeviceUtils
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import kotlin.collections.contains
|
|
||||||
|
|
||||||
class FeedGroupDialog : DialogFragment(), BackPressable {
|
class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||||
private var _feedGroupCreateBinding: DialogFeedGroupCreateBinding? = null
|
private var _feedGroupCreateBinding: DialogFeedGroupCreateBinding? = null
|
||||||
|
|
|
@ -24,6 +24,10 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewMo
|
||||||
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.map
|
||||||
|
import kotlin.collections.sortedBy
|
||||||
|
|
||||||
class FeedGroupReorderDialog : DialogFragment() {
|
class FeedGroupReorderDialog : DialogFragment() {
|
||||||
private var _binding: DialogFeedGroupReorderBinding? = null
|
private var _binding: DialogFeedGroupReorderBinding? = null
|
||||||
|
|
|
@ -35,15 +35,14 @@ import androidx.core.app.ServiceCompat;
|
||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
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.UserAction;
|
||||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.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, 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) {
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
package org.schabi.newpipe.report
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import kotlinx.android.parcel.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
class ErrorInfo(
|
|
||||||
val userAction: UserAction?,
|
|
||||||
val serviceName: String,
|
|
||||||
val request: String,
|
|
||||||
@field:StringRes @param:StringRes val message: Int
|
|
||||||
) : Parcelable {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun make(
|
|
||||||
userAction: UserAction?,
|
|
||||||
serviceName: String,
|
|
||||||
request: String,
|
|
||||||
@StringRes message: Int
|
|
||||||
) = ErrorInfo(userAction, serviceName, request, message)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.ReCaptchaActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
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.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
import org.schabi.newpipe.util.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.reportUiErrorInSnackbar(this, "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.reportUiErrorInSnackbar(this, "Importing database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Error
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void onError(final Throwable e) {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
ErrorActivity.reportError(activity, e,
|
|
||||||
activity.getClass(),
|
|
||||||
null,
|
|
||||||
ErrorInfo.make(UserAction.UI_ERROR,
|
|
||||||
"none", "", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
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;
|
||||||
|
|
||||||
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.UserAction;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.InfoCache;
|
import org.schabi.newpipe.util.InfoCache;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
@ -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) {
|
||||||
.setTitle(R.string.delete_view_history_alert)
|
return recordManager.deleteCompleteStreamStateHistory()
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.subscribe(
|
||||||
final Disposable onDeletePlaybackStates
|
howManyDeleted -> Toast.makeText(context,
|
||||||
= recordManager.deleteCompleteStreamStateHistory()
|
R.string.watch_history_states_deleted, Toast.LENGTH_SHORT).show(),
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
throwable -> ErrorActivity.reportError(context,
|
||||||
.subscribe(
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
"Delete playback states")));
|
||||||
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()
|
private static Disposable getWholeStreamHistoryDisposable(
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
.subscribe(
|
return recordManager.deleteWholeStreamHistory()
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
R.string.watch_history_deleted,
|
.subscribe(
|
||||||
Toast.LENGTH_SHORT).show(),
|
howManyDeleted -> Toast.makeText(context,
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
R.string.watch_history_deleted, Toast.LENGTH_SHORT).show(),
|
||||||
throwable,
|
throwable -> ErrorActivity.reportError(context,
|
||||||
SettingsActivity.class, null,
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
ErrorInfo.make(
|
"Delete from history")));
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
}
|
||||||
"none",
|
|
||||||
"Delete view history",
|
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
|
private static Disposable getRemoveOrphanedRecordsDisposable(
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
.subscribe(
|
return recordManager.removeOrphanedRecords()
|
||||||
howManyDeleted -> {
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
},
|
.subscribe(
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
howManyDeleted -> { },
|
||||||
throwable,
|
throwable -> ErrorActivity.reportError(context,
|
||||||
SettingsActivity.class, null,
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
ErrorInfo.make(
|
"Clear orphaned records")));
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
}
|
||||||
"none",
|
|
||||||
"Delete search history",
|
|
||||||
R.string.general_error)));
|
|
||||||
disposables.add(onDeletePlaybackStates);
|
|
||||||
disposables.add(onClearOrphans);
|
|
||||||
disposables.add(onDelete);
|
|
||||||
}))
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preference.getKey().equals(playbackStatesClearKey)) {
|
private static Disposable getDeleteSearchHistoryDisposable(
|
||||||
new AlertDialog.Builder(getActivity())
|
@NonNull final Context context, final HistoryRecordManager recordManager) {
|
||||||
.setTitle(R.string.delete_playback_states_alert)
|
return recordManager.deleteCompleteSearchHistory()
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.subscribe(
|
||||||
|
howManyDeleted -> Toast.makeText(context,
|
||||||
|
R.string.search_history_deleted, Toast.LENGTH_SHORT).show(),
|
||||||
|
throwable -> ErrorActivity.reportError(context,
|
||||||
|
new ErrorInfo(throwable, UserAction.DELETE_FROM_HISTORY,
|
||||||
|
"Delete search history")));
|
||||||
|
}
|
||||||
|
|
||||||
final Disposable onDeletePlaybackStates
|
public static void openDeleteWatchHistoryDialog(@NonNull final Context context,
|
||||||
= recordManager.deleteCompleteStreamStateHistory()
|
final HistoryRecordManager recordManager,
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
final CompositeDisposable disposables) {
|
||||||
.subscribe(
|
new AlertDialog.Builder(context)
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
.setTitle(R.string.delete_view_history_alert)
|
||||||
R.string.watch_history_states_deleted,
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
Toast.LENGTH_SHORT).show(),
|
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager));
|
||||||
throwable,
|
disposables.add(getWholeStreamHistoryDisposable(context, recordManager));
|
||||||
SettingsActivity.class, null,
|
disposables.add(getRemoveOrphanedRecordsDisposable(context, recordManager));
|
||||||
ErrorInfo.make(
|
}))
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
.create()
|
||||||
"none",
|
.show();
|
||||||
"Delete playback states",
|
}
|
||||||
R.string.general_error)));
|
|
||||||
|
|
||||||
disposables.add(onDeletePlaybackStates);
|
public static void openDeletePlaybackStatesDialog(@NonNull final Context context,
|
||||||
}))
|
final HistoryRecordManager recordManager,
|
||||||
.create()
|
final CompositeDisposable disposables) {
|
||||||
.show();
|
new AlertDialog.Builder(context)
|
||||||
}
|
.setTitle(R.string.delete_playback_states_alert)
|
||||||
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
|
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||||
|
disposables.add(getDeletePlaybackStatesDisposable(context, recordManager))))
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
if (preference.getKey().equals(searchHistoryClearKey)) {
|
public static void openDeleteSearchHistoryDialog(@NonNull final Context context,
|
||||||
new AlertDialog.Builder(getActivity())
|
final HistoryRecordManager recordManager,
|
||||||
.setTitle(R.string.delete_search_history_alert)
|
final CompositeDisposable disposables) {
|
||||||
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
new AlertDialog.Builder(context)
|
||||||
.setPositiveButton(R.string.delete, ((dialog, which) -> {
|
.setTitle(R.string.delete_search_history_alert)
|
||||||
final Disposable onDelete = recordManager.deleteCompleteSearchHistory()
|
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.setPositiveButton(R.string.delete, ((dialog, which) ->
|
||||||
.subscribe(
|
disposables.add(getDeleteSearchHistoryDisposable(context, recordManager))))
|
||||||
howManyDeleted -> Toast.makeText(getActivity(),
|
.create()
|
||||||
R.string.search_history_deleted,
|
.show();
|
||||||
Toast.LENGTH_SHORT).show(),
|
|
||||||
throwable -> ErrorActivity.reportError(getContext(),
|
|
||||||
throwable,
|
|
||||||
SettingsActivity.class, null,
|
|
||||||
ErrorInfo.make(
|
|
||||||
UserAction.DELETE_FROM_HISTORY,
|
|
||||||
"none",
|
|
||||||
"Delete search history",
|
|
||||||
R.string.general_error)));
|
|
||||||
disposables.add(onDelete);
|
|
||||||
}))
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onPreferenceTreeClick(preference);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import 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,17 @@ 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.reportUiErrorInSnackbar(SelectChannelFragment.this,
|
||||||
|
"Loading subscription", exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -173,16 +171,6 @@ public class SelectChannelFragment extends DialogFragment {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
|
||||||
// Error
|
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
|
||||||
|
|
||||||
protected void onError(final Throwable e) {
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorInfo
|
|
||||||
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Interfaces
|
// Interfaces
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -197,6 +185,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;
|
||||||
|
@ -16,11 +15,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
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.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.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.reportUiErrorInSnackbar(this, "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
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
@ -24,11 +24,11 @@ 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.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
@ -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.reportErrorInSnackbar(activity, new ErrorInfo(e,
|
||||||
.make(UserAction.UI_ERROR, "none", "load_playlists", R.string.app_ui_crash));
|
UserAction.UI_ERROR, "Loading playlists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -7,9 +7,9 @@ import android.util.Log;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
|
|
||||||
import static org.schabi.newpipe.MainActivity.DEBUG;
|
import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||||
|
|
||||||
|
@ -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, 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,10 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
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.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.settings.SelectChannelFragment;
|
import org.schabi.newpipe.settings.SelectChannelFragment;
|
||||||
import org.schabi.newpipe.settings.SelectKioskFragment;
|
import org.schabi.newpipe.settings.SelectKioskFragment;
|
||||||
import org.schabi.newpipe.settings.SelectPlaylistFragment;
|
import org.schabi.newpipe.settings.SelectPlaylistFragment;
|
||||||
|
@ -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.reportErrorInSnackbar(this,
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ import com.grack.nanojson.JsonSink;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.database.LocalItem.LocalItemType;
|
import org.schabi.newpipe.database.LocalItem.LocalItemType;
|
||||||
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.StreamingService;
|
import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
@ -25,9 +28,6 @@ import org.schabi.newpipe.local.feed.FeedFragment;
|
||||||
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.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;
|
||||||
|
@ -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.reportErrorInSnackbar(context, 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,7 +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.ReCaptchaActivity;
|
|
||||||
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;
|
||||||
|
@ -44,29 +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 org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.report.UserAction;
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -352,10 +274,9 @@ public final class ExtractorHelper {
|
||||||
final TextView metaInfoTextView,
|
final TextView metaInfoTextView,
|
||||||
final View metaInfoSeparator) {
|
final View metaInfoSeparator) {
|
||||||
final Context context = metaInfoTextView.getContext();
|
final Context context = metaInfoTextView.getContext();
|
||||||
final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context)
|
if (metaInfos == null || metaInfos.isEmpty()
|
||||||
.getBoolean(context.getString(R.string.show_meta_info_key), true);
|
|| !PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
|
||||||
|
context.getString(R.string.show_meta_info_key), true)) {
|
||||||
if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
|
|
||||||
metaInfoTextView.setVisibility(View.GONE);
|
metaInfoTextView.setVisibility(View.GONE);
|
||||||
metaInfoSeparator.setVisibility(View.GONE);
|
metaInfoSeparator.setVisibility(View.GONE);
|
||||||
return Disposable.empty();
|
return Disposable.empty();
|
||||||
|
|
|
@ -41,9 +41,9 @@ import com.google.android.material.snackbar.Snackbar;
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
|
|
||||||
|
@ -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,
|
||||||
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"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".report.ErrorActivity">
|
tools:context=".error.ErrorActivity">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
layout="@layout/toolbar_layout"
|
layout="@layout/toolbar_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" />
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
@ -14,16 +13,6 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="90dp"
|
android:layout_marginTop="90dp" />
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:background="?attr/toolbar_shadow"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swiperefresh"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@+id/refresh_root_view">
|
android:layout_below="@+id/refresh_root_view">
|
||||||
|
@ -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,11 +104,10 @@
|
||||||
<!--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_centerInParent="true"
|
||||||
android:layout_marginTop="50dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.schabi.newpipe.report;
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package org.schabi.newpipe
|
package org.schabi.newpipe.error
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.schabi.newpipe.ReCaptchaActivity.YT_URL
|
|
||||||
|
|
||||||
class ReCaptchaActivityTest {
|
class ReCaptchaActivityTest {
|
||||||
private fun assertSanitized(expected: String, actual: String?) {
|
private fun assertSanitized(expected: String, actual: String?) {
|
||||||
|
@ -10,9 +9,9 @@ class ReCaptchaActivityTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun `null, empty or blank url is sanitized correctly`() {
|
@Test fun `null, empty or blank url is sanitized correctly`() {
|
||||||
assertSanitized(YT_URL, null)
|
assertSanitized(ReCaptchaActivity.YT_URL, null)
|
||||||
assertSanitized(YT_URL, "")
|
assertSanitized(ReCaptchaActivity.YT_URL, "")
|
||||||
assertSanitized(YT_URL, " \n \t ")
|
assertSanitized(ReCaptchaActivity.YT_URL, " \n \t ")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun `YouTube url containing pbj=1 is sanitized correctly`() {
|
@Test fun `YouTube url containing pbj=1 is sanitized correctly`() {
|
Loading…
Reference in New Issue