1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-04-16 03:41:24 +00:00

Merge branch 'dev' into Merge-dev-to-refactor

# Conflicts:
#	app/build.gradle
#	app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java
#	app/src/main/res/values/strings.xml
This commit is contained in:
Isira Seneviratne
2025-07-20 05:56:30 +05:30
29 changed files with 170 additions and 142 deletions

View File

@@ -76,6 +76,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;
@@ -193,6 +194,7 @@ public class MainActivity extends AppCompatActivity {
}
Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
SettingMigrations.showUserInfoIfPresent(this);
}
@Override

View File

@@ -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)
}

View File

@@ -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;

View File

@@ -7,16 +7,57 @@ 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;
import org.schabi.newpipe.error.ErrorPanelHelper;
public class BlankFragment extends BaseFragment {
@State
@Nullable
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

View File

@@ -36,8 +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.extractor.exceptions.ExtractionException;
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;
@@ -303,9 +304,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
final Fragment fragment;
try {
fragment = tab.getFragment(context);
} catch (final ExtractionException e) {
ErrorUtil.showUiErrorSnackbar(context, "Getting fragment item", e);
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) {

View File

@@ -1,12 +1,12 @@
package org.schabi.newpipe.settings;
import static org.schabi.newpipe.MainActivity.DEBUG;
import android.content.Context;
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;
@@ -14,11 +14,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.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;
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:<br>
@@ -32,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 (e.g. showing alert
* dialogs) to inform the user about changes that were applied by migrations.
*/
private static final List<Consumer<Context>> MIGRATION_INFO = new ArrayList<>();
private static final Migration MIGRATION_0_1 = new Migration(0, 1) {
@Override
public void migrate(@NonNull final Context context) {
@@ -129,7 +143,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 +157,32 @@ 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<Tab> tabs = tabsManager.getTabs();
final List<Tab> 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);
// 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());
}
}
};
/**
* List of all implemented migrations.
* <p>
@@ -156,12 +196,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) {
@@ -208,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<Context> 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 {