Delete ui directory

This commit is contained in:
OTKUSteyler 2025-12-10 17:23:56 +01:00
parent f8047bb853
commit 21bdd39803
19 changed files with 0 additions and 771 deletions

View file

@ -1,4 +0,0 @@
import { lazyDestructure } from "@lib/utils/lazy";
import { findByProps } from "@metro";
export const { openAlert, dismissAlert } = lazyDestructure(() => findByProps("openAlert", "dismissAlert"));

View file

@ -1,30 +0,0 @@
import { constants } from "@metro/common";
import { findByProps, findByStoreNameLazy } from "@metro/wrappers";
//! This module is only found on 165.0+, under the assumption that iOS 165.0 is the same as Android 165.0.
//* In 167.1, most if not all traces of the old color modules were removed.
//* In 168.6, Discord restructured EVERYTHING again. SemanticColor on this module no longer works when passed to a stylesheet. We must now use what you see below.
//* In 173.10, Discord restructured a lot of the app. These changes included making the color module impossible to early-find.
// ? To stop duplication, it is now exported in our theming code.
// ? These comments are preserved for historical purposes.
// const colorModule = findByPropsProxy("colors", "meta");
const color = findByProps("SemanticColor");
// ? SemanticColor and default.colors are effectively ThemeColorMap
export const semanticColors = (color?.default?.colors ?? constants?.ThemeColorMap) as Record<string, any>;
// ? RawColor and default.unsafe_rawColors are effectively Colors
//* Note that constants.Colors does still appear to exist on newer versions despite Discord not internally using it - what the fuck?
export const rawColors = (color?.default?.unsafe_rawColors ?? constants?.Colors) as Record<string, string>;
const ThemeStore = findByStoreNameLazy("ThemeStore");
const colorResolver = color.default.meta ??= color.default.internal;
export function isSemanticColor(sym: any): boolean {
return colorResolver.isSemanticColor(sym);
}
export function resolveSemanticColor(sym: any, theme = ThemeStore.theme): string {
return colorResolver.resolveSemanticColor(theme, sym);
}

View file

@ -1,37 +0,0 @@
import { constants } from "@metro/common";
import { semanticColors } from "@ui/color";
import { createStyles } from "@ui/styles";
import { Platform, Text, TextInput } from "react-native";
export interface CodeblockProps {
selectable?: boolean;
style?: import("react-native").TextStyle;
children?: string;
}
const useStyles = createStyles({
codeBlock: {
fontFamily: constants.Fonts.CODE_NORMAL,
fontSize: 12,
textAlignVertical: "center",
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
color: semanticColors.TEXT_NORMAL,
borderWidth: 1,
borderRadius: 12,
borderColor: semanticColors.BACKGROUND_TERTIARY,
padding: 10,
},
});
// iOS doesn't support the selectable property on RN.Text...
const InputBasedCodeblock = ({ style, children }: CodeblockProps) => <TextInput editable={false} multiline style={[useStyles().codeBlock, style && style]} value={children} />;
const TextBasedCodeblock = ({ selectable, style, children }: CodeblockProps) => <Text selectable={selectable} style={[useStyles().codeBlock, style && style]}>{children}</Text>;
export default function Codeblock({ selectable, style, children }: CodeblockProps) {
if (!selectable) return <TextBasedCodeblock style={style} children={children} />;
return Platform.select({
ios: <InputBasedCodeblock style={style} children={children} />,
default: <TextBasedCodeblock style={style} children={children} selectable />,
});
}

View file

@ -1,36 +0,0 @@
import ErrorCard from "@core/ui/reporter/components/ErrorCard";
import { React } from "@metro/common";
import { ThemeContext } from "@ui/styles";
import { Falsy } from "react-native";
type ErrorBoundaryState = {
hasErr: false;
} | {
hasErr: true;
error: Error;
};
export interface ErrorBoundaryProps {
children: JSX.Element | Falsy | (JSX.Element | Falsy)[];
}
export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasErr: false };
}
static contextType = ThemeContext;
static getDerivedStateFromError = (error: Error) => ({ hasErr: true, error });
render() {
if (!this.state.hasErr) return this.props.children;
return (
<ErrorCard
error={this.state.error}
onRetryRender={() => this.setState({ hasErr: false })}
/>
);
}
}

View file

