Numbers of improvements

Mostltly refactorings, does not affect user experience
This commit is contained in:
amsyarasyiq 2024-03-07 09:09:31 +08:00
parent d93439d911
commit 8c8baaed5c
41 changed files with 600 additions and 184 deletions

View file

@ -43,6 +43,26 @@ try {
"@types": "src/utils/types.ts",
},
plugins: [
{
name: "runtimeGlobalAlias",
setup: async build => {
const globalMap = {
"react": "globalThis.React",
"react-native": "globalThis.ReactNative"
};
Object.keys(globalMap).forEach(key => {
const filter = new RegExp(`^${key}$`);
build.onResolve({ filter }, args => ({
namespace: "glob-" + key, path: args.path
}));
build.onLoad({ filter, namespace: "glob-" + key }, () => ({
contents: `Object.defineProperty(module, 'exports', { get: () => ${globalMap[key]} })`,
resolveDir: "src",
}));
});
}
},
{
name: "buildLog",
setup: async build => {

View file

@ -22,10 +22,16 @@
common
}
ui {
components
components {
discord {
Forms
Redesign
}
}
alerts
color
toasts
styles
}
utils {
constants

View file

@ -1,6 +1,6 @@
{
"name": "bunny-types",
"version": "0.0.3",
"version": "0.0.4",
"description": "Types for Bunny, a mod for Discord's mobile apps",
"author": "pylixonly",
"publishConfig": {
@ -30,4 +30,4 @@
"ts-patch": "^3.1.2",
"typescript": "^5.3.3"
}
}
}

View file

@ -72,6 +72,7 @@
"THEMES_RELOAD_FOR_CHANGES": "Reload the app to fully apply changes!",
"TOASTS_INSTALLED_PLUGIN": "Installed plugin",
"TOASTS_SUCCESSFULLY_INSTALLED": "Successfully installed",
"TOASTS_COPIED_TO_CLIPBOARD": "Copied plugin URL to clipboard",
"UH_OH": "Uh oh.",
"UNINSTALL": "Uninstall",
"UNINSTALL_TITLE": "Uninstall {title}",

View file

@ -5,6 +5,7 @@ import { after } from "@lib/api/patcher";
import { useProxy } from "@lib/api/storage";
import { installPlugin, plugins, removePlugin } from "@lib/managers/plugins";
import { installTheme, removeTheme, themes } from "@lib/managers/themes";
import { Button } from "@lib/ui/components/discord/Redesign";
import { findInReactTree } from "@lib/utils";
import { DISCORD_SERVER_ID, HTTP_REGEX_MULTI, PLUGINS_CHANNEL_ID, PROXY_PREFIX, THEMES_CHANNEL_ID } from "@lib/utils/constants";
import { findByName, findByProps } from "@metro/filters";
@ -14,7 +15,6 @@ type PostType = "Plugin" | "Theme";
const ForumPostLongPressActionSheet = findByName("ForumPostLongPressActionSheet", false);
const { ActionSheetRow } = findByProps("ActionSheetRow");
const { Button } = findByProps("TableRow");
const { useFirstForumPostMessage } = findByProps("useFirstForumPostMessage");
const forumReactions = findByProps("MostCommonForumPostReaction");

View file

@ -5,10 +5,11 @@ import { useProxy } from "@lib/api/storage";
import { findByProps } from "@lib/metro/filters";
import { settings } from "@lib/settings";
import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
import { clipboard, ReactNative as RN } from "@metro/common";
import { clipboard } from "@metro/common";
import { showInputAlert } from "@ui/alerts";
import { ErrorBoundary, HelpMessage, Search } from "@ui/components";
import fuzzysort from "fuzzysort";
import { FlatList, View } from "react-native";
interface AddonPageProps<T> {
title: string;
@ -47,12 +48,12 @@ export default function AddonPage<T>({ title, fetchFunction, items, safeModeMess
return (
<ErrorBoundary>
{/* TODO: Implement better searching than just by ID */}
<RN.FlatList
<FlatList
ListHeaderComponent={<>
{settings.safeMode?.enabled && <RN.View style={{ marginBottom: 10 }}>
{settings.safeMode?.enabled && <View style={{ marginBottom: 10 }}>
<HelpMessage messageType={0}>{safeModeMessage}</HelpMessage>
{safeModeExtras}
</RN.View>}
</View>}
<Search
style={{ marginBottom: 15 }}
onChangeText={(v: string) => setSearch(v.toLowerCase())}

View file

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

View file

@ -1,13 +1,12 @@
import { getAssetIDByName } from "@lib/api/assets";
import { ReactNative as RN, stylesheet } from "@metro/common";
import { findByProps } from "@metro/filters";
import { TableCheckboxRow, TableRow, TableRowGroup, TableRowIcon, TableSwitchRow } from "@lib/ui/components/discord/Redesign";
import { semanticColors } from "@ui/color";
import ContextMenu from "@ui/components/ContextMenu";
const { TableRow, TableRowIcon, TableSwitchRow, TableCheckboxRow, TableRowGroup } = findByProps("TableRow");
import { createStyles } from "@ui/styles";
import { Image, TouchableOpacity, View } from "react-native";
// TODO: These styles work weirdly. iOS has cramped text, Android with low DPI probably does too. Fix?
const styles = stylesheet.createThemedStyleSheet({
const useStyles = createStyles({
card: {
backgroundColor: semanticColors?.BACKGROUND_SECONDARY,
borderRadius: 15,
@ -66,6 +65,7 @@ interface CardProps {
}
export default function Card(props: CardProps) {
const styles = useStyles();
let pressableState = props.toggleValue ?? false;
const headerProps = {
@ -75,7 +75,7 @@ export default function Card(props: CardProps) {
};
return (
<RN.View style={[styles.card, { marginTop: props.index !== 0 ? 10 : 0 }]}>
<View style={[styles.card, { marginTop: props.index !== 0 ? 10 : 0 }]}>
<TableRowGroup>
{props.toggleType && (props.toggleType === "switch" ?
<TableSwitchRow
@ -94,7 +94,7 @@ export default function Card(props: CardProps) {
<TableRow
label={props.descriptionLabel}
trailing={
<RN.View style={styles.actions}>
<View style={styles.actions}>
{props.overflowActions &&
<ContextMenu
triggerOnLongPress={false}
@ -105,15 +105,15 @@ export default function Card(props: CardProps) {
}))}
align="auto"
title={props.overflowTitle!!}
children={props => <RN.TouchableOpacity {...props}>
<RN.Image style={styles.icon} source={getAssetIDByName("ic_more_24px")} />
</RN.TouchableOpacity>}
children={props => <TouchableOpacity {...props}>
<Image style={styles.icon} source={getAssetIDByName("ic_more_24px")} />
</TouchableOpacity>}
/>
}
</RN.View>
</View>
}
/>
</TableRowGroup>
</RN.View>
</View>
);
}

View file

@ -1,11 +1,13 @@
import { Strings } from "@core/i18n";
import { getAssetIDByName } from "@lib/api/assets";
import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
import { clipboard, ReactNative as RN, stylesheet } from "@metro/common";
import { clipboard } from "@metro/common";
import { showInputAlert } from "@ui/alerts";
import { semanticColors } from "@ui/color";
import { createStyles } from "@ui/styles";
import { Image, TouchableOpacity } from "react-native";
const styles = stylesheet.createThemedStyleSheet({
const useStyles = createStyles({
icon: {
marginRight: 10,
tintColor: semanticColors.HEADER_PRIMARY,
@ -18,8 +20,10 @@ interface InstallButtonProps {
}
export default function InstallButton({ alertTitle, installFunction: fetchFunction }: InstallButtonProps) {
const styles = useStyles();
return (
<RN.TouchableOpacity onPress={() =>
<TouchableOpacity onPress={() =>
clipboard.getString().then(content =>
showInputAlert({
title: alertTitle,
@ -31,7 +35,7 @@ export default function InstallButton({ alertTitle, installFunction: fetchFuncti
})
)
}>
<RN.Image style={styles.icon} source={getAssetIDByName("ic_add_24px")} />
</RN.TouchableOpacity>
<Image style={styles.icon} source={getAssetIDByName("ic_add_24px")} />
</TouchableOpacity>
);
}

View file

@ -3,10 +3,9 @@ import { getScreens } from "@core/ui/settings/data";
import { getAssetIDByName } from "@lib/api/assets";
import { useProxy } from "@lib/api/storage";
import { settings } from "@lib/settings";
import { FormDivider, FormRow, FormSection } from "@lib/ui/components/discord/Forms";
import { NavigationNative } from "@metro/common";
import { ErrorBoundary, Forms } from "@ui/components";
const { FormRow, FormSection, FormDivider } = Forms;
import { ErrorBoundary } from "@ui/components";
export default function SettingsSection() {
const navigation = NavigationNative.useNavigation();

View file

@ -1,7 +1,7 @@
import { getAssetIDByName } from "@lib/api/assets";
import { findByProps } from "@lib/metro/filters";
import { FormText } from "@lib/ui/components/discord/Forms";
import { TableRow } from "@lib/ui/components/discord/Redesign";
import { clipboard } from "@metro/common";
import { Forms } from "@ui/components";
import { showToast } from "@ui/toasts";
interface VersionProps {
@ -11,9 +11,6 @@ interface VersionProps {
padding: boolean;
}
const { TableRow } = findByProps("TableRow");
const { FormText } = Forms;
export default function Version({ label, version, icon, padding }: VersionProps) {
return (
<TableRow

View file

@ -13,11 +13,13 @@ 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, ReactNative as RN, stylesheet } from "@metro/common";
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");
@ -31,10 +33,10 @@ interface Screen {
render: React.ComponentType<any>;
}
const useStyles = stylesheet.createStyles({
const useStyles = createStyles({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY
backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY,
}
});
@ -133,7 +135,9 @@ export const getYouData = () => {
const styles = useStyles();
const { bottom: paddingBottom } = useSafeAreaInsets();
navigation.addListener("focus", () => navigation.setOptions(s.options));
return <RN.View style={[{ paddingBottom }, styles.container]}><s.render {...route.params} /></RN.View>;
return <View style={[{ paddingBottom }, styles.container]}>
<s.render {...route.params} />
</View>;
});
return {

View file

@ -1,28 +1,27 @@
import AssetDisplay from "@core/ui/components/AssetDisplay";
import { all } from "@lib/api/assets";
import { ReactNative as RN } from "@metro/common";
import { ErrorBoundary, Forms, Search } from "@ui/components";
const { FormDivider } = Forms;
import { FormDivider } from "@lib/ui/components/discord/Forms";
import { ErrorBoundary, Search } from "@ui/components";
import { FlatList, View } from "react-native";
export default function AssetBrowser() {
const [search, setSearch] = React.useState("");
return (
<ErrorBoundary>
<RN.View style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<Search
style={{ margin: 10 }}
onChangeText={(v: string) => setSearch(v)}
placeholder="Search"
/>
<RN.FlatList
<FlatList
data={Object.values(all).filter(a => a.name.includes(search) || a.id.toString() === search)}
renderItem={({ item }) => <AssetDisplay asset={item} />}
ItemSeparatorComponent={FormDivider}
keyExtractor={item => item.name}
/>
</RN.View>
</View>
</ErrorBoundary>
);
}

View file

@ -5,18 +5,19 @@ import { getReactDevToolsProp, getReactDevToolsVersion, isLoaderConfigSupported,
import { useProxy } from "@lib/api/storage";
import { connectToDebugger } from "@lib/debug";
import { loaderConfig, settings } from "@lib/settings";
import { NavigationNative, ReactNative as RN, stylesheet } from "@metro/common";
import { FormText } from "@lib/ui/components/discord/Forms";
import { Stack, TableRow, TableRowGroup, TableSwitchRow, TextInput } from "@lib/ui/components/discord/Redesign";
import { NavigationNative } from "@metro/common";
import { findByProps } from "@metro/filters";
import { semanticColors } from "@ui/color";
import { ErrorBoundary, Forms } from "@ui/components";
import { ErrorBoundary } from "@ui/components";
import { createStyles, TextStyleSheet } from "@ui/styles";
import { ScrollView, StyleSheet } from "react-native";
const { Stack, TableRow, TableSwitchRow, TableRowGroup, TextInput } = findByProps("TableRow");
const { FormText } = Forms;
const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
const { TextStyleSheet } = findByProps("TextStyleSheet");
const styles = stylesheet.createThemedStyleSheet({
const useStyles = createStyles({
leadingText: {
...TextStyleSheet["heading-md/semibold"],
color: semanticColors.TEXT_MUTED,
@ -25,6 +26,7 @@ const styles = stylesheet.createThemedStyleSheet({
});
export default function Developer() {
const styles = useStyles();
const navigation = NavigationNative.useNavigation();
useProxy(settings);
@ -32,7 +34,7 @@ export default function Developer() {
return (
<ErrorBoundary>
<RN.ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={24}>
<TextInput
label="Debugger URL"
@ -54,7 +56,7 @@ export default function Developer() {
icon={<TableRow.Icon source={getAssetIDByName("ic_badge_staff")} />}
onPress={() => window[getReactDevToolsProp() || "__vendetta_rdc"]?.connectToDevTools({
host: settings.debuggerUrl.split(":")?.[0],
resolveRNStyle: RN.StyleSheet.flatten,
resolveRNStyle: StyleSheet.flatten,
})}
/>
</>}
@ -120,7 +122,7 @@ export default function Developer() {
/>
</TableRowGroup>
</Stack>
</RN.ScrollView>
</ScrollView>
</ErrorBoundary>
);
}

View file

@ -3,17 +3,18 @@ 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 { findByProps } from "@lib/metro/filters";
import { settings } from "@lib/settings";
import { Stack, TableRow, TableRowGroup, TableSwitchRow } from "@lib/ui/components/discord/Redesign";
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants";
import { ReactNative as RN, stylesheet, url } from "@metro/common";
import { url } from "@metro/common";
import { semanticColors } from "@ui/color";
import { Summary } from "@ui/components";
import { createStyles } from "@ui/styles";
import { NativeModules, Platform, ScrollView } from "react-native";
const { Stack, TableRow, TableSwitchRow, TableRowGroup } = findByProps("TableRow");
const debugInfo = getDebugInfo();
const styles = stylesheet.createThemedStyleSheet({
const useStyles = createStyles({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_MOBILE_SECONDARY,
@ -21,6 +22,7 @@ const styles = stylesheet.createThemedStyleSheet({
});
export default function General() {
const styles = useStyles();
useProxy(settings);
const versions = [
@ -83,14 +85,14 @@ export default function General() {
icon: "ic_phonelink_24px"
},
{
label: RN.Platform.select({ android: Strings.CODENAME, ios: Strings.MACHINE_ID })!,
label: Platform.select({ android: Strings.CODENAME, ios: Strings.MACHINE_ID })!,
version: debugInfo.device.codename,
icon: "ic_compose_24px"
}
];
return (
<RN.ScrollView style={styles.container}>
<ScrollView style={styles.container}>
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={24}>
<TableRowGroup title={Strings.LINKS}>
<TableRow
@ -110,7 +112,7 @@ export default function General() {
<TableRow
label={Strings.RELOAD_DISCORD}
icon={<TableRow.Icon source={getAssetIDByName("ic_message_retry")} />}
onPress={() => RN.NativeModules.BundleUpdaterManager.reload()}
onPress={() => NativeModules.BundleUpdaterManager.reload()}
/>
<TableRow
label={settings.safeMode?.enabled ? Strings.RELOAD_IN_NORMAL_MODE : Strings.RELOAD_IN_SAFE_MODE}
@ -136,6 +138,6 @@ export default function General() {
</Summary>
</TableRowGroup>
</Stack>
</RN.ScrollView>
</ScrollView>
);
}

View file

@ -7,6 +7,9 @@ import * as debug from "@lib/debug";
import * as plugins from "@lib/managers/plugins";
import * as themes from "@lib/managers/themes";
import { loaderConfig, settings } from "@lib/settings";
import { Forms } from "@lib/ui/components/discord/Forms";
import { CompatfulRedesign } from "@lib/ui/components/discord/Redesign";
import { createThemedStyleSheet } from "@lib/ui/styles";
import * as utils from "@lib/utils";
import * as common from "@metro/common";
import * as metro from "@metro/filters";
@ -39,7 +42,10 @@ export const createVendettaObject = (unloads: any[]): any => ({
i18n: common.i18n,
url: common.url,
toasts: common.toasts,
stylesheet: common.stylesheet,
stylesheet: {
...CompatfulRedesign,
createThemedStyleSheet
},
clipboard: common.clipboard,
assets: common.assets,
invites: common.invites,
@ -80,8 +86,8 @@ export const createVendettaObject = (unloads: any[]): any => ({
},
ui: {
components: {
Forms: components.Forms,
General: components.General,
Forms,
General: common.ReactNative,
Alert: components.Alert,
Button: components.Button,
HelpMessage: components.HelpMessage,

View file

@ -1,8 +1,7 @@
import { ApplicationCommand, ApplicationCommandInputType, ApplicationCommandType } from "@lib/api/commands/types";
import { after } from "@lib/api/patcher";
import { commands as commandsModule } from "@metro/common";
import { ApplicationCommand, ApplicationCommandInputType, ApplicationCommandType } from "./types";
let commands: ApplicationCommand[] = [];
export function patchCommands() {

View file

@ -1,5 +1,5 @@
import { FileManager, MMKVManager } from "@lib/api/native/modules";
import { ReactNative as RN } from "@metro/common";
import { Platform } from "react-native";
export interface StorageBackend {
get: () => unknown | Promise<unknown>;
@ -8,7 +8,7 @@ export interface StorageBackend {
const ILLEGAL_CHARS_REGEX = /[<>:"/\\|?*]/g;
const filePathFixer = (file: string): string => RN.Platform.select({
const filePathFixer = (file: string): string => Platform.select({
default: file,
ios: FileManager.saveFileToGallery ? file : `Documents/${file}`,
});

View file

@ -5,10 +5,9 @@ import { after } from "@lib/api/patcher";
import { getThemeFromLoader, selectTheme } from "@lib/managers/themes";
import { settings } from "@lib/settings";
import logger from "@lib/utils/logger";
import { ReactNative as RN } from "@metro/common";
import { showToast } from "@ui/toasts";
import { version } from "bunny-build";
import type { PlatformConstants } from "react-native";
import { Platform, type PlatformConstants } from "react-native";
export let socket: WebSocket;
export interface RNConstants extends PlatformConstants {
@ -88,7 +87,7 @@ export function getDebugInfo() {
const padding = "for RN ";
// RN
const PlatformConstants = RN.Platform.constants as RNConstants;
const PlatformConstants = Platform.constants as RNConstants;
const rnVer = PlatformConstants.reactNativeVersion;
return {
@ -114,7 +113,7 @@ export function getDebugInfo() {
buildType: hermesProps.Build,
bytecodeVersion: hermesProps["Bytecode Version"],
},
...RN.Platform.select(
...Platform.select(
{
android: {
os: {
@ -131,7 +130,7 @@ export function getDebugInfo() {
}
}
)!,
...RN.Platform.select(
...Platform.select(
{
android: {
device: {

View file

@ -6,8 +6,9 @@ import { awaitSyncWrapper, createFileBackend, createMMKVBackend, createStorage,
import { findInReactTree, safeFetch } from "@lib/utils";
import logger from "@lib/utils/logger";
import { Author } from "@lib/utils/types";
import { chroma, ReactNative as RN } from "@metro/common";
import { chroma } from "@metro/common";
import { find, findByName, findByProps, findByStoreName } from "@metro/filters";
import { ImageBackground, Platform, processColor, StyleSheet } from "react-native";
export interface ThemeData {
name: string;
@ -71,7 +72,7 @@ export function patchChatBackground() {
if (!MessagesWrapper) return;
const patches = [
after("default", MessagesWrapperConnected, (_, ret) => React.createElement(RN.ImageBackground, {
after("default", MessagesWrapperConnected, (_, ret) => React.createElement(ImageBackground, {
style: { flex: 1, height: "100%" },
source: currentTheme?.data?.background?.url && { uri: currentTheme.data.background.url } || 0,
blurRadius: typeof currentTheme?.data?.background?.blur === "number" ? currentTheme?.data?.background?.blur : 0,
@ -81,7 +82,7 @@ export function patchChatBackground() {
const Messages = findInReactTree(ret, x => x && "HACK_fixModalInteraction" in x.props && x?.props?.style);
if (Messages)
Messages.props.style = Object.assign(
RN.StyleSheet.flatten(Messages.props.style ?? {}),
StyleSheet.flatten(Messages.props.style ?? {}),
{
backgroundColor: "#0000"
}
@ -97,7 +98,7 @@ export function patchChatBackground() {
function normalizeToHex(colorString: string): string {
if (chroma.valid(colorString)) return chroma(colorString).hex();
const color = Number(RN.processColor(colorString));
const color = Number(processColor(colorString));
return chroma.rgb(
color >> 16 & 0xff, // red
@ -126,7 +127,7 @@ function processData(data: ThemeData) {
data.rawColors[key] = normalizeToHex(rawColors[key]);
}
if (RN.Platform.OS === "android") applyAndroidAlphaKeys(rawColors);
if (Platform.OS === "android") applyAndroidAlphaKeys(rawColors);
}
return data;

View file

@ -1,34 +1,6 @@
import { find, findByProps, findByStoreName } from "@metro/filters";
import type { StyleSheet } from "react-native";
import { find, findByProps } from "@metro/filters";
export interface DiscordStyleSheet {
[index: string]: any,
createStyles: <T extends import("react-native").StyleSheet.NamedStyles<T>>(sheet: T | (() => T)) => () => T;
createThemedStyleSheet: typeof import("react-native").StyleSheet.create;
}
const ThemeStore = findByStoreName("ThemeStore");
const colorModule = findByProps("colors", "unsafe_rawColors");
const colorResolver = colorModule.internal ??= colorModule.meta;
// Reimplementation of Discord's createThemedStyleSheet, which was removed since 204201
// Not exactly a 1:1 reimplementation, but sufficient to keep compatibility with existing plugins
function createThemedStyleSheet<T extends StyleSheet.NamedStyles<T>>(sheet: T) {
if (!colorModule) return;
for (const key in sheet) {
// @ts-ignore
sheet[key] = new Proxy(ReactNative.StyleSheet.flatten(sheet[key]), {
get(target, prop, receiver) {
const res = Reflect.get(target, prop, receiver);
return colorResolver.isSemanticColor(res)
? colorResolver.resolveSemanticColor(ThemeStore.theme, res)
: res;
}
});
}
return sheet;
}
// Discord
export const constants = findByProps("Fonts", "Permissions");
@ -36,14 +8,6 @@ export const channels = findByProps("getVoiceChannelId");
export const i18n = findByProps("Messages");
export const url = findByProps("openURL", "openDeeplink");
export const toasts = find(m => m.open && m.close && !m.startDrag && !m.init && !m.openReplay && !m.setAlwaysOnTop && !m.setAccountFlag);
// Compatible with pre-204201 versions since createThemedStyleSheet is undefined.
export const stylesheet = {
...find(m => m.createStyles && !m.ActionSheet),
createThemedStyleSheet,
...findByProps("createThemedStyleSheet") as {},
} as DiscordStyleSheet;
export const clipboard = findByProps("setString", "getString", "hasString") as typeof import("@react-native-clipboard/clipboard").default;
export const assets = findByProps("registerAsset");
export const invites = findByProps("acceptInviteAndTransitionToInviteChannel");
@ -59,7 +23,7 @@ export const FluxDispatcher = findByProps("_currentDispatchActionType");
// React
export const React = window.React = findByProps("createElement") as typeof import("react");
export const ReactNative = findByProps("AppRegistry") as typeof import("react-native");
export const ReactNative = window.ReactNative = findByProps("AppRegistry") as typeof import("react-native");
// Moment
export const moment = findByProps("isMoment") as typeof import("moment");

View file

@ -1,7 +1,6 @@
import { getLoaderConfigPath } from "@lib/api/native/loader";
import { createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@lib/api/storage";
import { getLoaderConfigPath } from "./api/native/loader";
export interface Settings {
debuggerUrl: string;
developerSettings: boolean;

View file

@ -1,4 +1,5 @@
import { color } from "@lib/managers/themes";
import { findByStoreName } from "@lib/metro/filters";
import { constants } from "@metro/common";
//! This module is only found on 165.0+, under the assumption that iOS 165.0 is the same as Android 165.0.
@ -10,8 +11,19 @@ import { constants } from "@metro/common";
// const colorModule = findByProps("colors", "meta");
// ? SemanticColor and default.colors are effectively ThemeColorMap
export const semanticColors = (color?.default?.colors ?? constants?.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);
export const rawColors = (color?.default?.unsafe_rawColors ?? constants?.Colors) as Record<string, string>;
const ThemeStore = findByStoreName("ThemeStore");
const colorResolver = color.default.internal ??= color.default.meta;
export function isSemanticColor(sym: Symbol): boolean {
return colorResolver.isSemanticColor(sym);
}
export function resolveSemanticColor(sym: Symbol, theme = ThemeStore.theme): string {
return colorResolver.resolveSemanticColor(theme, sym);
}

View file

@ -1,13 +1,15 @@
import { constants,ReactNative as RN, stylesheet } from "@metro/common";
import { constants } from "@metro/common";
import { semanticColors } from "@ui/color";
import { createStyles } from "@ui/styles";
import { Platform, Text, TextInput, type TextStyle } from "react-native";
export interface CodeblockProps {
selectable?: boolean;
style?: import("react-native").TextStyle;
style?: TextStyle;
children?: string;
}
const styles = stylesheet.createThemedStyleSheet({
const useStyles = createStyles({
codeBlock: {
fontFamily: constants.Fonts.CODE_SEMIBOLD,
fontSize: 12,
@ -22,13 +24,13 @@ const styles = stylesheet.createThemedStyleSheet({
});
// iOS doesn't support the selectable property on RN.Text...
const InputBasedCodeblock = ({ style, children }: CodeblockProps) => <RN.TextInput editable={false} multiline style={[styles.codeBlock, style && style]} value={children} />;
const TextBasedCodeblock = ({ selectable, style, children }: CodeblockProps) => <RN.Text selectable={selectable} style={[styles.codeBlock, style && style]}>{children}</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 RN.Platform.select({
return Platform.select({
ios: <InputBasedCodeblock style={style} children={children} />,
default: <TextBasedCodeblock style={style} children={children} selectable />,
});

View file

@ -1,9 +1,8 @@
import { instead } from "@lib/api/patcher";
import { ReactNative } from "@lib/metro/common";
import { findByProps } from "@lib/metro/filters";
import { AccessibilityInfo, Platform, View } from "react-native";
const { ContextMenu: _ContextMenu } = findByProps("ContextMenu");
const { AccessibilityInfo } = ReactNative;
type ContextMenuItem = {
label: string,
@ -26,7 +25,7 @@ export default function ContextMenu(props: ContextMenuProps) {
React.useEffect(() => {
// hacky discord fix for android where pressing back with context menu open crashes the client
if (ReactNative.Platform.OS !== "android") return;
if (Platform.OS !== "android") return;
// @ts-ignore
const ctxMenuReactTag = ref.current?._children?.[0]?._nativeTag;
@ -44,9 +43,9 @@ export default function ContextMenu(props: ContextMenuProps) {
};
}, []);
return <ReactNative.View ref={ref}>
return <View ref={ref}>
<_ContextMenu
{...props}
/>
</ReactNative.View>;
</View>;
}

View file

@ -1,6 +1,11 @@
import { Strings } from "@core/i18n";
import { React, ReactNative as RN, stylesheet } from "@metro/common";
import { Button, Codeblock, Forms } from "@ui/components";
import { React } from "@metro/common";
import { Button, Codeblock } from "@ui/components";
import { ScrollView } from "react-native";
import { createThemedStyleSheet } from "../styles";
import { FormText } from "./discord/Forms";
interface ErrorBoundaryState {
hasErr: boolean;
@ -11,7 +16,7 @@ export interface ErrorBoundaryProps {
children: JSX.Element | JSX.Element[];
}
const styles = stylesheet.createThemedStyleSheet({
const styles = createThemedStyleSheet({
view: {
flex: 1,
flexDirection: "column",
@ -36,8 +41,8 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
if (!this.state.hasErr) return this.props.children;
return (
<RN.ScrollView style={styles.view}>
<Forms.FormText style={styles.title}>{Strings.UH_OH}</Forms.FormText>
<ScrollView style={styles.view}>
<FormText style={styles.title}>{Strings.UH_OH}</FormText>
<Codeblock selectable style={{ marginBottom: 5 }}>{this.state.errText}</Codeblock>
<Button
color={Button.Colors.RED}
@ -46,7 +51,7 @@ export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, E
onPress={() => this.setState({ hasErr: false, errText: undefined })}
text={Strings.RETRY}
/>
</RN.ScrollView>
</ScrollView>
);
}
}

View file

@ -1,8 +1,8 @@
import { ButtonColors } from "@lib/utils/types";
import { findByProps } from "@metro/filters";
import { Alert, Forms } from "@ui/components";
import { Alert } from "@ui/components";
import { FormInput } from "@ui/components/discord/Forms";
const { FormInput } = Forms;
const Alerts = findByProps("openLazy", "close");
export interface InputAlertProps {

View file

@ -1,10 +1,9 @@
import { Strings } from "@core/i18n";
import { getAssetIDByName } from "@lib/api/assets";
import { ReactNative as RN } from "@metro/common";
import { findByProps } from "@metro/filters";
import ErrorBoundary from "@ui/components/ErrorBoundary";
import { Image, View } from "react-native";
const { TextInput } = findByProps("TableRow");
import { TextInput } from "./discord/Redesign";
export interface SearchProps {
onChangeText?: (v: string) => void;
@ -14,7 +13,7 @@ export interface SearchProps {
function SearchIcon() {
return <RN.Image style={{ transform: [{ scale: 0.8 }] }} source={getAssetIDByName("search")} />;
return <Image style={{ transform: [{ scale: 0.8 }] }} source={getAssetIDByName("search")} />;
}
export default ({ onChangeText, placeholder, style }: SearchProps) => {
@ -26,8 +25,10 @@ export default ({ onChangeText, placeholder, style }: SearchProps) => {
};
return <ErrorBoundary>
<RN.View style={style}>
<TextInput isClearable grow
<View style={style}>
<TextInput
grow
isClearable
leadingIcon={SearchIcon}
placeholder={placeholder ?? Strings.SEARCH}
onChange={onChange}
@ -37,6 +38,6 @@ export default ({ onChangeText, placeholder, style }: SearchProps) => {
autoCorrect={false}
value={query}
/>
</RN.View>
</View>
</ErrorBoundary>;
};

View file

@ -1,9 +1,8 @@
import { getAssetIDByName } from "@lib/api/assets";
import { findByProps } from "@lib/metro/filters";
import { ReactNative as RN } from "@metro/common";
import { FormRow } from "@ui/components/discord/Forms";
import { TableRow } from "@ui/components/discord/Redesign";
import { LayoutAnimation, View } from "react-native";
const { FormRow } = findByProps("FormRow");
const { TableRow } = findByProps("TableRow");
export interface SummaryProps {
label: string;
icon?: string;
@ -23,11 +22,11 @@ export default function Summary({ label, icon, noPadding = false, noAnimation =
trailing={<FormRow.Arrow style={{ transform: [{ rotate: `${hidden ? 180 : 90}deg` }] }} />}
onPress={() => {
setHidden(!hidden);
if (!noAnimation) RN.LayoutAnimation.configureNext(RN.LayoutAnimation.Presets.easeInEaseOut);
if (!noAnimation) LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
}}
/>
{!hidden && <>
<RN.View style={!noPadding && { paddingHorizontal: 15 }}>{children}</RN.View>
<View style={!noPadding && { paddingHorizontal: 15 }}>{children}</View>
</>}
</>
);

View file

@ -0,0 +1,32 @@
import { findByProps } from "@lib/metro";
export const Forms = findByProps("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
} = Forms;

View file

@ -0,0 +1,149 @@
import { findByProps } from "@lib/metro";
import { RedesignModuleKeys } from "@lib/ui/types";
export const Redesign = findByProps("TableRow") as Record<RedesignModuleKeys, any>;
export const CompatfulRedesign = findByProps("ActionSheetRow") as unknown as {
TextStyleSheet: typeof import("@ui/styles").TextStyleSheet;
[key: string]: any;
};
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
} = Redesign;
export const {
AccessibilityAnnouncer,
AccessibilityPreferencesContext,
AccessibilityView,
AccessibilityViewAnimated,
ActionSheet,
ActionSheetCloseButton,
ActionSheetContentContainer,
ActionSheetIconHeader,
ActionSheetPresenter,
ActionSheetRow,
ActionSheetSwitchRow,
ActionSheetTitleHeader,
AnimatedEnterExitItem,
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
} = CompatfulRedesign;

View file

@ -0,0 +1,2 @@
export * as Forms from "./Forms";
export * as Redesign from "./Redesign";

View file

@ -1,16 +1,17 @@
import { findByDisplayName, findByName, findByProps } from "@metro/filters";
export * as discord from "./discord";
// Discord
export const Forms = findByProps("Form", "FormSection");
export const General = findByProps("Button", "Text", "View");
export const Alert = findByDisplayName("FluxContainer(Alert)");
export const Button = findByProps("Looks", "Colors", "Sizes") as React.ComponentType<any> & { Looks: any, Colors: any, Sizes: any };
export const Button = findByProps("Looks", "Colors", "Sizes") as React.ComponentType<any> & { Looks: any, Colors: any, Sizes: any; };
export const HelpMessage = findByName("HelpMessage");
// React Native's included SafeAreaView only adds padding on iOS.
export const SafeAreaView = findByProps("useSafeAreaInsets").SafeAreaView as any;
// Vendetta
export { default as Codeblock } from "@ui/components/Codeblock";
export { default as ContextMenu } from "@ui/components/ContextMenu";
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,4 +1,5 @@
export * as alerts from "./alerts";
export * as color from "./color";
export * as components from "./components";
export * as styles from "./styles";
export * as toasts from "./toasts";

View file

@ -3,20 +3,18 @@ import { after } from "@lib/api/patcher";
import { toggleSafeMode } from "@lib/debug";
import { settings } from "@lib/settings";
import { ButtonColors } from "@lib/utils/types";
import { ReactNative as RN, stylesheet } from "@metro/common";
import { findByName, findByProps, findByStoreName } from "@metro/filters";
import { findByName, findByProps } from "@metro/filters";
import { semanticColors } from "@ui/color";
import { Button, Codeblock, ErrorBoundary as _ErrorBoundary, SafeAreaView } from "@ui/components";
import { createThemedStyleSheet, TextStyleSheet } from "@ui/styles";
import { Text, View } from "react-native";
const ErrorBoundary = findByName("ErrorBoundary");
// Let's just pray they have this.
const { BadgableTabBar } = findByProps("BadgableTabBar");
const ThemeStore = findByStoreName("ThemeStore");
const { TextStyleSheet } = findByProps("TextStyleSheet");
const styles = stylesheet.createThemedStyleSheet({
const styles = createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
@ -85,22 +83,22 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
return (
<_ErrorBoundary>
<SafeAreaView style={styles.container}>
<RN.View style={styles.header}>
<View style={styles.header}>
<ret.props.Illustration style={{ transform: [{ scale: 0.6 }], marginLeft: -40, marginRight: -80 }} />
<RN.View style={{ flex: 2, paddingLeft: 24 }}>
<RN.Text style={styles.headerTitle}>{ret.props.title}</RN.Text>
<RN.Text style={styles.headerDescription}>{ret.props.body}</RN.Text>
</RN.View>
</RN.View>
<RN.View style={{ flex: 6 }}>
<RN.View style={{ paddingBottom: 8 }}>
<View style={{ flex: 2, paddingLeft: 24 }}>
<Text style={styles.headerTitle}>{ret.props.title}</Text>
<Text style={styles.headerDescription}>{ret.props.body}</Text>
</View>
</View>
<View style={{ flex: 6 }}>
<View style={{ paddingBottom: 8 }}>
{/* Are errors caught by ErrorBoundary guaranteed to have the component stack? */}
<BadgableTabBar
tabs={tabs}
activeTab={this.state.activeTab}
onTabSelected={(tab: string) => { this.setState({ activeTab: tab }); }}
/>
</RN.View>
</View>
<Codeblock
selectable
style={{ flex: 1, textAlignVertical: "top" }}
@ -111,8 +109,8 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
*/}
{tabData?.trimWhitespace ? errorText.split("\n").filter(i => i.length !== 0).map(i => i.trim()).join("\n") : errorText}
</Codeblock>
</RN.View>
<RN.View style={styles.footer}>
</View>
<View style={styles.footer}>
{buttons.map(button => {
const buttonIndex = buttons.indexOf(button) !== 0 ? 8 : 0;
@ -124,7 +122,7 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
style={DeviceManager.isTablet ? { flex: `0.${buttons.length}`, marginLeft: buttonIndex } : { marginTop: buttonIndex }}
/>;
})}
</RN.View>
</View>
</SafeAreaView>
</_ErrorBoundary>
);

30
src/lib/ui/styles.ts Normal file
View file

@ -0,0 +1,30 @@
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 };
export const { TextStyleSheet } = CompatfulRedesign as unknown as {
TextStyleSheet: { [key in DiscordTextStyles]: TextStyle; };
};
export function createStyles<T extends NamedStyles<T>>(sheet: T | ((props: any) => T)): () => T {
return CompatfulRedesign.createStyles(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
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 typeof res === "symbol" && isSemanticColor(res) ? resolveSemanticColor(res) : res;
}
});
}
return sheet;
}

View file

@ -1,6 +1,8 @@
import { Strings } from "@core/i18n";
import { getAssetIDByName } from "@lib/api/assets";
import { ReactNative, toasts } from "@metro/common";
import { toasts } from "@metro/common";
import { findByProps } from "@metro/filters";
import { Platform } from "react-native";
const { uuid4 } = findByProps("uuid4");
@ -13,9 +15,9 @@ export const showToast = (content: string, asset?: number) => toasts.open({
icon: asset,
});
showToast.showCopyToClipboard = (message = "Copied plugin URL to clipboard.") => {
showToast.showCopyToClipboard = (message = Strings.TOASTS_COPIED_TO_CLIPBOARD) => {
// On Android, only show toast for 12 and lower
if (ReactNative.Platform.OS !== "android" || ReactNative.Platform.Version <= 32) {
if (Platform.OS !== "android" || Platform.Version <= 32) {
showToast(message, getAssetIDByName("toast_copy_link"));
}
};

163
src/lib/ui/types.ts Normal file
View file

@ -0,0 +1,163 @@
import { OptionalKeys } from "@lib/utils/types";
export type DiscordTextStyles = OptionalKeys<[
"heading-sm/normal",
"heading-sm/medium",
"heading-sm/semibold",
"heading-sm/bold",
"heading-sm/extrabold",
"heading-md/normal",
"heading-md/medium",
"heading-md/semibold",
"heading-md/bold",
"heading-md/extrabold",
"heading-lg/normal",
"heading-lg/medium",
"heading-lg/semibold",
"heading-lg/bold",
"heading-lg/extrabold",
"heading-xl/normal",
"heading-xl/medium",
"heading-xl/semibold",
"heading-xl/bold",
"heading-xl/extrabold",
"heading-xxl/normal",
"heading-xxl/medium",
"heading-xxl/semibold",
"heading-xxl/bold",
"heading-xxl/extrabold",
"eyebrow",
"heading-deprecated-12/normal",
"heading-deprecated-12/medium",
"heading-deprecated-12/semibold",
"heading-deprecated-12/bold",
"heading-deprecated-12/extrabold",
"redesign/heading-18/bold",
"text-xxs/normal",
"text-xxs/medium",
"text-xxs/semibold",
"text-xxs/bold",
"text-xs/normal",
"text-xs/medium",
"text-xs/semibold",
"text-xs/bold",
"text-sm/normal",
"text-sm/medium",
"text-sm/semibold",
"text-sm/bold",
"text-md/normal",
"text-md/medium",
"text-md/semibold",
"text-md/bold",
"text-lg/normal",
"text-lg/medium",
"text-lg/semibold",
"text-lg/bold",
"redesign/message-preview/normal",
"redesign/message-preview/medium",
"redesign/message-preview/semibold",
"redesign/message-preview/bold",
"redesign/channel-title/normal",
"redesign/channel-title/medium",
"redesign/channel-title/semibold",
"redesign/channel-title/bold",
"display-sm",
"display-md",
"display-lg"
]>;
export type RedesignModuleKeys = OptionalKeys<[
"AlertActionButton",
"AlertModal",
"AlertModalContainer",
"AvatarDuoPile",
"AvatarPile",
"BACKDROP_OPAQUE_MAX_OPACITY",
"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",
"NAV_BAR_HEIGHT",
"NAV_BAR_HEIGHT_MULTILINE",
"Navigator",
"NavigatorHeader",
"NavigatorScreen",
"Pile",
"PileOverflow",
"RedesignCompat",
"RedesignCompatContext",
"RowButton",
"STATUS_BAR_HEIGHT",
"SceneLoadingIndicator",
"SearchField",
"SegmentedControl",
"SegmentedControlPages",
"Slider",
"Stack",
"StepModal",
"StickyContext",
"StickyHeader",
"StickyWrapper",
"TABLE_ROW_CONTENT_HEIGHT",
"TABLE_ROW_HEIGHT",
"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"
]>;

View file

@ -1,3 +1,7 @@
export type StringUnion<T> = T | (string & Record<any, unknown>);
export type OptionalKeys<T extends Array<string>> = StringUnion<T[number]>;
export interface Author {
name: string;
id?: string;

View file

@ -12,6 +12,7 @@ import * as utils from "@lib/utils";
function createBunnyObject(unloads: any[]) {
return {
api: {
...api,
commands: { ...api.commands },
native: {
fs: { ...api.native.fs },
@ -27,26 +28,39 @@ function createBunnyObject(unloads: any[]) {
}
},
managers: {
...managers,
plugins: { ...managers.plugins },
themes: { ...managers.themes }
},
metro: {
...metro,
common: { ...metro.common },
filters: { ...metro.filters },
...metro.filters
},
ui: {
components: { ...ui.components },
...ui,
components: {
...ui.components,
discord: {
...ui.components.discord,
Forms: { ...ui.components.discord.Forms },
Redesign: { ...ui.components.discord.Redesign }
}
},
alerts: { ...ui.alerts },
color: { ...ui.color },
toasts: { ...ui.toasts },
styles: { ...ui.styles }
},
utils: {
...utils,
constants: { ...utils.constants },
types: { ...utils.types },
logger: { ...utils.logger }
logger: { ...utils.logger },
},
settings: {
...settings,
settings: settings.settings,
loaderConfig: { ...settings.loaderConfig }
},

View file

@ -20,7 +20,7 @@
"@core/*": ["src/core/*"],
"@metro/*": ["src/lib/metro/*"],
"@ui/*": ["src/lib/ui/*"],
"@types": ["src/utils/types.ts"],
"@types": ["src/lib/utils/types.ts"],
"bunny-build": ["src/modules.d.ts"]
},
"plugins": [