refactor(ui): the work begin

This commit is contained in:
amsyarasyiq 2024-05-23 16:21:22 +08:00
parent dd6742d08d
commit e8f60c92cf
27 changed files with 297 additions and 278 deletions

View file

@ -7,9 +7,9 @@ import { installPlugin, plugins, removePlugin } from "@lib/managers/plugins";
import { installTheme, removeTheme, themes } from "@lib/managers/themes";
import { DISCORD_SERVER_ID, HTTP_REGEX_MULTI, PLUGINS_CHANNEL_ID, PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants";
import { lazyDestructure } from "@lib/utils/lazy";
import { Button } from "@metro/common/components";
import { findByProps, findByPropsProxy } from "@metro/utils";
import { ErrorBoundary } from "@ui/components";
import { Button } from "@ui/components/discord/Redesign";
import { showToast } from "@ui/toasts";
type PostType = "Plugin" | "Theme";

View file

@ -6,10 +6,10 @@ import { settings } from "@lib/settings";
import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
import { lazyDestructure } from "@lib/utils/lazy";
import { clipboard } from "@metro/common";
import { HelpMessage } from "@metro/common/components";
import { findByProps, findByPropsProxy } from "@metro/utils";
import { showInputAlert } from "@ui/alerts";
import { ErrorBoundary, Search } from "@ui/components";
import { HelpMessage } from "@ui/components/discord";
import fuzzysort from "fuzzysort";
import { createContext } from "react";
import { FlatList, View } from "react-native";

View file

@ -1,6 +1,6 @@
import { Asset } from "@lib/api/assets";
import { clipboard } from "@metro/common";
import { FormRow } from "@ui/components/discord/Forms";
import { LegacyFormRow } from "@metro/common/components";
import { showToast } from "@ui/toasts";
import { Image } from "react-native";
@ -8,7 +8,7 @@ interface AssetDisplayProps { asset: Asset; }
export default function AssetDisplay({ asset }: AssetDisplayProps) {
return (
<FormRow
<LegacyFormRow
label={`${asset.name} - ${asset.id}`}
trailing={<Image source={asset.id} style={{ width: 32, height: 32 }} />}
onPress={() => {

View file

@ -1,10 +1,9 @@
import { requireAssetIndex } from "@lib/api/assets";
import { lazyDestructure } from "@lib/utils/lazy";
import { ReactNative as RN } from "@metro/common";
import { FormCheckbox, FormSwitch, IconButton, LegacyFormRow } from "@metro/common/components";
import { findByProps } from "@metro/utils";
import { semanticColors } from "@ui/color";
import { FormRow } from "@ui/components/discord/Forms";
import { FormCheckbox, FormSwitch, IconButton } from "@ui/components/discord/Redesign";
import { createStyles, TextStyleSheet } from "@ui/styles";
import { ImageBackground, View } from "react-native";
@ -100,7 +99,7 @@ export default function Card(props: CardProps) {
resizeMode="cover"
imageStyle={styles.iconStyle}
>
<FormRow
<LegacyFormRow
style={styles.header}
label={
<View style={styles.headerLeading}>
@ -119,7 +118,7 @@ export default function Card(props: CardProps) {
key: "CardOverflow",
header: {
title: props.overflowTitle,
icon: props.headerIcon && <FormRow.Icon style={{ marginRight: 8 }} source={requireAssetIndex(props.headerIcon)} />,
icon: props.headerIcon && <LegacyFormRow.Icon style={{ marginRight: 8 }} source={requireAssetIndex(props.headerIcon)} />,
onClose: () => hideActionSheet(),
},
options: props.overflowActions?.map(i => ({ ...i, icon: requireAssetIndex(i.icon) })),
@ -153,7 +152,7 @@ export default function Card(props: CardProps) {
</View>
}
/>
<FormRow
<LegacyFormRow
label={
<RN.View>
<RN.Text style={styles.descriptionLabel}>{props.descriptionLabel}</RN.Text>

View file

@ -3,7 +3,7 @@ import { CardWrapper } from "@core/ui/components/Card";
import { requireAssetIndex } from "@lib/api/assets";
import { useProxy } from "@lib/api/storage";
import { FontDefinition, fonts, removeFont, selectFont } from "@lib/managers/fonts";
import { FormCheckbox, IconButton, TableRow, TableRowGroup } from "@ui/components/discord/Redesign";
import { FormCheckbox, IconButton, TableRow, TableRowGroup } from "@metro/common/components";
import { showToast } from "@ui/toasts";
import { useContext } from "react";
import { Pressable, View } from "react-native";

View file

@ -1,7 +1,6 @@
import { requireAssetIndex } from "@lib/api/assets";
import { clipboard } from "@metro/common";
import { FormText } from "@ui/components/discord/Forms";
import { TableRow } from "@ui/components/discord/Redesign";
import { LegacyFormText, TableRow } from "@metro/common/components";
import { showToast } from "@ui/toasts";
interface VersionProps {
@ -15,7 +14,7 @@ export default function Version({ label, version, icon }: VersionProps) {
<TableRow
label={label}
icon={<TableRow.Icon source={requireAssetIndex(icon)} />}
trailing={<FormText>{version}</FormText>}
trailing={<LegacyFormText>{version}</LegacyFormText>}
onPress={() => {
clipboard.setString(`${label} - ${version}`);
showToast.showCopyToClipboard();

View file

@ -3,7 +3,7 @@ import Version from "@core/ui/components/Version";
import { useProxy } from "@lib/api/storage";
import { getDebugInfo } from "@lib/debug";
import { settings } from "@lib/settings";
import { Stack, TableRowGroup } from "@ui/components/discord/Redesign";
import { Stack, TableRowGroup } from "@metro/common/components";
import { Platform, ScrollView } from "react-native";
export default function About() {

View file

@ -1,7 +1,7 @@
import AssetDisplay from "@core/ui/components/AssetDisplay";
import { assetsMap } from "@lib/api/assets";
import { LegacyFormDivider } from "@metro/common/components";
import { ErrorBoundary, Search } from "@ui/components";
import { FormDivider } from "@ui/components/discord/Forms";
import { FlatList, View } from "react-native";
export default function AssetBrowser() {
@ -17,7 +17,7 @@ export default function AssetBrowser() {
<FlatList
data={Object.values(assetsMap).filter(a => a.name.includes(search) || a.id.toString() === search)}
renderItem={({ item }) => <AssetDisplay asset={item} />}
ItemSeparatorComponent={FormDivider}
ItemSeparatorComponent={LegacyFormDivider}
keyExtractor={item => item.name}
/>
</View>

View file

@ -8,11 +8,10 @@ import { connectToDebugger } from "@lib/debug";
import { loaderConfig, settings } from "@lib/settings";
import { lazyDestructure } from "@lib/utils/lazy";
import { NavigationNative } from "@metro/common";
import { Button, LegacyFormText, Stack, TableRow, TableRowGroup, TableSwitchRow, TextInput } from "@metro/common/components";
import { findByProps } from "@metro/utils";
import { semanticColors } from "@ui/color";
import { ErrorBoundary } from "@ui/components";
import { FormText } from "@ui/components/discord/Forms";
import { Button, Stack, TableRow, TableRowGroup, TableSwitchRow, TextInput } from "@ui/components/discord/Redesign";
import { createStyles, TextStyleSheet } from "@ui/styles";
import { ScrollView, StyleSheet } from "react-native";
@ -46,7 +45,7 @@ export default function Developer() {
label={Strings.DEBUGGER_URL}
placeholder="127.0.0.1:9090"
size="md"
leadingIcon={() => <FormText style={styles.leadingText}>ws://</FormText>}
leadingIcon={() => <LegacyFormText style={styles.leadingText}>ws://</LegacyFormText>}
defaultValue={settings.debuggerUrl}
onChange={(v: string) => settings.debuggerUrl = v}
/>

View file

@ -4,9 +4,10 @@ import { createProxy, useProxy } from "@lib/api/storage";
import { FontDefinition, saveFont, validateFont } from "@lib/managers/fonts";
import { getCurrentTheme } from "@lib/managers/themes";
import { safeFetch } from "@lib/utils";
import { NavigationNative } from "@metro/common";
import { ActionSheet, BottomSheetTitleHeader, Button, IconButton, Stack, TableRow, TableRowGroup, Text, TextInput } from "@metro/common/components";
import { findByPropsProxy } from "@metro/utils";
import { ErrorBoundary } from "@ui/components";
import { ActionSheet, BottomSheetTitleHeader, Button, IconButton, Stack, TableRow, TableRowGroup, Text, TextInput, useNavigation } from "@ui/components/discord/Redesign";
import { useMemo, useRef, useState } from "react";
import { ScrollView, View } from "react-native";
@ -35,7 +36,7 @@ function RevengeFontsExtractor({ fonts, setName }: {
const themeFonts = currentTheme!.fonts as Record<string, string>;
const [fontName, setFontName] = useState(guessFontName(Object.values(themeFonts)));
const [error, setError] = useState<string | void>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
return <View style={{ padding: 8, paddingBottom: 16, gap: 12 }}>
<TextInput
@ -84,7 +85,7 @@ function JsonFontImporter({ fonts, setName }: {
}) {
const [fontLink, setFontLink] = useState<string>("");
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | void>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
return <View style={{ padding: 8, paddingBottom: 16, gap: 12 }}>
<TextInput
@ -144,7 +145,7 @@ function EntryRow({ fontEntry }: { fontEntry: Record<string, string>; }) {
const urlRef = useRef<string>();
const [nameSet, setNameSet] = useState(false);
const [error, setError] = useState<string | void>();
const [error, setError] = useState<string | undefined>();
return <View style={{ flexDirection: "row", gap: 8, justifyContent: "flex-start" }}>
<View style={{ flex: 1 }}>
@ -203,7 +204,7 @@ export default function FontImporter() {
const memoEntry = useMemo(() => createProxy({}).proxy, []);
const fontEntries: Record<string, string> = useProxy(memoEntry);
const navigation = useNavigation();
const navigation = NavigationNative.useNavigation();
return <ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={12}>

View file

@ -4,8 +4,8 @@ import FontCard from "@core/ui/components/FontCard";
import { useProxy } from "@lib/api/storage";
import { FontDefinition, fonts, installFont } from "@lib/managers/fonts";
import { settings } from "@lib/settings";
import { FormText } from "@ui/components/discord/Forms";
import { useNavigation } from "@ui/components/discord/Redesign";
import { NavigationNative } from "@metro/common";
import { LegacyFormText } from "@metro/common/components";
import { useEffect, useState } from "react";
import { TouchableOpacity } from "react-native";
@ -17,7 +17,7 @@ export default function Plugins() {
const [removeMode, setRemoveMode] = useState(false);
const navigation = useNavigation();
const navigation = NavigationNative.useNavigation();
useEffect(() => {
const onPressCallback = () => {
@ -26,9 +26,9 @@ export default function Plugins() {
navigation.setOptions({
headerRight: () => <TouchableOpacity onPress={onPressCallback}>
<FormText style={{ marginRight: 12 }}>
<LegacyFormText style={{ marginRight: 12 }}>
{removeMode ? Strings.DONE : Strings.REMOVE}
</FormText>
</LegacyFormText>
</TouchableOpacity>
});
}, [removeMode]);

View file

@ -5,8 +5,8 @@ import { useProxy } from "@lib/api/storage";
import { getDebugInfo, toggleSafeMode } from "@lib/debug";
import { settings } from "@lib/settings";
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants";
import { url } from "@metro/common";
import { Stack, TableRow, TableRowGroup, TableSwitchRow, useNavigation } from "@ui/components/discord/Redesign";
import { NavigationNative, url } from "@metro/common";
import { Stack, TableRow, TableRowGroup, TableSwitchRow } from "@metro/common/components";
import { NativeModules, ScrollView } from "react-native";
import About from "./About";
@ -15,7 +15,7 @@ export default function General() {
useProxy(settings);
const debugInfo = getDebugInfo();
const navigation = useNavigation();
const navigation = NavigationNative.useNavigation();
return (
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>

View file

@ -5,7 +5,7 @@ import { useProxy } from "@lib/api/storage";
import { installTheme, Theme, themes } from "@lib/managers/themes";
import { settings } from "@lib/settings";
import { ButtonColors } from "@lib/utils/types";
import { Button } from "@ui/components/discord";
import { Button } from "@metro/common/components";
export default function Themes() {
useProxy(settings);

View file

@ -10,11 +10,11 @@ import { loaderConfig, settings } from "@lib/settings";
import * as utils from "@lib/utils";
import * as metro from "@metro";
import * as common from "@metro/common";
import { Forms } from "@metro/common/components";
import * as commonComponents from "@metro/common/components";
import * as alerts from "@ui/alerts";
import * as color from "@ui/color";
import * as components from "@ui/components";
import { Forms } from "@ui/components/discord/Forms";
import { CompatfulRedesign } from "@ui/components/discord/Redesign";
import { createThemedStyleSheet } from "@ui/styles";
import * as toasts from "@ui/toasts";
import SparkMD5 from "spark-md5";
@ -64,7 +64,6 @@ export const initVendettaObject = (): any => {
url: common.url,
toasts: common.toasts,
stylesheet: {
...CompatfulRedesign,
createThemedStyleSheet
},
clipboard: common.clipboard,
@ -109,10 +108,10 @@ export const initVendettaObject = (): any => {
components: {
Forms,
General: common.ReactNative,
Alert: components.discord.Alert,
Button: components.discord.Button,
HelpMessage: components.discord.HelpMessage,
SafeAreaView: components.discord.SafeAreaView,
Alert: commonComponents.Alert,
Button: commonComponents.Button,
HelpMessage: commonComponents.HelpMessage,
SafeAreaView: commonComponents.SafeAreaView,
Summary: components.Summary,
ErrorBoundary: components.ErrorBoundary,
Codeblock: components.Codeblock,

View file

@ -1,8 +1,7 @@
import { Strings } from "@core/i18n";
import { React } from "@metro/common";
import { Button, LegacyFormText } from "@metro/common/components";
import { Codeblock } from "@ui/components";
import { Button } from "@ui/components/discord";
import { FormText } from "@ui/components/discord/Forms";
import { createThemedStyleSheet } from "@ui/styles";
import { ScrollView } from "react-native";
@ -43,16 +42,15 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
return (
<ScrollView style={styles.view}>
<FormText style={styles.title}>{Strings.UH_OH}</FormText>
<LegacyFormText style={styles.title}>{Strings.UH_OH}</LegacyFormText>
<Codeblock selectable style={{ marginBottom: 5 }}>{this.state.error.name}</Codeblock>
<Codeblock selectable style={{ marginBottom: 5 }}>{this.state.error.message}</Codeblock>
{this.state.error.stack && <ScrollView style={{ maxHeight: 420, marginBottom: 5 }}>
<Codeblock selectable>{this.state.error.stack}</Codeblock>
</ScrollView>}
<Button
color={Button.Colors.RED}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
size="md"
variant="destructive"
onPress={() => this.setState({ hasErr: false })}
text={Strings.RETRY}
/>

View file

@ -1,7 +1,6 @@
import { ButtonColors } from "@lib/utils/types";
import { Alert, LegacyFormInput } from "@metro/common/components";
import { findByPropsProxy } from "@metro/utils";
import { Alert } from "@ui/components/discord";
import { FormInput } from "@ui/components/discord/Forms";
const Alerts = findByPropsProxy("openLazy", "close");
@ -40,7 +39,7 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
cancelText={cancelText}
onCancel={() => Alerts.close()}
>
<FormInput
<LegacyFormInput
placeholder={placeholder}
value={value}
onChange={(v: string | { text: string; }) => {

View file

@ -1,6 +1,6 @@
import { Strings } from "@core/i18n";
import { requireAssetIndex } from "@lib/api/assets";
import { TextInput } from "@ui/components/discord/Redesign";
import { TextInput } from "@metro/common/components";
import ErrorBoundary from "@ui/components/ErrorBoundary";
import { Image, View } from "react-native";

View file

@ -1,6 +1,5 @@
import { requireAssetIndex } from "@lib/api/assets";
import { FormRow } from "@ui/components/discord/Forms";
import { TableRow } from "@ui/components/discord/Redesign";
import { LegacyFormRow, TableRow } from "@metro/common/components";
import { LayoutAnimation, View } from "react-native";
export interface SummaryProps {
@ -19,7 +18,7 @@ export default function Summary({ label, icon, noPadding = false, noAnimation =
<TableRow
label={label}
icon={icon && <TableRow.Icon source={requireAssetIndex(icon)} />}
trailing={<FormRow.Arrow style={{ transform: [{ rotate: `${hidden ? 180 : 90}deg` }] }} />}
trailing={<LegacyFormRow.Arrow style={{ transform: [{ rotate: `${hidden ? 180 : 90}deg` }] }} />}
onPress={() => {
setHidden(!hidden);
if (!noAnimation) LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);

View file

@ -1,33 +0,0 @@
import { lazyDestructure } from "@lib/utils/lazy";
import { findByPropsProxy } from "@metro/utils";
export const Forms = findByPropsProxy("Form", "FormSection");
export const {
Form,
FormArrow,
FormCTA,
FormCTAButton,
FormCardSection,
FormCheckbox,
FormCheckboxRow,
FormCheckmark,
FormDivider,
FormHint,
FormIcon,
FormInput,
FormLabel,
FormRadio,
FormRadioGroup,
FormRadioRow,
FormRow,
FormSection,
FormSelect,
FormSliderRow,
FormSubLabel,
FormSwitch,
FormSwitchRow,
FormTernaryCheckBox,
FormText,
FormTitle
} = lazyDestructure(() => Forms);

View file

@ -1,170 +0,0 @@
import { lazyDestructure, proxyLazy } from "@lib/utils/lazy";
import { findExports } from "@metro";
import { createFilterDefinition, findByPropsProxy } from "@metro/utils";
import { RedesignModuleKeys } from "@ui/types";
import { useEffect } from "react";
import { View } from "react-native";
const bySingularProp = createFilterDefinition<[string]>(
([prop], m) => m[prop] && Object.keys(m).length === 1,
prop => `bunny.ui.bySingularProp(${prop})`
);
const findSingular = (prop: string) => proxyLazy(() => findExports(bySingularProp(prop))?.[prop]);
export const Redesign = findByPropsProxy("TableRow") as Record<RedesignModuleKeys, any>;
export const CompatfulRedesign = findByPropsProxy("ActionSheetRow") as unknown as {
TextStyleSheet: typeof import("@ui/styles").TextStyleSheet;
[key: string]: any;
};
// Funny polyfill, hopefully same props :3
CompatfulRedesign.ActionSheetTitleHeader ??= CompatfulRedesign.BottomSheetTitleHeader;
CompatfulRedesign.ActionSheetContentContainer ??= ({ children }: any) => {
useEffect(() => console.warn("Discord has removed 'ActionSheetContentContainer', please move into something else. It has been temporarily replaced with View"), []);
return <View>{children}</View>;
};
export const FormSwitch = findSingular("FormSwitch");
export const FormRadio = findSingular("FormRadio");
export const FormCheckbox = findSingular("FormCheckbox");
export const {
AlertActionButton,
AlertModal,
AlertModalContainer,
AvatarDuoPile,
AvatarPile,
Backdrop,
Button,
Card,
ContextMenu,
ContextMenuContainer,
FauxHeader,
FloatingActionButton,
GhostInput,
GuildIconPile,
HeaderActionButton,
HeaderButton,
HeaderSubmittingIndicator,
IconButton,
Input,
InputButton,
InputContainer,
LayerContext,
LayerScope,
Modal,
ModalActionButton,
ModalContent,
ModalDisclaimer,
ModalFloatingAction,
ModalFloatingActionSpacer,
ModalFooter,
ModalScreen,
ModalStepIndicator,
Navigator,
NavigatorHeader,
NavigatorScreen,
Pile,
PileOverflow,
RedesignCompat,
RedesignCompatContext,
RowButton,
SceneLoadingIndicator,
SearchField,
SegmentedControl,
SegmentedControlPages,
Slider,
Stack,
StepModal,
StickyContext,
StickyHeader,
StickyWrapper,
TableCheckboxRow,
TableRadioGroup,
TableRadioRow,
TableRow,
TableRowGroup,
TableRowGroupTitle,
TableRowIcon,
TableSwitchRow,
Tabs,
TextArea,
TextField,
TextInput,
Toast,
dismissAlerts,
getHeaderBackButton,
getHeaderCloseButton,
getHeaderConditionalBackButton,
getHeaderNoTitle,
getHeaderTextButton,
hideContextMenu,
navigatorShouldCrossfade,
openAlert,
useAccessibilityNativeStackOptions,
useAndroidNavScrim,
useCoachmark,
useFloatingActionButtonScroll,
useFloatingActionButtonState,
useNativeStackNavigation,
useNavigation,
useNavigationTheme,
useNavigatorBackPressHandler,
useNavigatorScreens,
useNavigatorShouldCrossfade,
useSegmentedControlState,
useStackNavigation,
useTabNavigation,
useTooltip
} = lazyDestructure(() => Redesign);
export const {
AccessibilityAnnouncer,
AccessibilityPreferencesContext,
AccessibilityView,
AccessibilityViewAnimated,
ActionSheet,
ActionSheetCloseButton,
ActionSheetIconHeader,
ActionSheetPresenter,
ActionSheetRow,
ActionSheetSwitchRow,
AnimatedEnterExitItem,
BottomSheetTitleHeader,
BottomSheetTextInput,
Dialog,
DisableCustomTheme,
Menu,
MenuGroup,
MenuItem,
MenuPopout,
RootThemeContextProvider,
SimpleActionSheet,
Text,
TextStyleSheet,
ThemeContextFlags,
ThemeContextForLegacyStyles,
ThemeContextProvider,
TransitionGroup,
TransitionItem,
TransitionStates,
UseThemeContext,
WCAGContrastRatios,
createLegacyClassComponentStyles,
createNativeStyleProperties,
createStyleProperties,
createStyles,
darkenColor,
experimental_createToken,
getContrastingColor,
getSemanticColorContextFromThemeContext,
hasThemeFlag,
setColorOpacity,
setThemeFlag,
showSimpleActionSheet,
useLegacyClassComponentStyles,
useThemeContext,
useToken
} = lazyDestructure(() => CompatfulRedesign);

View file

@ -1,11 +0,0 @@
import { findByDisplayNameProxy, findByNameProxy, findByPropsProxy } from "@metro/utils";
export * as Forms from "./Forms";
export * as Redesign from "./Redesign";
// Discord
export const Alert = findByDisplayNameProxy("FluxContainer(Alert)");
export const Button = findByPropsProxy("Looks", "Colors", "Sizes") as React.ComponentType<any> & { Looks: any, Colors: any, Sizes: any; };
export const HelpMessage = findByNameProxy("HelpMessage");
// React Native's included SafeAreaView only adds padding on iOS.
export const SafeAreaView = findByPropsProxy("useSafeAreaInsets").SafeAreaView as any;

View file

@ -1,5 +1,5 @@
// Discord
export * as discord from "./discord";
// export * as discord from "./discord";
// Vendetta
export { default as Codeblock } from "@ui/components/Codeblock";

View file

@ -5,10 +5,10 @@ import { toggleSafeMode } from "@lib/debug";
import { settings } from "@lib/settings";
import { lazyDestructure } from "@lib/utils/lazy";
import { ButtonColors } from "@lib/utils/types";
import { Button, SafeAreaView } from "@metro/common/components";
import { findByNameProxy, findByProps } from "@metro/utils";
import { semanticColors } from "@ui/color";
import { Codeblock, ErrorBoundary as _ErrorBoundary } from "@ui/components";
import { Button, SafeAreaView } from "@ui/components/discord";
import { createThemedStyleSheet, TextStyleSheet } from "@ui/styles";
import { Text, View } from "react-native";

View file

@ -1,8 +1,8 @@
import { after } from "@lib/api/patcher";
import { findInReactTree } from "@lib/utils";
import { i18n, NavigationNative } from "@metro/common";
import { LegacyFormIcon, LegacyFormRow, LegacyFormSection } from "@metro/common/components";
import { findByNameProxy } from "@metro/utils";
import { FormIcon, FormRow, FormSection } from "@ui/components/discord/Forms";
import { registeredSections } from "@ui/settings";
import { CustomPageRenderer, wrapOnPress } from "./shared";
@ -12,17 +12,17 @@ function SettingsSection() {
return <>
{Object.keys(registeredSections).map(sect => (
<FormSection key={sect} title={sect}>
<LegacyFormSection key={sect} title={sect}>
{ /** Is usePredicate here safe? */}
{registeredSections[sect].filter(r => r.usePredicate?.() ?? true).map(row => (
<FormRow
<LegacyFormRow
label={row.title()}
leading={<FormIcon source={row.icon} />}
trailing={FormRow.Arrow}
leading={<LegacyFormIcon source={row.icon} />}
trailing={LegacyFormRow.Arrow}
onPress={wrapOnPress(row.onPress, navigation, row.render, row.title())}
/>
))}
</FormSection>
</LegacyFormSection>
))}
</>;
}
@ -49,7 +49,7 @@ export function patchPanelUI(unpatches: (() => void | boolean)[]) {
const sections = findInReactTree(
res.props.children,
n => n?.children?.[1]?.type === FormSection
n => n?.children?.[1]?.type === LegacyFormSection
).children;
const index = sections.findIndex((c: any) => titles.includes(c?.props.label));

View file

@ -1,12 +1,13 @@
import { lazyDestructure } from "@lib/utils/lazy";
import { findByProps } from "@metro/utils";
import { findByProps, findByPropsProxy } from "@metro/utils";
import { isSemanticColor, resolveSemanticColor } from "@ui/color";
import { CompatfulRedesign } from "@ui/components/discord/Redesign";
import { DiscordTextStyles } from "@ui/types";
import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from "react-native";
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
const CompatfulRedesign = findByPropsProxy("createStyles");
export const { TextStyleSheet } = lazyDestructure(() => findByProps("TextStyleSheet")) as unknown as {
TextStyleSheet: { [key in DiscordTextStyles]: TextStyle; };
};

View file

@ -0,0 +1,95 @@
/* eslint-disable prefer-destructuring */
import { lazyDestructure, proxyLazy } from "@lib/utils/lazy";
import { findExports } from "@metro/finders";
import { createFilterDefinition, findByDisplayNameProxy, findByNameProxy, findByProps, findByPropsProxy } from "@metro/utils";
import * as t from "./types/components";
const bySingularProp = createFilterDefinition<[string]>(
([prop], m) => m[prop] && Object.keys(m).length === 1,
prop => `bunny.metro.common.components.bySingularProp(${prop})`
);
const findSingular = (prop: string) => proxyLazy(() => findExports(bySingularProp(prop))?.[prop]);
const findProp = (prop: string) => proxyLazy(() => findByProps(prop)[prop]);
// Discord
export const Alert = findByDisplayNameProxy("FluxContainer(Alert)");
export const CompatButton = findByPropsProxy("Looks", "Colors", "Sizes");
export const HelpMessage = findByNameProxy("HelpMessage");
// React Native's included SafeAreaView only adds padding on iOS.
export const SafeAreaView = proxyLazy(() => findByProps("useSafeAreaInsets").SafeAreaView);
// Buttons
export const Button = findSingular("Button") as t.Button;
export const TwinButtons = findProp("TwinButtons");
export const IconButton = findSingular("IconButton") as t.IconButton;
export const RowButton = findProp("RowButton") as t.RowButton;
export const PressableScale = findProp("PressableScale");
// Tables
export const TableRow = findProp("TableRow");
export const TableRowIcon = findProp("TableRowIcon");
export const TableRowTrailingText = findProp("TableRowTrailingText");
export const TableRowGroup = findProp("TableRowGroup");
export const TableSwitchRow = findProp("TableSwitchRow");
export const TableSwitch = findSingular("FormSwitch");
export const TableRadio = findSingular("FormRadio");
export const TableCheckbox = findSingular("FormCheckbox");
export const FormSwitch = findSingular("FormSwitch");
export const FormRadio = findSingular("FormRadio");
export const FormCheckbox = findSingular("FormCheckbox");
// Card
export const Card = findProp("Card");
export const RedesignCompat = proxyLazy(() => findByProps("RedesignCompat").RedesignCompat);
// Misc.
export const Stack = findProp("Stack") as t.Stack;
// Inputs
export const TextInput = findSingular("TextInput") as t.TextInput;
// SegmentedControl
export const SegmentedControl = findProp("SegmentedControl") as t.SegmentedControl;
export const CompatSegmentedControl = findProp("CompatSegmentedControl") as t.CompatSegmentedControl;
export const FloatingActionButton = findProp("FloatingActionButton") as t.FloatingActionButton;
export const ActionSheet = findProp("ActionSheet");
export const BottomSheetTitleHeader = findProp("BottomSheetTitleHeader");
const textsModule = findByPropsProxy("Text", "LegacyText");
export const Text = proxyLazy(() => textsModule.Text) as t.Text;
export const Forms = findByPropsProxy("Form", "FormSection");
export const {
Form: LegacyForm,
FormArrow: LegacyFormArrow,
FormCTA: LegacyFormCTA,
FormCTAButton: LegacyFormCTAButton,
FormCardSection: LegacyFormCardSection,
FormCheckbox: LegacyFormCheckbox,
FormCheckboxRow: LegacyFormCheckboxRow,
FormCheckmark: LegacyFormCheckmark,
FormDivider: LegacyFormDivider,
FormHint: LegacyFormHint,
FormIcon: LegacyFormIcon,
FormInput: LegacyFormInput,
FormLabel: LegacyFormLabel,
FormRadio: LegacyFormRadio,
FormRadioGroup: LegacyFormRadioGroup,
FormRadioRow: LegacyFormRadioRow,
FormRow: LegacyFormRow,
FormSection: LegacyFormSection,
FormSelect: LegacyFormSelect,
FormSliderRow: LegacyFormSliderRow,
FormSubLabel: LegacyFormSubLabel,
FormSwitch: LegacyFormSwitch,
FormSwitchRow: LegacyFormSwitchRow,
FormTernaryCheckBox: LegacyFormTernaryCheckBox,
FormText: LegacyFormText,
FormTitle: LegacyFormTitle
} = lazyDestructure(() => Forms);

View file

@ -0,0 +1,144 @@
import { DiscordTextStyles } from "@lib/ui/types";
import { Nullish } from "@lib/utils/types";
import { ReactNode } from "react";
import type * as RN from "react-native";
import { ImageSourcePropType, PressableProps } from "react-native";
import { LiteralUnion } from "type-fest";
// Abandon all hope, ye who enter here
type Style = RN.ViewStyle & RN.ImageStyle & RN.TextStyle;
type InteractiveSize = "sm" | "md" | "lg";
// Buttons
type ButtonVariant = "primary" | "secondary" | "tertiary" | "destructive" | "active";
interface ButtonProps {
disabled?: boolean;
onPress: () => void;
loading?: boolean;
variant?: LiteralUnion<ButtonVariant, string>;
text?: string;
size?: LiteralUnion<InteractiveSize, string>;
iconPosition?: "start" | "end";
scaleAmountInPx?: number;
icon?: ImageSourcePropType | ReactNode;
style?: Style;
}
export type Button = React.ForwardRefExoticComponent<ButtonProps>;
// Segmented Control
interface SegmentedControlItem {
id: string;
label: string;
page: JSX.Element | null;
}
interface SegmentedControlProps {
state: any;
// state: {
// activeIndex: SharedValue<number>;
// pagerRef: RefObject<View>;
// scrollTarget: SharedValue<number>;
// scrollOverflow: SharedValue<number>;
// scrollOffset: SharedValue<number>;
// items: SegmentedControlItem[];
// itemDimensions: SharedValue<unknown[]>;
// pageWidth: number;
// pressedIndex: SharedValue<number>;
// onPageChangeRef: MutableRefObject<unknown>; // { current: undefined }
// setActiveIndex: (index: number) => void;
// setItemDimensions: (index: number, dimensions: unknown[]) => void;
// useReducedMotion: boolean;
// };
}
export type SegmentedControl = React.FC<SegmentedControlProps>;
interface CompatSegmentedControlProps {
values: string[];
selectedSegmentIndex?: number;
onValueChange?: (index: number) => void;
onSetActiveIndex?: (index: number) => void;
}
export type CompatSegmentedControl = React.FC<CompatSegmentedControlProps>;
// TODO: Confirm if this is real
interface TextInputProps extends Omit<RN.TextInputProps, "onChange" | "onChangeText" | "value"> {
defaultValue?: string;
description?: string;
editable?: boolean;
errorMessage?: string;
focusable?: boolean;
grow?: boolean;
isCentered?: boolean;
isClearable?: boolean;
isDisabled?: boolean;
isRound?: boolean;
label?: string;
leadingIcon?: React.FC<any>;
leadingPressableProps?: PressableProps;
leadingText?: string;
onChange?: (text: string) => void;
size?: "sm" | "md" | "lg";
status?: "error" | "default";
style?: Style;
trailingIcon?: React.FC<any>;
trailingPressableProps?: PressableProps;
trailingText?: string;
value?: string | Nullish;
}
export type TextInput = React.FC<TextInputProps>;
interface RowButtonProps {
variant?: LiteralUnion<ButtonVariant, string>;
style?: Style;
icon?: ImageSourcePropType | ReactNode;
label: string | ReactNode;
subLabel?: string | ReactNode;
onPress: () => void;
disabled?: boolean;
}
export type RowButton = React.FC<RowButtonProps>;
interface StackProps {
/** defaults to vertical */
direction?: "vertical" | "horizontal";
/** defaults to 8 */
spacing?: number;
}
export type Stack = React.FC<React.PropsWithChildren<StackProps> & RN.ViewProps>;
interface FABProps {
icon: ImageSourcePropType | ReactNode;
style?: Style;
onPress: () => void;
}
export type FloatingActionButton = React.FC<FABProps>;
type TextProps = React.ComponentProps<typeof RN.Text> & {
variant?: DiscordTextStyles;
color?: string; // TODO: type this
lineClamp?: number;
maxFontSizeMultiplier?: number;
style?: Style;
};
export type Text = React.FC<TextProps>;
interface IconButtonProps {
icon: ImageSourcePropType | ReactNode;
onPress: () => void;
disabled?: boolean;
size?: InteractiveSize;
variant?: ButtonVariant;
style?: Style;
}
export type IconButton = React.FC<IconButtonProps>;