feat(YouTube - SponsorBlock): Add opacity setting to category segment colors (#4582)

This commit is contained in:
LisoUseInAIKyrios 2025-03-19 18:02:06 +01:00 committed by GitHub
parent 6e4882e74f
commit 6e8ffbade9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 382 additions and 167 deletions

View File

@ -1,5 +1,7 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
@ -8,17 +10,23 @@ import android.util.AttributeSet;
import android.widget.Button;
import android.widget.EditText;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.Logger;
import androidx.annotation.Nullable;
import java.util.Objects;
import static app.revanced.extension.shared.StringRef.str;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
@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);
}
@ -32,12 +40,22 @@ public class ResettableEditTextPreference extends EditTextPreference {
super(context);
}
public void setSetting(@Nullable Setting<?> setting) {
this.setting = setting;
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
Utils.setEditTextDialogTheme(builder);
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_settings_reset"), null);
}
@ -54,8 +72,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

View File

@ -365,24 +365,34 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false);
public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY_ONCE.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", MANUAL_SKIP.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", MANUAL_SKIP.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", MANUAL_SKIP.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", MANUAL_SKIP.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", IGNORE.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", IGNORE.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);
// Deprecated migrations
private static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024

View File

@ -14,6 +14,7 @@ import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch;
@ -237,6 +238,8 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"));
preferenceScreen.addPreference(statisticPreference);
}
Utils.setPreferenceTitlesToMultiLineIfNeeded(preferenceScreen);
} catch (Exception ex) {
Logger.printException(() -> "onCreate failure", ex);
}

View File

@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
@ -44,8 +45,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
private SwitchPreference showTimeWithoutSegments;
private SwitchPreference toastOnConnectionError;
private EditTextPreference newSegmentStep;
private EditTextPreference minSegmentDuration;
private ResettableEditTextPreference newSegmentStep;
private ResettableEditTextPreference minSegmentDuration;
private EditTextPreference privateUserId;
private EditTextPreference importExport;
private Preference apiUrl;
@ -159,6 +160,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
addAboutCategory(context, preferenceScreen);
Utils.setPreferenceTitlesToMultiLineIfNeeded(preferenceScreen);
updateUI();
} catch (Exception ex) {
Logger.printException(() -> "onCreate failure", ex);
@ -268,7 +271,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
return true;
});
newSegmentStep = new EditTextPreference(context);
newSegmentStep = new ResettableEditTextPreference(context);
newSegmentStep.setSetting(Settings.SB_CREATE_NEW_SEGMENT_STEP);
newSegmentStep.setTitle(str("revanced_sb_general_adjusting"));
newSegmentStep.setSummary(str("revanced_sb_general_adjusting_sum"));
newSegmentStep.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
@ -326,7 +330,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
});
category.addPreference(trackSkips);
minSegmentDuration = new EditTextPreference(context);
minSegmentDuration = new ResettableEditTextPreference(context);
minSegmentDuration.setSetting(Settings.SB_SEGMENT_MIN_DURATION);
minSegmentDuration.setTitle(str("revanced_sb_general_min_duration"));
minSegmentDuration.setSummary(str("revanced_sb_general_min_duration_sum"));
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
@ -345,7 +350,15 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
});
category.addPreference(minSegmentDuration);
privateUserId = new EditTextPreference(context);
privateUserId = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString());
});
}
};
privateUserId.setTitle(str("revanced_sb_general_uuid"));
privateUserId.setSummary(str("revanced_sb_general_uuid_sum"));
privateUserId.setOnPreferenceChangeListener((preference1, newValue) -> {
@ -504,7 +517,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
if (stats.totalSegmentCountIncludingIgnored > 0) {
// If user has not created any segments, there's no reason to set a username.
EditTextPreference preference = new EditTextPreference(context);
EditTextPreference preference = new ResettableEditTextPreference(context);
statsCategory.addPreference(preference);
String userName = stats.userName;
preference.setTitle(fromHtml(str("revanced_sb_stats_username", userName)));

View File

@ -136,7 +136,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) {

View File

@ -5,7 +5,12 @@ import static app.revanced.extension.shared.StringRef.str;
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,7 +38,7 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController
* Not thread safe. All fields/methods must be accessed from the main thread.
*/
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
= Pattern.compile("((\\d{1,2}):)?(\\d{1,2}):(\\d{2})(\\.(\\d{1,3}))?");
@ -160,32 +165,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("<font color=\"%s\">%s</font>", 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:
case DOWNVOTE:
SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption);
break;
case CATEGORY_CHANGE:
onNewCategorySelect(segment, context);
break;
}
})
.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);
}
@ -282,7 +289,6 @@ public class SponsorBlockUtils {
return;
}
final int numberOfSegments = segments.length;
CharSequence[] titles = new CharSequence[numberOfSegments];
for (int i = 0; i < numberOfSegments; i++) {
@ -290,22 +296,33 @@ public class SponsorBlockUtils {
if (segment.category == SegmentCategory.UNSUBMITTED) {
continue;
}
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br>",
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("</b>");
if (i + 1 != numberOfSegments) // prevents trailing new line after last segment
htmlBuilder.append("<br>");
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);
}

