Skip to content
This repository has been archived by the owner on Jan 13, 2024. It is now read-only.

rewrite themer #53

Merged
merged 11 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/Aliucord.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { startCorePlugins, startPlugins } from "./api/PluginManager";
import { Settings } from "./api/Settings";
import { mkdir } from "./native/fs";
import patchTheme from "./themer/patchTheme";
import patchSettings from "./ui/patchSettings";
import patchTheme from "./ui/patchTheme";
import { PLUGINS_DIRECTORY, SETTINGS_DIRECTORY, THEME_DIRECTORY } from "./utils/constants";
import { startDebugWs } from "./utils/debug/DebugWS";
import { startReactDevTools } from "./utils/debug/ReactDevTools";
Expand All @@ -13,7 +13,6 @@ interface SettingsSchema {
autoUpdatePlugins: boolean;
disablePluginsOnCrash: boolean;
plugins: Record<string, boolean>;
enableAMOLEDTheme: boolean;
theme: string;
}

Expand Down
158 changes: 56 additions & 102 deletions src/api/Themer.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,78 @@
/* eslint-disable indent */
import { Theme } from "../entities";
import { Constants, Dialog, ReactNative } from "../metro";
import { Dialog, ReactNative, Toasts } from "../metro";
import { excludedThemes, ThemeErrors, themeState } from "../themer/themerInit";
import { getAssetId, Logger } from "../utils";

export const themes = {} as Record<string, Theme>;
export let currentTheme: Theme;
export let themeApplied: boolean, themeErrorReason;
let discordConstants: typeof Constants;
enum ThemeType {
DARK,
LIGHT,
AMOLED
}

const { externalStorageDirectory } = window.nativeModuleProxy.AliucordNative;
const SETTINGS_DIRECTORY = externalStorageDirectory + "/AliucordRN/settings/";
const THEMES_DIRECTORY = externalStorageDirectory + "/AliucordRN/themes/";
const logger = new Logger("Themer");

