From c01d0c98a45800d7dd6f8c05df340dc7fea18d61 Mon Sep 17 00:00:00 2001 From: kmmiio99o <164557248+kmmiio99o@users.noreply.github.com> Date: Sun, 12 Oct 2025 11:04:56 +0200 Subject: [PATCH] Add Theme Manager core plugin and update version to 1.1.0-1 --- scripts/build.mjs | 2 +- src/core/plugins/index.ts | 1 + src/core/plugins/thememanager/index.ts | 181 +++++++++++++++++++++++++ src/modules.d.ts | 2 +- 4 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 src/core/plugins/thememanager/index.ts diff --git a/scripts/build.mjs b/scripts/build.mjs index 4491efa..e1ba51e 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -74,7 +74,7 @@ const config = { constModules: { globals: { "bunny-build-info": { - version: `"MINING"`, + version: `"1.1.0-1"`, }, }, }, diff --git a/src/core/plugins/index.ts b/src/core/plugins/index.ts index 0ee9ede..74982d2 100644 --- a/src/core/plugins/index.ts +++ b/src/core/plugins/index.ts @@ -12,6 +12,7 @@ export const getCorePlugins = (): Record => ({ "bunny.notrack": { ...require("./notrack"), preenabled: true }, "bunny.messagefix": { ...require("./messagefix") }, "bunny.fixembed": { ...require("./fixembed"), preenabled: true }, + "bunny.thememanager": require("./thememanager"), "bunny.themelister": { ...require("./themelister"), preenabled: false }, }); diff --git a/src/core/plugins/thememanager/index.ts b/src/core/plugins/thememanager/index.ts new file mode 100644 index 0000000..620480b --- /dev/null +++ b/src/core/plugins/thememanager/index.ts @@ -0,0 +1,181 @@ +import { defineCorePlugin } from ".."; +import { before } from "@lib/api/patcher"; +import { settings } from "@lib/api/settings"; +import { createLazyModule } from "@metro/lazy"; +import { byMutableProp } from "@metro/filters"; +import { writeThemeToNative, getThemeFromLoader } from "@lib/addons/themes"; +import { logger } from "@lib/utils/logger"; + +declare module "@lib/api/settings" { + interface Settings { + themesManager?: { + forceMode?: "auto" | "dark" | "light"; + }; + } +} + +/** + * Theme Manager (core plugin) + * + * Responsibilities: + * - Provide a global, central place to "force" appearance mode for parts of the app + * that do not correctly follow the selected theme (dark/light/auto). + * - Keep the native-side theme file in sync (best-effort) when the plugin starts + * and when force mode changes. + * + * Settings shape (stored in `settings.themesManager`): + * { + * forceMode?: 'auto' | 'dark' | 'light' + * } + * + * Behaviour: + * - When `forceMode === 'dark'`, patched `isThemeDark()` will return `true` and + * `isThemeLight()` false. + * - When `forceMode === 'light'`, the inverse occurs. + * - When `forceMode === 'auto'` (default), native/original behaviour is preserved. + * + * Notes: + * - This plugin is intentionally conservative: patches return `undefined` to + * fall through to original behaviour when not forcing. + * - Writing the native theme file is best-effort; failures are logged but do not + * prevent the plugin from functioning. + */ + +type ForceMode = "auto" | "dark" | "light"; + +export default defineCorePlugin({ + manifest: { + id: "bunny.thememanager", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "Theme Manager", + description: + "Fixes components that don't respect dark/light mode and keeps native theme in sync.", + authors: [{ name: "ShiggyCord Team" }], + }, + }, + + async start() { + // Ensure settings container exists with a sensible default. + (settings as any).themesManager ??= { + forceMode: + ((settings as any).themesManager?.forceMode as ForceMode) ?? "dark", + }; + + // Resolve the module that exposes theme helpers like isThemeDark/isThemeLight. + // We use a lazy module so we don't crash if it's not available immediately. + const themeModule = createLazyModule(byMutableProp("isThemeDark")); + + // Helper to read the configured mode + const getMode = (): ForceMode => { + try { + return ( + ((settings as any).themesManager?.forceMode as ForceMode) ?? "auto" + ); + } catch { + return "auto"; + } + }; + + // Patch isThemeDark: when forced to dark, return true; when forced to light return false. + const unpatchIsDark = before("isThemeDark", themeModule, (args: any[]) => { + const mode = getMode(); + if (mode === "dark") return true; + if (mode === "light") return false; + return; + }); + + // Patch isThemeLight: when forced to light, return true; when forced to dark return false. + const unpatchIsLight = before( + "isThemeLight", + themeModule, + (args: any[]) => { + const mode = getMode(); + if (mode === "light") return true; + if (mode === "dark") return false; + return; + }, + ); + + // When plugin starts and whenever force mode is changed by other parts of the app, + // we attempt to write the current theme (or empty object) to the native theme file. + // This helps native components match the chosen theme. + const tryWriteNativeTheme = async () => { + try { + const cur = getThemeFromLoader(); + // If in `auto` mode, still write the current theme (best-effort). + await writeThemeToNative(cur?.data ?? {}); + } catch (e) { + // Non-fatal: log for diagnostics. + try { + logger.error("ThemeManager: Failed to write native theme", e); + } catch { + // fallback console if logger isn't available in some contexts + // eslint-disable-next-line no-console + console.error("ThemeManager: Failed to write native theme", e); + } + } + }; + + // Write native theme once at start (best-effort). + void tryWriteNativeTheme(); + + // Track unpatch functions so we can clean them up at stop() + (this as any)._unpatches = [unpatchIsDark, unpatchIsLight]; + + // Also expose a simple watcher that will react when settings.themesManager.forceMode changes. + // We don't have a dedicated reactive API here, so we poll the setting at a low frequency. + // This is intentionally lightweight and only runs while plugin is enabled. + let lastMode = getMode(); + let stopPoll = false; + + // Poll interval in ms. This is a compromise to avoid adding new dependencies. + const POLL_MS = 500; + + (async () => { + while (!stopPoll) { + await new Promise((res) => setTimeout(res, POLL_MS)); + const current = getMode(); + if (current !== lastMode) { + lastMode = current; + // Try to write native theme whenever mode changes. + void tryWriteNativeTheme(); + } + } + })(); + + // Save stop function so we can terminate loop on stop() + (this as any)._stopPoll = () => { + stopPoll = true; + }; + }, + + stop() { + // Stop polling loop if present + try { + (this as any)._stopPoll?.(); + } catch { + // ignore + } + + // Unpatch everything that we registered + try { + const unpatches: Function[] = (this as any)._unpatches ?? []; + for (const u of unpatches) { + try { + u(); + } catch (e) { + // best-effort unpatch + // eslint-disable-next-line no-console + console.error("ThemeManager: failed to unpatch", e); + } + } + } catch (e) { + // eslint-disable-next-line no-console + console.error("ThemeManager: cleanup failure", e); + } + }, +}); diff --git a/src/modules.d.ts b/src/modules.d.ts index 0bed22b..25d9432 100644 --- a/src/modules.d.ts +++ b/src/modules.d.ts @@ -1,5 +1,5 @@ declare module "bunny-build-info" { - const version: "MINING"; + const version: "1.1.0-1"; } declare module "*.png" {