From 7e0ee4eb7adcc8b8bfb3127bd2f43a56827bb307 Mon Sep 17 00:00:00 2001 From: tobigr Date: Fri, 18 Jul 2025 11:00:20 +0200 Subject: [PATCH 1/6] Update Extractor and add migration to remove SoundCloud Top 50 kiosk --- app/build.gradle | 2 +- .../newpipe/settings/SettingMigrations.java | 29 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 652eeb792..094cc7b47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -214,7 +214,7 @@ dependencies { // the corresponding commit hash, since JitPack sometimes deletes artifacts. // If there’s already a git hash, just add more of it to the end (or remove a letter) // to cause jitpack to regenerate the artifact. - implementation 'com.github.TeamNewPipe:NewPipeExtractor:68b4c9acbae2d167e7b1209bb6bf0ae086dd427e' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:7adbc48a0aa872c016b8ec089e278d5e12772054' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index d731f2f5e..6bdfc287e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -12,13 +12,19 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.extractor.ServiceList; +import org.schabi.newpipe.settings.tabs.Tab; +import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.util.DeviceUtils; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; /** * In order to add a migration, follow these steps, given P is the previous version:
@@ -129,7 +135,7 @@ public final class SettingMigrations { } }; - public static final Migration MIGRATION_5_6 = new Migration(5, 6) { + private static final Migration MIGRATION_5_6 = new Migration(5, 6) { @Override protected void migrate(@NonNull final Context context) { final boolean loadImages = sp.getBoolean("download_thumbnail_key", true); @@ -143,6 +149,24 @@ public final class SettingMigrations { } }; + private static final Migration MIGRATION_6_7 = new Migration(6, 7) { + @Override + protected void migrate(@NonNull final Context context) { + // The SoundCloud Top 50 Kiosk was removed in the extractor, + // so we remove the corresponding tab if it exists. + final TabsManager tabsManager = TabsManager.getManager(context); + final List tabs = tabsManager.getTabs(); + final List cleanedTabs = tabs.stream() + .filter(tab -> !(tab instanceof Tab.KioskTab kioskTab + && kioskTab.getKioskServiceId() == SoundCloud.getServiceId() + && kioskTab.getKioskId().equals("Top 50"))) + .collect(Collectors.toUnmodifiableList()); + if (tabs.size() != cleanedTabs.size()) { + tabsManager.saveTabs(cleanedTabs); + } + } + }; + /** * List of all implemented migrations. *

@@ -156,12 +180,13 @@ public final class SettingMigrations { MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, + MIGRATION_6_7 }; /** * Version number for preferences. Must be incremented every time a migration is necessary. */ - private static final int VERSION = 6; + private static final int VERSION = 7; public static void runMigrationsIfNeeded(@NonNull final Context context) { From 941f85781b7ad63f97f1a2c21eb07bc907de9fb9 Mon Sep 17 00:00:00 2001 From: tobigr Date: Fri, 18 Jul 2025 12:13:51 +0200 Subject: [PATCH 2/6] Display dialog informing the user about the removal of the Top 50 kiosk --- .../java/org/schabi/newpipe/MainActivity.java | 2 ++ .../newpipe/settings/SettingMigrations.java | 33 ++++++++++++++++++- app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index b709c1107..ef0f78fc0 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -80,6 +80,7 @@ import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.settings.SettingMigrations; import org.schabi.newpipe.settings.UpdateSettingsFragment; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; @@ -197,6 +198,7 @@ public class MainActivity extends AppCompatActivity { } Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext()); + SettingMigrations.showUserInfoIfPresent(this); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index 6bdfc287e..88ab3eaeb 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -5,6 +5,8 @@ import android.content.SharedPreferences; import android.util.Log; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.util.Consumer; import androidx.preference.PreferenceManager; import org.schabi.newpipe.App; @@ -12,11 +14,11 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.util.DeviceUtils; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -38,6 +40,12 @@ public final class SettingMigrations { private static final String TAG = SettingMigrations.class.toString(); private static SharedPreferences sp; + /** + * List of UI actions that are performed after the UI is initialized + * to inform the user about changes that were applied by migrations. + */ + private static final List> MIGRATION_INFO = new ArrayList<>(); + private static final Migration MIGRATION_0_1 = new Migration(0, 1) { @Override public void migrate(@NonNull final Context context) { @@ -163,6 +171,14 @@ public final class SettingMigrations { .collect(Collectors.toUnmodifiableList()); if (tabs.size() != cleanedTabs.size()) { tabsManager.saveTabs(cleanedTabs); + // create an AlertDialog to inform the user about the change + MIGRATION_INFO.add((Context uiContext) -> new AlertDialog.Builder(uiContext) + .setTitle(R.string.migration_info_6_7_title) + .setMessage(R.string.migration_info_6_7_message) + .setPositiveButton(R.string.ok, null) + .setCancelable(false) + .create() + .show()); } } }; @@ -233,6 +249,21 @@ public final class SettingMigrations { sp.edit().putInt(lastPrefVersionKey, currentVersion).apply(); } + /** + * Perform UI actions informing about migrations that took place if they are present. + * @param context Context that can be used to show dialogs/snackbars/toasts + */ + public static void showUserInfoIfPresent(@NonNull final Context context) { + for (final Consumer consumer : MIGRATION_INFO) { + try { + consumer.accept(context); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(context, "Showing migration info to the user", e); + } + } + MIGRATION_INFO.clear(); + } + private SettingMigrations() { } abstract static class Migration { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c909a0632..ddf49c65b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -865,4 +865,6 @@ Show more Show less The settings in the export being imported use a vulnerable format that was deprecated since NewPipe 0.27.0. Make sure the export being imported is from a trusted source, and prefer using only exports obtained from NewPipe 0.27.0 or newer in the future. Support for importing settings in this vulnerable format will soon be removed completely, and then old versions of NewPipe will not be able to import settings of exports from new versions anymore. + SoundCloud Top 50 page removed + SoundCloud has discontinued the original Top 50 charts. The corresponding tab has been removed from your main page. From fe58ec85ed8013626a9bdde4e8cff864513086d3 Mon Sep 17 00:00:00 2001 From: tobigr Date: Fri, 18 Jul 2025 19:06:26 +0200 Subject: [PATCH 3/6] Fix error detection when loading main page tabs Do not crash if something unexpected happens. --- .../main/java/org/schabi/newpipe/fragments/MainFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 381de5003..4df905a31 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -37,7 +37,6 @@ import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.FragmentMainBinding; import org.schabi.newpipe.error.ErrorUtil; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; @@ -303,8 +302,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte final Fragment fragment; try { fragment = tab.getFragment(context); - } catch (final ExtractionException e) { + } catch (final Exception e) { ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e); + // TODO: show an error fragment instead return new BlankFragment(); } From 9697112db632a6b7b7076e32b803c3d9b9d250d0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 19 Jul 2025 19:41:13 +0200 Subject: [PATCH 4/6] Show error panel in EmptyFragment --- .../schabi/newpipe/error/ErrorPanelHelper.kt | 17 ++++---- .../org/schabi/newpipe/error/UserAction.java | 3 +- .../newpipe/fragments/BlankFragment.java | 40 ++++++++++++++++++- .../newpipe/fragments/MainFragment.java | 9 +++-- app/src/main/res/layout/fragment_blank.xml | 4 +- 5 files changed, 59 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt index fcc062102..14ec41148 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit class ErrorPanelHelper( private val fragment: Fragment, rootView: View, - onRetry: Runnable + onRetry: Runnable?, ) { private val context: Context = rootView.context!! @@ -56,12 +56,15 @@ class ErrorPanelHelper( errorPanelRoot.findViewById(R.id.error_open_in_browser) private var errorDisposable: Disposable? = null + private var retryShouldBeShown: Boolean = (onRetry != null) init { - errorDisposable = errorRetryButton.clicks() - .debounce(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { onRetry.run() } + if (onRetry != null) { + errorDisposable = errorRetryButton.clicks() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { onRetry.run() } + } } private fun ensureDefaultVisibility() { @@ -101,7 +104,7 @@ class ErrorPanelHelper( errorActionButton.setOnClickListener(null) } - errorRetryButton.isVisible = true + errorRetryButton.isVisible = retryShouldBeShown showAndSetOpenInBrowserButtonAction(errorInfo) } else if (errorInfo.throwable is AccountTerminatedException) { errorTextView.setText(R.string.account_terminated) @@ -130,7 +133,7 @@ class ErrorPanelHelper( errorInfo.throwable !is ContentNotSupportedException ) { // show retry button only for content which is not unavailable or unsupported - errorRetryButton.isVisible = true + errorRetryButton.isVisible = retryShouldBeShown } showAndSetOpenInBrowserButtonAction(errorInfo) } diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java index 6ca66e0d2..afb880a29 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -32,7 +32,8 @@ public enum UserAction { PREFERENCES_MIGRATION("migration of preferences"), SHARE_TO_NEWPIPE("share to newpipe"), CHECK_FOR_NEW_APP_VERSION("check for new app version"), - OPEN_INFO_ITEM_DIALOG("open info item dialog"); + OPEN_INFO_ITEM_DIALOG("open info item dialog"), + GETTING_MAIN_SCREEN_TAB("getting main screen tab"); private final String message; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java index fe4eef37a..6f879a7e1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java @@ -9,14 +9,52 @@ import androidx.annotation.Nullable; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorPanelHelper; public class BlankFragment extends BaseFragment { + + @Nullable + final ErrorInfo errorInfo; + @Nullable + ErrorPanelHelper errorPanel = null; + + /** + * Builds a blank fragment that just says the app name and suggests clicking on search. + */ + public BlankFragment() { + this(null); + } + + /** + * @param errorInfo if null acts like {@link BlankFragment}, else shows an error panel. + */ + public BlankFragment(@Nullable final ErrorInfo errorInfo) { + this.errorInfo = errorInfo; + } + @Nullable @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, final Bundle savedInstanceState) { setTitle("NewPipe"); - return inflater.inflate(R.layout.fragment_blank, container, false); + final View view = inflater.inflate(R.layout.fragment_blank, container, false); + if (errorInfo != null) { + errorPanel = new ErrorPanelHelper(this, view, null); + errorPanel.showError(errorInfo); + view.findViewById(R.id.blank_page_content).setVisibility(View.GONE); + } + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + if (errorPanel != null) { + errorPanel.dispose(); + errorPanel = null; + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 4df905a31..1a5e5aa45 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -36,7 +36,9 @@ import com.google.android.material.tabs.TabLayout; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.FragmentMainBinding; +import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; @@ -302,10 +304,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte final Fragment fragment; try { fragment = tab.getFragment(context); - } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e); - // TODO: show an error fragment instead - return new BlankFragment(); + } catch (final Throwable t) { + return new BlankFragment(new ErrorInfo(t, UserAction.GETTING_MAIN_SCREEN_TAB, + "Tab " + tab.getClass().getSimpleName() + ":" + tab.getTabName(context))); } if (fragment instanceof BaseFragment) { diff --git a/app/src/main/res/layout/fragment_blank.xml b/app/src/main/res/layout/fragment_blank.xml index 6c2978e95..4d874ebdb 100644 --- a/app/src/main/res/layout/fragment_blank.xml +++ b/app/src/main/res/layout/fragment_blank.xml @@ -4,7 +4,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + Date: Sat, 19 Jul 2025 20:34:09 +0200 Subject: [PATCH 5/6] Improve comment --- .../java/org/schabi/newpipe/settings/SettingMigrations.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index 88ab3eaeb..d13e73090 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -41,8 +41,8 @@ public final class SettingMigrations { private static SharedPreferences sp; /** - * List of UI actions that are performed after the UI is initialized - * to inform the user about changes that were applied by migrations. + * List of UI actions that are performed after the UI is initialized (e.g. showing alert + * dialogs) to inform the user about changes that were applied by migrations. */ private static final List> MIGRATION_INFO = new ArrayList<>(); From 991d9ea3dfcdb4d07e855a14cddc05dcbc97f3f6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 19 Jul 2025 20:39:55 +0200 Subject: [PATCH 6/6] Fix state not saved --- .../java/org/schabi/newpipe/fragments/BlankFragment.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java index 6f879a7e1..66e132aff 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java @@ -7,6 +7,8 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; +import com.evernote.android.state.State; + import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; @@ -14,8 +16,9 @@ import org.schabi.newpipe.error.ErrorPanelHelper; public class BlankFragment extends BaseFragment { + @State @Nullable - final ErrorInfo errorInfo; + ErrorInfo errorInfo; @Nullable ErrorPanelHelper errorPanel = null;