export function setTheme(theme: Theme) {
currentTheme = themes[theme.name];
window.Aliucord.settings.set("theme", theme.name);
export function setTheme(theme: Theme | null) {
if (!theme) {
window.Aliucord.settings.delete("theme");
} else {
window.Aliucord.settings.set("theme", theme.name);
}

Dialog.show({
title: "Restart for theme to apply",
body: "Restart the app for the theme to apply correctly.",
title: "Restart to apply",
body: `Restart is required to apply the ${theme ? "new" : "default"} theme.`,
confirmText: "Restart",
isDismissable: false,
onConfirm: ReactNative.NativeModules.BundleUpdaterManager.reload
});
}

// WARNING: this function is called before the Aliucord loads, meaning we're having limited access.
export function themerInit(constants: typeof Constants) {
try {
if (!AliuFS.exists(SETTINGS_DIRECTORY + "Aliucord.json")) return;
discordConstants = constants;
AliuHermes.unfreeze(constants.ThemeColorMap);
AliuHermes.unfreeze(constants.Colors);
AliuHermes.unfreeze(constants.UNSAFE_Colors);

const settings = JSON.parse(AliuFS.readFile(SETTINGS_DIRECTORY + "Aliucord.json", "text") as string);

for (const file of AliuFS.readdir(THEMES_DIRECTORY)) {
if (!file.name.endsWith(".json")) continue;

const themeFile = JSON.parse(AliuFS.readFile(THEMES_DIRECTORY + file.name, "text") as string) as Theme;

if (!themeFile?.name || !themeFile?.theme_color_map || !themeFile?.colors) throw new Error(`Theme file ${file.name} does not contain a name, theme_color_map or colors key.`);
if (themes[themeFile.name]) throw new Error(`A theme called ${themeFile.name} already exists.`);

themes[themeFile.name] = themeFile;
}

if (themes[settings?.theme]) {
currentTheme = themes[settings.theme];

applyTheme();
}
} catch (e) {
themeApplied = false;
themeErrorReason = e;
export function onStartup() {
if (themeState.isApplied) {
logger.info("Theme has been successfully applied");
} else if (themeState.anError) {
logger.error("Failed to apply theme: " + themeState.reason ?? "Unknown reason");
handleErrors();

themeState.errorArgs?.forEach((arg) => {
logger.error(arg);
});
return;
} else if (themeState.reason) {
logger.info("Theme was not applied: " + themeState.reason);
}
}

// WARNING: this function is called before Aliucord loads, meaning we're having limited access.
export function applyTheme() {
try {
if (currentTheme === undefined) return;
for (const key in currentTheme.theme_color_map) {
if (!discordConstants.ThemeColorMap[key]) continue;

discordConstants.ThemeColorMap[key][ThemeType.AMOLED] = discordConstants.ThemeColorMap[key][ThemeType.DARK];
if (currentTheme.theme_color_map[key]) {
discordConstants.ThemeColorMap[key][ThemeType.AMOLED] = currentTheme.theme_color_map[key]?.[ThemeType.DARK];
discordConstants.ThemeColorMap[key][ThemeType.LIGHT] = currentTheme.theme_color_map[key]?.[ThemeType.LIGHT];
discordConstants.ThemeColorMap[key][ThemeType.DARK] = currentTheme.theme_color_map[key]?.[ThemeType.DARK];
}
}

// Enmity compat
if (currentTheme.colors && !currentTheme.colours) {
for (const key in currentTheme.colors) {
if (!discordConstants.Colors[key]) continue;

discordConstants.Colors[key] = currentTheme.colors[key];
}
} else {
for (const key in currentTheme.colours) {
if (!discordConstants.Colors[key]) continue;

discordConstants.Colors[key] = currentTheme.colours[key];
}
}

for (const key in currentTheme.unsafe_colors) {
if (!discordConstants.UNSAFE_Colors[key]) continue;
for (const theme of excludedThemes.invalidThemes) {
logger.error(`The theme "${theme.name}" is invalid: ${theme.reason}`);
Toasts.open({ content: `Invalid theme(s): ${theme.name}, check theme settings.`, source: getAssetId("Small") });
}

discordConstants.UNSAFE_Colors[key] = currentTheme.unsafe_colors[key];
}
for (const theme of excludedThemes.duplicatedThemes) {
logger.warn(`A theme named "${theme}" already existed.`);
Toasts.open({ content: "Duplicated themes found, check theme settings.", source: getAssetId("Small") });
}

if (!currentTheme.theme_color_map["CHAT_BACKGROUND"]) {
discordConstants.ThemeColorMap.CHAT_BACKGROUND[ThemeType.AMOLED] = currentTheme.theme_color_map["BACKGROUND_PRIMARY"][ThemeType.DARK];
discordConstants.ThemeColorMap.CHAT_BACKGROUND[ThemeType.LIGHT] = currentTheme.theme_color_map["BACKGROUND_PRIMARY"][ThemeType.LIGHT];
discordConstants.ThemeColorMap.CHAT_BACKGROUND[ThemeType.DARK] = currentTheme.theme_color_map["BACKGROUND_PRIMARY"][ThemeType.DARK];
}
logger.info(themeState);
}

themeApplied = true;
} catch (e) {
themeApplied = false;
themeErrorReason = e;
function handleErrors() {
if (themeState.isApplied) return;

switch (themeState.reason) {
case ThemeErrors.UNKNOWN_THEME:
showFailDialog(`An unknown theme was applied: ${themeState.currentTheme}.\nFalling back to default theme...`);
window.Aliucord.settings.delete("theme");
break;
case ThemeErrors.UNEXPECTED_ERROR:
showFailDialog(
`An unexpected error occurred: ${themeState.errorArgs?.[0]?.message ?? "¯\\_(ツ)_/¯"}.\n`
+ "Falling back to default theme..."
);
window.Aliucord.settings.delete("theme");
break;
}
}

export function useDiscordThemes() {
window.Aliucord.settings.set("theme", "");

console.info("Using Discord's themes");
function showFailDialog(message: string) {
Dialog.show({
title: "Restart to apply",
body: "Restart the app for Discord to use its own themes.",
confirmText: "Restart",
isDismissable: false,
onConfirm: ReactNative.NativeModules.BundleUpdaterManager.reload
title: "Failed to apply theme",
body: message,
confirmText: "OK"
});
}

2 changes: 1 addition & 1 deletion src/metro/constants.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4171,7 +4171,7 @@ interface Constants {
PRIMARY: string;
PRIMARY_BOLD: string;
PRIMARY_LIGHT: string;
PRIMARY_REGULAR: string;
PRIMARY_NORMAL: string;
PRIMARY_SEMIBOLD: string;
};
FormStates: {
Expand Down
7 changes: 5 additions & 2 deletions src/metro/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { EmitterSubscription, ImageSourcePropType, ImageStyle, TextStyle, ViewStyle } from "react-native";
import { themerInit } from "../api/Themer";
import { themerInit } from "../themer/themerInit";
import { Logger } from "../utils/Logger";

declare const __r: (moduleId: number) => any;
Expand Down Expand Up @@ -297,7 +297,10 @@ export enum AMOLEDThemeState {
}

export const setAMOLEDThemeEnabledBypass = (state) => {
FluxDispatcher.dispatch({ type: "UNSYNCED_USER_SETTINGS_UPDATE", settings: { useAMOLEDTheme: state ? AMOLEDThemeState.ON : AMOLEDThemeState.OFF } });
FluxDispatcher.dispatch({
type: "UNSYNCED_USER_SETTINGS_UPDATE",
settings: { useAMOLEDTheme: state ? AMOLEDThemeState.ON : AMOLEDThemeState.OFF }
});
};

export const Dialog = getByProps("show", "openLazy", "confirm", "close") as {
Expand Down
29 changes: 10 additions & 19 deletions src/ui/patchTheme.ts → src/themer/patchTheme.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import { logger } from "../Aliucord";
import { currentTheme, themeApplied, themeErrorReason } from "../api/Themer";
import { AMOLEDThemeManager, Dialog, FluxDispatcher, ReactNative, ThemeManager, ThemeStore, UnsyncedUserSettingsStore } from "../metro";
import { onStartup } from "../api/Themer";
import { AMOLEDThemeManager, AMOLEDThemeState, FluxDispatcher, setAMOLEDThemeEnabledBypass, ThemeManager, ThemeStore, UnsyncedUserSettingsStore } from "../metro";
import { themeState } from "./themerInit";

export default function patchTheme() {
if (!themeApplied && themeErrorReason) {
logger.error("Failed to apply theme: ", themeErrorReason);
Dialog.show({
title: "Failed to apply theme",
body: `${currentTheme?.name} failed to apply. Theme will be disabled on restart.`,
isDismissable: false,
cancelText: "Do not restart",
confirmText: "Restart",
onConfirm: ReactNative.NativeModules.BundleUpdaterManager.reload
});

window.Aliucord.settings.set("theme", "");
} else if (themeApplied) {
logger.log("Applied theme: ", currentTheme.name);
}

try {
// Handle custom theme info which was not possible during themer initialization
onStartup();

// 'I18N_LOAD_START' dispatch is the best time I can find to override the theme without breaking it.
// Therefore, there's no guarantee that this will fix it for everyone
logger.info("Patching theme...");
Expand All @@ -29,7 +17,10 @@ export default function patchTheme() {
logger.info(`Overrode theme to ${ThemeStore.theme ?? "dark"}`);

if (AMOLEDThemeManager) {
if (UnsyncedUserSettingsStore.useAMOLEDTheme === 2) {
if (themeState.isApplied && themeState.noAMOLED) {
setAMOLEDThemeEnabledBypass(false);
logger.info("Disabled AMOLED theme as it's unsupported by current custom theme.");
} else if (UnsyncedUserSettingsStore.useAMOLEDTheme === AMOLEDThemeState.ON) {
AMOLEDThemeManager.setAMOLEDThemeEnabled(true);
logger.info("Enabled AMOLED theme");
}
Expand Down
Loading