Merge branch 'dev' into pr9236

This commit is contained in:
Stypox 2024-03-28 18:43:29 +01:00
commit 37f7fa7ef4
No known key found for this signature in database
GPG key ID: 4BDF1B40A49FDD23
418 changed files with 12465 additions and 4342 deletions

View file

@ -12,7 +12,9 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.image.PreferredImageQuality;
import java.io.IOException;
@ -35,9 +37,11 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
.getPreferredContentCountry(requireContext());
initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en");
findPreference(getString(R.string.download_thumbnail_key)).setOnPreferenceChangeListener(
final Preference imageQualityPreference = requirePreference(R.string.image_quality_key);
imageQualityPreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
PicassoHelper.setShouldLoadImages((Boolean) newValue);
ImageStrategy.setPreferredImageQuality(PreferredImageQuality
.fromPreferenceKey(requireContext(), (String) newValue));
try {
PicassoHelper.clearCache(preference.getContext());
Toast.makeText(preference.getContext(),

View file

@ -10,7 +10,7 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import java.util.Optional;

View file

@ -23,7 +23,7 @@ public class MainSettingsFragment extends BasePreferenceFragment {
setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called
// Check if the app is updatable
if (!ReleaseVersionUtil.isReleaseApk()) {
if (!ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
getPreferenceScreen().removePreference(
findPreference(getString(R.string.update_pref_screen_key)));

View file

@ -19,7 +19,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.List;

View file

@ -25,7 +25,7 @@ import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import java.util.List;
import java.util.Vector;

View file

@ -128,6 +128,20 @@ public final class SettingMigrations {
}
};
public 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);
sp.edit()
.putString(context.getString(R.string.image_quality_key),
context.getString(loadImages
? R.string.image_quality_default
: R.string.image_quality_none_key))
.apply();
}
};
/**
* List of all implemented migrations.
* <p>
@ -140,12 +154,13 @@ public final class SettingMigrations {
MIGRATION_2_3,
MIGRATION_3_4,
MIGRATION_4_5,
MIGRATION_5_6,
};
/**
* Version number for preferences. Must be incremented every time a migration is necessary.
*/
private static final int VERSION = 5;
private static final int VERSION = 6;
public static void runMigrationsIfNeeded(@NonNull final Context context,

View file

@ -266,7 +266,7 @@ public class SettingsActivity extends AppCompatActivity implements
*/
private void ensureSearchRepresentsApplicationState() {
// Check if the update settings are available
if (!ReleaseVersionUtil.isReleaseApk()) {
if (!ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
SettingsResourceRegistry.getInstance()
.getEntryByPreferencesResId(R.xml.update_settings)
.setSearchable(false);

View file

@ -13,6 +13,7 @@ import androidx.preference.ListPreference;
import com.google.android.material.snackbar.Snackbar;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.PermissionHelper;
import java.util.LinkedList;
@ -26,7 +27,7 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
addPreferencesFromResourceRegistry();
updateSeekOptions();
updateResolutionOptions();
listener = (sharedPreferences, key) -> {
// on M and above, if user chooses to minimise to popup player on exit
@ -48,10 +49,84 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
}
} else if (getString(R.string.use_inexact_seek_key).equals(key)) {
updateSeekOptions();
} else if (getString(R.string.show_higher_resolutions_key).equals(key)) {
updateResolutionOptions();
}
};
}
/**
* Update default resolution, default popup resolution & mobile data resolution options.
* <br />
* Show high resolutions when "Show higher resolution" option is enabled.
* Set default resolution to "best resolution" when "Show higher resolution" option
* is disabled.
*/
private void updateResolutionOptions() {
final Resources resources = getResources();
final boolean showHigherResolutions = getPreferenceManager().getSharedPreferences()
.getBoolean(resources.getString(R.string.show_higher_resolutions_key), false);
// get sorted resolution lists
final List<String> resolutionListDescriptions = ListHelper.getSortedResolutionList(
resources,
R.array.resolution_list_description,
R.array.high_resolution_list_descriptions,
showHigherResolutions);
final List<String> resolutionListValues = ListHelper.getSortedResolutionList(
resources,
R.array.resolution_list_values,
R.array.high_resolution_list_values,
showHigherResolutions);
final List<String> limitDataUsageResolutionValues = ListHelper.getSortedResolutionList(
resources,
R.array.limit_data_usage_values_list,
R.array.high_resolution_limit_data_usage_values_list,
showHigherResolutions);
final List<String> limitDataUsageResolutionDescriptions = ListHelper
.getSortedResolutionList(resources,
R.array.limit_data_usage_description_list,
R.array.high_resolution_list_descriptions,
showHigherResolutions);
// get resolution preferences
final ListPreference defaultResolution = findPreference(
getString(R.string.default_resolution_key));
final ListPreference defaultPopupResolution = findPreference(
getString(R.string.default_popup_resolution_key));
final ListPreference mobileDataResolution = findPreference(
getString(R.string.limit_mobile_data_usage_key));
// update resolution preferences with new resolutions, entries & values for each
defaultResolution.setEntries(resolutionListDescriptions.toArray(new String[0]));
defaultResolution.setEntryValues(resolutionListValues.toArray(new String[0]));
defaultPopupResolution.setEntries(resolutionListDescriptions.toArray(new String[0]));
defaultPopupResolution.setEntryValues(resolutionListValues.toArray(new String[0]));
mobileDataResolution.setEntries(
limitDataUsageResolutionDescriptions.toArray(new String[0]));
mobileDataResolution.setEntryValues(limitDataUsageResolutionValues.toArray(new String[0]));
// if "Show higher resolution" option is disabled,
// set default resolution to "best resolution"
if (!showHigherResolutions) {
if (ListHelper.isHighResolutionSelected(defaultResolution.getValue(),
R.array.high_resolution_list_values,
resources)) {
defaultResolution.setValueIndex(0);
}
if (ListHelper.isHighResolutionSelected(defaultPopupResolution.getValue(),
R.array.high_resolution_list_values,
resources)) {
defaultPopupResolution.setValueIndex(0);
}
if (ListHelper.isHighResolutionSelected(mobileDataResolution.getValue(),
R.array.high_resolution_limit_data_usage_values_list,
resources)) {
mobileDataResolution.setValueIndex(0);
}
}
}
/**
* Update fast-forward/-rewind seek duration options
* according to language and inexact seek setting.

View file

@ -5,35 +5,22 @@ import static org.schabi.newpipe.player.notification.NotificationConstants.ACTIO
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.widget.TextViewCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.player.notification.NotificationConstants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
@ -45,8 +32,9 @@ public class NotificationActionsPreference extends Preference {
}
@Nullable private NotificationSlot[] notificationSlots = null;
@Nullable private List<Integer> compactSlots = null;
private NotificationSlot[] notificationSlots;
private List<Integer> compactSlots;
////////////////////////////////////////////////////////////////////////////
// Lifecycle
@ -56,6 +44,11 @@ public class NotificationActionsPreference extends Preference {
public void onBindViewHolder(@NonNull final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
((TextView) holder.itemView.findViewById(R.id.summary))
.setText(R.string.notification_actions_summary_android13);
}
holder.itemView.setClickable(false);
setupActions(holder.itemView);
}
@ -75,13 +68,29 @@ public class NotificationActionsPreference extends Preference {
////////////////////////////////////////////////////////////////////////////
private void setupActions(@NonNull final View view) {
compactSlots = NotificationConstants.getCompactSlotsFromPreferences(getContext(),
getSharedPreferences(), 5);
compactSlots = new ArrayList<>(NotificationConstants.getCompactSlotsFromPreferences(
getContext(), getSharedPreferences()));
notificationSlots = IntStream.range(0, 5)
.mapToObj(i -> new NotificationSlot(i, view))
.mapToObj(i -> new NotificationSlot(getContext(), getSharedPreferences(), i, view,
compactSlots.contains(i), this::onToggleCompactSlot))
.toArray(NotificationSlot[]::new);
}
private void onToggleCompactSlot(final int i, final CheckBox checkBox) {
if (checkBox.isChecked()) {
compactSlots.remove((Integer) i);
} else if (compactSlots.size() < 3) {
compactSlots.add(i);
} else {
Toast.makeText(getContext(),
R.string.notification_actions_at_most_three,
Toast.LENGTH_SHORT).show();
return;
}
checkBox.toggle();
}
////////////////////////////////////////////////////////////////////////////
// Saving
@ -99,143 +108,10 @@ public class NotificationActionsPreference extends Preference {
for (int i = 0; i < 5; i++) {
editor.putInt(getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
notificationSlots[i].selectedAction);
notificationSlots[i].getSelectedAction());
}
editor.apply();
}
}
////////////////////////////////////////////////////////////////////////////
// Notification action
////////////////////////////////////////////////////////////////////////////
private static final int[] SLOT_ITEMS = {
R.id.notificationAction0,
R.id.notificationAction1,
R.id.notificationAction2,
R.id.notificationAction3,
R.id.notificationAction4,
};
private static final int[] SLOT_TITLES = {
R.string.notification_action_0_title,
R.string.notification_action_1_title,
R.string.notification_action_2_title,
R.string.notification_action_3_title,
R.string.notification_action_4_title,
};
private class NotificationSlot {
final int i;
@NotificationConstants.Action int selectedAction;
ImageView icon;
TextView summary;
NotificationSlot(final int actionIndex, final View parentView) {
this.i = actionIndex;
final View view = parentView.findViewById(SLOT_ITEMS[i]);
setupSelectedAction(view);
setupTitle(view);
setupCheckbox(view);
}
void setupTitle(final View view) {
((TextView) view.findViewById(R.id.notificationActionTitle))
.setText(SLOT_TITLES[i]);
view.findViewById(R.id.notificationActionClickableArea).setOnClickListener(
v -> openActionChooserDialog());
}
void setupCheckbox(final View view) {
final CheckBox compactSlotCheckBox = view.findViewById(R.id.notificationActionCheckBox);
compactSlotCheckBox.setChecked(compactSlots.contains(i));
view.findViewById(R.id.notificationActionCheckBoxClickableArea).setOnClickListener(
v -> {
if (compactSlotCheckBox.isChecked()) {
compactSlots.remove((Integer) i);
} else if (compactSlots.size() < 3) {
compactSlots.add(i);
} else {
Toast.makeText(getContext(),
R.string.notification_actions_at_most_three,
Toast.LENGTH_SHORT).show();
return;
}
compactSlotCheckBox.toggle();
});
}
void setupSelectedAction(final View view) {
icon = view.findViewById(R.id.notificationActionIcon);
summary = view.findViewById(R.id.notificationActionSummary);
selectedAction = getSharedPreferences().getInt(
getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
NotificationConstants.SLOT_DEFAULTS[i]);
updateInfo();
}
void updateInfo() {
if (NotificationConstants.ACTION_ICONS[selectedAction] == 0) {
icon.setImageDrawable(null);
} else {
icon.setImageDrawable(AppCompatResources.getDrawable(getContext(),
NotificationConstants.ACTION_ICONS[selectedAction]));
}
summary.setText(NotificationConstants.getActionName(getContext(), selectedAction));
}
void openActionChooserDialog() {
final LayoutInflater inflater = LayoutInflater.from(getContext());
final SingleChoiceDialogViewBinding binding =
SingleChoiceDialogViewBinding.inflate(inflater);
final AlertDialog alertDialog = new AlertDialog.Builder(getContext())
.setTitle(SLOT_TITLES[i])
.setView(binding.getRoot())
.setCancelable(true)
.create();
final View.OnClickListener radioButtonsClickListener = v -> {
selectedAction = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][v.getId()];
updateInfo();
alertDialog.dismiss();
};
for (int id = 0; id < NotificationConstants.SLOT_ALLOWED_ACTIONS[i].length; ++id) {
final int action = NotificationConstants.SLOT_ALLOWED_ACTIONS[i][id];
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater)
.getRoot();
// if present set action icon with correct color
final int iconId = NotificationConstants.ACTION_ICONS[action];
if (iconId != 0) {
radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconId, 0);
final var color = ColorStateList.valueOf(ThemeHelper
.resolveColorFromAttr(getContext(), android.R.attr.textColorPrimary));
TextViewCompat.setCompoundDrawableTintList(radioButton, color);
}
radioButton.setText(NotificationConstants.getActionName(getContext(), action));
radioButton.setChecked(action == selectedAction);
radioButton.setId(id);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener);
binding.list.addView(radioButton);
}
alertDialog.show();
if (DeviceUtils.isTv(getContext())) {
FocusOverlayView.setupFocusObserver(alertDialog);
}
}
}
}

View file

@ -0,0 +1,172 @@
package org.schabi.newpipe.settings.custom;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.widget.TextViewCompat;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
import org.schabi.newpipe.player.notification.NotificationConstants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
import java.util.Objects;
import java.util.function.BiConsumer;
class NotificationSlot {
private static final int[] SLOT_ITEMS = {
R.id.notificationAction0,
R.id.notificationAction1,
R.id.notificationAction2,
R.id.notificationAction3,
R.id.notificationAction4,
};
private static final int[] SLOT_TITLES = {
R.string.notification_action_0_title,
R.string.notification_action_1_title,
R.string.notification_action_2_title,
R.string.notification_action_3_title,
R.string.notification_action_4_title,
};
private final int i;
private @NotificationConstants.Action int selectedAction;
private final Context context;
private final BiConsumer<Integer, CheckBox> onToggleCompactSlot;
private ImageView icon;
private TextView summary;
NotificationSlot(final Context context,
final SharedPreferences prefs,
final int actionIndex,
final View parentView,
final boolean isCompactSlotChecked,
final BiConsumer<Integer, CheckBox> onToggleCompactSlot) {
this.context = context;
this.i = actionIndex;
this.onToggleCompactSlot = onToggleCompactSlot;
selectedAction = Objects.requireNonNull(prefs).getInt(
context.getString(NotificationConstants.SLOT_PREF_KEYS[i]),
NotificationConstants.SLOT_DEFAULTS[i]);
final View view = parentView.findViewById(SLOT_ITEMS[i]);
// only show the last two notification slots on Android 13+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || i >= 3) {
setupSelectedAction(view);
setupTitle(view);
setupCheckbox(view, isCompactSlotChecked);
} else {
view.setVisibility(View.GONE);
}
}
void setupTitle(final View view) {
((TextView) view.findViewById(R.id.notificationActionTitle))
.setText(SLOT_TITLES[i]);
view.findViewById(R.id.notificationActionClickableArea).setOnClickListener(
v -> openActionChooserDialog());
}
void setupCheckbox(final View view, final boolean isCompactSlotChecked) {
final CheckBox compactSlotCheckBox = view.findViewById(R.id.notificationActionCheckBox);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// there are no compact slots to customize on Android 13+
compactSlotCheckBox.setVisibility(View.GONE);
view.findViewById(R.id.notificationActionCheckBoxClickableArea)
.setVisibility(View.GONE);
return;
}
compactSlotCheckBox.setChecked(isCompactSlotChecked);
view.findViewById(R.id.notificationActionCheckBoxClickableArea).setOnClickListener(
v -> onToggleCompactSlot.accept(i, compactSlotCheckBox));
}
void setupSelectedAction(final View view) {
icon = view.findViewById(R.id.notificationActionIcon);
summary = view.findViewById(R.id.notificationActionSummary);
updateInfo();
}
void updateInfo() {
if (NotificationConstants.ACTION_ICONS[selectedAction] == 0) {
icon.setImageDrawable(null);
} else {
icon.setImageDrawable(AppCompatResources.getDrawable(context,
NotificationConstants.ACTION_ICONS[selectedAction]));
}
summary.setText(NotificationConstants.getActionName(context, selectedAction));
}
void openActionChooserDialog() {
final LayoutInflater inflater = LayoutInflater.from(context);
final SingleChoiceDialogViewBinding binding =
SingleChoiceDialogViewBinding.inflate(inflater);
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setTitle(SLOT_TITLES[i])
.setView(binding.getRoot())
.setCancelable(true)
.create();
final View.OnClickListener radioButtonsClickListener = v -> {
selectedAction = NotificationConstants.ALL_ACTIONS[v.getId()];
updateInfo();
alertDialog.dismiss();
};
for (int id = 0; id < NotificationConstants.ALL_ACTIONS.length; ++id) {
final int action = NotificationConstants.ALL_ACTIONS[id];
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater)
.getRoot();
// if present set action icon with correct color
final int iconId = NotificationConstants.ACTION_ICONS[action];
if (iconId != 0) {
radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconId, 0);
final var color = ColorStateList.valueOf(ThemeHelper
.resolveColorFromAttr(context, android.R.attr.textColorPrimary));
TextViewCompat.setCompoundDrawableTintList(radioButton, color);
}
radioButton.setText(NotificationConstants.getActionName(context, action));
radioButton.setChecked(action == selectedAction);
radioButton.setId(id);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
radioButton.setOnClickListener(radioButtonsClickListener);
binding.list.addView(radioButton);
}
alertDialog.show();
if (DeviceUtils.isTv(context)) {
FocusOverlayView.setupFocusObserver(alertDialog);
}
}
@NotificationConstants.Action
public int getSelectedAction() {
return selectedAction;
}
}