diff --git a/scripts/build.mjs b/scripts/build.mjs index 6475874..63f3ad5 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -28,84 +28,84 @@ let context = null; /** @type {import("esbuild").BuildOptions} */ const config = { - entryPoints: ["src/entry.ts"], - bundle: true, - outfile: "dist/shiggycord.js", - format: "iife", - splitting: false, - // Enable minification by default for release builds (when a release-branch is provided). - // This keeps development builds unminified for easier debugging. - minify: true, - external: [], - supported: { - // Hermes does not actually support const and let, even though it syntactically - // accepts it, but it's treated just like 'var' and causes issues - "const-and-let": false, - }, - loader: { - ".png": "dataurl", - ".html": "text", - }, - define: { - window: "globalThis", - __DEV__: JSON.stringify(releaseBranch !== "main"), - }, - inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js", "./shims/weakref.js"], - legalComments: "none", - alias: { - "!ShiggyCord-deps-shim!": "./shims/depsModule.ts", - spitroast: "./node_modules/spitroast", - "react/jsx-runtime": "./shims/jsxRuntime", - }, - plugins: [ - globalPlugin({ - ...metroDeps.reduce((obj, key) => { - obj[key] = `require("!ShiggyCord-deps-shim!")[${JSON.stringify(key)}]`; - return obj; - }, {}), - }), - { - name: "swc", - setup(build) { - build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => { - const result = await swc.transformFile(args.path, { - jsc: { - externalHelpers: true, - transform: { - constModules: { - globals: { - "bunny-build-info": { - version: `"1.4.1.2"`, - }, - }, - }, - react: { - runtime: "automatic", - }, - }, - }, - // https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md - // https://github.com/facebook/hermes/issues/696#issuecomment-1396235791 - env: { - targets: "fully supports es6", - include: [ - "transform-block-scoping", - "transform-classes", - "transform-async-to-generator", - "transform-async-generator-functions", - ], - exclude: [ - "transform-parameters", - "transform-template-literals", - "transform-exponentiation-operator", - "transform-named-capturing-groups-regex", - "transform-nullish-coalescing-operator", - "transform-object-rest-spread", - "transform-optional-chaining", - "transform-logical-assignment-operators", - ], - }, - }); + entryPoints: ["src/entry.ts"], + bundle: true, + outfile: "dist/kettu.js", + format: "iife", + splitting: false, + external: [], + supported: { + // Hermes does not actually support const and let, even though it syntactically + // accepts it, but it's treated just like 'var' and causes issues + "const-and-let": false + }, + footer: { + js: "//# sourceURL=kettu" + }, + loader: { + ".png": "dataurl", + ".html": "text" + }, + define: { + window: "globalThis", + __DEV__: JSON.stringify(releaseBranch !== "main") + }, + inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"], + legalComments: "none", + alias: { + "!kettu-deps-shim!": "./shims/depsModule.ts", + "spitroast": "./node_modules/spitroast", + "react/jsx-runtime": "./shims/jsxRuntime" + }, + plugins: [ + globalPlugin({ + ...metroDeps.reduce((obj, key) => { + obj[key] = `require("!kettu-deps-shim!")[${JSON.stringify(key)}]`; + return obj; + }, {}) + }), + { + name: "swc", + setup(build) { + build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async args => { + const result = await swc.transformFile(args.path, { + jsc: { + externalHelpers: true, + transform: { + constModules: { + globals: { + "bunny-build-info": { + version: `"v1.4.2"` + } + } + }, + react: { + runtime: "automatic" + } + }, + }, + // https://github.com/facebook/hermes/blob/3815fec63d1a6667ca3195160d6e12fee6a0d8d5/doc/Features.md + // https://github.com/facebook/hermes/issues/696#issuecomment-1396235791 + env: { + targets: "fully supports es6", + include: [ + "transform-block-scoping", + "transform-classes", + "transform-async-to-generator", + "transform-async-generator-functions" + ], + exclude: [ + "transform-parameters", + "transform-template-literals", + "transform-exponentiation-operator", + "transform-named-capturing-groups-regex", + "transform-nullish-coalescing-operator", + "transform-object-rest-spread", + "transform-optional-chaining", + "transform-logical-assignment-operators" + ] + }, + }); return { contents: result.code }; }); diff --git a/src/core/i18n/default.json b/src/core/i18n/default.json index 8d49a1e..32b8906 100644 --- a/src/core/i18n/default.json +++ b/src/core/i18n/default.json @@ -78,7 +78,6 @@ "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", diff --git a/src/core/ui/settings/pages/Themes/index.tsx b/src/core/ui/settings/pages/Themes/index.tsx index 5489644..6f97fae 100644 --- a/src/core/ui/settings/pages/Themes/index.tsx +++ b/src/core/ui/settings/pages/Themes/index.tsx @@ -33,8 +33,6 @@ 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; @@ -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() { useProxy(settings); useProxy(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(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([]); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); diff --git a/src/lib/addons/themes/colors/patches/resolver.ts b/src/lib/addons/themes/colors/patches/resolver.ts index 73b8bd3..e577f9c 100644 --- a/src/lib/addons/themes/colors/patches/resolver.ts +++ b/src/lib/addons/themes/colors/patches/resolver.ts @@ -2,12 +2,14 @@ import { _colorRef } from "@lib/addons/themes/colors/updater"; import { NativeThemeModule } 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"; -const getChroma = () => require("chroma-js") as typeof import("chroma-js").default; +import chroma from "chroma-js"; 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 = { "BG_BACKDROP": "BACKGROUND_FLOATING", @@ -22,13 +24,19 @@ const SEMANTIC_FALLBACK_MAP: Record = { "BG_SURFACE_RAISED": "BACKGROUND_MOBILE_PRIMARY" }; -const origRawColor = { ...tokenReference.RawColor }; - export default function patchDefinitionAndResolver() { - const callback = ([theme]: any[]) => { - if (!_colorRef.current) return void 0; - return theme === _colorRef.key ? [_colorRef.current.reference] : void 0; - }; + const callback = ([theme]: any[]) => 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.defineProperty(tokenReference.RawColor, key, { @@ -37,50 +45,48 @@ export default function patchDefinitionAndResolver() { get: () => { const ret = _colorRef.current?.raw[key]; if (ret) return ret; - return origRawColor[key]; } }); }); const unpatches = [ - before("isThemeDark", isThemeModule, callback), - before("isThemeLight", isThemeModule, callback), 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); - // Use the captured reference value - args[0] = currentRef.reference; + args[0] = _colorRef.current.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 - 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]]; + 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 (semanticDef?.value) { - if (semanticDef.opacity === 1) return semanticDef.value; - return getChroma()(semanticDef.value).alpha(semanticDef.opacity).hex(); + return semanticDef.opacity === 1 + ? semanticDef.value + : chroma(semanticDef.value).alpha(semanticDef.opacity).hex(); } - const rawKey = colorDef.raw; - const rawValue = rawKey ? currentRef.raw[rawKey] : undefined; + const rawValue = _colorRef.current.raw[colorDef.raw]; if (rawValue) { // 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 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", { configurable: true, writable: true, @@ -95,13 +101,7 @@ 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 entry = tokenReference.SemanticColor[propName]; + const colorDef = tokenReference.SemanticColor[propName]; - if (!entry || typeof entry !== "object") { - return [propName, undefined]; - } - - const colorDef = Object.prototype.hasOwnProperty.call(entry, themeName) ? entry[themeName] : undefined; - - return [propName, colorDef]; + return [propName, colorDef[themeName]]; } diff --git a/src/lib/addons/themes/colors/updater.ts b/src/lib/addons/themes/colors/updater.ts index 46cd2f4..6a3c80f 100644 --- a/src/lib/addons/themes/colors/updater.ts +++ b/src/lib/addons/themes/colors/updater.ts @@ -24,46 +24,35 @@ export const _colorRef: InternalColorRef = { current: null, key: `bn-theme-${_inc}`, 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; 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; - 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 + 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 (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); - const fallback = (_colorRef.lastSetDiscordTheme && !_colorRef.lastSetDiscordTheme.startsWith("bn-theme-")) - ? _colorRef.lastSetDiscordTheme - : "dark"; - AppearanceManager.updateTheme(internalDef != null ? ref.key : fallback); + AppearanceManager.updateTheme(internalDef != null ? ref.key : ref.lastSetDiscordTheme); } }