diff --git a/src/def.d.ts b/src/def.d.ts index ec8becc..f4f8d75 100644 --- a/src/def.d.ts +++ b/src/def.d.ts @@ -124,8 +124,17 @@ interface ThemeData { description?: string; authors?: Author[]; spec: number; - semanticColors?: Record; + semanticColors?: Record; rawColors?: Record; + 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; + } } interface Theme { diff --git a/src/index.ts b/src/index.ts index 44b07d5..48e4304 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { patchLogHook } from "@lib/debug"; import { patchCommands } from "@lib/commands"; import { initPlugins } from "@lib/plugins"; import { patchAssets } from "@ui/assets"; +import { patchChatBackground } from "@lib/themes"; import initQuickInstall from "@ui/quickInstall"; import initSafeMode from "@ui/safeMode"; import initSettings from "@ui/settings"; @@ -15,6 +16,7 @@ export default async () => { patchLogHook(), patchAssets(), patchCommands(), + patchChatBackground(), initFixes(), initSafeMode(), initSettings(), diff --git a/src/lib/plugins.ts b/src/lib/plugins.ts index 5075981..12a7f09 100644 --- a/src/lib/plugins.ts +++ b/src/lib/plugins.ts @@ -68,7 +68,7 @@ export async function evalPlugin(plugin: Plugin) { const raw = (0, eval)(pluginString)(vendettaForPlugins); const ret = typeof raw == "function" ? raw() : raw; - return ret.default || ret; + return ret?.default || ret; } export async function startPlugin(id: string) { @@ -83,12 +83,12 @@ export async function startPlugin(id: string) { pluginRet.onLoad?.(); } plugin.enabled = true; - } catch(e) { + } catch (e) { logger.error(`Plugin ${plugin.id} errored whilst loading, and will be unloaded`, e); try { loadedPlugins[plugin.id]?.onUnload?.(); - } catch(e2) { + } catch (e2) { logger.error(`Plugin ${plugin.id} errored whilst unloading`, e2); } @@ -106,10 +106,10 @@ export function stopPlugin(id: string, disable = true) { if (!settings.safeMode?.enabled) { try { pluginRet?.onUnload?.(); - } catch(e) { + } catch (e) { logger.error(`Plugin ${plugin.id} errored whilst unloading`, e); } - + delete loadedPlugins[id]; } diff --git a/src/lib/themes.ts b/src/lib/themes.ts index 3497a2e..ea6b27a 100644 --- a/src/lib/themes.ts +++ b/src/lib/themes.ts @@ -1,6 +1,6 @@ import { Theme, ThemeData } from "@types"; -import { findByProps } from "@metro/filters"; import { ReactNative, chroma } from "@metro/common"; +import { findByName, findByProps } from "@metro/filters"; import { instead } from "@lib/patcher"; import { createFileBackend, createMMKVBackend, createStorage, wrapSync, awaitSyncWrapper } from "@lib/storage"; import { safeFetch } from "@utils"; @@ -19,6 +19,21 @@ async function writeTheme(theme: Theme | {}) { await createFileBackend("vendetta_theme.json").set(theme); } +export function patchChatBackground() { + const currentTheme = getCurrentTheme()?.data?.background; + if (!currentTheme) return; + + const MessagesWrapperConnected = findByName("MessagesWrapperConnected", false); + if (!MessagesWrapperConnected) return; + + return instead("default", MessagesWrapperConnected, (args, orig) => React.createElement(ReactNative.ImageBackground, { + style: { flex: 1, height: "100%" }, + source: { uri: currentTheme.url }, + blurRadius: currentTheme.blur, + children: orig(...args), + })); +} + function normalizeToHex(colorString: string): string { if (chroma.valid(colorString)) return chroma(colorString).hex(); @@ -39,7 +54,7 @@ function processData(data: ThemeData) { for (const key in semanticColors) { for (const index in semanticColors[key]) { - semanticColors[key][index] = normalizeToHex(semanticColors[key][index]); + semanticColors[key][index] &&= normalizeToHex(semanticColors[key][index] as string); } } } @@ -158,6 +173,10 @@ export async function initThemes() { const themeIndex = theme === "amoled" ? 2 : theme === "light" ? 1 : 0; const semanticColorVal = selectedTheme.data?.semanticColors?.[name]?.[themeIndex]; + if (name === "CHAT_BACKGROUND" && typeof selectedTheme.data?.background?.alpha === "number") { + return chroma(semanticColorVal || "black").alpha(1 - selectedTheme.data.background.alpha).hex(); + } + if (semanticColorVal) return semanticColorVal; const rawValue = selectedTheme.data?.rawColors?.[colorDef.raw]; diff --git a/src/ui/safeMode.tsx b/src/ui/safeMode.tsx index 736d717..68fe368 100644 --- a/src/ui/safeMode.tsx +++ b/src/ui/safeMode.tsx @@ -62,7 +62,7 @@ interface Button { } const tabs: Tab[] = [ - { id: "message",title: "Message" }, + { id: "message", title: "Message" }, { id: "stack", title: "Stack Trace" }, { id: "componentStack", title: "Component", trimWhitespace: true }, ]; @@ -74,7 +74,7 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an this.state.activeTab ??= "message"; const tabData = tabs.find(t => t.id === this.state.activeTab); const errorText: string = this.state.error[this.state.activeTab]; - + // This is in the patch and not outside of it so that we can use `this`, e.g. for setting state const buttons: Button[] = [ { text: "Restart Discord", onPress: this.handleReload },