Merge branch 'kmmiio99o:main' into main

This commit is contained in:
OTKUSteyler 2026-01-25 11:59:31 +01:00 • committed by GitHub
commit 1450dc20af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 147 additions and 70 deletions

View file

@ -70,6 +70,7 @@
"MANUFACTURER": "Manufacturer",
"MESSAGE": "Message",
"MISCELLANEOUS": "Miscellaneous",
"MODAL_RELOAD_THEME_CHANGE": "Reload the app to apply the theme change?",
"MODAL_RELOAD_REQUIRED": "Reload app?",
"MODAL_RELOAD_REQUIRED_DESC": "A reload is required to see the changes. Do you want to reload now?",
"MODAL_THEME_REFETCHED": "Theme refetched",

View file

@ -3,16 +3,40 @@ import { PluginInstanceInternal } from "@lib/addons/plugins/types";
interface CorePlugin {
default: PluginInstanceInternal;
preenabled: boolean;
loader: () => PluginInstanceInternal;
}
type CorePluginRecord = Record<string, CorePlugin>;
function makeLazyPlugin(loaderFn: () => any, preenabled = true): CorePlugin {
const container: any = {
loader: () => {
// loaderFn should synchronously require the module
const mod = loaderFn();
// module may be an object with `.default` or the instance directly
const inst = mod && (mod.default ?? mod);
return inst;
},
preenabled,
get default() {
// load and cache the instance on first access
const inst = container.loader();
Object.defineProperty(container, "default", { value: inst, writable: false, enumerable: true });
return inst;
},
};
return container as CorePlugin;
}
// Called from @lib/plugins
export const getCorePlugins = (): Record<string, CorePlugin> => ({
"bunny.quickinstall": { ...require("./quickinstall"), preenabled: true },
"bunny.badges": require("./badges"),
"bunny.notrack": require("./notrack"),
"bunny.messagefix": require("./messagefix"),
"bunny.fixembed": require("./fixembed"),
"bunny.enhancements": require("./shiggyenhancements")
export const getCorePlugins = (): CorePluginRecord => ({
"bunny.quickinstall": makeLazyPlugin(() => require("./quickinstall"), true),
"bunny.badges": makeLazyPlugin(() => require("./badges")),
"bunny.notrack": makeLazyPlugin(() => require("./notrack")),
"bunny.messagefix": makeLazyPlugin(() => require("./messagefix")),
"bunny.fixembed": makeLazyPlugin(() => require("./fixembed")),
"bunny.enhancements": makeLazyPlugin(() => require("./shiggyenhancements")),
});
/**

View file

@ -33,6 +33,8 @@ import {
Text,
} from "@metro/common/components";
import { View, Image } from "react-native";
import { showConfirmationAlert } from "@core/vendetta/alerts";
import { BundleUpdaterManager } from "@lib/api/native/modules";
const { useReducer } = React;
@ -361,6 +363,47 @@ export default function Themes() {
const navigation = NavigationNative.useNavigation();
const [mode, setMode] = React.useState<"installed" | "browse">("installed");
// Track the theme that was active when the user entered the Themes page.
// If it changes while the user stays on this page, we'll prompt to reload when they leave.
const initialThemeIdRef = React.useRef<string | null>(getCurrentTheme()?.id ?? null);
React.useEffect(() => {
const beforeRemoveHandler = (e: any) => {
try {
const currentId = getCurrentTheme()?.id ?? null;
if (currentId !== initialThemeIdRef.current) {
// Notify the user that a reload is required for full application of theme changes
showConfirmationAlert({
title: Strings.MODAL_RELOAD_THEME_CHANGE,
content: Strings.MODAL_THEME_REFETCHED_DESC,
confirmText: Strings.RELOAD,
cancelText: Strings.CANCEL,
confirmColor: "red",
onConfirm: () => {
try {
BundleUpdaterManager.reload();
} catch (err) {
try {
(global as any).NativeModules?.BundleUpdaterManager?.reload?.();
} catch (_) {}
}
},
});
}
} catch (_) {
// swallow any unexpected errors — we don't want to block navigation
}
};
const unsub = navigation?.addListener ? navigation.addListener("beforeRemove", beforeRemoveHandler) : undefined;
return () => {
try {
if (unsub && typeof unsub === "function") unsub();
else if (unsub && typeof unsub.remove === "function") unsub.remove();
} catch (_) {}
};
}, [navigation]);
const [themesList, setThemesList] = React.useState<ThemeData[]>([]);
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);

View file

@ -3,6 +3,7 @@ import patchDefinitionAndResolver from "./patches/resolver";
import patchStorage from "./patches/storage";
import { ColorManifest } from "./types";
import { updateBunnyColor } from "./updater";
import fixStatusBar from "./statusbar";
/** @internal */
export default function initColors(manifest: ColorManifest | null) {
@ -11,8 +12,9 @@ export default function initColors(manifest: ColorManifest | null) {
const patches = [
patchStorage(),
patchDefinitionAndResolver(),
patchChatBackground()
patchChatBackground(),
fixStatusBar()
];
return () => patches.forEach(p => p());
}
return () => patches.forEach(p => p && (typeof p === "function" ? p() : null));
}

View file

@ -25,7 +25,10 @@ const SEMANTIC_FALLBACK_MAP: Record<string, string> = {
const origRawColor = { ...tokenReference.RawColor };
export default function patchDefinitionAndResolver() {
const callback = ([theme]: any[]) => theme === _colorRef.key ? [_colorRef.current!.reference] : void 0;
const callback = ([theme]: any[]) => {
if (!_colorRef.current) return void 0;
return theme === _colorRef.key ? [_colorRef.current.reference] : void 0;
};
Object.keys(tokenReference.RawColor).forEach(key => {
Object.defineProperty(tokenReference.RawColor, key, {
@ -34,7 +37,7 @@ export default function patchDefinitionAndResolver() {
get: () => {
const ret = _colorRef.current?.raw[key];
if (ret) return ret;
return origRawColor[key];
}
});
@ -46,15 +49,20 @@ export default function patchDefinitionAndResolver() {
before("updateTheme", NativeThemeModule, callback),
instead("resolveSemanticColor", tokenReference.default.meta ?? tokenReference.default.internal, (args: any[], orig: any) => {
if (!_colorRef.current) return orig(...args);
const currentRef = _colorRef.current;
if (args[0] !== _colorRef.key) return orig(...args);
args[0] = _colorRef.current.reference;
// Use the captured reference value
args[0] = currentRef.reference;
const [name, colorDef] = extractInfo(_colorRef.current!.reference, args[1]);
const [name, colorDef] = extractInfo(currentRef.reference, args[1]);
let semanticDef = _colorRef.current.semantic[name];
if (!semanticDef && _colorRef.current.spec === 2 && name in SEMANTIC_FALLBACK_MAP) {
semanticDef = _colorRef.current.semantic[SEMANTIC_FALLBACK_MAP[name]];
// If extractInfo couldn't resolve a color definition, fall back to the original resolver
if (!name || !colorDef) return orig(...args);
let semanticDef = currentRef.semantic[name];
if (!semanticDef && currentRef.spec === 2 && name in SEMANTIC_FALLBACK_MAP) {
semanticDef = currentRef.semantic[SEMANTIC_FALLBACK_MAP[name]];
}
if (semanticDef?.value) {
@ -62,10 +70,11 @@ export default function patchDefinitionAndResolver() {
return chroma(semanticDef.value).alpha(semanticDef.opacity).hex();
}
const rawValue = _colorRef.current.raw[colorDef.raw];
const rawKey = colorDef.raw;
const rawValue = rawKey ? currentRef.raw[rawKey] : undefined;
if (rawValue) {
// Set opacity if needed
return colorDef.opacity === 1 ? rawValue : chroma(rawValue).alpha(colorDef.opacity).hex();
return (colorDef.opacity === 1) ? rawValue : chroma(rawValue).alpha(colorDef.opacity).hex();
}
// Fallback to default
@ -86,7 +95,13 @@ export default function patchDefinitionAndResolver() {
function extractInfo(themeName: string, colorObj: any): [name: string, colorDef: any] {
// @ts-ignore - assigning to extractInfo._sym
const propName = colorObj[extractInfo._sym ??= Object.getOwnPropertySymbols(colorObj)[0]];
const colorDef = tokenReference.SemanticColor[propName];
const entry = tokenReference.SemanticColor[propName];
return [propName, colorDef[themeName]];
}
if (!entry || typeof entry !== "object") {
return [propName, undefined];
}
const colorDef = Object.prototype.hasOwnProperty.call(entry, themeName) ? entry[themeName] : undefined;
return [propName, colorDef];
}

View file

@ -1,4 +1,4 @@
import { StatusBar, TextInput, Platform } from "react-native";
import { StatusBar, Platform } from "react-native";
import { findByStoreNameLazy } from "@metro/wrappers";
import { _colorRef } from "./updater";
@ -7,15 +7,8 @@ const ThemeStore = findByStoreNameLazy("ThemeStore");
function getBarStyle() {
// Check for custom theme
const custom = _colorRef.current;
if (custom) {
// Bunny theme (spec 3)
if (custom.spec === 3 && custom.reference) {
return custom.reference === "light" ? "dark-content" : "light-content";
}
// Vendetta theme (spec 2)
if (custom.spec === 2 && custom.reference) {
return custom.reference === "light" ? "dark-content" : "light-content";
}
if (custom?.reference) {
return custom.reference === "light" ? "dark-content" : "light-content";
}
// Fallback to Discord theme string
@ -25,33 +18,35 @@ function getBarStyle() {
return (lowerTheme.includes("dark") || lowerTheme.includes("midnight")) ? "light-content" : "dark-content";
}
return "dark-content";
return "light-content";
}
export default function fixStatusBar() {
function applyStatusBar() {
const style = getBarStyle();
StatusBar.setBarStyle(style, true);
if (Platform.OS == "android") {
if (Platform.OS === "android") {
StatusBar.setTranslucent?.(true);
StatusBar.setBackgroundColor?.("#00000000", true);
}
}
if (Platform.OS === "android") {
const origSetBarStyle = StatusBar.setBarStyle;
StatusBar.setBarStyle = function (_style: any, ...args: any[]) {
return origSetBarStyle.call(this, getBarStyle(), ...args);
};
}
if (Platform.OS == "ios") {
function applyStatusBar() {
const style = getBarStyle();
StatusBar.setBarStyle(style, true);
}
const unsubscribe = ThemeStore?.addChangeListener?.(applyStatusBar);
const unsubscribe = ThemeStore?.addChangeListener?.(applyStatusBar);
applyStatusBar();
StatusBar.setBarStyle(getBarStyle());
let delay = 200;
for (let i = 0; i < 5; i++, delay *= 2) {
setTimeout(applyStatusBar, delay);
}
return unsubscribe;
let delay = 200;
for (let i = 0; i < 5; i++, delay *= 2) {
setTimeout(applyStatusBar, delay);
}
return unsubscribe;
}

View file

@ -9,7 +9,6 @@ const origRawColor = { ...tokenRef.RawColor };
const AppearanceManager = findByPropsLazy("updateTheme");
const ThemeStore = findByStoreNameLazy("ThemeStore");
const FormDivider = findByPropsLazy("DIVIDER_COLORS");
import { colorsPref } from "./preferences";
let _inc = 1;
@ -25,32 +24,28 @@ export const _colorRef: InternalColorRef = {
current: null,
key: `bn-theme-${_inc}`,
origRaw: origRawColor,
lastSetDiscordTheme: "darker"
lastSetDiscordTheme: "dark"
};
export function updateBunnyColor(colorManifest: ColorManifest | null, { update = true } ) {
if (settings.safeMode?.enabled) return;
const resolveType = (type = "dark") => (colorsPref.type ?? type) === "dark" ? "darker" : "light";
const internalDef = colorManifest ? parseColorManifest(colorManifest) : null;
if (resolveType() == "light" || update) {
var ref = Object.assign(_colorRef, {
current: internalDef,
key: `bn-theme-${++_inc}`,
lastSetDiscordTheme: !ThemeStore.theme.startsWith("bn-theme-") || !ThemeStore.theme.startsWith("darker")
? ThemeStore.theme
: _colorRef.lastSetDiscordTheme
});
} else {
var ref = Object.assign(_colorRef, {
current: internalDef,
key: `dark`,
lastSetDiscordTheme: !ThemeStore.theme.startsWith("bn-theme-") || !ThemeStore.theme.startsWith("darker")
? ThemeStore.theme
: _colorRef.lastSetDiscordTheme
});
}
const keyName = internalDef && internalDef.reference === "light" ? `bn-theme-${++_inc}` : `dark`;
const tsTheme = typeof ThemeStore.theme === "string" ? ThemeStore.theme : undefined;
const currentDiscordTheme = tsTheme === "darker" ? "dark" : (tsTheme ?? "");
const lastSet = (currentDiscordTheme && !currentDiscordTheme.startsWith("bn-theme-") && currentDiscordTheme !== keyName)
? currentDiscordTheme
: _colorRef.lastSetDiscordTheme;
var ref = Object.assign(_colorRef, {
current: internalDef,
key: keyName,
lastSetDiscordTheme: lastSet
});
if (internalDef != null) {
tokenRef.Theme[ref.key.toUpperCase()] = ref.key;
@ -66,7 +61,9 @@ export function updateBunnyColor(colorManifest: ColorManifest | null, { update =
if (update) {
AppearanceManager.setShouldSyncAppearanceSettings(false);
AppearanceManager.updateTheme(internalDef != null ? ref.key : "darker");
const fallback = (_colorRef.lastSetDiscordTheme && !_colorRef.lastSetDiscordTheme.startsWith("bn-theme-"))
? _colorRef.lastSetDiscordTheme
: "dark";
AppearanceManager.updateTheme(internalDef != null ? ref.key : fallback);
}
}