diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java index 43305c23c..a67639cc0 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java @@ -10,6 +10,8 @@ import android.util.AttributeSet; import android.widget.Button; import android.widget.EditText; +import androidx.annotation.Nullable; + import java.util.Objects; import app.revanced.extension.shared.settings.Setting; @@ -19,6 +21,12 @@ import app.revanced.extension.shared.utils.Utils; @SuppressWarnings({"unused", "deprecation"}) public class ResettableEditTextPreference extends EditTextPreference { + /** + * Setting to reset. + */ + @Nullable + private Setting setting; + public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -35,6 +43,10 @@ public class ResettableEditTextPreference extends EditTextPreference { super(context); } + public void setSetting(@Nullable Setting setting) { + this.setting = setting; + } + @Override protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { Utils.setEditTextDialogTheme(builder); @@ -44,7 +56,12 @@ public class ResettableEditTextPreference extends EditTextPreference { if (title != null) { builder.setTitle(getTitle()); } - final Setting setting = Setting.getSettingFromPath(getKey()); + if (setting == null) { + String key = getKey(); + if (key != null) { + setting = Setting.getSettingFromPath(key); + } + } if (setting != null) { builder.setNeutralButton(str("revanced_extended_settings_reset"), null); } @@ -65,8 +82,7 @@ public class ResettableEditTextPreference extends EditTextPreference { } button.setOnClickListener(v -> { try { - Setting setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey())); - String defaultStringValue = setting.defaultValue.toString(); + String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString(); EditText editText = getEditText(); editText.setText(defaultStringValue); editText.setSelection(defaultStringValue.length()); // move cursor to end of text diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 8be012903..b5bc6994e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -618,24 +618,34 @@ public class Settings extends BaseSettings { public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY.reVancedKeyValue); public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#00D400"); + public static final FloatSetting SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f); public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", SKIP_AUTOMATICALLY.reVancedKeyValue); public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00"); + public static final FloatSetting SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f); public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF"); + public static final FloatSetting SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f); public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684"); + public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f); public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF"); + public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f); public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED"); + public static final FloatSetting SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f); public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6"); + public static final FloatSetting SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f); public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF"); + public static final FloatSetting SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f); public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900"); + public static final FloatSetting SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f); public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue); public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF"); + public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f); // SB Setting not exported public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java index b94ee3135..c62353d60 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java @@ -1,6 +1,7 @@ package app.revanced.extension.youtube.settings.preference; import static app.revanced.extension.shared.utils.StringRef.str; +import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor; import android.app.AlertDialog; import android.content.Context; @@ -12,11 +13,10 @@ import android.text.InputType; import android.text.TextWatcher; import android.util.AttributeSet; import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.TableLayout; -import android.widget.TableRow; +import android.widget.GridLayout; import android.widget.TextView; +import java.util.Locale; import java.util.Objects; import app.revanced.extension.shared.utils.Logger; @@ -27,26 +27,38 @@ import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory; @SuppressWarnings({"unused", "deprecation"}) public class SegmentCategoryListPreference extends ListPreference { - private SegmentCategory mCategory; - private EditText mEditText; - private int mClickedDialogEntryIndex; + private SegmentCategory category; + private TextView colorDotView; + private EditText colorEditText; + private EditText opacityEditText; + /** + * #RRGGBB + */ + private int categoryColor; + /** + * [0, 1] + */ + private float categoryOpacity; + private int selectedDialogEntryIndex; private void init() { final SegmentCategory segmentCategory = SegmentCategory.byCategoryKey(getKey()); - final boolean isHighlightCategory = segmentCategory == SegmentCategory.HIGHLIGHT; - mCategory = Objects.requireNonNull(segmentCategory); + category = Objects.requireNonNull(segmentCategory); + // Edit: Using preferences to sync together multiple pieces - // of code together is messy and should be rethought. + // of code is messy and should be rethought. setKey(segmentCategory.behaviorSetting.key); setDefaultValue(segmentCategory.behaviorSetting.defaultValue); + final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT; setEntries(isHighlightCategory ? CategoryBehaviour.getBehaviorDescriptionsWithoutSkipOnce() : CategoryBehaviour.getBehaviorDescriptions()); setEntryValues(isHighlightCategory ? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce() : CategoryBehaviour.getBehaviorKeyValues()); - updateTitle(); + + updateTitleFromCategory(); } public SegmentCategoryListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -73,28 +85,41 @@ public class SegmentCategoryListPreference extends ListPreference { protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { try { Utils.setEditTextDialogTheme(builder); - super.onPrepareDialogBuilder(builder); + + categoryColor = category.getColorNoOpacity(); + categoryOpacity = category.getOpacity(); Context context = builder.getContext(); - TableLayout table = new TableLayout(context); - table.setOrientation(LinearLayout.HORIZONTAL); - table.setPadding(70, 0, 150, 0); - - TableRow row = new TableRow(context); + GridLayout gridLayout = new GridLayout(context); + gridLayout.setPadding(70, 0, 150, 0); // Padding for the entire layout. + gridLayout.setColumnCount(3); + gridLayout.setRowCount(2); + GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(0); // First row. + gridParams.columnSpec = GridLayout.spec(0); // First column. TextView colorTextLabel = new TextView(context); colorTextLabel.setText(str("revanced_sb_color_dot_label")); - row.addView(colorTextLabel); + colorTextLabel.setLayoutParams(gridParams); + gridLayout.addView(colorTextLabel); - TextView colorDotView = new TextView(context); - colorDotView.setText(mCategory.getCategoryColorDot()); - colorDotView.setPadding(30, 0, 30, 0); - row.addView(colorDotView); + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(0); // First row. + gridParams.columnSpec = GridLayout.spec(1); // Second column. + gridParams.setMargins(0, 0, 10, 0); + colorDotView = new TextView(context); + colorDotView.setLayoutParams(gridParams); + gridLayout.addView(colorDotView); + updateCategoryColorDot(); - mEditText = new EditText(context); - mEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); - mEditText.setText(mCategory.colorString()); - mEditText.addTextChangedListener(new TextWatcher() { + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(0); // First row. + gridParams.columnSpec = GridLayout.spec(2); // Third column. + colorEditText = new EditText(context); + colorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); + colorEditText.setTextLocale(Locale.US); + colorEditText.setText(category.getColorString()); + colorEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @@ -104,44 +129,111 @@ public class SegmentCategoryListPreference extends ListPreference { } @Override - public void afterTextChanged(Editable s) { + public void afterTextChanged(Editable edit) { try { - String colorString = s.toString(); + String colorString = edit.toString(); + final int colorStringLength = colorString.length(); + if (!colorString.startsWith("#")) { - s.insert(0, "#"); // recursively calls back into this method + edit.insert(0, "#"); // Recursively calls back into this method. return; } - if (colorString.length() > 7) { - s.delete(7, colorString.length()); + + final int maxColorStringLength = 7; // #RRGGBB + if (colorStringLength > maxColorStringLength) { + edit.delete(maxColorStringLength, colorStringLength); return; } - final int color = Color.parseColor(colorString); - colorDotView.setText(SegmentCategory.getCategoryColorDot(color)); + + categoryColor = Color.parseColor(colorString); + updateCategoryColorDot(); } catch (IllegalArgumentException ex) { - // ignore + // Ignore. } } }); - mEditText.setLayoutParams(new TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1f)); - row.addView(mEditText); + colorEditText.setLayoutParams(gridParams); + gridLayout.addView(colorEditText); - table.addView(row); - builder.setView(table); - builder.setTitle(mCategory.title.toString()); + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(1); // Second row. + gridParams.columnSpec = GridLayout.spec(0, 1); // First and second column. + TextView opacityLabel = new TextView(context); + opacityLabel.setText(str("revanced_sb_color_opacity_label")); + opacityLabel.setLayoutParams(gridParams); + gridLayout.addView(opacityLabel); + + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(1); // Second row. + gridParams.columnSpec = GridLayout.spec(2); // Third column. + opacityEditText = new EditText(context); + opacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); + opacityEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable edit) { + try { + String editString = edit.toString(); + final int opacityStringLength = editString.length(); + + final int maxOpacityStringLength = 4; // [0.00, 1.00] + if (opacityStringLength > maxOpacityStringLength) { + edit.delete(maxOpacityStringLength, opacityStringLength); + return; + } + + final float opacity = opacityStringLength == 0 + ? 0 + : Float.parseFloat(editString); + if (opacity < 0) { + categoryOpacity = 0; + edit.replace(0, opacityStringLength, "0"); + return; + } else if (opacity > 1.0f) { + categoryOpacity = 1; + edit.replace(0, opacityStringLength, "1.0"); + return; + } else if (!editString.endsWith(".")) { + // Ignore "0." and "1." until the user finishes entering a valid number. + categoryOpacity = opacity; + } + + updateCategoryColorDot(); + } catch (NumberFormatException ex) { + // Should never happen. + Logger.printException(() -> "Could not parse opacity string", ex); + } + } + }); + opacityEditText.setLayoutParams(gridParams); + gridLayout.addView(opacityEditText); + updateOpacityText(); + + builder.setView(gridLayout); + builder.setTitle(category.title.toString()); builder.setPositiveButton(android.R.string.ok, (dialog, which) -> onClick(dialog, DialogInterface.BUTTON_POSITIVE)); builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> { try { - mCategory.resetColor(); - updateTitle(); + category.resetColorAndOpacity(); + updateTitleFromCategory(); Utils.showToastShort(str("revanced_sb_color_reset")); } catch (Exception ex) { Logger.printException(() -> "setNeutralButton failure", ex); } }); builder.setNegativeButton(android.R.string.cancel, null); - mClickedDialogEntryIndex = findIndexOfValue(getValue()); - builder.setSingleChoiceItems(getEntries(), mClickedDialogEntryIndex, (dialog, which) -> mClickedDialogEntryIndex = which); + + selectedDialogEntryIndex = findIndexOfValue(getValue()); + builder.setSingleChoiceItems(getEntries(), selectedDialogEntryIndex, + (dialog, which) -> selectedDialogEntryIndex = which); } catch (Exception ex) { Logger.printException(() -> "onPrepareDialogBuilder failure", ex); } @@ -150,31 +242,51 @@ public class SegmentCategoryListPreference extends ListPreference { @Override protected void onDialogClosed(boolean positiveResult) { try { - if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) { - String value = getEntryValues()[mClickedDialogEntryIndex].toString(); + if (positiveResult && selectedDialogEntryIndex >= 0 && getEntryValues() != null) { + String value = getEntryValues()[selectedDialogEntryIndex].toString(); if (callChangeListener(value)) { setValue(value); - mCategory.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value))); + category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value))); SegmentCategory.updateEnabledCategories(); } - String colorString = mEditText.getText().toString(); try { - if (!colorString.equals(mCategory.colorString())) { - mCategory.setColor(colorString); + String colorString = colorEditText.getText().toString(); + if (!colorString.equals(category.getColorString()) || categoryOpacity != category.getOpacity()) { + category.setColor(colorString); + category.setOpacity(categoryOpacity); Utils.showToastShort(str("revanced_sb_color_changed")); } } catch (IllegalArgumentException ex) { Utils.showToastShort(str("revanced_sb_color_invalid")); } - updateTitle(); + + updateTitleFromCategory(); } } catch (Exception ex) { Logger.printException(() -> "onDialogClosed failure", ex); } } - private void updateTitle() { - setTitle(mCategory.getTitleWithColorDot()); + private void applyOpacityToCategoryColor() { + categoryColor = applyOpacityToColor(categoryColor, categoryOpacity); + } + + private void updateTitleFromCategory() { + categoryColor = category.getColorNoOpacity(); + categoryOpacity = category.getOpacity(); + applyOpacityToCategoryColor(); + + setTitle(category.getTitleWithColorDot(categoryColor)); setEnabled(Settings.SB_ENABLED.get()); } + + private void updateCategoryColorDot() { + applyOpacityToCategoryColor(); + + colorDotView.setText(SegmentCategory.getCategoryColorDot(categoryColor)); + } + + private void updateOpacityText() { + opacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity)); + } } \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java index d4228f3ec..11a588fba 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java @@ -139,7 +139,7 @@ public class SponsorBlockSettings { for (SegmentCategory category : categories) { JSONObject categoryObject = new JSONObject(); String categoryKey = category.keyValue; - categoryObject.put("color", category.colorString()); + categoryObject.put("color", category.getColorString()); barTypesObject.put(categoryKey, categoryObject); if (category.behaviour != CategoryBehaviour.IGNORE) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java index 56dc52977..3d4da44e9 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java @@ -6,7 +6,12 @@ import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.text.Html; +import android.graphics.Color; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.widget.EditText; import androidx.annotation.NonNull; @@ -33,10 +38,9 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController /** * Not thread safe. All fields/methods must be accessed from the main thread. * - * @noinspection deprecation */ public class SponsorBlockUtils { - private static final String LOCKED_COLOR = "#FFC83D"; + private static final int LOCKED_COLOR = Color.parseColor("#FFC83D"); private static final String MANUAL_EDIT_TIME_TEXT_HINT = "hh:mm:ss.sss"; private static final Pattern manualEditTimePattern @@ -162,28 +166,34 @@ public class SponsorBlockUtils { SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT) ? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category : SegmentVote.values(); - CharSequence[] items = new CharSequence[voteOptions.length]; + final int voteOptionsLength = voteOptions.length; + final boolean userIsVip = Settings.SB_USER_IS_VIP.get(); + CharSequence[] items = new CharSequence[voteOptionsLength]; - for (int i = 0; i < voteOptions.length; i++) { + for (int i = 0; i < voteOptionsLength; i++) { SegmentVote voteOption = voteOptions[i]; - String title = voteOption.title.toString(); - if (Settings.SB_USER_IS_VIP.get() && segment.isLocked && voteOption.shouldHighlight) { - items[i] = Html.fromHtml(String.format("%s", LOCKED_COLOR, title)); - } else { - items[i] = title; + CharSequence title = voteOption.title.toString(); + if (userIsVip && segment.isLocked && voteOption.highlightIfVipAndVideoIsLocked) { + SpannableString coloredTitle = new SpannableString(title); + coloredTitle.setSpan(new ForegroundColorSpan(LOCKED_COLOR), + 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + title = coloredTitle; } + items[i] = title; } - new AlertDialog.Builder(context) - .setItems(items, (dialog1, which1) -> { - SegmentVote voteOption = voteOptions[which1]; - switch (voteOption) { - case UPVOTE, DOWNVOTE -> - SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption); - case CATEGORY_CHANGE -> onNewCategorySelect(segment, context); - } - }) - .show(); + new AlertDialog.Builder(context).setItems(items, (dialog1, which1) -> { + SegmentVote voteOption = voteOptions[which1]; + switch (voteOption) { + case UPVOTE: + case DOWNVOTE: + SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption); + break; + case CATEGORY_CHANGE: + onNewCategorySelect(segment, context); + break; + } + }).show(); } catch (Exception ex) { Logger.printException(() -> "segmentVoteClickListener failure", ex); } @@ -287,22 +297,33 @@ public class SponsorBlockUtils { if (segment.category == SegmentCategory.UNSUBMITTED) { continue; } - StringBuilder htmlBuilder = new StringBuilder(); - htmlBuilder.append(String.format(" %s
", - segment.category.color, segment.category.title)); - htmlBuilder.append(formatSegmentTime(segment.start)); - if (segment.category != SegmentCategory.HIGHLIGHT) { - htmlBuilder.append(" to ").append(formatSegmentTime(segment.end)); + + SpannableStringBuilder spannableBuilder = new SpannableStringBuilder(); + + spannableBuilder.append(segment.category.getTitleWithColorDot()); + spannableBuilder.append('\n'); + + String startTime = formatSegmentTime(segment.start); + if (segment.category == SegmentCategory.HIGHLIGHT) { + spannableBuilder.append(startTime); + } else { + String toFromString = str("revanced_sb_vote_segment_time_to_from", + startTime, formatSegmentTime(segment.end)); + spannableBuilder.append(toFromString); } - htmlBuilder.append("
"); - if (i + 1 != numberOfSegments) // prevents trailing new line after last segment - htmlBuilder.append("
"); - titles[i] = Html.fromHtml(htmlBuilder.toString()); + + if (i + 1 != numberOfSegments) { + // prevents trailing new line after last segment + spannableBuilder.append('\n'); + } + + spannableBuilder.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), + 0, spannableBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + titles[i] = spannableBuilder; } - new AlertDialog.Builder(context) - .setItems(titles, segmentVoteClickListener) - .show(); + new AlertDialog.Builder(context).setItems(titles, segmentVoteClickListener).show(); } catch (Exception ex) { Logger.printException(() -> "onVotingClicked failure", ex); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java index 3d1e90f66..fd8360a95 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java @@ -1,32 +1,14 @@ package app.revanced.extension.youtube.sponsorblock.objects; import static app.revanced.extension.shared.utils.StringRef.sf; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR_COLOR; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED; -import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED_COLOR; +import static app.revanced.extension.youtube.settings.Settings.*; import android.graphics.Color; import android.graphics.Paint; -import android.text.Html; -import android.text.Spanned; +import android.text.Spannable; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -34,44 +16,46 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; +import app.revanced.extension.shared.settings.FloatSetting; import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.StringRef; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.settings.Settings; -@SuppressWarnings({"deprecation", "StaticFieldLeak"}) +@SuppressWarnings("StaticFieldLeak") public enum SegmentCategory { SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_skip_button_sponsor"), sf("revanced_sb_skipped_sponsor"), - SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR), + SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR, SB_CATEGORY_SPONSOR_OPACITY), SELF_PROMO("selfpromo", sf("revanced_sb_segments_selfpromo"), sf("revanced_sb_skip_button_selfpromo"), sf("revanced_sb_skipped_selfpromo"), - SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR), + SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR, SB_CATEGORY_SELF_PROMO_OPACITY), INTERACTION("interaction", sf("revanced_sb_segments_interaction"), sf("revanced_sb_skip_button_interaction"), sf("revanced_sb_skipped_interaction"), - SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR), + SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR, SB_CATEGORY_INTERACTION_OPACITY), /** * Unique category that is treated differently than the rest. */ HIGHLIGHT("poi_highlight", sf("revanced_sb_segments_highlight"), sf("revanced_sb_skip_button_highlight"), sf("revanced_sb_skipped_highlight"), - SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR), + SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR, SB_CATEGORY_HIGHLIGHT_OPACITY), INTRO("intro", sf("revanced_sb_segments_intro"), sf("revanced_sb_skip_button_intro_beginning"), sf("revanced_sb_skip_button_intro_middle"), sf("revanced_sb_skip_button_intro_end"), sf("revanced_sb_skipped_intro_beginning"), sf("revanced_sb_skipped_intro_middle"), sf("revanced_sb_skipped_intro_end"), - SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR), + SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR, SB_CATEGORY_INTRO_OPACITY), OUTRO("outro", sf("revanced_sb_segments_outro"), sf("revanced_sb_skip_button_outro"), sf("revanced_sb_skipped_outro"), - SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR), + SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR, SB_CATEGORY_OUTRO_OPACITY), PREVIEW("preview", sf("revanced_sb_segments_preview"), sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"), sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"), - SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR), + SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY), FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"), - SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR), + SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY), MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"), - SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR), + SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY), UNSUBMITTED("unsubmitted", StringRef.empty, sf("revanced_sb_skip_button_unsubmitted"), sf("revanced_sb_skipped_unsubmitted"), - SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR), + SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR, SB_CATEGORY_UNSUBMITTED_OPACITY); ; private static final StringRef skipSponsorTextCompact = sf("revanced_sb_skip_button_compact"); @@ -111,12 +95,10 @@ public enum SegmentCategory { mValuesMap.put(value.keyValue, value); } - @NonNull public static SegmentCategory[] categoriesWithoutUnsubmitted() { return categoriesWithoutUnsubmitted; } - @NonNull public static SegmentCategory[] categoriesWithoutHighlights() { return categoriesWithoutHighlights; } @@ -127,7 +109,7 @@ public enum SegmentCategory { } /** - * Must be called if behavior of any category is changed + * Must be called if behavior of any category is changed. */ public static void updateEnabledCategories() { Utils.verifyOnMainThread(); @@ -154,30 +136,32 @@ public enum SegmentCategory { updateEnabledCategories(); } - @NonNull - public final String keyValue; - @NonNull - public final StringSetting behaviorSetting; - @NonNull - private final StringSetting colorSetting; + public static int applyOpacityToColor(int color, float opacity) { + if (opacity < 0 || opacity > 1.0f) { + throw new IllegalArgumentException("Invalid opacity: " + opacity); + } + final int opacityInt = (int) (255 * opacity); + return (color & 0x00FFFFFF) | (opacityInt << 24); + } + + public final String keyValue; + public final StringSetting behaviorSetting; // TODO: Replace with EnumSetting. + private final StringSetting colorSetting; + private final FloatSetting opacitySetting; - @NonNull public final StringRef title; /** * Skip button text, if the skip occurs in the first quarter of the video */ - @NonNull public final StringRef skipButtonTextBeginning; /** * Skip button text, if the skip occurs in the middle half of the video */ - @NonNull public final StringRef skipButtonTextMiddle; /** * Skip button text, if the skip occurs in the last quarter of the video */ - @NonNull public final StringRef skipButtonTextEnd; /** * Skipped segment toast, if the skip occurred in the first quarter of the video @@ -198,10 +182,7 @@ public enum SegmentCategory { @NonNull public final Paint paint; - /** - * Value must be changed using {@link #setColor(String)}. - */ - public int color; + private int color; /** * Value must be changed using {@link #setBehaviour(CategoryBehaviour)}. @@ -213,17 +194,20 @@ public enum SegmentCategory { SegmentCategory(String keyValue, StringRef title, StringRef skipButtonText, StringRef skippedToastText, - StringSetting behavior, StringSetting color) { + StringSetting behavior, + StringSetting color, FloatSetting opacity) { this(keyValue, title, skipButtonText, skipButtonText, skipButtonText, skippedToastText, skippedToastText, skippedToastText, - behavior, color); + behavior, + color, opacity); } SegmentCategory(String keyValue, StringRef title, StringRef skipButtonTextBeginning, StringRef skipButtonTextMiddle, StringRef skipButtonTextEnd, StringRef skippedToastBeginning, StringRef skippedToastMiddle, StringRef skippedToastEnd, - StringSetting behavior, StringSetting color) { + StringSetting behavior, + StringSetting color, FloatSetting opacity) { this.keyValue = Objects.requireNonNull(keyValue); this.title = Objects.requireNonNull(title); this.skipButtonTextBeginning = Objects.requireNonNull(skipButtonTextBeginning); @@ -234,6 +218,7 @@ public enum SegmentCategory { this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd); this.behaviorSetting = Objects.requireNonNull(behavior); this.colorSetting = Objects.requireNonNull(color); + this.opacitySetting = Objects.requireNonNull(opacity); this.paint = new Paint(); loadFromSettings(); } @@ -250,11 +235,14 @@ public enum SegmentCategory { this.behaviour = savedBehavior; String colorString = colorSetting.get(); + final float opacity = opacitySetting.get(); try { setColor(colorString); + setOpacity(opacity); } catch (Exception ex) { - Logger.printException(() -> "Invalid color: " + colorString, ex); + Logger.printException(() -> "Invalid color: " + colorString + " opacity: " + opacity, ex); colorSetting.resetToDefault(); + opacitySetting.resetToDefault(); loadFromSettings(); } } @@ -264,45 +252,77 @@ public enum SegmentCategory { this.behaviorSetting.save(behaviour.reVancedKeyValue); } - /** - * @return HTML color format string - */ - @NonNull - public String colorString() { - return String.format("#%06X", color); - } - - public void setColor(@NonNull String colorString) throws IllegalArgumentException { - final int color = Color.parseColor(colorString) & 0xFFFFFF; - this.color = color; + private void updateColor() { + color = applyOpacityToColor(color, opacitySetting.get()); paint.setColor(color); - paint.setAlpha(255); - colorSetting.save(colorString); // Save after parsing. } - public void resetColor() { + /** + * @param opacity Segment color opacity between [0, 1]. + */ + public void setOpacity(float opacity) throws IllegalArgumentException { + if (opacity < 0 || opacity > 1) { + throw new IllegalArgumentException("Invalid opacity: " + opacity); + } + + opacitySetting.save(opacity); + updateColor(); + } + + public float getOpacity() { + return opacitySetting.get(); + } + + public void resetColorAndOpacity() { setColor(colorSetting.defaultValue); + setOpacity(opacitySetting.defaultValue); } - @NonNull - private static String getCategoryColorDotHTML(int color) { - color &= 0xFFFFFF; - return String.format("", color); + /** + * @param colorString Segment color with #RRGGBB format. + */ + public void setColor(String colorString) throws IllegalArgumentException { + color = Color.parseColor(colorString); + colorSetting.save(colorString); + + updateColor(); } - @NonNull - public static Spanned getCategoryColorDot(int color) { - return Html.fromHtml(getCategoryColorDotHTML(color)); + /** + * @return Integer color of #RRGGBB format. + */ + public int getColorNoOpacity() { + return color & 0x00FFFFFF; } - @NonNull - public Spanned getCategoryColorDot() { + /** + * @return Hex color string of #RRGGBB format with no opacity level. + */ + public String getColorString() { + return String.format(Locale.US, "#%06X", getColorNoOpacity()); + } + + private static SpannableString getCategoryColorDotSpan(String text, int color) { + SpannableString dotSpan = new SpannableString('⬤' + text); + dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return dotSpan; + } + + public static SpannableString getCategoryColorDot(int color) { + return getCategoryColorDotSpan("", color); + } + + public SpannableString getCategoryColorDot() { return getCategoryColorDot(color); } - @NonNull - public Spanned getTitleWithColorDot() { - return Html.fromHtml(getCategoryColorDotHTML(color) + " " + title); + public SpannableString getTitleWithColorDot(int categoryColor) { + return getCategoryColorDotSpan(" " + title, categoryColor); + } + + public SpannableString getTitleWithColorDot() { + return getTitleWithColorDot(color); } /** @@ -310,7 +330,6 @@ public enum SegmentCategory { * @param videoLength length of the video * @return the skip button text */ - @NonNull StringRef getSkipButtonText(long segmentStartTime, long videoLength) { if (Settings.SB_COMPACT_SKIP_BUTTON.get()) { return (this == SegmentCategory.HIGHLIGHT) @@ -319,7 +338,7 @@ public enum SegmentCategory { } if (videoLength == 0) { - return skipButtonTextBeginning; // video is still loading. Assume it's the beginning + return skipButtonTextBeginning; // Video is still loading. Assume it's the beginning. } final float position = segmentStartTime / (float) videoLength; if (position < 0.25f) { @@ -335,10 +354,9 @@ public enum SegmentCategory { * @param videoLength length of the video * @return 'skipped segment' toast message */ - @NonNull StringRef getSkippedToastText(long segmentStartTime, long videoLength) { if (videoLength == 0) { - return skippedToastBeginning; // video is still loading. Assume it's the beginning + return skippedToastBeginning; // Video is still loading. Assume it's the beginning. } final float position = segmentStartTime / (float) videoLength; if (position < 0.25f) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java index 51208c1cc..0bd5aa435 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java @@ -24,12 +24,15 @@ public class SponsorSegment implements Comparable { @NonNull public final StringRef title; public final int apiVoteType; - public final boolean shouldHighlight; + /** + * If the option should be highlighted for VIP users. + */ + public final boolean highlightIfVipAndVideoIsLocked; - SegmentVote(@NonNull StringRef title, int apiVoteType, boolean shouldHighlight) { + SegmentVote(@NonNull StringRef title, int apiVoteType, boolean highlightIfVipAndVideoIsLocked) { this.title = title; this.apiVoteType = apiVoteType; - this.shouldHighlight = shouldHighlight; + this.highlightIfVipAndVideoIsLocked = highlightIfVipAndVideoIsLocked; } } diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index 4d511c850..c2bdc0677 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -1964,6 +1964,7 @@ Click to see how to issue an API key." Show in seekbar Disable + Opacity: Color: Color changed. Color reset. @@ -2036,6 +2037,8 @@ Click to see how to issue an API key." Downvote Change category There are no segments to vote for. + + %1$s to %2$s Choose the segment category Category is disabled in settings. Enable category to submit.