[Themes] Implement chat background image (#64)

* [Themes] hardcode fuyu chat bg

* [Themes > BG] listen to theme file

* remove .apply()

* some uhh misc changes

* readd

* how

* [Global] Minor changes

---------

Co-authored-by: Beef <beefers@riseup.net>
This commit is contained in:
Amsyar Rasyiq 2023-04-16 00:08:08 +08:00 committed by GitHub
parent ef0b162d3a
commit 20310db733
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 40 additions and 10 deletions

11
src/def.d.ts vendored
View file

@ -124,8 +124,17 @@ interface ThemeData {
description?: string; description?: string;
authors?: Author[]; authors?: Author[];
spec: number; spec: number;
semanticColors?: Record<string, string[]>; semanticColors?: Record<string, (string | false)[]>;
rawColors?: Record<string, string>; 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;
}
} }
interface Theme { interface Theme {

View file

@ -2,6 +2,7 @@ import { patchLogHook } from "@lib/debug";
import { patchCommands } from "@lib/commands"; import { patchCommands } from "@lib/commands";
import { initPlugins } from "@lib/plugins"; import { initPlugins } from "@lib/plugins";
import { patchAssets } from "@ui/assets"; import { patchAssets } from "@ui/assets";
import { patchChatBackground } from "@lib/themes";
import initQuickInstall from "@ui/quickInstall"; import initQuickInstall from "@ui/quickInstall";
import initSafeMode from "@ui/safeMode"; import initSafeMode from "@ui/safeMode";
import initSettings from "@ui/settings"; import initSettings from "@ui/settings";
@ -15,6 +16,7 @@ export default async () => {
patchLogHook(), patchLogHook(),
patchAssets(), patchAssets(),
patchCommands(), patchCommands(),
patchChatBackground(),
initFixes(), initFixes(),
initSafeMode(), initSafeMode(),
initSettings(), initSettings(),

View file

@ -68,7 +68,7 @@ export async function evalPlugin(plugin: Plugin) {
const raw = (0, eval)(pluginString)(vendettaForPlugins); const raw = (0, eval)(pluginString)(vendettaForPlugins);
const ret = typeof raw == "function" ? raw() : raw; const ret = typeof raw == "function" ? raw() : raw;
return ret.default || ret; return ret?.default || ret;
} }
export async function startPlugin(id: string) { export async function startPlugin(id: string) {
@ -83,12 +83,12 @@ export async function startPlugin(id: string) {
pluginRet.onLoad?.(); pluginRet.onLoad?.();
} }
plugin.enabled = true; plugin.enabled = true;
} catch(e) { } catch (e) {
logger.error(`Plugin ${plugin.id} errored whilst loading, and will be unloaded`, e); logger.error(`Plugin ${plugin.id} errored whilst loading, and will be unloaded`, e);
try { try {
loadedPlugins[plugin.id]?.onUnload?.(); loadedPlugins[plugin.id]?.onUnload?.();
} catch(e2) { } catch (e2) {
logger.error(`Plugin ${plugin.id} errored whilst unloading`, 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) { if (!settings.safeMode?.enabled) {
try { try {
pluginRet?.onUnload?.(); pluginRet?.onUnload?.();
} catch(e) { } catch (e) {
logger.error(`Plugin ${plugin.id} errored whilst unloading`, e); logger.error(`Plugin ${plugin.id} errored whilst unloading`, e);
} }
delete loadedPlugins[id]; delete loadedPlugins[id];
} }

View file

@ -1,6 +1,6 @@
import { Theme, ThemeData } from "@types"; import { Theme, ThemeData } from "@types";
import { findByProps } from "@metro/filters";
import { ReactNative, chroma } from "@metro/common"; import { ReactNative, chroma } from "@metro/common";
import { findByName, findByProps } from "@metro/filters";
import { instead } from "@lib/patcher"; import { instead } from "@lib/patcher";
import { createFileBackend, createMMKVBackend, createStorage, wrapSync, awaitSyncWrapper } from "@lib/storage"; import { createFileBackend, createMMKVBackend, createStorage, wrapSync, awaitSyncWrapper } from "@lib/storage";
import { safeFetch } from "@utils"; import { safeFetch } from "@utils";
@ -19,6 +19,21 @@ async function writeTheme(theme: Theme | {}) {
await createFileBackend("vendetta_theme.json").set(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 { function normalizeToHex(colorString: string): string {
if (chroma.valid(colorString)) return chroma(colorString).hex(); if (chroma.valid(colorString)) return chroma(colorString).hex();
@ -39,7 +54,7 @@ function processData(data: ThemeData) {
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]); 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 themeIndex = theme === "amoled" ? 2 : theme === "light" ? 1 : 0;
const semanticColorVal = selectedTheme.data?.semanticColors?.[name]?.[themeIndex]; 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; if (semanticColorVal) return semanticColorVal;
const rawValue = selectedTheme.data?.rawColors?.[colorDef.raw]; const rawValue = selectedTheme.data?.rawColors?.[colorDef.raw];

View file

@ -62,7 +62,7 @@ interface Button {
} }
const tabs: Tab[] = [ const tabs: Tab[] = [
{ id: "message",title: "Message" }, { id: "message", title: "Message" },
{ id: "stack", title: "Stack Trace" }, { id: "stack", title: "Stack Trace" },
{ id: "componentStack", title: "Component", trimWhitespace: true }, { id: "componentStack", title: "Component", trimWhitespace: true },
]; ];
@ -74,7 +74,7 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
this.state.activeTab ??= "message"; this.state.activeTab ??= "message";
const tabData = tabs.find(t => t.id === this.state.activeTab); const tabData = tabs.find(t => t.id === this.state.activeTab);
const errorText: string = this.state.error[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 // This is in the patch and not outside of it so that we can use `this`, e.g. for setting state
const buttons: Button[] = [ const buttons: Button[] = [
{ text: "Restart Discord", onPress: this.handleReload }, { text: "Restart Discord", onPress: this.handleReload },