From b90cddd3d89e3e65f9c9b46b1d88b1af1bcf6ee9 Mon Sep 17 00:00:00 2001 From: Amsyar Rasyiq <82711525+amsyarasyiq@users.noreply.github.com> Date: Mon, 28 Aug 2023 08:36:19 +0800 Subject: [PATCH] [UI] Fix You tab on recent versions (#142) * Fix You tab crash * Fix You tab patch on recent versions Compatibility with older version is implemented, however, untested --- src/lib/preinit.ts | 30 ++++++++-- src/ui/settings/data.tsx | 2 + src/ui/settings/patches/you.tsx | 102 ++++++++++++++++++++++---------- 3 files changed, 97 insertions(+), 37 deletions(-) diff --git a/src/lib/preinit.ts b/src/lib/preinit.ts index 140ac25..12b0970 100644 --- a/src/lib/preinit.ts +++ b/src/lib/preinit.ts @@ -1,19 +1,39 @@ import { initThemes } from "@lib/themes"; +import { instead } from "spitroast"; // Hoist required modules // This used to be in filters.ts, but things became convoluted -// Early find logic -const basicFind = (prop: string) => Object.values(window.modules).find(m => m?.publicModule.exports?.[prop])?.publicModule?.exports; +const basicFind = (filter: (m: any) => any | string) => { + for (const key in window.modules) { + const exp = window.modules[key]?.publicModule.exports; + if (exp && filter(exp)) return exp; + } +} + +const requireNativeComponent = basicFind(m => m?.default?.name === "requireNativeComponent"); + +if (requireNativeComponent) { + // > "Tried to register two views with the same name DCDVisualEffectView" + // This serves as a workaround for the crashing You tab on Android starting from version 192.x + // How? We simply ignore it. + instead("default", requireNativeComponent, (args, orig) => { + try { + return orig(...args); + } catch { + return () => null; + } + }) +} // Hoist React on window -window.React = basicFind("createElement") as typeof import("react"); +window.React = basicFind(m => m.createElement) as typeof import("react"); // Export ReactNative -export const ReactNative = basicFind("AppRegistry") as typeof import("react-native"); +export const ReactNative = basicFind(m => m.AppRegistry) as typeof import("react-native"); // Export chroma.js -export const chroma = basicFind("brewer") as typeof import("chroma-js"); +export const chroma = basicFind(m => m.brewer) as typeof import("chroma-js"); // Themes if (window.__vendetta_loader?.features.themes) { diff --git a/src/ui/settings/data.tsx b/src/ui/settings/data.tsx index 6fb6703..16d376b 100644 --- a/src/ui/settings/data.tsx +++ b/src/ui/settings/data.tsx @@ -111,6 +111,7 @@ export const getYouData = () => { return { getLayout: () => ({ title: "Vendetta", + label: "Vendetta", // We can't use our keyMap function here since `settings` is an array not an object settings: getRenderableScreens(true).map(s => s.key) }), @@ -124,6 +125,7 @@ export const getYouData = () => { return { type: "route", + title: () => s.title, icon: s.icon ? getAssetIDByName(s.icon) : null, screen: { // TODO: This is bad, we should not re-convert the key casing diff --git a/src/ui/settings/patches/you.tsx b/src/ui/settings/patches/you.tsx index 6c96022..62a4c44 100644 --- a/src/ui/settings/patches/you.tsx +++ b/src/ui/settings/patches/you.tsx @@ -1,39 +1,37 @@ -import { i18n } from "@metro/common"; import { findByProps } from "@metro/filters"; -import { after } from "@lib/patcher"; +import { after, before } from "@lib/patcher"; import { getRenderableScreens, getScreens, getYouData } from "@ui/settings/data"; - -const layoutModule = findByProps("useOverviewSettings"); -const titleConfigModule = findByProps("getSettingTitleConfig"); -const miscModule = findByProps("SETTING_RELATIONSHIPS", "SETTING_RENDERER_CONFIGS"); - -// Checks for 189.4 and above -// When dropping support for 189.3 and below, following can be done: (unless Discord changes things again) -// const gettersModule = findByProps("getSettingListItems"); -const OLD_GETTER_FUNCTION = "getSettingSearchListItems"; -const NEW_GETTER_FUNCTION = "getSettingListItems"; -const oldGettersModule = findByProps(OLD_GETTER_FUNCTION); -const usingNewGettersModule = !oldGettersModule; -const getterFunctionName = usingNewGettersModule ? NEW_GETTER_FUNCTION : OLD_GETTER_FUNCTION; -const gettersModule = oldGettersModule ?? findByProps(NEW_GETTER_FUNCTION); +import { i18n } from "@lib/metro/common"; export default function patchYou() { + const patches = new Array; + + newYouPatch(patches) || oldYouPatch(patches); + return () => patches.forEach(p => p?.()); +} + +function oldYouPatch(patches: Function[]) { + const layoutModule = findByProps("useOverviewSettings"); + const titleConfigModule = findByProps("getSettingTitleConfig"); + const miscModule = findByProps("SETTING_RELATIONSHIPS", "SETTING_RENDERER_CONFIGS"); + + // Checks for 189.4 and above + // When dropping support for 189.3 and below, following can be done: (unless Discord changes things again) + // const gettersModule = findByProps("getSettingListItems"); + const OLD_GETTER_FUNCTION = "getSettingSearchListItems"; + const NEW_GETTER_FUNCTION = "getSettingListItems"; + const oldGettersModule = findByProps(OLD_GETTER_FUNCTION); + const usingNewGettersModule = !oldGettersModule; + const getterFunctionName = usingNewGettersModule ? NEW_GETTER_FUNCTION : OLD_GETTER_FUNCTION; + const gettersModule = oldGettersModule ?? findByProps(NEW_GETTER_FUNCTION); + if (!gettersModule || !layoutModule) return; - const patches = new Array; const screens = getScreens(true); const renderableScreens = getRenderableScreens(true); const data = getYouData(); - patches.push(after("useOverviewSettings", layoutModule, (_, ret) => { - // Add our settings - const accountSettingsIndex = ret.findIndex((i: any) => i.title === i18n.Messages.ACCOUNT_SETTINGS); - ret.splice(accountSettingsIndex + 1, 0, data.getLayout()); - - // Upload Logs button be gone - const supportCategory = ret.find((i: any) => i.title === i18n.Messages.SUPPORT); - supportCategory.settings = supportCategory.settings.filter((s: string) => s !== "UPLOAD_DEBUG_LOGS"); - })); + patches.push(after("useOverviewSettings", layoutModule, (_, ret) => manipulateSections(ret, data.getLayout()))); patches.push(after("getSettingTitleConfig", titleConfigModule, (_, ret) => ({ ...ret, @@ -53,16 +51,56 @@ export default function patchYou() { ...ret.filter((i: any) => (usingNewGettersModule || !screens.map(s => s.key).includes(i.setting))) ].map((item, index, parent) => ({ ...item, index, total: parent.length })))); - // TODO: We could use a proxy for these const oldRelationships = miscModule.SETTING_RELATIONSHIPS; - miscModule.SETTING_RELATIONSHIPS = { ...oldRelationships, ...data.relationships }; - const oldRendererConfigs = miscModule.SETTING_RENDERER_CONFIGS; + + miscModule.SETTING_RELATIONSHIPS = { ...oldRelationships, ...data.relationships }; miscModule.SETTING_RENDERER_CONFIGS = { ...oldRendererConfigs, ...data.rendererConfigs }; - return () => { + patches.push(() => { miscModule.SETTING_RELATIONSHIPS = oldRelationships; miscModule.SETTING_RENDERER_CONFIGS = oldRendererConfigs; - patches.forEach(p => p()); - }; + }); + + return true; +} + +function newYouPatch(patches: Function[]) { + const settingsListComponents = findByProps("SearchableSettingsList"); + const settingConstantsModule = findByProps("SETTING_RENDERER_CONFIG"); + const gettersModule = findByProps("getSettingListItems"); + + if (!gettersModule || !settingsListComponents || !settingConstantsModule) return false; + + const screens = getScreens(true); + const data = getYouData(); + + patches.push(before("type", settingsListComponents.SearchableSettingsList, ([{ sections }]) => manipulateSections(sections, data.getLayout()))); + + patches.push(after("getSettingListSearchResultItems", gettersModule, (_, ret) => { + ret.forEach((s: any) => screens.some(b => b.key === s.setting) && (s.breadcrumbs = ["Vendetta"])) + })); + + const oldRendererConfig = settingConstantsModule.SETTING_RENDERER_CONFIG; + settingConstantsModule.SETTING_RENDERER_CONFIG = { ...oldRendererConfig, ...data.rendererConfigs }; + + patches.push(() => { + settingConstantsModule.SETTING_RENDERER_CONFIG = oldRendererConfig; + }); + + return true; +} + +const isLabel = (i: any, name: string) => i?.label === name || i?.title === name; + +function manipulateSections(sections: any[], layout: any) { + if (!Array.isArray(sections) || sections.find((i: any) => isLabel(i, "Vendetta"))) return; + + // Add our settings + const accountSettingsIndex = sections.findIndex((i: any) => isLabel(i, i18n.Messages.ACCOUNT_SETTINGS)); + sections.splice(accountSettingsIndex + 1, 0, layout); + + // Upload Logs button be gone + const supportCategory = sections.find((i: any) => isLabel(i, i18n.Messages.SUPPORT)); + if (supportCategory) supportCategory.settings = supportCategory.settings.filter((s: string) => s !== "UPLOAD_DEBUG_LOGS") } \ No newline at end of file