Rewrite settings patch and add to api
This commit is contained in:
parent
91246366c6
commit
218d09a5a3
14 changed files with 255 additions and 312 deletions
|
|
@ -33,6 +33,7 @@
|
|||
color
|
||||
toasts
|
||||
styles
|
||||
settings
|
||||
}
|
||||
utils {
|
||||
constants
|
||||
|
|
|
|||
BIN
src/assets/icons/pyon64.png
Normal file
BIN
src/assets/icons/pyon64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 810 B |
|
|
@ -1,155 +0,0 @@
|
|||
import { Strings } from "@core/i18n";
|
||||
import InstallButton from "@core/ui/components/InstallButton";
|
||||
import Developer from "@core/ui/settings/pages/Developer";
|
||||
import General from "@core/ui/settings/pages/General";
|
||||
import Plugins from "@core/ui/settings/pages/Plugins";
|
||||
import Themes from "@core/ui/settings/pages/Themes";
|
||||
import { getAssetIDByName } from "@lib/api/assets";
|
||||
import { isThemeSupported } from "@lib/api/native/loader";
|
||||
import { useProxy } from "@lib/api/storage";
|
||||
import { installPlugin } from "@lib/managers/plugins";
|
||||
import { installTheme } from "@lib/managers/themes";
|
||||
import { findByProps } from "@lib/metro/filters";
|
||||
import { settings } from "@lib/settings";
|
||||
import { without } from "@lib/utils";
|
||||
import { PROXY_PREFIX } from "@lib/utils/constants";
|
||||
import { NavigationNative } from "@metro/common";
|
||||
import { showConfirmationAlert } from "@ui/alerts";
|
||||
import { semanticColors } from "@ui/color";
|
||||
import ErrorBoundary from "@ui/components/ErrorBoundary";
|
||||
import { createStyles } from "@ui/styles";
|
||||
import { showToast } from "@ui/toasts";
|
||||
import { View } from "react-native";
|
||||
|
||||
const { useSafeAreaInsets } = findByProps("useSafeAreaInsets");
|
||||
|
||||
interface Screen {
|
||||
[index: string]: any;
|
||||
key: string;
|
||||
title: () => string;
|
||||
icon?: string;
|
||||
shouldRender?: () => boolean;
|
||||
options?: Record<string, any>;
|
||||
render: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
const useStyles = createStyles({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY,
|
||||
}
|
||||
});
|
||||
|
||||
// If a function is passed, it is called with the screen object, and the return value is mapped. If a string is passed, we map to the value of the property with that name on the screen. Else, just map to the given data.
|
||||
// Question: Isn't this overengineered?
|
||||
// Answer: Maybe.
|
||||
const keyMap = (screens: Screen[], data: string | ((s: Screen) => any) | null) => Object.fromEntries(screens.map(s => [s.key, typeof data === "function" ? data(s) : typeof data === "string" ? s[data] : data]));
|
||||
|
||||
export const getScreens = (): Screen[] => [
|
||||
{
|
||||
key: "VendettaSettings",
|
||||
title: () => Strings.GENERAL,
|
||||
icon: "settings",
|
||||
render: General,
|
||||
},
|
||||
{
|
||||
key: "VendettaPlugins",
|
||||
title: () => Strings.PLUGINS,
|
||||
icon: "debug",
|
||||
options: {
|
||||
headerRight: () => (
|
||||
<InstallButton
|
||||
alertTitle={Strings.INSTALL_PLUGIN}
|
||||
installFunction={async input => {
|
||||
if (!input.startsWith(PROXY_PREFIX) && !settings.developerSettings)
|
||||
setImmediate(() => showConfirmationAlert({
|
||||
title: Strings.MODAL_UNPROXIED_PLUGIN_HEADER,
|
||||
content: Strings.MODAL_UNPROXIED_PLUGIN_DESC,
|
||||
confirmText: Strings.INSTALL,
|
||||
onConfirm: () =>
|
||||
installPlugin(input)
|
||||
.then(() => showToast(Strings.TOASTS_INSTALLED_PLUGIN, getAssetIDByName("Check")))
|
||||
.catch((x: any) => showToast(x?.message ?? `${x}`, getAssetIDByName("Small"))),
|
||||
cancelText: Strings.CANCEL,
|
||||
}));
|
||||
else return await installPlugin(input);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
render: Plugins,
|
||||
},
|
||||
{
|
||||
key: "VendettaThemes",
|
||||
title: () => Strings.THEMES,
|
||||
icon: "ic_theme_24px",
|
||||
// TODO: bad
|
||||
shouldRender: () => isThemeSupported(),
|
||||
options: {
|
||||
headerRight: () => !settings.safeMode?.enabled && <InstallButton alertTitle={Strings.INSTALL_THEME} installFunction={installTheme} />,
|
||||
},
|
||||
render: Themes,
|
||||
},
|
||||
{
|
||||
key: "VendettaDeveloper",
|
||||
title: () => Strings.DEVELOPER,
|
||||
icon: "ic_progress_wrench_24px",
|
||||
shouldRender: () => settings.developerSettings ?? false,
|
||||
render: Developer,
|
||||
},
|
||||
{
|
||||
key: "VendettaCustomPage",
|
||||
title: () => "Vendetta Page",
|
||||
shouldRender: () => false,
|
||||
render: ({ render: PageView, noErrorBoundary, ...options }: { render: React.ComponentType; noErrorBoundary: boolean; } & Record<string, object>) => {
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
navigation.addListener("focus", () => navigation.setOptions(without(options, "render", "noErrorBoundary")));
|
||||
return noErrorBoundary ? <PageView /> : <ErrorBoundary><PageView /></ErrorBoundary>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const getPanelsScreens = () => keyMap(getScreens(), s => ({
|
||||
title: s.title(),
|
||||
render: s.render,
|
||||
...s.options,
|
||||
}));
|
||||
|
||||
export const getYouData = () => {
|
||||
const screens = getScreens();
|
||||
|
||||
return {
|
||||
getLayout: () => ({
|
||||
// This is an old key, keeping it as "Vendetta" is good as some plugins actually checks
|
||||
// to determine if this section is Vendetta's
|
||||
title: "Vendetta",
|
||||
label: Strings.BUNNY,
|
||||
// We can't use our keyMap function here since `settings` is an array not an object
|
||||
settings: screens.map(s => s.key)
|
||||
}),
|
||||
titleConfig: keyMap(screens, s => s.title()),
|
||||
relationships: keyMap(screens, null),
|
||||
rendererConfigs: keyMap(screens, s => {
|
||||
const WrappedComponent = React.memo(({ navigation, route }: any) => {
|
||||
const styles = useStyles();
|
||||
const { bottom: paddingBottom } = useSafeAreaInsets();
|
||||
navigation.addListener("focus", () => navigation.setOptions(s.options));
|
||||
return <View style={[{ paddingBottom }, styles.container]}>
|
||||
<s.render {...route.params} />
|
||||
</View>;
|
||||
});
|
||||
|
||||
return {
|
||||
type: "route",
|
||||
title: s.title,
|
||||
icon: s.icon ? getAssetIDByName(s.icon) : null,
|
||||
usePredicate: s.shouldRender && (() => useProxy(settings) && s.shouldRender!!()),
|
||||
screen: {
|
||||
route: s.key,
|
||||
getComponent: () => WrappedComponent,
|
||||
}
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
@ -1,12 +1,47 @@
|
|||
// TODO: make settings patches part of the api
|
||||
import patchPanels from "@core/ui/settings/patches/panels";
|
||||
import patchYou from "@core/ui/settings/patches/you";
|
||||
import { Strings } from "@core/i18n";
|
||||
import { getAssetIDByName } from "@lib/api/assets";
|
||||
import { isThemeSupported } from "@lib/api/native/loader";
|
||||
import { useProxy } from "@lib/api/storage";
|
||||
import { settings } from "@lib/settings";
|
||||
import { registerSection } from "@lib/ui/settings";
|
||||
import { version } from "bunny-build";
|
||||
|
||||
// @ts-ignore
|
||||
import PyoncordIcon from "../../../assets/icons/pyon64.png";
|
||||
|
||||
export default function initSettings() {
|
||||
const patches = [
|
||||
patchPanels(),
|
||||
patchYou(),
|
||||
];
|
||||
|
||||
return () => patches.forEach(p => p?.());
|
||||
registerSection({
|
||||
name: "Bunny",
|
||||
items: [
|
||||
{
|
||||
key: "BUNNY",
|
||||
title: () => Strings.BUNNY,
|
||||
icon: { uri: PyoncordIcon },
|
||||
render: () => import("@core/ui/settings/pages/General"),
|
||||
rawTabsConfig: {
|
||||
useTrailing: () => `(${version})`
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "BUNNY_PLUGINS",
|
||||
title: () => Strings.PLUGINS,
|
||||
icon: getAssetIDByName("ActivitiesIcon"),
|
||||
render: () => import("@core/ui/settings/pages/Plugins")
|
||||
},
|
||||
{
|
||||
key: "BUNNY_THEMES",
|
||||
title: () => Strings.THEMES,
|
||||
icon: getAssetIDByName("PaintPaletteIcon"),
|
||||
render: () => import("@core/ui/settings/pages/Themes"),
|
||||
usePredicate: () => isThemeSupported()
|
||||
},
|
||||
{
|
||||
key: "BUNNY_DEVELOPER",
|
||||
title: () => Strings.DEVELOPER,
|
||||
icon: getAssetIDByName("WrenchIcon"),
|
||||
render: () => import("@core/ui/settings/pages/Developer"),
|
||||
usePredicate: () => useProxy(settings).developerSettings ?? false
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Strings } from "@core/i18n";
|
|||
import Version from "@core/ui/components/Version";
|
||||
import { getAssetIDByName } from "@lib/api/assets";
|
||||
import { useProxy } from "@lib/api/storage";
|
||||
import { getDebugInfo, _toggleSafeMode } from "@lib/debug";
|
||||
import { _toggleSafeMode, getDebugInfo } from "@lib/debug";
|
||||
import { settings } from "@lib/settings";
|
||||
import { Stack, TableRow, TableRowGroup, TableSwitchRow } from "@lib/ui/components/discord/Redesign";
|
||||
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants";
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
import SettingsSection from "@core/ui/components/SettingsSection";
|
||||
import { getPanelsScreens } from "@core/ui/settings/data";
|
||||
import { after } from "@lib/api/patcher";
|
||||
import { findInReactTree } from "@lib/utils";
|
||||
import { i18n } from "@metro/common";
|
||||
import { findByName } from "@metro/filters";
|
||||
|
||||
const screensModule = findByName("getScreens", false);
|
||||
const settingsModule = findByName("UserSettingsOverviewWrapper", false);
|
||||
|
||||
export default function patchPanels() {
|
||||
const patches = new Array<Function>;
|
||||
|
||||
patches.push(after("default", screensModule, (_, existingScreens) => ({
|
||||
...existingScreens,
|
||||
...getPanelsScreens(),
|
||||
})));
|
||||
|
||||
after("default", settingsModule, (_, ret) => {
|
||||
const Overview = findInReactTree(ret.props.children, i => i.type && i.type.name === "UserSettingsOverview");
|
||||
|
||||
// Upload logs button gone
|
||||
patches.push(after("renderSupportAndAcknowledgements", Overview.type.prototype, (_, { props: { children } }) => {
|
||||
const index = children.findIndex((c: any) => c?.type?.name === "UploadLogsButton");
|
||||
if (index !== -1) children.splice(index, 1);
|
||||
}));
|
||||
|
||||
// TODO: Rewrite this whole patch, the index hasn't been properly found for months now
|
||||
patches.push(after("render", Overview.type.prototype, (_, { props: { children } }) => {
|
||||
const titles = [i18n.Messages.BILLING_SETTINGS, i18n.Messages.PREMIUM_SETTINGS];
|
||||
//! Fix for Android 174201 and iOS 42188
|
||||
({ children } = findInReactTree(children, i => i.children?.[1].type?.name === "FormSection"));
|
||||
const index = children.findIndex((c: any) => titles.includes(c?.props.label));
|
||||
children.splice(index === -1 ? 4 : index, 0, <SettingsSection />);
|
||||
}));
|
||||
}, true);
|
||||
|
||||
return () => patches.forEach(p => p());
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
import { Strings } from "@core/i18n";
|
||||
import { getScreens, getYouData } from "@core/ui/settings/data";
|
||||
import { after, before } from "@lib/api/patcher";
|
||||
import { i18n } from "@lib/metro/common";
|
||||
import { findByProps } from "@metro/filters";
|
||||
|
||||
export default function patchYou() {
|
||||
const patches = new Array<Function>;
|
||||
|
||||
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 screens = getScreens();
|
||||
const data = getYouData();
|
||||
|
||||
patches.push(after("useOverviewSettings", layoutModule, (_, ret) => manipulateSections(ret, data.getLayout())));
|
||||
|
||||
patches.push(after("getSettingTitleConfig", titleConfigModule, (_, ret) => ({
|
||||
...ret,
|
||||
...data.titleConfig,
|
||||
})));
|
||||
|
||||
patches.push(after(getterFunctionName, gettersModule, ([settings], ret) => [
|
||||
...(screens.filter(s => settings.includes(s.key) && (s.shouldRender?.() ?? true))).map(s => ({
|
||||
type: "setting_search_result",
|
||||
ancestorRendererData: data.rendererConfigs[s.key],
|
||||
setting: s.key,
|
||||
title: data.titleConfig[s.key],
|
||||
breadcrumbs: [Strings.BUNNY],
|
||||
icon: data.rendererConfigs[s.key].icon,
|
||||
})),
|
||||
// .filter can be removed when dropping support for 189.3 and below (unless Discord changes things again)
|
||||
...ret.filter((i: any) => (usingNewGettersModule || !screens.map(s => s.key).includes(i.setting)))
|
||||
].map((item, index, parent) => ({ ...item, index, total: parent.length }))));
|
||||
|
||||
const oldRelationships = miscModule.SETTING_RELATIONSHIPS;
|
||||
const oldRendererConfigs = miscModule.SETTING_RENDERER_CONFIGS;
|
||||
|
||||
miscModule.SETTING_RELATIONSHIPS = { ...oldRelationships, ...data.relationships };
|
||||
miscModule.SETTING_RENDERER_CONFIGS = { ...oldRendererConfigs, ...data.rendererConfigs };
|
||||
|
||||
patches.push(() => {
|
||||
miscModule.SETTING_RELATIONSHIPS = oldRelationships;
|
||||
miscModule.SETTING_RENDERER_CONFIGS = oldRendererConfigs;
|
||||
});
|
||||
|
||||
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();
|
||||
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 = [Strings.BUNNY]));
|
||||
}));
|
||||
|
||||
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, Strings.BUNNY))) 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");
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import { isThemeSupported } from "@lib/api/native/loader";
|
|||
import { _patchLogHook } from "@lib/debug";
|
||||
import { _initPlugins } from "@lib/managers/plugins";
|
||||
import { _initThemes, _patchChatBackground } from "@lib/managers/themes";
|
||||
import { _patchSettings } from "@lib/ui/settings";
|
||||
import { logger } from "@lib/utils/logger";
|
||||
import initWindowObject from "@lib/windowObject";
|
||||
import initSafeMode from "@ui/safeMode";
|
||||
|
|
@ -25,13 +26,14 @@ export default async () => {
|
|||
// Load everything in parallel
|
||||
const unloads = await Promise.all([
|
||||
_injectFluxInterceptor(),
|
||||
_patchSettings(),
|
||||
_patchLogHook(),
|
||||
_patchAssets(),
|
||||
_patchCommands(),
|
||||
_patchChatBackground(),
|
||||
initSettings(),
|
||||
initFixes(),
|
||||
initSafeMode(),
|
||||
initSettings(),
|
||||
initCorePlugins(),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export * as alerts from "./alerts";
|
||||
export * as color from "./color";
|
||||
export * as components from "./components";
|
||||
export * as settings from "./settings";
|
||||
export * as styles from "./styles";
|
||||
export * as toasts from "./toasts";
|
||||
|
|
|
|||
33
src/lib/ui/settings/index.tsx
Normal file
33
src/lib/ui/settings/index.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Good luck reading this!
|
||||
import type { ImageURISource } from "react-native";
|
||||
|
||||
import { patchPanelUI } from "./patches/panel";
|
||||
import { patchTabsUI } from "./patches/tabs";
|
||||
|
||||
export interface RowConfig {
|
||||
key: string;
|
||||
title: () => string;
|
||||
onPress?: () => any;
|
||||
render?: Parameters<typeof React.lazy>[0];
|
||||
icon?: ImageURISource | number;
|
||||
usePredicate?: () => boolean,
|
||||
rawTabsConfig?: Record<string, any>;
|
||||
}
|
||||
|
||||
export const registeredSections = {} as {
|
||||
[key: string]: RowConfig[];
|
||||
};
|
||||
|
||||
export function registerSection(section: { name: string; items: RowConfig[]; }) {
|
||||
registeredSections[section.name] = section.items;
|
||||
return () => delete registeredSections[section.name];
|
||||
}
|
||||
|
||||
export function _patchSettings() {
|
||||
const unpatches = new Array<() => boolean>;
|
||||
|
||||
patchPanelUI(unpatches);
|
||||
patchTabsUI(unpatches);
|
||||
|
||||
return () => unpatches.forEach(u => u());
|
||||
}
|
||||
62
src/lib/ui/settings/patches/panel.tsx
Normal file
62
src/lib/ui/settings/patches/panel.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { findByName } from "@lib/metro";
|
||||
import { i18n, NavigationNative } from "@lib/metro/common";
|
||||
import { FormIcon, FormRow, FormSection } from "@lib/ui/components/discord/Forms";
|
||||
import { registeredSections } from "@lib/ui/settings";
|
||||
import { findInReactTree } from "@lib/utils";
|
||||
import { after } from "spitroast";
|
||||
|
||||
import { CustomPageRenderer, wrapOnPress } from "./shared";
|
||||
|
||||
function SettingsSection() {
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
|
||||
return <>
|
||||
{Object.keys(registeredSections).map(sect => (
|
||||
<FormSection key={sect} title={sect}>
|
||||
{ /** Is usePredicate here safe? */}
|
||||
{registeredSections[sect].filter(r => r.usePredicate?.() ?? true).map(row => (
|
||||
<FormRow
|
||||
label={row.title()}
|
||||
leading={<FormIcon source={row.icon} />}
|
||||
trailing={FormRow.Arrow}
|
||||
onPress={wrapOnPress(row.onPress, navigation, row.render, row.title())}
|
||||
/>
|
||||
))}
|
||||
</FormSection>
|
||||
))}
|
||||
</>;
|
||||
}
|
||||
|
||||
export function patchPanelUI(unpatches: (() => void | boolean)[]) {
|
||||
unpatches.push(after("default", findByName("getScreens", false), (_a, screens) => ({
|
||||
...screens,
|
||||
VendettaCustomPage: {
|
||||
title: "Bnuuy",
|
||||
render: () => <CustomPageRenderer />
|
||||
}
|
||||
})));
|
||||
|
||||
const unpatch = after("default", findByName("UserSettingsOverviewWrapper", false), (_a, ret) => {
|
||||
const UserSettingsOverview = findInReactTree(ret.props.children, n => n.type?.name === "UserSettingsOverview");
|
||||
|
||||
unpatches.push(after("renderSupportAndAcknowledgements", UserSettingsOverview.type.prototype, (_args, { props: { children } }) => {
|
||||
const index = children.findIndex((c: any) => c?.type?.name === "UploadLogsButton");
|
||||
if (index !== -1) children.splice(index, 1);
|
||||
}));
|
||||
|
||||
unpatches.push(after("render", UserSettingsOverview.type.prototype, (_args, res) => {
|
||||
const titles = [i18n.Messages.BILLING_SETTINGS, i18n.Messages.PREMIUM_SETTINGS];
|
||||
|
||||
const sections = findInReactTree(
|
||||
res.props.children,
|
||||
n => n?.children?.[1]?.type === FormSection
|
||||
).children;
|
||||
|
||||
const index = sections.findIndex((c: any) => titles.includes(c?.props.label));
|
||||
sections.splice(-~index || 4, 0, <SettingsSection />);
|
||||
}));
|
||||
}, true);
|
||||
|
||||
unpatches.push(unpatch);
|
||||
}
|
||||
|
||||
41
src/lib/ui/settings/patches/shared.tsx
Normal file
41
src/lib/ui/settings/patches/shared.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { NavigationNative } from "@lib/metro/common";
|
||||
import { findByProps } from "@lib/metro/filters";
|
||||
import { RowConfig } from "@lib/ui/settings";
|
||||
|
||||
const tabsNavigationRef = findByProps("getRootNavigationRef");
|
||||
|
||||
export const CustomPageRenderer = React.memo(() => {
|
||||
const navigation = NavigationNative.useNavigation();
|
||||
const route = NavigationNative.useRoute();
|
||||
|
||||
const { render: PageComponent, ...args } = route.params;
|
||||
|
||||
React.useEffect(() => void navigation.setOptions({ ...args }), []);
|
||||
|
||||
return <PageComponent />;
|
||||
});
|
||||
|
||||
export function wrapOnPress(
|
||||
onPress: (() => unknown) | undefined,
|
||||
navigation?: any,
|
||||
renderPromise?: RowConfig["render"],
|
||||
screenOptions?: string | Record<string, any>,
|
||||
props?: any,
|
||||
) {
|
||||
return async () => {
|
||||
if (onPress) return void onPress();
|
||||
|
||||
const Component = await renderPromise!!().then(m => m.default);
|
||||
|
||||
if (typeof screenOptions === "string") {
|
||||
screenOptions = { title: screenOptions };
|
||||
}
|
||||
|
||||
navigation ??= tabsNavigationRef.getRootNavigationRef();
|
||||
navigation.navigate("VendettaCustomPage", {
|
||||
...screenOptions,
|
||||
render: () => <Component {...props} />
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
67
src/lib/ui/settings/patches/tabs.tsx
Normal file
67
src/lib/ui/settings/patches/tabs.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { findByName, findByProps } from "@lib/metro";
|
||||
import { i18n } from "@lib/metro/common";
|
||||
import { registeredSections } from "@lib/ui/settings";
|
||||
import { findInReactTree } from "@lib/utils";
|
||||
import { after } from "spitroast";
|
||||
|
||||
import { CustomPageRenderer, wrapOnPress } from "./shared";
|
||||
|
||||
const settingConstants = findByProps("SETTING_RENDERER_CONFIG");
|
||||
const SettingsOverviewScreen = findByName("SettingsOverviewScreen", false);
|
||||
|
||||
function useIsFirstRender() {
|
||||
let firstRender = false;
|
||||
React.useEffect(() => void (firstRender = true), []);
|
||||
return firstRender;
|
||||
}
|
||||
|
||||
export function patchTabsUI(unpatches: (() => void | boolean)[]) {
|
||||
settingConstants.SETTING_RENDERER_CONFIG.VendettaCustomPage = {
|
||||
type: "route",
|
||||
title: () => "Bnuuy",
|
||||
screen: {
|
||||
route: "VendettaCustomPage",
|
||||
getComponent: () => CustomPageRenderer
|
||||
}
|
||||
};
|
||||
|
||||
const getRows = () => Object.values(registeredSections)
|
||||
.flatMap(sect => sect.map(row => ({
|
||||
[row.key]: {
|
||||
type: "pressable",
|
||||
title: row.title(),
|
||||
icon: row.icon,
|
||||
usePredicate: row.usePredicate,
|
||||
onPress: wrapOnPress(row.onPress, null, row.render, row.title()),
|
||||
withArrow: true,
|
||||
...row.rawTabsConfig
|
||||
}
|
||||
})))
|
||||
.reduce((a, c) => Object.assign(a, c));
|
||||
|
||||
const origRendererConfig = settingConstants.SETTING_RENDERER_CONFIG;
|
||||
settingConstants.SETTING_RENDERER_CONFIG = new Proxy(origRendererConfig, {
|
||||
get: (target, prop: string, reciever) => Reflect.get({ ...target, ...getRows() }, prop, reciever),
|
||||
getOwnPropertyDescriptor: (target, prop: string) => Reflect.getOwnPropertyDescriptor({ ...target, ...getRows() }, prop),
|
||||
ownKeys: target => [...Reflect.ownKeys(target), ...Object.keys(getRows())]
|
||||
});
|
||||
|
||||
unpatches.push(() => {
|
||||
settingConstants.SETTING_RENDERER_CONFIG = origRendererConfig;
|
||||
delete settingConstants.SETTING_RENDERER_CONFIG.VendettaCustomPage;
|
||||
});
|
||||
|
||||
unpatches.push(after("default", SettingsOverviewScreen, (_, ret) => {
|
||||
if (useIsFirstRender()) return; // :shrug:
|
||||
|
||||
const { sections } = findInReactTree(ret, i => i.props?.sections).props;
|
||||
let index = -~sections.findIndex((i: any) => i.label === i18n.Messages.ACCOUNT_SETTINGS) || 1;
|
||||
|
||||
Object.keys(registeredSections).forEach(sect => {
|
||||
sections.splice(index++, 0, {
|
||||
label: sect,
|
||||
settings: registeredSections[sect].map(a => a.key)
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
|
@ -88,7 +88,8 @@ const _bunnyObject = () => ({ // this function is never used, only exists for ty
|
|||
alerts: { ...ui.alerts },
|
||||
color: { ...ui.color },
|
||||
toasts: { ...ui.toasts },
|
||||
styles: { ...ui.styles }
|
||||
styles: { ...ui.styles },
|
||||
settings: { ...ui.settings }
|
||||
},
|
||||
utils: {
|
||||
...utils,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue