Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
kmmiio99o 2026-03-06 18:06:55 +01:00
commit 2f1702f5a4
No known key found for this signature in database
GPG key ID: 163FE39F7772D2EC
5 changed files with 133 additions and 193 deletions

View file

@ -28,84 +28,84 @@ let context = null;
/** @type {import("esbuild").BuildOptions} */ /** @type {import("esbuild").BuildOptions} */
const config = { const config = {
entryPoints: ["src/entry.ts"], entryPoints: ["src/entry.ts"],
bundle: true, bundle: true,
outfile: "dist/shiggycord.js", outfile: "dist/kettu.js",
format: "iife", format: "iife",
splitting: false, splitting: false,
// Enable minification by default for release builds (when a release-branch is provided). external: [],
// This keeps development builds unminified for easier debugging. supported: {
minify: true, // Hermes does not actually support const and let, even though it syntactically
external: [], // accepts it, but it's treated just like 'var' and causes issues
supported: { "const-and-let": false
// Hermes does not actually support const and let, even though it syntactically },
// accepts it, but it's treated just like 'var' and causes issues footer: {
"const-and-let": false, js: "//# sourceURL=kettu"
}, },
loader: { loader: {
".png": "dataurl", ".png": "dataurl",
".html": "text", ".html": "text"
}, },
define: { define: {
window: "globalThis", window: "globalThis",
__DEV__: JSON.stringify(releaseBranch !== "main"), __DEV__: JSON.stringify(releaseBranch !== "main")
}, },
inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js", "./shims/weakref.js"], inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"],
legalComments: "none", legalComments: "none",
alias: { alias: {
"!ShiggyCord-deps-shim!": "./shims/depsModule.ts", "!kettu-deps-shim!": "./shims/depsModule.ts",
spitroast: "./node_modules/spitroast", "spitroast": "./node_modules/spitroast",
"react/jsx-runtime": "./shims/jsxRuntime", "react/jsx-runtime": "./shims/jsxRuntime"
}, },
plugins: [ plugins: [
globalPlugin({ globalPlugin({
...metroDeps.reduce((obj, key) => { ...metroDeps.reduce((obj, key) => {
obj[key] = `require("!ShiggyCord-deps-shim!")[${JSON.stringify(key)}]`; obj[key] = `require("!kettu-deps-shim!")[${JSON.stringify(key)}]`;
return obj; return obj;
}, {}), }, {})
}), }),
{ {
name: "swc", name: "swc",
setup(build) { setup(build) {
build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => { build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async args => {
const result = await swc.transformFile(args.path, { const result = await swc.transformFile(args.path, {
jsc: { jsc: {
externalHelpers: true, externalHelpers: true,
transform: { transform: {
constModules: { constModules: {
globals: { globals: {
"bunny-build-info": { "bunny-build-info": {
version: `"1.4.1.2"`, version: `"v1.4.2"`
}, }
}, }
}, },
react: { react: {
runtime: "automatic", runtime: "automatic"
}, }
}, },
}, },
// https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md // https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md
// https://github.com/facebook/hermes/issues/696#issuecomment-1396235791 // https://github.com/facebook/hermes/issues/696#issuecomment-1396235791
env: { env: {
targets: "fully supports es6", targets: "fully supports es6",
include: [ include: [
"transform-block-scoping", "transform-block-scoping",
"transform-classes", "transform-classes",
"transform-async-to-generator", "transform-async-to-generator",
"transform-async-generator-functions", "transform-async-generator-functions"
], ],
exclude: [ exclude: [
"transform-parameters", "transform-parameters",
"transform-template-literals", "transform-template-literals",
"transform-exponentiation-operator", "transform-exponentiation-operator",
"transform-named-capturing-groups-regex", "transform-named-capturing-groups-regex",
"transform-nullish-coalescing-operator", "transform-nullish-coalescing-operator",
"transform-object-rest-spread", "transform-object-rest-spread",
"transform-optional-chaining", "transform-optional-chaining",
"transform-logical-assignment-operators", "transform-logical-assignment-operators"
], ]
}, },
}); });
return { contents: result.code }; return { contents: result.code };
}); });

View file

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

View file

@ -33,8 +33,6 @@ import {
Text, Text,
} from "@metro/common/components"; } from "@metro/common/components";
import { View, Image } from "react-native"; import { View, Image } from "react-native";
import { showConfirmationAlert } from "@core/vendetta/alerts";
import { BundleUpdaterManager } from "@lib/api/native/modules";
const { useReducer } = React; const { useReducer } = React;
@ -352,58 +350,12 @@ function BrowseThemePage(props: BrowseThemePageProps) {
); );
} }
/**
* Theme options have been changed from radio groups to individual switches
* for better UX. Each option can now be toggled independently.
*/
export default function Themes() { export default function Themes() {
useProxy(settings); useProxy(settings);
useProxy(themes); useProxy(themes);
const navigation = NavigationNative.useNavigation(); const navigation = NavigationNative.useNavigation();
const [mode, setMode] = React.useState<"installed" | "browse">("installed"); 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 [themesList, setThemesList] = React.useState<ThemeData[]>([]);
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);

View file

@ -2,12 +2,14 @@ import { _colorRef } from "@lib/addons/themes/colors/updater";
import { NativeThemeModule } from "@lib/api/native/modules"; import { NativeThemeModule } from "@lib/api/native/modules";
import { before, instead } from "@lib/api/patcher"; import { before, instead } from "@lib/api/patcher";
import { findByProps } from "@metro"; import { findByProps } from "@metro";
import { byMutableProp } from "@metro/filters"; import chroma from "chroma-js";
import { createLazyModule } from "@metro/lazy";
const getChroma = () => require("chroma-js") as typeof import("chroma-js").default;
const tokenReference = findByProps("SemanticColor"); const tokenReference = findByProps("SemanticColor");
const isThemeModule = createLazyModule(byMutableProp("isThemeDark")); const themeTypes = findByProps("ThemeTypes")?.ThemeTypes;
const origRawColor = { ...tokenReference.RawColor };
const origDarker = themeTypes.DARKER as string;
const origLight = themeTypes.LIGHT as string;
const SEMANTIC_FALLBACK_MAP: Record<string, string> = { const SEMANTIC_FALLBACK_MAP: Record<string, string> = {
"BG_BACKDROP": "BACKGROUND_FLOATING", "BG_BACKDROP": "BACKGROUND_FLOATING",
@ -22,13 +24,19 @@ const SEMANTIC_FALLBACK_MAP: Record<string, string> = {
"BG_SURFACE_RAISED": "BACKGROUND_MOBILE_PRIMARY" "BG_SURFACE_RAISED": "BACKGROUND_MOBILE_PRIMARY"
}; };
const origRawColor = { ...tokenReference.RawColor };
export default function patchDefinitionAndResolver() { export default function patchDefinitionAndResolver() {
const callback = ([theme]: any[]) => { const callback = ([theme]: any[]) => theme === _colorRef.key ? [_colorRef.current!.reference] : void 0;
if (!_colorRef.current) return void 0;
return theme === _colorRef.key ? [_colorRef.current.reference] : void 0; Object.defineProperty(themeTypes, "DARKER", {
}; configurable: true,
enumerable: true,
get: () => _colorRef.current?.reference === "darker" ? _colorRef.key : origDarker,
});
Object.defineProperty(themeTypes, "LIGHT", {
configurable: true,
enumerable: true,
get: () => _colorRef.current?.reference === "light" ? _colorRef.key : origLight,
});
Object.keys(tokenReference.RawColor).forEach(key => { Object.keys(tokenReference.RawColor).forEach(key => {
Object.defineProperty(tokenReference.RawColor, key, { Object.defineProperty(tokenReference.RawColor, key, {
@ -37,50 +45,48 @@ export default function patchDefinitionAndResolver() {
get: () => { get: () => {
const ret = _colorRef.current?.raw[key]; const ret = _colorRef.current?.raw[key];
if (ret) return ret; if (ret) return ret;
return origRawColor[key]; return origRawColor[key];
} }
}); });
}); });
const unpatches = [ const unpatches = [
before("isThemeDark", isThemeModule, callback),
before("isThemeLight", isThemeModule, callback),
before("updateTheme", NativeThemeModule, callback), before("updateTheme", NativeThemeModule, callback),
instead("resolveSemanticColor", tokenReference.default.meta ?? tokenReference.default.internal, (args: any[], orig: any) => { instead("resolveSemanticColor", tokenReference.default.meta ?? tokenReference.default.internal, (args: any[], orig: any) => {
if (!_colorRef.current) return orig(...args); if (!_colorRef.current) return orig(...args);
const currentRef = _colorRef.current;
if (args[0] !== _colorRef.key) return orig(...args); if (args[0] !== _colorRef.key) return orig(...args);
// Use the captured reference value args[0] = _colorRef.current.reference;
args[0] = currentRef.reference;
const [name, colorDef] = extractInfo(currentRef.reference, args[1]); const [name, colorDef] = extractInfo(_colorRef.current!.reference, args[1]);
// If extractInfo couldn't resolve a color definition, fall back to the original resolver let semanticDef = _colorRef.current.semantic[name];
if (!name || !colorDef) return orig(...args); if (!semanticDef && _colorRef.current.spec === 2 && name in SEMANTIC_FALLBACK_MAP) {
semanticDef = _colorRef.current.semantic[SEMANTIC_FALLBACK_MAP[name]];
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) { if (semanticDef?.value) {
if (semanticDef.opacity === 1) return semanticDef.value; return semanticDef.opacity === 1
return getChroma()(semanticDef.value).alpha(semanticDef.opacity).hex(); ? semanticDef.value
: chroma(semanticDef.value).alpha(semanticDef.opacity).hex();
} }
const rawKey = colorDef.raw; const rawValue = _colorRef.current.raw[colorDef.raw];
const rawValue = rawKey ? currentRef.raw[rawKey] : undefined;
if (rawValue) { if (rawValue) {
// Set opacity if needed // Set opacity if needed
return (colorDef.opacity === 1) ? rawValue : getChroma()(rawValue).alpha(colorDef.opacity).hex(); return colorDef.opacity === 1 ? rawValue : chroma(rawValue).alpha(colorDef.opacity).hex();
} }
// Fallback to default // Fallback to default
return orig(...args); return orig(...args);
}), }),
() => { () => {
Object.defineProperty(themeTypes, "DARKER", {
configurable: true, writable: true, value: origDarker
});
Object.defineProperty(themeTypes, "LIGHT", {
configurable: true, writable: true, value: origLight
});
Object.defineProperty(tokenReference, "RawColor", { Object.defineProperty(tokenReference, "RawColor", {
configurable: true, configurable: true,
writable: true, writable: true,
@ -95,13 +101,7 @@ export default function patchDefinitionAndResolver() {
function extractInfo(themeName: string, colorObj: any): [name: string, colorDef: any] { function extractInfo(themeName: string, colorObj: any): [name: string, colorDef: any] {
// @ts-ignore - assigning to extractInfo._sym // @ts-ignore - assigning to extractInfo._sym
const propName = colorObj[extractInfo._sym ??= Object.getOwnPropertySymbols(colorObj)[0]]; const propName = colorObj[extractInfo._sym ??= Object.getOwnPropertySymbols(colorObj)[0]];
const entry = tokenReference.SemanticColor[propName]; const colorDef = tokenReference.SemanticColor[propName];
if (!entry || typeof entry !== "object") { return [propName, colorDef[themeName]];
return [propName, undefined];
}
const colorDef = Object.prototype.hasOwnProperty.call(entry, themeName) ? entry[themeName] : undefined;
return [propName, colorDef];
} }

View file

@ -24,46 +24,35 @@ export const _colorRef: InternalColorRef = {
current: null, current: null,
key: `bn-theme-${_inc}`, key: `bn-theme-${_inc}`,
origRaw: origRawColor, origRaw: origRawColor,
lastSetDiscordTheme: "dark" lastSetDiscordTheme: "darker"
}; };
export function updateBunnyColor(colorManifest: ColorManifest | null, { update = true } ) { export function updateBunnyColor(colorManifest: ColorManifest | null, { update = true }) {
if (settings.safeMode?.enabled) return; if (settings.safeMode?.enabled) return;
const internalDef = colorManifest ? parseColorManifest(colorManifest) : null; const internalDef = colorManifest ? parseColorManifest(colorManifest) : null;
const ref = Object.assign(_colorRef, {
current: internalDef,
key: `bn-theme-${++_inc}`,
lastSetDiscordTheme: !ThemeStore.theme.startsWith("bn-theme-")
? ThemeStore.theme
: _colorRef.lastSetDiscordTheme
});
const keyName = internalDef && internalDef.reference === "light" ? `bn-theme-${++_inc}` : `dark`; if (internalDef != null) {
tokenRef.Theme[ref.key.toUpperCase()] = ref.key;
FormDivider.DIVIDER_COLORS[ref.key] = FormDivider.DIVIDER_COLORS[ref.current!.reference];
const tsTheme = typeof ThemeStore.theme === "string" ? ThemeStore.theme : undefined; Object.keys(tokenRef.Shadow).forEach(k => tokenRef.Shadow[k][ref.key] = tokenRef.Shadow[k][ref.current!.reference]);
const currentDiscordTheme = tsTheme === "darker" ? "dark" : (tsTheme ?? ""); Object.keys(tokenRef.SemanticColor).forEach(k => {
tokenRef.SemanticColor[k][ref.key] = {
const lastSet = (currentDiscordTheme && !currentDiscordTheme.startsWith("bn-theme-") && currentDiscordTheme !== keyName) ...tokenRef.SemanticColor[k][ref.current!.reference]
? currentDiscordTheme };
: _colorRef.lastSetDiscordTheme;
var ref = Object.assign(_colorRef, {
current: internalDef,
key: keyName,
lastSetDiscordTheme: lastSet
}); });
}
if (internalDef != null) {
tokenRef.Theme[ref.key.toUpperCase()] = ref.key;
FormDivider.DIVIDER_COLORS[ref.key] = FormDivider.DIVIDER_COLORS[ref.current!.reference];
Object.keys(tokenRef.Shadow).forEach(k => tokenRef.Shadow[k][ref.key] = tokenRef.Shadow[k][ref.current!.reference]);
Object.keys(tokenRef.SemanticColor).forEach(k => {
tokenRef.SemanticColor[k][ref.key] = {
...tokenRef.SemanticColor[k][ref.current!.reference]
};
});
}
if (update) { if (update) {
AppearanceManager.setShouldSyncAppearanceSettings(false); AppearanceManager.setShouldSyncAppearanceSettings(false);
const fallback = (_colorRef.lastSetDiscordTheme && !_colorRef.lastSetDiscordTheme.startsWith("bn-theme-")) AppearanceManager.updateTheme(internalDef != null ? ref.key : ref.lastSetDiscordTheme);
? _colorRef.lastSetDiscordTheme
: "dark";
AppearanceManager.updateTheme(internalDef != null ? ref.key : fallback);
} }
} }