refactor: port theme engine from dev
This commit is contained in:
parent
f44791049a
commit
d7be92ff49
12 changed files with 464 additions and 314 deletions
|
|
@ -2,22 +2,21 @@ import { formatString, Strings } from "@core/i18n";
|
||||||
import AddonCard, { CardWrapper } from "@core/ui/components/AddonCard";
|
import AddonCard, { CardWrapper } from "@core/ui/components/AddonCard";
|
||||||
import { showConfirmationAlert } from "@core/vendetta/alerts";
|
import { showConfirmationAlert } from "@core/vendetta/alerts";
|
||||||
import { useProxy } from "@core/vendetta/storage";
|
import { useProxy } from "@core/vendetta/storage";
|
||||||
import { applyTheme, fetchTheme, removeTheme, selectTheme, Theme, themes } from "@lib/addons/themes";
|
import { fetchTheme, removeTheme, selectTheme, themes, VdThemeInfo } from "@lib/addons/themes";
|
||||||
import { findAssetId } from "@lib/api/assets";
|
import { findAssetId } from "@lib/api/assets";
|
||||||
import { settings } from "@lib/api/settings";
|
import { settings } from "@lib/api/settings";
|
||||||
import { clipboard } from "@metro/common";
|
import { clipboard } from "@metro/common";
|
||||||
import { showToast } from "@ui/toasts";
|
import { showToast } from "@ui/toasts";
|
||||||
|
|
||||||
function selectAndApply(value: boolean, theme: Theme) {
|
function selectAndApply(value: boolean, theme: VdThemeInfo) {
|
||||||
try {
|
try {
|
||||||
selectTheme(value ? theme : null);
|
selectTheme(value ? theme : null);
|
||||||
applyTheme(value ? theme : null);
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("Error while selectAndApply,", e);
|
console.error("Error while selectAndApply,", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ThemeCard({ item: theme }: CardWrapper<Theme>) {
|
export default function ThemeCard({ item: theme }: CardWrapper<VdThemeInfo>) {
|
||||||
useProxy(theme);
|
useProxy(theme);
|
||||||
|
|
||||||
const [removed, setRemoved] = React.useState(false);
|
const [removed, setRemoved] = React.useState(false);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { initVendettaObject } from "@core/vendetta/api";
|
||||||
import { VdPluginManager } from "@core/vendetta/plugins";
|
import { VdPluginManager } from "@core/vendetta/plugins";
|
||||||
import { updateFonts } from "@lib/addons/fonts";
|
import { updateFonts } from "@lib/addons/fonts";
|
||||||
import { initPlugins, updatePlugins } from "@lib/addons/plugins";
|
import { initPlugins, updatePlugins } from "@lib/addons/plugins";
|
||||||
import { initThemes, patchChatBackground } from "@lib/addons/themes";
|
import { initThemes } from "@lib/addons/themes";
|
||||||
import { patchCommands } from "@lib/api/commands";
|
import { patchCommands } from "@lib/api/commands";
|
||||||
import { patchLogHook } from "@lib/api/debug";
|
import { patchLogHook } from "@lib/api/debug";
|
||||||
import { injectFluxInterceptor } from "@lib/api/flux";
|
import { injectFluxInterceptor } from "@lib/api/flux";
|
||||||
|
|
@ -40,7 +40,6 @@ export default async () => {
|
||||||
patchSettings(),
|
patchSettings(),
|
||||||
patchLogHook(),
|
patchLogHook(),
|
||||||
patchCommands(),
|
patchCommands(),
|
||||||
patchChatBackground(),
|
|
||||||
patchJsx(),
|
patchJsx(),
|
||||||
initVendettaObject(),
|
initVendettaObject(),
|
||||||
initFetchI18nStrings(),
|
initFetchI18nStrings(),
|
||||||
|
|
|
||||||
19
src/lib/addons/themes/colors/index.ts
Normal file
19
src/lib/addons/themes/colors/index.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
import patchChatBackground from "./patches/background";
|
||||||
|
import patchDefinitionAndResolver from "./patches/resolver";
|
||||||
|
import patchStorage from "./patches/storage";
|
||||||
|
import { ColorManifest } from "./types";
|
||||||
|
import { updateBunnyColor } from "./updater";
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export default function initColors(manifest: ColorManifest | null) {
|
||||||
|
const patches = [
|
||||||
|
patchStorage(),
|
||||||
|
patchDefinitionAndResolver(),
|
||||||
|
patchChatBackground()
|
||||||
|
];
|
||||||
|
|
||||||
|
if (manifest) updateBunnyColor(manifest, { update: false });
|
||||||
|
|
||||||
|
return () => patches.forEach(p => p());
|
||||||
|
}
|
||||||
133
src/lib/addons/themes/colors/parser.ts
Normal file
133
src/lib/addons/themes/colors/parser.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
import { findByProps } from "@metro";
|
||||||
|
import chroma from "chroma-js";
|
||||||
|
import { omit } from "es-toolkit";
|
||||||
|
import { Platform, processColor } from "react-native";
|
||||||
|
|
||||||
|
import { ColorManifest, InternalColorDefinition } from "./types";
|
||||||
|
|
||||||
|
const tokenRef = findByProps("SemanticColor");
|
||||||
|
|
||||||
|
export function parseColorManifest(manifest: ColorManifest): InternalColorDefinition {
|
||||||
|
const resolveType = (type = "dark") => /* (ColorManager.preferences.type ?? type) */ type === "dark" ? "darker" : "light";
|
||||||
|
|
||||||
|
if (manifest.spec === 3) {
|
||||||
|
const semanticColorDefinitions: InternalColorDefinition["semantic"] = {};
|
||||||
|
|
||||||
|
for (const [semanticColorKey, semanticColorValue] of Object.entries(manifest.semantic ?? {})) {
|
||||||
|
if (typeof semanticColorValue === "object") {
|
||||||
|
const { type, value, opacity: semanticColorOpacity } = semanticColorValue;
|
||||||
|
|
||||||
|
if (type === "raw") {
|
||||||
|
semanticColorDefinitions[semanticColorKey] = {
|
||||||
|
value,
|
||||||
|
opacity: semanticColorOpacity ?? 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const rawColorValue = tokenRef.RawColor[value];
|
||||||
|
semanticColorDefinitions[semanticColorKey] = {
|
||||||
|
value: rawColorValue,
|
||||||
|
opacity: semanticColorOpacity ?? 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (typeof semanticColorValue === "string") {
|
||||||
|
if (semanticColorValue.startsWith("#")) {
|
||||||
|
semanticColorDefinitions[semanticColorKey] = {
|
||||||
|
value: chroma.hex(semanticColorValue).hex(),
|
||||||
|
opacity: 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
semanticColorDefinitions[semanticColorKey] = {
|
||||||
|
value: tokenRef.RawColor[semanticColorValue],
|
||||||
|
opacity: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid semantic definitions: ${semanticColorValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
reference: resolveType(manifest.type),
|
||||||
|
semantic: semanticColorDefinitions,
|
||||||
|
raw: manifest.raw ?? {},
|
||||||
|
background: manifest.background,
|
||||||
|
};
|
||||||
|
} else if (manifest.spec === 2) { // is Vendetta theme
|
||||||
|
const semanticDefinitions: InternalColorDefinition["semantic"] = {};
|
||||||
|
const background: InternalColorDefinition["background"] | undefined = manifest.background ? {
|
||||||
|
...omit(manifest.background, ["alpha"]),
|
||||||
|
opacity: manifest.background.alpha
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
if (manifest.semanticColors) {
|
||||||
|
for (const key in manifest.semanticColors) {
|
||||||
|
const values = manifest.semanticColors[key].map(c => c || undefined).slice(0, 2);
|
||||||
|
if (!values[0]) continue;
|
||||||
|
|
||||||
|
semanticDefinitions[key] = {
|
||||||
|
value: normalizeToHex(values[resolveType() === "light" ? 1 : 0])!,
|
||||||
|
opacity: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.rawColors) {
|
||||||
|
for (const key in manifest.rawColors) {
|
||||||
|
const value = manifest.rawColors[key];
|
||||||
|
if (!value) continue;
|
||||||
|
manifest.rawColors[key] = normalizeToHex(value)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform.OS === "android") applyAndroidAlphaKeys(manifest.rawColors);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
reference: resolveType(),
|
||||||
|
semantic: semanticDefinitions,
|
||||||
|
raw: manifest.rawColors ?? {},
|
||||||
|
background
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Invalid theme spec");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyAndroidAlphaKeys(rawColors?: Record<string, string>) {
|
||||||
|
if (!rawColors) return;
|
||||||
|
|
||||||
|
// these are native Discord Android keys
|
||||||
|
const alphaMap: Record<string, [string, number]> = {
|
||||||
|
"BLACK_ALPHA_60": ["BLACK", 0.6],
|
||||||
|
"BRAND_NEW_360_ALPHA_20": ["BRAND_360", 0.2],
|
||||||
|
"BRAND_NEW_360_ALPHA_25": ["BRAND_360", 0.25],
|
||||||
|
"BRAND_NEW_500_ALPHA_20": ["BRAND_500", 0.2],
|
||||||
|
"PRIMARY_DARK_500_ALPHA_20": ["PRIMARY_500", 0.2],
|
||||||
|
"PRIMARY_DARK_700_ALPHA_60": ["PRIMARY_700", 0.6],
|
||||||
|
"STATUS_GREEN_500_ALPHA_20": ["GREEN_500", 0.2],
|
||||||
|
"STATUS_RED_500_ALPHA_20": ["RED_500", 0.2],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key in alphaMap) {
|
||||||
|
const [colorKey, alpha] = alphaMap[key];
|
||||||
|
if (!rawColors[colorKey]) continue;
|
||||||
|
rawColors[key] = chroma(rawColors[colorKey]).alpha(alpha).hex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeToHex(colorString: string | undefined): string | undefined {
|
||||||
|
if (colorString === undefined) return undefined;
|
||||||
|
if (chroma.valid(colorString)) return chroma(colorString).hex();
|
||||||
|
|
||||||
|
const color = Number(processColor(colorString));
|
||||||
|
|
||||||
|
return chroma.rgb(
|
||||||
|
color >> 16 & 0xff, // red
|
||||||
|
color >> 8 & 0xff, // green
|
||||||
|
color & 0xff, // blue
|
||||||
|
color >> 24 & 0xff // alpha
|
||||||
|
).hex();
|
||||||
|
}
|
||||||
|
|
||||||
53
src/lib/addons/themes/colors/patches/background.tsx
Normal file
53
src/lib/addons/themes/colors/patches/background.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { _colorRef } from "@lib/addons/themes/colors/updater";
|
||||||
|
import { after } from "@lib/api/patcher";
|
||||||
|
import { findInReactTree } from "@lib/utils";
|
||||||
|
import { lazyDestructure } from "@lib/utils/lazy";
|
||||||
|
import { findByNameLazy, findByProps } from "@metro";
|
||||||
|
import chroma from "chroma-js";
|
||||||
|
import { ImageBackground } from "react-native";
|
||||||
|
|
||||||
|
const MessagesWrapperConnected = findByNameLazy("MessagesWrapperConnected", false);
|
||||||
|
const { MessagesWrapper } = lazyDestructure(() => findByProps("MessagesWrapper"));
|
||||||
|
|
||||||
|
export default function patchChatBackground() {
|
||||||
|
const patches = [
|
||||||
|
after("default", MessagesWrapperConnected, (_, ret) => {
|
||||||
|
// TODO: support custom preferences
|
||||||
|
// useObservable([ColorManager.preferences]);
|
||||||
|
// if (!_colorRef.current || ColorManager.preferences.customBackground === "hidden") return ret;
|
||||||
|
|
||||||
|
if (!_colorRef.current || !_colorRef.current.background?.url) return ret;
|
||||||
|
|
||||||
|
return <ImageBackground
|
||||||
|
style={{ flex: 1, height: "100%" }}
|
||||||
|
source={_colorRef.current.background?.url && { uri: _colorRef.current.background.url } || 0}
|
||||||
|
blurRadius={typeof _colorRef.current.background?.blur === "number" ? _colorRef.current.background?.blur : 0}
|
||||||
|
>
|
||||||
|
{ret}
|
||||||
|
</ImageBackground>;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
after("render", MessagesWrapper.prototype, (_, ret) => {
|
||||||
|
if (!_colorRef.current || !_colorRef.current.background?.url) return;
|
||||||
|
const messagesComponent = findInReactTree(
|
||||||
|
ret,
|
||||||
|
x => x && "HACK_fixModalInteraction" in x.props && x?.props?.style
|
||||||
|
);
|
||||||
|
|
||||||
|
if (messagesComponent) {
|
||||||
|
const backgroundColor = chroma(
|
||||||
|
messagesComponent.props.style.backgroundColor || "black"
|
||||||
|
).alpha(
|
||||||
|
1 - (_colorRef.current.background?.opacity ?? 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
messagesComponent.props.style = [
|
||||||
|
messagesComponent.props.style,
|
||||||
|
{ backgroundColor }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => patches.forEach(x => x());
|
||||||
|
}
|
||||||
71
src/lib/addons/themes/colors/patches/resolver.ts
Normal file
71
src/lib/addons/themes/colors/patches/resolver.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { _colorRef } from "@lib/addons/themes/colors/updater";
|
||||||
|
import { ThemeManager } from "@lib/api/native/modules";
|
||||||
|
import { before, instead } from "@lib/api/patcher";
|
||||||
|
import { findByProps } from "@metro";
|
||||||
|
import { byMutableProp } from "@metro/filters";
|
||||||
|
import { createLazyModule } from "@metro/lazy";
|
||||||
|
import chroma from "chroma-js";
|
||||||
|
|
||||||
|
const tokenReference = findByProps("SemanticColor");
|
||||||
|
const isThemeModule = createLazyModule(byMutableProp("isThemeDark"));
|
||||||
|
|
||||||
|
export default function patchDefinitionAndResolver() {
|
||||||
|
const callback = ([theme]: any[]) => theme === _colorRef.key ? [_colorRef.current!.reference] : void 0;
|
||||||
|
|
||||||
|
Object.keys(tokenReference.RawColor).forEach(key => {
|
||||||
|
Object.defineProperty(tokenReference.RawColor, key, {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: () => {
|
||||||
|
const ret = _colorRef.current?.raw[key];
|
||||||
|
return ret || _colorRef.origRaw[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const unpatches = [
|
||||||
|
before("isThemeDark", isThemeModule, callback),
|
||||||
|
before("isThemeLight", isThemeModule, callback),
|
||||||
|
before("updateTheme", ThemeManager, callback),
|
||||||
|
instead("resolveSemanticColor", tokenReference.default.meta ?? tokenReference.default.internal, (args: any[], orig: any) => {
|
||||||
|
if (!_colorRef.current) return orig(...args);
|
||||||
|
if (args[0] !== _colorRef.key) return orig(...args);
|
||||||
|
|
||||||
|
args[0] = _colorRef.current.reference;
|
||||||
|
|
||||||
|
const [name, colorDef] = extractInfo(_colorRef.current!.reference, args[1]);
|
||||||
|
|
||||||
|
const semanticDef = _colorRef.current.semantic[name];
|
||||||
|
if (semanticDef?.value) {
|
||||||
|
return chroma(semanticDef.value).alpha(semanticDef.opacity).hex();
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawValue = _colorRef.current.raw[colorDef.raw];
|
||||||
|
if (rawValue) {
|
||||||
|
// Set opacity if needed
|
||||||
|
return colorDef.opacity === 1 ? rawValue : chroma(rawValue).alpha(colorDef.opacity).hex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to default
|
||||||
|
return orig(...args);
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
|
// Not the actual module but.. yeah.
|
||||||
|
Object.defineProperty(tokenReference, "RawColor", {
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
value: _colorRef.origRaw
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => unpatches.forEach(p => p());
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
return [propName, colorDef[themeName]];
|
||||||
|
}
|
||||||
38
src/lib/addons/themes/colors/patches/storage.ts
Normal file
38
src/lib/addons/themes/colors/patches/storage.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { _colorRef } from "@lib/addons/themes/colors/updater";
|
||||||
|
import { after, before } from "@lib/api/patcher";
|
||||||
|
import { findInTree } from "@lib/utils";
|
||||||
|
import { proxyLazy } from "@lib/utils/lazy";
|
||||||
|
import { findByProps } from "@metro";
|
||||||
|
|
||||||
|
const mmkvStorage = proxyLazy(() => {
|
||||||
|
const newModule = findByProps("impl");
|
||||||
|
if (typeof newModule?.impl === "object") return newModule.impl;
|
||||||
|
return findByProps("storage");
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function patchStorage() {
|
||||||
|
const patchedKeys = new Set(["ThemeStore", "SelectivelySyncedUserSettingsStore"]);
|
||||||
|
|
||||||
|
const patches = [
|
||||||
|
after("get", mmkvStorage, ([key], ret) => {
|
||||||
|
if (!_colorRef.current || !patchedKeys.has(key)) return;
|
||||||
|
|
||||||
|
const state = findInTree(ret._state, s => typeof s.theme === "string");
|
||||||
|
if (state) state.theme = _colorRef.key;
|
||||||
|
}),
|
||||||
|
before("set", mmkvStorage, ([key, value]) => {
|
||||||
|
if (!patchedKeys.has(key)) return;
|
||||||
|
|
||||||
|
const json = JSON.stringify(value);
|
||||||
|
const lastSetDiscordTheme = _colorRef.lastSetDiscordTheme ?? "darker";
|
||||||
|
const replaced = json.replace(
|
||||||
|
/"theme":"bn-theme-\d+"/,
|
||||||
|
`"theme":${JSON.stringify(lastSetDiscordTheme)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return [key, JSON.parse(replaced)];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => patches.forEach(p => p());
|
||||||
|
}
|
||||||
48
src/lib/addons/themes/colors/types.ts
Normal file
48
src/lib/addons/themes/colors/types.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Author, BunnyManifest } from "@lib/addons/types";
|
||||||
|
|
||||||
|
interface SemanticReference {
|
||||||
|
type: "color" | "raw";
|
||||||
|
value: string;
|
||||||
|
opacity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BackgroundDefinition {
|
||||||
|
url: string;
|
||||||
|
blur?: number;
|
||||||
|
opacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BunnyColorManifest extends BunnyManifest {
|
||||||
|
spec: 3;
|
||||||
|
type: "dark" | "light";
|
||||||
|
semantic?: Record<string, string | SemanticReference>;
|
||||||
|
raw?: Record<string, string>;
|
||||||
|
background?: BackgroundDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VendettaThemeManifest {
|
||||||
|
spec: 2;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
authors?: Author[];
|
||||||
|
semanticColors?: Record<string, (string | false)[]>;
|
||||||
|
rawColors?: Record<string, string>;
|
||||||
|
background?: {
|
||||||
|
url: string;
|
||||||
|
blur?: number;
|
||||||
|
alpha?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export interface InternalColorDefinition {
|
||||||
|
reference: "darker" | "light";
|
||||||
|
semantic: Record<string, {
|
||||||
|
value: string;
|
||||||
|
opacity: number;
|
||||||
|
}>;
|
||||||
|
raw: Record<string, string>;
|
||||||
|
background?: BackgroundDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ColorManifest = BunnyColorManifest | VendettaThemeManifest;
|
||||||
59
src/lib/addons/themes/colors/updater.ts
Normal file
59
src/lib/addons/themes/colors/updater.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { settings } from "@lib/api/settings";
|
||||||
|
import { findByProps, findByPropsLazy, findByStoreNameLazy } from "@metro";
|
||||||
|
|
||||||
|
import { parseColorManifest } from "./parser";
|
||||||
|
import { ColorManifest, InternalColorDefinition } from "./types";
|
||||||
|
|
||||||
|
const tokenRef = findByProps("SemanticColor");
|
||||||
|
const origRawColor = { ...tokenRef.RawColor };
|
||||||
|
const AppearanceManager = findByPropsLazy("updateTheme");
|
||||||
|
const ThemeStore = findByStoreNameLazy("ThemeStore");
|
||||||
|
const FormDivider = findByPropsLazy("DIVIDER_COLORS");
|
||||||
|
|
||||||
|
let _inc = 1;
|
||||||
|
|
||||||
|
interface InternalColorRef {
|
||||||
|
key: `bn-theme-${string}`;
|
||||||
|
current: InternalColorDefinition | null;
|
||||||
|
readonly origRaw: Record<string, string>;
|
||||||
|
lastSetDiscordTheme: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const _colorRef: InternalColorRef = {
|
||||||
|
current: null,
|
||||||
|
key: `bn-theme-${_inc}`,
|
||||||
|
origRaw: origRawColor,
|
||||||
|
lastSetDiscordTheme: "darker"
|
||||||
|
};
|
||||||
|
|
||||||
|
export function updateBunnyColor(colorManifest: ColorManifest | null, { update = true }) {
|
||||||
|
if (settings.safeMode?.enabled) return;
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
AppearanceManager.setShouldSyncAppearanceSettings(false);
|
||||||
|
AppearanceManager.updateTheme(internalDef != null ? ref.key : ref.lastSetDiscordTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,77 +1,22 @@
|
||||||
/**
|
|
||||||
* Theming system in Bunny is currently a prototype, expect an unreadable theme implementation below
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { awaitStorage, createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@core/vendetta/storage";
|
import { awaitStorage, createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@core/vendetta/storage";
|
||||||
import { Author } from "@lib/addons/types";
|
|
||||||
import { getStoredTheme, getThemeFilePath } from "@lib/api/native/loader";
|
import { getStoredTheme, getThemeFilePath } from "@lib/api/native/loader";
|
||||||
import { ThemeManager } from "@lib/api/native/modules";
|
import { safeFetch } from "@lib/utils";
|
||||||
import { after, before, instead } from "@lib/api/patcher";
|
import { Platform } from "react-native";
|
||||||
import { findInReactTree, safeFetch } from "@lib/utils";
|
|
||||||
import { lazyDestructure, proxyLazy } from "@lib/utils/lazy";
|
|
||||||
import { byMutableProp } from "@metro/filters";
|
|
||||||
import { createLazyModule } from "@metro/lazy";
|
|
||||||
import { findByNameLazy, findByProps, findByPropsLazy, findByStoreNameLazy } from "@metro/wrappers";
|
|
||||||
import chroma from "chroma-js";
|
|
||||||
import { ImageBackground, Platform, processColor } from "react-native";
|
|
||||||
|
|
||||||
export interface ThemeData {
|
import initColors from "./colors";
|
||||||
name: string;
|
import { applyAndroidAlphaKeys, normalizeToHex } from "./colors/parser";
|
||||||
description?: string;
|
import { VendettaThemeManifest } from "./colors/types";
|
||||||
authors?: Author[];
|
import { updateBunnyColor } from "./colors/updater";
|
||||||
spec: number;
|
|
||||||
semanticColors?: Record<string, (string | false)[]>;
|
|
||||||
rawColors?: Record<string, string>;
|
|
||||||
background?: {
|
|
||||||
url: string;
|
|
||||||
blur?: number;
|
|
||||||
/**
|
|
||||||
* The alpha value of the background.
|
|
||||||
* `CHAT_BACKGROUND` of semanticColors alpha value will be ignored when this is specified
|
|
||||||
*/
|
|
||||||
alpha?: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Theme {
|
export interface VdThemeInfo {
|
||||||
id: string;
|
id: string;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
data: ThemeData;
|
data: VendettaThemeManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! As of 173.10, early-finding this does not work.
|
export const themes = wrapSync(createStorage<Record<string, VdThemeInfo>>(createMMKVBackend("VENDETTA_THEMES")));
|
||||||
// Somehow, this is late enough, though?
|
|
||||||
export const color = findByPropsLazy("SemanticColor");
|
|
||||||
|
|
||||||
const mmkvStorage = proxyLazy(() => {
|
async function writeTheme(theme: VdThemeInfo | {}) {
|
||||||
const newModule = findByProps("impl");
|
|
||||||
if (typeof newModule?.impl === "object") return newModule.impl;
|
|
||||||
return findByProps("storage");
|
|
||||||
});
|
|
||||||
|
|
||||||
const appearanceManager = findByPropsLazy("updateTheme");
|
|
||||||
const ThemeStore = findByStoreNameLazy("ThemeStore");
|
|
||||||
const formDividerModule = findByPropsLazy("DIVIDER_COLORS");
|
|
||||||
const MessagesWrapperConnected = findByNameLazy("MessagesWrapperConnected", false);
|
|
||||||
const { MessagesWrapper } = lazyDestructure(() => findByProps("MessagesWrapper"));
|
|
||||||
const isThemeModule = createLazyModule(byMutableProp("isThemeDark"));
|
|
||||||
|
|
||||||
export const themes = wrapSync(createStorage<Record<string, Theme>>(createMMKVBackend("VENDETTA_THEMES")));
|
|
||||||
|
|
||||||
const semanticAlternativeMap: Record<string, string> = {
|
|
||||||
"BG_BACKDROP": "BACKGROUND_FLOATING",
|
|
||||||
"BG_BASE_PRIMARY": "BACKGROUND_PRIMARY",
|
|
||||||
"BG_BASE_SECONDARY": "BACKGROUND_SECONDARY",
|
|
||||||
"BG_BASE_TERTIARY": "BACKGROUND_SECONDARY_ALT",
|
|
||||||
"BG_MOD_FAINT": "BACKGROUND_MODIFIER_ACCENT",
|
|
||||||
"BG_MOD_STRONG": "BACKGROUND_MODIFIER_ACCENT",
|
|
||||||
"BG_MOD_SUBTLE": "BACKGROUND_MODIFIER_ACCENT",
|
|
||||||
"BG_SURFACE_OVERLAY": "BACKGROUND_FLOATING",
|
|
||||||
"BG_SURFACE_OVERLAY_TMP": "BACKGROUND_FLOATING",
|
|
||||||
"BG_SURFACE_RAISED": "BACKGROUND_MOBILE_PRIMARY"
|
|
||||||
};
|
|
||||||
|
|
||||||
async function writeTheme(theme: Theme | {}) {
|
|
||||||
if (typeof theme !== "object") throw new Error("Theme must be an object");
|
if (typeof theme !== "object") throw new Error("Theme must be an object");
|
||||||
|
|
||||||
// Save the current theme as current-theme.json. When supported by loader,
|
// Save the current theme as current-theme.json. When supported by loader,
|
||||||
|
|
@ -79,59 +24,14 @@ async function writeTheme(theme: Theme | {}) {
|
||||||
await createFileBackend(getThemeFilePath() || "theme.json").set(theme);
|
await createFileBackend(getThemeFilePath() || "theme.json").set(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export function patchChatBackground() {
|
|
||||||
const patches = [
|
|
||||||
after("default", MessagesWrapperConnected, (_, ret) => enabled ? React.createElement(ImageBackground, {
|
|
||||||
style: { flex: 1, height: "100%" },
|
|
||||||
source: currentTheme?.data?.background?.url && { uri: currentTheme.data.background.url } || 0,
|
|
||||||
blurRadius: typeof currentTheme?.data?.background?.blur === "number" ? currentTheme?.data?.background?.blur : 0,
|
|
||||||
children: ret,
|
|
||||||
}) : ret),
|
|
||||||
after("render", MessagesWrapper.prototype, (_, ret) => {
|
|
||||||
if (!enabled || !currentTheme?.data?.background?.url) return;
|
|
||||||
|
|
||||||
// HORRIBLE
|
|
||||||
const Messages = findInReactTree(ret, x => x && "HACK_fixModalInteraction" in x.props && x?.props?.style);
|
|
||||||
if (Messages) {
|
|
||||||
Messages.props.style = [
|
|
||||||
Messages.props.style,
|
|
||||||
{
|
|
||||||
backgroundColor: chroma(Messages.props.style.backgroundColor || "black")
|
|
||||||
.alpha(1 - (currentTheme?.data.background?.alpha ?? 1)).hex()
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
else console.error("Didn't find Messages when patching MessagesWrapper!");
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
return () => patches.forEach(x => x());
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeToHex(colorString: string): string {
|
|
||||||
if (chroma.valid(colorString)) return chroma(colorString).hex();
|
|
||||||
|
|
||||||
const color = Number(processColor(colorString));
|
|
||||||
|
|
||||||
return chroma.rgb(
|
|
||||||
color >> 16 & 0xff, // red
|
|
||||||
color >> 8 & 0xff, // green
|
|
||||||
color & 0xff, // blue
|
|
||||||
color >> 24 & 0xff // alpha
|
|
||||||
).hex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process data for some compatiblity with native side
|
// Process data for some compatiblity with native side
|
||||||
function processData(data: ThemeData) {
|
function processData(data: VendettaThemeManifest) {
|
||||||
if (data.semanticColors) {
|
if (data.semanticColors) {
|
||||||
const { semanticColors } = data;
|
const { semanticColors } = data;
|
||||||
|
|
||||||
for (const key in semanticColors) {
|
for (const key in semanticColors) {
|
||||||
for (const index in semanticColors[key]) {
|
for (const index in semanticColors[key]) {
|
||||||
semanticColors[key][index] &&= normalizeToHex(semanticColors[key][index] as string);
|
semanticColors[key][index] &&= normalizeToHex(semanticColors[key][index] as string) || false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +40,8 @@ function processData(data: ThemeData) {
|
||||||
const { rawColors } = data;
|
const { rawColors } = data;
|
||||||
|
|
||||||
for (const key in rawColors) {
|
for (const key in rawColors) {
|
||||||
data.rawColors[key] = normalizeToHex(rawColors[key]);
|
const normalized = normalizeToHex(rawColors[key]);
|
||||||
|
if (normalized) data.rawColors[key] = normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.OS === "android") applyAndroidAlphaKeys(rawColors);
|
if (Platform.OS === "android") applyAndroidAlphaKeys(rawColors);
|
||||||
|
|
@ -153,26 +54,6 @@ function processData(data: ThemeData) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAndroidAlphaKeys(rawColors: Record<string, string>) {
|
|
||||||
// these are native Discord Android keys
|
|
||||||
const alphaMap: Record<string, [string, number]> = {
|
|
||||||
"BLACK_ALPHA_60": ["BLACK", 0.6],
|
|
||||||
"BRAND_NEW_360_ALPHA_20": ["BRAND_360", 0.2],
|
|
||||||
"BRAND_NEW_360_ALPHA_25": ["BRAND_360", 0.25],
|
|
||||||
"BRAND_NEW_500_ALPHA_20": ["BRAND_500", 0.2],
|
|
||||||
"PRIMARY_DARK_500_ALPHA_20": ["PRIMARY_500", 0.2],
|
|
||||||
"PRIMARY_DARK_700_ALPHA_60": ["PRIMARY_700", 0.6],
|
|
||||||
"STATUS_GREEN_500_ALPHA_20": ["GREEN_500", 0.2],
|
|
||||||
"STATUS_RED_500_ALPHA_20": ["RED_500", 0.2],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const key in alphaMap) {
|
|
||||||
const [colorKey, alpha] = alphaMap[key];
|
|
||||||
if (!rawColors[colorKey]) continue;
|
|
||||||
rawColors[key] = chroma(rawColors[colorKey]).alpha(alpha).hex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchTheme(id: string, selected = false) {
|
export async function fetchTheme(id: string, selected = false) {
|
||||||
let themeJSON: any;
|
let themeJSON: any;
|
||||||
|
|
||||||
|
|
@ -188,10 +69,9 @@ export async function fetchTheme(id: string, selected = false) {
|
||||||
data: processData(themeJSON),
|
data: processData(themeJSON),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Should we prompt when the selected theme is updated?
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
writeTheme(themes[id]);
|
writeTheme(themes[id]);
|
||||||
applyTheme(themes[id], vdThemeFallback);
|
updateBunnyColor(themes[id].data, { update: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,15 +80,17 @@ export async function installTheme(id: string) {
|
||||||
await fetchTheme(id);
|
await fetchTheme(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectTheme(theme: Theme | null, write = true) {
|
export function selectTheme(theme: VdThemeInfo | null, write = true) {
|
||||||
if (theme) theme.selected = true;
|
if (theme) theme.selected = true;
|
||||||
Object.keys(themes).forEach(
|
Object.keys(themes).forEach(
|
||||||
k => themes[k].selected = themes[k].id === theme?.id
|
k => themes[k].selected = themes[k].id === theme?.id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (theme == null && write) {
|
if (theme == null && write) {
|
||||||
|
updateBunnyColor(null, { update: true });
|
||||||
return writeTheme({});
|
return writeTheme({});
|
||||||
} else if (theme) {
|
} else if (theme) {
|
||||||
|
updateBunnyColor(theme.data, { update: true });
|
||||||
return writeTheme(theme);
|
return writeTheme(theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -221,13 +103,6 @@ export async function removeTheme(id: string) {
|
||||||
return theme.selected;
|
return theme.selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export function getThemeFromLoader(): Theme | null {
|
|
||||||
return getStoredTheme();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateThemes() {
|
export async function updateThemes() {
|
||||||
await awaitStorage(themes);
|
await awaitStorage(themes);
|
||||||
const currentTheme = getThemeFromLoader();
|
const currentTheme = getThemeFromLoader();
|
||||||
|
|
@ -235,157 +110,14 @@ export async function updateThemes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentTheme() {
|
export function getCurrentTheme() {
|
||||||
return currentTheme;
|
return Object.values(themes).find(t => t.selected) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const origRawColor = { ...color.RawColor };
|
/**
|
||||||
|
* @internal
|
||||||
let inc = 0;
|
*/
|
||||||
let vdKey = "vd-theme";
|
export function getThemeFromLoader(): VdThemeInfo | null {
|
||||||
|
return getStoredTheme();
|
||||||
let vdThemeFallback = "darker";
|
|
||||||
let enabled = false;
|
|
||||||
let currentTheme: Theme | null;
|
|
||||||
let storageResolved = false;
|
|
||||||
|
|
||||||
const discordThemes = new Set(["darker", "midnight", "dark", "light"]);
|
|
||||||
function isDiscordTheme(name: string) {
|
|
||||||
return discordThemes.has(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function patchColor() {
|
|
||||||
const callback = ([theme]: any[]) => theme === vdKey ? [vdThemeFallback] : void 0;
|
|
||||||
|
|
||||||
Object.keys(color.RawColor).forEach(k => {
|
|
||||||
Object.defineProperty(color.RawColor, k, {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
get: () => {
|
|
||||||
return enabled ? currentTheme?.data?.rawColors?.[k] ?? origRawColor[k] : origRawColor[k];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
before("isThemeDark", isThemeModule, callback);
|
|
||||||
before("isThemeLight", isThemeModule, callback);
|
|
||||||
before("updateTheme", ThemeManager, callback);
|
|
||||||
|
|
||||||
after("get", mmkvStorage, ([a], ret) => {
|
|
||||||
if (a === "SelectivelySyncedUserSettingsStore") {
|
|
||||||
storageResolved = true;
|
|
||||||
if (ret?._state?.appearance?.settings?.theme && enabled) {
|
|
||||||
vdThemeFallback = ret._state.appearance.settings.theme;
|
|
||||||
ret._state.appearance.settings.theme = vdKey;
|
|
||||||
}
|
|
||||||
} else if (a === "ThemeStore") {
|
|
||||||
storageResolved = true;
|
|
||||||
if (ret?._state?.theme && enabled) {
|
|
||||||
vdThemeFallback = ret._state.theme;
|
|
||||||
ret._state.theme = vdKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prevent setting to real Discord settings
|
|
||||||
before("set", mmkvStorage, args => {
|
|
||||||
if (!args[1]) return;
|
|
||||||
|
|
||||||
const key = args[0];
|
|
||||||
const value = JSON.parse(JSON.stringify(args[1]));
|
|
||||||
|
|
||||||
const interceptors: Record<string, () => void> = ({
|
|
||||||
SelectivelySyncedUserSettingsStore: () => {
|
|
||||||
if (value._state?.appearance?.settings?.theme) {
|
|
||||||
const { theme } = value._state?.appearance?.settings ?? {};
|
|
||||||
if (isDiscordTheme(theme)) {
|
|
||||||
vdThemeFallback = theme;
|
|
||||||
} else {
|
|
||||||
value._state.appearance.settings.theme = vdThemeFallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ThemeStore: () => {
|
|
||||||
if (value._state?.theme) {
|
|
||||||
const { theme } = value._state;
|
|
||||||
if (isDiscordTheme(theme)) {
|
|
||||||
vdThemeFallback = theme;
|
|
||||||
} else {
|
|
||||||
value._state.theme = vdThemeFallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!(key in interceptors)) return args;
|
|
||||||
|
|
||||||
interceptors[key]();
|
|
||||||
return [key, value];
|
|
||||||
});
|
|
||||||
|
|
||||||
instead("resolveSemanticColor", color.default.meta ?? color.default.internal, (args, orig) => {
|
|
||||||
if (!enabled || !currentTheme) return orig(...args);
|
|
||||||
if (args[0] !== vdKey) return orig(...args);
|
|
||||||
|
|
||||||
args[0] = vdThemeFallback;
|
|
||||||
|
|
||||||
const [name, colorDef] = extractInfo(vdThemeFallback, args[1]);
|
|
||||||
|
|
||||||
const themeIndex = vdThemeFallback === "midnight" ? 2 : vdThemeFallback === "light" ? 1 : 0;
|
|
||||||
|
|
||||||
//! As of 192.7, Tabs v2 uses BG_ semantic colors instead of BACKGROUND_ ones
|
|
||||||
const alternativeName = semanticAlternativeMap[name] ?? name;
|
|
||||||
|
|
||||||
const semanticColorVal = (currentTheme.data?.semanticColors?.[name] ?? currentTheme.data?.semanticColors?.[alternativeName])?.[themeIndex];
|
|
||||||
|
|
||||||
if (semanticColorVal) return semanticColorVal;
|
|
||||||
|
|
||||||
const rawValue = currentTheme.data?.rawColors?.[colorDef.raw];
|
|
||||||
if (rawValue) {
|
|
||||||
// Set opacity if needed
|
|
||||||
return colorDef.opacity === 1 ? rawValue : chroma(rawValue).alpha(colorDef.opacity).hex();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to default
|
|
||||||
return orig(...args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultFallbackTheme(fallback: string = vdThemeFallback) {
|
|
||||||
const theme = ThemeStore.theme.toLowerCase() as string;
|
|
||||||
|
|
||||||
if (isDiscordTheme(theme)) {
|
|
||||||
return theme;
|
|
||||||
} else {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyTheme(appliedTheme: Theme | null, fallbackTheme?: string) {
|
|
||||||
if (!fallbackTheme) fallbackTheme = getDefaultFallbackTheme();
|
|
||||||
|
|
||||||
currentTheme = appliedTheme;
|
|
||||||
enabled = !!currentTheme;
|
|
||||||
vdThemeFallback = fallbackTheme!!;
|
|
||||||
vdKey = `vd-theme-${inc++}-${fallbackTheme}`;
|
|
||||||
|
|
||||||
if (appliedTheme) {
|
|
||||||
color.Theme[vdKey.toUpperCase()] = vdKey;
|
|
||||||
|
|
||||||
formDividerModule.DIVIDER_COLORS[vdKey] = formDividerModule.DIVIDER_COLORS[vdThemeFallback];
|
|
||||||
|
|
||||||
Object.keys(color.Shadow).forEach(k => color.Shadow[k][vdKey] = color.Shadow[k][vdThemeFallback]);
|
|
||||||
Object.keys(color.SemanticColor).forEach(k => {
|
|
||||||
color.SemanticColor[k][vdKey] = {
|
|
||||||
...color.SemanticColor[k][vdThemeFallback],
|
|
||||||
override: appliedTheme?.data?.semanticColors?.[k]?.[0]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storageResolved) {
|
|
||||||
appearanceManager.setShouldSyncAppearanceSettings(false);
|
|
||||||
appearanceManager.updateTheme(appliedTheme ? vdKey : fallbackTheme);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -393,18 +125,7 @@ export function applyTheme(appliedTheme: Theme | null, fallbackTheme?: string) {
|
||||||
*/
|
*/
|
||||||
export function initThemes() {
|
export function initThemes() {
|
||||||
const currentTheme = getThemeFromLoader();
|
const currentTheme = getThemeFromLoader();
|
||||||
enabled = Boolean(currentTheme);
|
initColors(currentTheme?.data ?? null);
|
||||||
|
|
||||||
patchColor();
|
|
||||||
applyTheme(currentTheme, vdThemeFallback);
|
|
||||||
|
|
||||||
updateThemes().catch(e => console.error("Failed to update themes", e));
|
updateThemes().catch(e => console.error("Failed to update themes", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = color.SemanticColor[propName];
|
|
||||||
|
|
||||||
return [propName, colorDef[themeName]];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
export interface Author {
|
export type Author = { name: string, id?: `${bigint}`; };
|
||||||
name: string;
|
|
||||||
|
export interface BunnyManifest {
|
||||||
id: string;
|
id: string;
|
||||||
|
display: {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
authors?: Author[];
|
||||||
|
};
|
||||||
|
extras?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { color } from "@lib/addons/themes";
|
|
||||||
import { constants } from "@metro/common";
|
import { constants } from "@metro/common";
|
||||||
import { findByStoreNameLazy } from "@metro/wrappers";
|
import { findByProps, findByStoreNameLazy } from "@metro/wrappers";
|
||||||
|
|
||||||
//! This module is only found on 165.0+, under the assumption that iOS 165.0 is the same as Android 165.0.
|
//! This module is only found on 165.0+, under the assumption that iOS 165.0 is the same as Android 165.0.
|
||||||
//* In 167.1, most if not all traces of the old color modules were removed.
|
//* In 167.1, most if not all traces of the old color modules were removed.
|
||||||
|
|
@ -10,6 +9,8 @@ import { findByStoreNameLazy } from "@metro/wrappers";
|
||||||
// ? These comments are preserved for historical purposes.
|
// ? These comments are preserved for historical purposes.
|
||||||
// const colorModule = findByPropsProxy("colors", "meta");
|
// const colorModule = findByPropsProxy("colors", "meta");
|
||||||
|
|
||||||
|
const color = findByProps("SemanticColor");
|
||||||
|
|
||||||
// ? SemanticColor and default.colors are effectively ThemeColorMap
|
// ? SemanticColor and default.colors are effectively ThemeColorMap
|
||||||
export const semanticColors = (color?.default?.colors ?? constants?.ThemeColorMap) as Record<string, any>;
|
export const semanticColors = (color?.default?.colors ?? constants?.ThemeColorMap) as Record<string, any>;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue