feat(YouTube - Theme): Add option for black and white splash screen animation (#5119)

This commit is contained in:
LisoUseInAIKyrios
2025-06-05 22:06:12 +02:00
committed by GitHub
parent 50b68f03d4
commit 42db0c2e36
7 changed files with 125 additions and 32 deletions

View File

@ -2,6 +2,7 @@ package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.clamp;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
import android.content.res.Resources;
import android.graphics.Color;
@ -173,23 +174,15 @@ public final class SeekbarColorPatch {
*/
public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) {
try {
if (!SEEKBAR_CUSTOM_COLOR_ENABLED) {
SplashScreenAnimationStyle animationStyle = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
if (!SEEKBAR_CUSTOM_COLOR_ENABLED
// Black and white animations cannot use color replacements.
|| animationStyle == SplashScreenAnimationStyle.FPS_30_BLACK_AND_WHITE
|| animationStyle == SplashScreenAnimationStyle.FPS_60_BLACK_AND_WHITE) {
view.patch_setAnimation(resourceId);
return;
}
//noinspection ConstantConditions
if (false) { // Set true to force slow animation for development.
final int longAnimation = Utils.getResourceIdentifier(
Utils.isDarkModeEnabled()
? "startup_animation_5s_30fps_dark"
: "startup_animation_5s_30fps_light",
"raw");
if (longAnimation != 0) {
resourceId = longAnimation;
}
}
// Must specify primary key name otherwise the morphing YT logo color is also changed.
String originalKey = "\"k\":";
String originalPrimary = originalKey + "[1,0,0.2,1]";
@ -199,21 +192,16 @@ public final class SeekbarColorPatch {
String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]);
String json = loadRawResourceAsString(resourceId);
if (json == null) {
return; // Should never happen.
}
String replacement = json
.replace(originalPrimary, replacementPrimary)
.replace(originalAccent, replacementAccent);
if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) {
String jsonFinal = json;
Logger.printException(() -> "Could not replace launch animation colors: " + jsonFinal);
Logger.printException(() -> "Could not replace splash animation colors: " + json);
}
Logger.printDebug(() -> "Replacing Lottie animation JSON");
json = json.replace(originalPrimary, replacementPrimary);
json = json.replace(originalAccent, replacementAccent);
// cacheKey is not needed since the animation will not be reused.
view.patch_setAnimation(new ByteArrayInputStream(json.getBytes()), null);
view.patch_setAnimation(new ByteArrayInputStream(replacement.getBytes()), null);
} catch (Exception ex) {
Logger.printException(() -> "setSplashAnimationLottie failure", ex);
}
@ -234,8 +222,7 @@ public final class SeekbarColorPatch {
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
return scanner.next();
} catch (IOException e) {
Logger.printException(() -> "Could not load resource: " + resourceId);
return null;
throw new IllegalStateException("Could not load resource: " + resourceId);
}
}

View File