@ -1,58 +0,0 @@
import { LegacyAlert, LegacyFormInput } from "@metro/common/components";
import { findByPropsLazy } from "@metro/wrappers";
const Alerts = findByPropsLazy("openLazy", "close");
export interface InputAlertProps {
title?: string;
confirmText?: string;
confirmColor?: string;
onConfirm: (input: string) => (void | Promise<void>);
cancelText?: string;
placeholder?: string;
initialValue?: string;
secureTextEntry?: boolean;
}
export default function InputAlert({ title, confirmText, confirmColor, onConfirm, cancelText, placeholder, initialValue = "", secureTextEntry }: InputAlertProps) {
const [value, setValue] = React.useState(initialValue);
const [error, setError] = React.useState("");
function onConfirmWrapper() {
const asyncOnConfirm = Promise.resolve(onConfirm(value));
asyncOnConfirm.then(() => {
Alerts.close();
}).catch((e: Error) => {
setError(e.message);
});
}
return (
<LegacyAlert
title={title}
confirmText={confirmText}
confirmColor={confirmColor}
isConfirmButtonDisabled={error.length !== 0}
onConfirm={onConfirmWrapper}
cancelText={cancelText}
onCancel={() => Alerts.close()}
>
<LegacyFormInput
placeholder={placeholder}
value={value}
onChange={(v: string | { text: string; }) => {
setValue(typeof v === "string" ? v : v.text);
if (error) setError("");
}}
returnKeyType="done"
onSubmitEditing={onConfirmWrapper}
error={error || undefined}
secureTextEntry={secureTextEntry}
autoFocus={true}
showBorder={true}
style={{ alignSelf: "stretch" }}
/>
</LegacyAlert>
);
}

View file

@ -1,58 +0,0 @@
import { React } from "@metro/common";
import { Strings } from "@core/i18n";
import { findAssetId } from "@lib/api/assets";
import { TextInput } from "@metro/common/components";
import ErrorBoundary from "@ui/components/ErrorBoundary";
import { Image, View, ViewStyle } from "react-native";
export interface SearchProps {
onChangeText?: (v: string) => void;
placeholder?: string;
style?: ViewStyle;
isRound?: boolean;
}
function SearchIcon() {
return (
<Image
style={{ width: 16, height: 16 }}
source={findAssetId("icon-search")!}
/>
);
}
export default ({ onChangeText, placeholder, style, isRound }: SearchProps) => {
const [query, setQuery] = React.useState("");
const onChange = (value: string) => {
setQuery(value);
onChangeText?.(value);
};
// When isRound is true, provide an explicit pill-style appearance so the
// input looks rounded regardless of underlying component defaults.
const inputStyle = isRound
? ({ borderRadius: 9999, overflow: "hidden" } as ViewStyle)
: undefined;
return (
<ErrorBoundary>
<View style={style}>
<TextInput
grow={true}
isClearable={true}
leadingIcon={SearchIcon}
placeholder={placeholder ?? Strings.SEARCH}
onChange={onChange}
returnKeyType="search"
size="md"
autoCapitalize="none"
autoCorrect={false}
isRound={isRound}
style={inputStyle}
value={query}
/>
</View>
</ErrorBoundary>
);
};

View file

@ -1,32 +0,0 @@
import { findAssetId } from "@lib/api/assets";
import { LegacyFormRow, TableRow } from "@metro/common/components";
import { LayoutAnimation, View } from "react-native";
export interface SummaryProps {
label: string;
icon?: string;
noPadding?: boolean;
noAnimation?: boolean;
children: JSX.Element | JSX.Element[];
}
export default function Summary({ label, icon, noPadding = false, noAnimation = false, children }: SummaryProps) {
const [hidden, setHidden] = React.useState(true);
return (
<>
<TableRow
label={label}
icon={icon && <TableRow.Icon source={findAssetId(icon)} />}
trailing={<LegacyFormRow.Arrow style={{ transform: [{ rotate: `${hidden ? 180 : 90}deg` }] }} />}
onPress={() => {
setHidden(!hidden);
if (!noAnimation) LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
}}
/>
{!hidden && <>
<View style={!noPadding && { paddingHorizontal: 15 }}>{children}</View>
</>}
</>
);
}

View file

@ -1,5 +0,0 @@
export * as wrappers from "./wrappers";
export { default as Codeblock } from "@ui/components/Codeblock";
export { default as ErrorBoundary } from "@ui/components/ErrorBoundary";
export { default as Search } from "@ui/components/Search";
export { default as Summary } from "@ui/components/Summary";