View File

@ -1,13 +1,14 @@
package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.youtube.settings.Settings.*;
import static app.revanced.extension.shared.StringRef.sf;
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;
@ -15,43 +16,45 @@ 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.Utils;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringRef;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.FloatSetting;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.settings.Settings;
public enum SegmentCategory {
SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_segments_sponsor_sum"), 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_segments_selfpromo_sum"), 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_segments_interaction_sum"), 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_segments_highlight_sum"), 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_segments_intro_sum"),
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_segments_outro_sum"), 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_segments_preview_sum"),
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_segments_filler_sum"), 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_segments_nomusic_sum"), 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, 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");
private static final StringRef skipSponsorTextCompactHighlight = sf("revanced_sb_skip_button_compact_highlight");
@ -90,12 +93,10 @@ public enum SegmentCategory {
mValuesMap.put(value.keyValue, value);
}
@NonNull
public static SegmentCategory[] categoriesWithoutUnsubmitted() {
return categoriesWithoutUnsubmitted;
}
@NonNull
public static SegmentCategory[] categoriesWithoutHighlights() {
return categoriesWithoutHighlights;
}
@ -106,7 +107,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();
@ -133,32 +134,33 @@ 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;
@NonNull
public final StringRef description;
/**
* 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
@ -179,10 +181,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)}.
@ -194,17 +193,20 @@ public enum SegmentCategory {
SegmentCategory(String keyValue, StringRef title, StringRef description,
StringRef skipButtonText,
StringRef skippedToastText,
StringSetting behavior, StringSetting color) {
StringSetting behavior,
StringSetting color, FloatSetting opacity) {
this(keyValue, title, description,
skipButtonText, skipButtonText, skipButtonText,
skippedToastText, skippedToastText, skippedToastText,
behavior, color);
behavior,
color, opacity);
}
SegmentCategory(String keyValue, StringRef title, StringRef description,
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.description = Objects.requireNonNull(description);
@ -216,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();
}
@ -232,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();
}
}
@ -245,45 +251,78 @@ public enum SegmentCategory {
this.behaviour = Objects.requireNonNull(behaviour);
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("<font color=\"#%06X\">⬤</font>", 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);
}
/**
@ -291,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)
@ -300,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) {
@ -316,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) {

View File

@ -1,6 +1,7 @@
package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
import android.app.AlertDialog;
import android.content.Context;
@ -11,11 +12,10 @@ import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
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.Logger;
@ -24,27 +24,38 @@ import app.revanced.extension.shared.Utils;
@SuppressWarnings("deprecation")
public class SegmentCategoryListPreference extends ListPreference {
private final SegmentCategory category;
private EditText mEditText;
private int mClickedDialogEntryIndex;
private TextView colorDotView;
private EditText colorEditText;
private EditText opacityEditText;
/**
* #RRGGBB
*/
private int categoryColor;
/**
* [0, 1]
*/
private float categoryOpacity;
private int selectedDialogEntryIndex;
public SegmentCategoryListPreference(Context context, SegmentCategory category) {
super(context);
final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT;
this.category = Objects.requireNonNull(category);
// 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(category.behaviorSetting.key);
setDefaultValue(category.behaviorSetting.defaultValue);
final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT;
setEntries(isHighlightCategory
? CategoryBehaviour.getBehaviorDescriptionsWithoutSkipOnce()
: CategoryBehaviour.getBehaviorDescriptions());
setEntryValues(isHighlightCategory
? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce()
: CategoryBehaviour.getBehaviorKeyValues());
setSummary(category.description.toString());
updateTitle();
updateTitleFromCategory();
}
@Override
@ -52,26 +63,40 @@ public class SegmentCategoryListPreference extends ListPreference {
try {
Utils.setEditTextDialogTheme(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(category.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(category.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) {
}
@ -81,29 +106,94 @@ 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);
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) -> {
@ -111,8 +201,8 @@ public class SegmentCategoryListPreference extends ListPreference {
});
builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> {
try {
category.resetColor();
updateTitle();
category.resetColorAndOpacity();
updateTitleFromCategory();
Utils.showToastShort(str("revanced_sb_color_reset"));
} catch (Exception ex) {
Logger.printException(() -> "setNeutralButton failure", ex);
@ -120,8 +210,9 @@ public class SegmentCategoryListPreference extends ListPreference {
});
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);
}
@ -130,30 +221,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);
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
SegmentCategory.updateEnabledCategories();
}
String colorString = mEditText.getText().toString();
try {
if (!colorString.equals(category.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(category.getTitleWithColorDot());
private void applyOpacityToCategoryColor() {
categoryColor = applyOpacityToColor(categoryColor, categoryOpacity);
}
private void updateTitleFromCategory() {
categoryColor = category.getColorNoOpacity();
categoryOpacity = category.getOpacity();
applyOpacityToCategoryColor();
setTitle(category.getTitleWithColorDot(categoryColor));
}
private void updateCategoryColorDot() {
applyOpacityToCategoryColor();
colorDotView.setText(SegmentCategory.getCategoryColorDot(categoryColor));
}
private void updateOpacityText() {
opacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity));
}
}