@ -1,11 +1,49 @@
package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle.styleFromOrdinal;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ThemePatch {
public enum SplashScreenAnimationStyle {
DEFAULT(0),
FPS_60_ONE_SECOND(1),
FPS_60_TWO_SECOND(2),
FPS_60_FIVE_SECOND(3),
FPS_60_BLACK_AND_WHITE(4),
FPS_30_ONE_SECOND(5),
FPS_30_TWO_SECOND(6),
FPS_30_FIVE_SECOND(7),
FPS_30_BLACK_AND_WHITE(8);
// There exists a 10th json style used as the switch statement default,
// but visually it is identical to 60fps one second.
@Nullable
static SplashScreenAnimationStyle styleFromOrdinal(int style) {
// Alternatively can return using values()[style]
for (SplashScreenAnimationStyle value : values()) {
if (value.style == style) {
return value;
}
}
return null;
}
final int style;
SplashScreenAnimationStyle(int style) {
this.style = style;
}
}
// color constants used in relation with litho components
private static final int[] WHITE_VALUES = {
-1, // comments chip background
@ -58,4 +96,22 @@ public class ThemePatch {
public static boolean gradientLoadingScreenEnabled(boolean original) {
return GRADIENT_LOADING_SCREEN_ENABLED;
}
/**
* Injection point.
*/
public static int getLoadingScreenType(int original) {
SplashScreenAnimationStyle style = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
if (style == SplashScreenAnimationStyle.DEFAULT) {
return original;
}
final int replacement = style.style;
if (original != replacement) {
Logger.printDebug(() -> "Overriding splash screen style from: "
+ styleFromOrdinal(original) + " to: " + style);
}
return replacement;
}
}

View File

@ -21,14 +21,12 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
import android.graphics.Color;
import app.revanced.extension.shared.Logger;
@ -40,12 +38,14 @@ import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.shared.settings.LongSetting;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
import app.revanced.extension.youtube.patches.MiniplayerPatch;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
public class Settings extends BaseSettings {
// Video
@ -226,6 +226,8 @@ public class Settings extends BaseSettings {
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");

View File

@ -44,6 +44,19 @@ internal val themeHelperLightColorFingerprint = fingerprint {
}
}
internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L
internal val useGradientLoadingScreenFingerprint = fingerprint {
literal { GRADIENT_LOADING_SCREEN_AB_CONSTANT }
}
internal const val SPLASH_SCREEN_STYLE_FEATURE_FLAG = 269032877L
internal val splashScreenStyleFingerprint = fingerprint {
returns("V")
parameters("Landroid/os/Bundle;")
literal { SPLASH_SCREEN_STYLE_FEATURE_FLAG }
custom { method, classDef ->
method.name == "onCreate" && classDef.endsWith("/MainActivity;")
}
}

View File

@ -10,6 +10,7 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
@ -17,6 +18,7 @@ import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.layout.seekbar.seekbarColorPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_47_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
@ -27,8 +29,6 @@ import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/theme/ThemePatch;"
internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L
val themePatch = bytecodePatch(
name = "Theme",
description = "Adds options for theming and applies a custom background theme (dark background theme defaults to amoled black).",
@ -232,15 +232,32 @@ val themePatch = bytecodePatch(
addResources("youtube", "layout.theme.themePatch")
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_gradient_loading_screen"),
SwitchPreference("revanced_gradient_loading_screen")
)
if (is_19_47_or_greater) {
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
ListPreference(
key = "splash_screen_animation_style",
summaryKey = null
)
)
}
useGradientLoadingScreenFingerprint.method.insertLiteralOverride(
GRADIENT_LOADING_SCREEN_AB_CONSTANT,
"$EXTENSION_CLASS_DESCRIPTOR->gradientLoadingScreenEnabled(Z)Z"
)
mapOf(
if (is_19_47_or_greater) {
// Lottie splash screen exists in earlier versions, but it may not be always on.
splashScreenStyleFingerprint.method.insertLiteralOverride(
SPLASH_SCREEN_STYLE_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->getLoadingScreenType(I)I"
)
}
arrayOf(
themeHelperLightColorFingerprint to lightThemeBackgroundColor,
themeHelperDarkColorFingerprint to darkThemeBackgroundColor,
).forEach { (fingerprint, color) ->

View File

@ -187,6 +187,21 @@
<item>AUTOMOTIVE</item>
</string-array>
</patch>
<patch id="layout.theme.themePatch">
<!-- Since the 30fps looks bad, and the 2/5 second styles are not useful
(YouTube closes the animation as soon as the feed is loaded),
only the 60fps 1 second styles are exposed in the settings.
Imported settings data can still be manually edited to force the other styles. -->
<string-array name="splash_screen_animation_style_entries">
<item>@string/splash_screen_animation_style_entry_1</item>
<item>@string/splash_screen_animation_style_entry_2</item>
</string-array>
<string-array name="splash_screen_animation_style_entry_values">
<item>FPS_60_ONE_SECOND</item>
<item>FPS_60_BLACK_AND_WHITE</item>
</string-array>
</patch>
<patch id="layout.player.fullscreen.exitFullscreenPatch">
<string-array name="revanced_exit_fullscreen_entries">
<item>@string/revanced_exit_fullscreen_entry_1</item>

View File

@ -1323,6 +1323,9 @@ Swipe to expand or close"</string>
<string name="revanced_gradient_loading_screen_title">Enable gradient loading screen</string>
<string name="revanced_gradient_loading_screen_summary_on">Loading screen will have a gradient background</string>
<string name="revanced_gradient_loading_screen_summary_off">Loading screen will have a solid background</string>
<string name="splash_screen_animation_style_title">Splash screen style</string>
<string name="splash_screen_animation_style_entry_1">Color</string>
<string name="splash_screen_animation_style_entry_2">Black and white</string>
<string name="revanced_seekbar_custom_color_title">Enable custom seekbar color</string>
<string name="revanced_seekbar_custom_color_summary_on">Custom seekbar color is shown</string>
<string name="revanced_seekbar_custom_color_summary_off">Original seekbar color is shown</string>