View file

@ -1,39 +0,0 @@
import { lazyDestructure } from "@lib/utils/lazy";
import { findByFilePath, findByProps } from "@metro";
import { Text } from "@metro/common/components";
import { Button } from "@metro/common/types/components";
import { ComponentProps, ComponentType, ReactNode } from "react";
import { View } from "react-native";
type ActionButtonProps = Omit<ComponentProps<Button>, "onPress"> & {
onPress?: () => void | Promise<unknown>;
};
const {
AlertModal: _AlertModal,
AlertActionButton: _AlertActionButton
} = lazyDestructure(() => findByProps("AlertModal", "AlertActions"));
export const AlertActionButton = _AlertActionButton as ComponentType<ActionButtonProps>;
export default function AlertModal(props: Record<string, unknown>) {
const forwardFailedModal = findByFilePath("modules/forwarding/native/ForwardFailedAlertModal.tsx");
// ponyfill for extraContent
if (!forwardFailedModal && "extraContent" in props) {
props.content = (
<View style={{ gap: 16 }}>
<Text variant="text-md/medium" color="text-muted">
{props.content as string}
</Text>
<View>
{props.extraContent as ReactNode}
</View>
</View>
);
delete props.extraContent;
}
return <_AlertModal {...props} />;
}

View file

@ -1,3 +0,0 @@
import AlertModal, { AlertActionButton } from "./AlertModal";
export { AlertActionButton, AlertModal };

View file

@ -1,7 +0,0 @@
export * as alerts from "./alerts";
export * as components from "./components";
export * as settings from "./settings";
export * as sheets from "./sheets";
export * as styles from "./styles";
export * as toasts from "./toasts";

View file

@ -1,37 +0,0 @@
// Good luck reading this!
import { lazy } from "react";
import type { ImageURISource } from "react-native";
import { patchTabsUI } from "./patches/tabs";
export interface RowConfig {
key: string;
title: () => string;
onPress?: () => any;
render?: Parameters<typeof lazy>[0];
icon?: ImageURISource | number;
IconComponent?: React.ReactNode;
usePredicate?: () => boolean;
useTrailing?: () => string | JSX.Element;
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];
}
/**
* @internal
*/
export function patchSettings() {
const unpatches = new Array<() => boolean>();
patchTabsUI(unpatches);
return () => unpatches.forEach((u) => u());
}

View file

@ -1,123 +0,0 @@
import { after } from "@lib/api/patcher";
import { findInReactTree } from "@lib/utils";
import { i18n, NavigationNative } from "@metro/common";
import {
LegacyFormDivider,
LegacyFormIcon,
LegacyFormRow,
LegacyFormSection,
} from "@metro/common/components";
import { findByNameLazy } from "@metro/wrappers";
import { registeredSections } from "@ui/settings";
import { CustomPageRenderer, wrapOnPress } from "./shared";
function SettingsSection() {
const navigation = NavigationNative.useNavigation();
return (
<>
{Object.keys(registeredSections).map(
(sect) =>
registeredSections[sect].length > 0 && (
<LegacyFormSection key={sect} title={sect}>
{/** Is usePredicate here safe? */}
{registeredSections[sect]
.filter((r) => r.usePredicate?.() ?? true)
.map((row, i, arr) => (
<>
<LegacyFormRow
label={row.title()}
leading={<LegacyFormIcon source={row.icon} />}
trailing={
<LegacyFormRow.Arrow
label={row.useTrailing?.() || undefined}
/>
}
onPress={wrapOnPress(
row.onPress,
navigation,
row.render,
row.title(),
)}
/>
{i !== arr.length - 1 && <LegacyFormDivider />}
</>
))}
</LegacyFormSection>
),
)}
</>
);
}
export function patchPanelUI(unpatches: (() => void | boolean)[]) {
try {
unpatches.push(
after("default", findByNameLazy("getScreens", false), (_a, screens) => ({
...screens,
VendettaCustomPage: {
title: "ShiggyCord",
render: () => <CustomPageRenderer />,
},
BUNNY_CUSTOM_PAGE: {
title: "ShiggyCord",
render: () => <CustomPageRenderer />,
},
SHIGGYCORD_CUSTOM_PAGE: {
title: "ShiggyCord",
render: () => <CustomPageRenderer />,
},
})),
);
const unpatch = after(
"default",
findByNameLazy("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 === LegacyFormSection,
)?.children || res.props.children;
if (sections) {
const index = sections.findIndex((c: any) =>
titles.includes(c?.props.label),
);
sections.splice(-~index || 4, 0, <SettingsSection />);
}
}),
);
},
true,
);
unpatches.push(unpatch);
} catch {}
}