View File

@ -23,12 +23,15 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
@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;
}
}

View File

@ -1068,6 +1068,8 @@ Already exists"</string>
<string name="revanced_sb_vote_downvote">Downvote</string>
<string name="revanced_sb_vote_category">Change category</string>
<string name="revanced_sb_vote_no_segments">There are no segments to vote for</string>
<!-- A segment start and end time, such as "02:10 to 03:40" -->
<string name="revanced_sb_vote_segment_time_to_from">%1$s to %2$s</string>
<string name="revanced_sb_new_segment_choose_category">Choose the segment category</string>
<string name="revanced_sb_new_segment_disabled_category">Category is disabled in settings. Enable category to submit.</string>
<string name="revanced_sb_new_segment_title">New SponsorBlock segment</string>
@ -1115,6 +1117,7 @@ Ready to submit?"</string>
<string name="revanced_sb_stats_saved_hour_format">%1$s hours %2$s minutes</string>
<string name="revanced_sb_stats_saved_minute_format">%1$s minutes %2$s seconds</string>
<string name="revanced_sb_stats_saved_second_format">%s seconds</string>
<string name="revanced_sb_color_opacity_label">Opacity:</string>
<string name="revanced_sb_color_dot_label">Color:</string>
<string name="revanced_sb_color_changed">Color changed</string>
<string name="revanced_sb_color_reset">Color reset</string>