View file

@ -1,44 +0,0 @@
import { NavigationNative } from "@metro/common";
import { findByPropsLazy } from "@metro/wrappers";
import { ErrorBoundary } from "@ui/components";
import { RowConfig } from "@ui/settings";
const tabsNavigationRef = findByPropsLazy("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 (
<ErrorBoundary>
<PageComponent />
</ErrorBoundary>
);
});
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("SHIGGYCORD_CUSTOM_PAGE", {
...screenOptions,
render: () => <Component {...props} />,
});
};
}

View file

@ -1,141 +0,0 @@
import { after } from "@lib/api/patcher";
import { TableRow } from "@metro/common/components";
import { findByNameLazy, findByPropsLazy } from "@metro/wrappers";
import { registeredSections } from "@ui/settings";
import { Strings } from "@core/i18n";
import { CustomPageRenderer, wrapOnPress } from "./shared";
import { findInReactTree } from "@lib/utils";
const settingConstants = findByPropsLazy("SETTING_RENDERER_CONFIG");
const createListModule = findByPropsLazy("createList");
const SettingsOverviewScreen = findByNameLazy("SettingsOverviewScreen", false);
function useIsFirstRender() {
let firstRender = false;
React.useEffect(() => void (firstRender = true), []);
return firstRender;
}
export function patchTabsUI(unpatches: (() => void | boolean)[]) {
const getRows = () =>
Object.values(registeredSections)
.flatMap((sect) =>
sect.map((row) => ({
[row.key]: {
type: "pressable",
title: row.title,
icon: row.icon,
IconComponent: () => <TableRow.Icon source={row.icon} />,
usePredicate: row.usePredicate,
useTrailing: row.useTrailing,
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;
let rendererConfigValue = settingConstants.SETTING_RENDERER_CONFIG;
Object.defineProperty(settingConstants, "SETTING_RENDERER_CONFIG", {
enumerable: true,
configurable: true,
get: () => ({
...rendererConfigValue,
VendettaCustomPage: {
type: "route",
title: () => "ShiggyCord",
screen: {
route: "VendettaCustomPage",
getComponent: () => CustomPageRenderer,
},
},
SHIGGYCORD_CUSTOM_PAGE: {
type: "route",
title: () => "ShiggyCord",
screen: {
route: "SHIGGYCORD_CUSTOM_PAGE",
getComponent: () => CustomPageRenderer,
},
},
BUNNY_CUSTOM_PAGE: {
type: "route",
title: () => "ShiggyCord",
screen: {
route: "BUNNY_CUSTOM_PAGE",
getComponent: () => CustomPageRenderer,
},
},
...getRows(),
}),
set: (v) => (rendererConfigValue = v),
});
unpatches.push(() => {
Object.defineProperty(settingConstants, "SETTING_RENDERER_CONFIG", {
value: origRendererConfig,
writable: true,
get: undefined,
set: undefined,
});
});
try {
unpatches.push(
after("createList", createListModule, function (args, ret) {
const [config] = args;
if (config?.sections && Array.isArray(config.sections)) {
const sections = config.sections;
const accountSectionIndex = sections.findIndex((i: any) =>
i.settings?.includes("ACCOUNT"),
);
if (accountSectionIndex !== -1) {
// Credit to @palmdevs - https://discord.com/channels/1196075698301968455/1243605828783571024/1307940348378742816
let index = accountSectionIndex + 1;
Object.keys(registeredSections).forEach((sect) => {
const alreadyExists = sections.some((s: any) => s.label === sect);
if (!alreadyExists) {
sections.splice(index++, 0, {
label: sect,
title: sect,
settings: registeredSections[sect].map((a) => a.key),
});
}
});
}
}
return ret;
}),
);
} catch {
unpatches.push(
after("default", SettingsOverviewScreen, (_, ret) => {
if (useIsFirstRender()) return; // :shrug:
const { sections } = findInReactTree(
ret,
(i) => i.props?.sections,
).props;
// Credit to @palmdevs - https://discord.com/channels/1196075698301968455/1243605828783571024/1307940348378742816
let index =
-~sections.findIndex((i: any) => i.settings.includes("ACCOUNT")) || 1;
Object.keys(registeredSections).forEach((sect) => {
sections.splice(index++, 0, {
label: sect,
title: sect,
settings: registeredSections[sect].map((a) => a.key),
});
});
}),
);
}
}

View file

@ -1,16 +0,0 @@
import { findByPropsLazy } from "@metro/wrappers";
const actionSheet = findByPropsLazy("openLazy", "hideActionSheet");
export function showSheet<T extends React.ComponentType<any>>(
key: string,
lazyImport: Promise<{ default: T; }> | T,
props?: React.ComponentProps<T>
) {
if (!("then" in lazyImport)) lazyImport = Promise.resolve({ default: lazyImport });
actionSheet.openLazy(lazyImport, key, props ?? {});
}
export function hideSheet(key: string) {
actionSheet.hideActionSheet(key);
}

View file

@ -1,75 +0,0 @@
import { lazyDestructure, proxyLazy } from "@lib/utils/lazy";
import { findByProps, findByPropsLazy } from "@metro/wrappers";
import { isSemanticColor, resolveSemanticColor } from "@ui/color";
import { TextStyles } from "@ui/types";
import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from "react-native";
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
const Styles = findByPropsLazy("createStyles");
export const { ThemeContext } = lazyDestructure(() => findByProps("ThemeContext"), { hint: "object" });
export const { TextStyleSheet } = lazyDestructure(() => findByProps("TextStyleSheet")) as unknown as {
TextStyleSheet: Record<TextStyles, TextStyle>;
};
/**
* Get themed styles based on the current theme
* @returns A hook that returns the themed stylesheet
* @example
* const useStyles = createStyles({
* container: {
* flex: 1,
* backgroundColor: tokens.colors.BACKGROUND_PRIMARY,
* },
* });
*
* function MyComponent() {
* const styles = useStyles();
* return <View style={styles.container} />;
* }
*/
export function createStyles<T extends NamedStyles<T>>(sheet: T | ((props: any) => T)): () => T {
return proxyLazy(() => Styles.createStyles(sheet));
}
/**
* Get themed styles based on the current theme for class components
* @example
* const getStyles = createStyles({
* container: {
* flex: 1,
* backgroundColor: tokens.colors.BACKGROUND_PRIMARY,
* },
* });
*
* class MyComponent extends React.Component {
* static contextType = ThemeContext;
* render() {
* const styles = getStyles(this.context);
* return <View style={styles.container} />;
* }
* }
*/
export function createLegacyClassComponentStyles<T extends NamedStyles<T>>(sheet: T | ((props: any) => T)): (ctxt: typeof ThemeContext) => T {
return proxyLazy(() => Styles.createLegacyClassComponentStyles(sheet));
}
/**
* Reimplementation of Discord's createThemedStyleSheet, which was removed since 204201
* Not exactly a 1:1 reimplementation, but sufficient to keep compatibility with existing plugins
* @deprecated Use createStyles or createLegacyClassComponentStyles instead
*/
export function createThemedStyleSheet<T extends StyleSheet.NamedStyles<T>>(sheet: T) {
for (const key in sheet) {
// @ts-ignore
sheet[key] = new Proxy(StyleSheet.flatten(sheet[key]), {
get(target, prop, receiver) {
const res = Reflect.get(target, prop, receiver);
return isSemanticColor(res) ? resolveSemanticColor(res) : res;
}
});
}
return sheet;
}

View file

@ -1,20 +0,0 @@
import { Strings } from "@core/i18n";
import { findAssetId } from "@lib/api/assets";
import { lazyDestructure } from "@lib/utils/lazy";
import { toasts } from "@metro/common";
import { findByProps } from "@metro/wrappers";
const { uuid4 } = lazyDestructure(() => findByProps("uuid4"));
export const showToast = (content: string, asset?: number) => toasts.open({
// ? In build 182205/44707, Discord changed their toasts, source is no longer used, rather icon, and a key is needed.
// TODO: We could probably have the developer specify a key themselves, but this works to fix toasts
key: `vd-toast-${uuid4()}`,
content: content,
source: asset,
icon: asset,
});
showToast.showCopyToClipboard = (message = Strings.COPIED_TO_CLIPBOARD) => {
showToast(message, findAssetId("toast_copy_link"));
};

File diff suppressed because one or more lines are too long