refactor: expose more stuff and move some stuff

This commit is contained in:
pylixonly 2024-10-13 00:35:36 +08:00
parent fe937a4ab5
commit f2f1f6ac73
53 changed files with 248 additions and 407 deletions

View file

@ -1,4 +1,4 @@
import { PluginInstanceInternal } from "@lib/plugins/types";
import { PluginInstanceInternal } from "@lib/addons/plugins/types";
interface CorePlugin {
default: PluginInstanceInternal;

View file

@ -4,8 +4,8 @@ import { findAssetId } from "@lib/api/assets";
import { isThemeSupported } from "@lib/api/native/loader";
import { after } from "@lib/api/patcher";
import { useProxy } from "@lib/api/storage";
import { installTheme, removeTheme, themes } from "@lib/themes";
import { DISCORD_SERVER_ID, HTTP_REGEX_MULTI, PLUGINS_CHANNEL_ID, THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
import { installTheme, removeTheme, themes } from "@lib/addons/themes";
import { VD_DISCORD_SERVER_ID, HTTP_REGEX_MULTI, VD_PLUGINS_CHANNEL_ID, VD_THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
import { lazyDestructure } from "@lib/utils/lazy";
import { Button } from "@metro/common/components";
import { findByProps, findByPropsLazy } from "@metro/wrappers";
@ -39,13 +39,13 @@ const postMap = {
};
function useExtractThreadContent(thread: any, _firstMessage = null, actionSheet = false): ([PostType, string]) | void {
if (thread.guild_id !== DISCORD_SERVER_ID) return;
if (thread.guild_id !== VD_DISCORD_SERVER_ID) return;
// Determine what type of addon this is.
let postType: PostType;
if (thread.parent_id === PLUGINS_CHANNEL_ID) {
if (thread.parent_id === VD_PLUGINS_CHANNEL_ID) {
postType = "Plugin";
} else if (thread.parent_id === THEMES_CHANNEL_ID && isThemeSupported()) {
} else if (thread.parent_id === VD_THEMES_CHANNEL_ID && isThemeSupported()) {
postType = "Theme";
} else return;

View file

@ -3,14 +3,14 @@ import { VdPluginManager } from "@core/vendetta/plugins";
import { findAssetId } from "@lib/api/assets";
import { isThemeSupported } from "@lib/api/native/loader";
import { after, instead } from "@lib/api/patcher";
import { installTheme } from "@lib/themes";
import { THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
import { installTheme } from "@lib/addons/themes";
import { VD_THEMES_CHANNEL_ID, VD_PROXY_PREFIX } from "@lib/utils/constants";
import { lazyDestructure } from "@lib/utils/lazy";
import { channels, url } from "@metro/common";
import { byMutableProp } from "@metro/filters";
import { findExports } from "@metro/finders";
import { findByProps, findByPropsLazy } from "@metro/wrappers";
import { showConfirmationAlert } from "@ui/alerts";
import { showConfirmationAlert } from "@core/vendetta/alerts";
import { showToast } from "@ui/toasts";
const showSimpleActionSheet = findExports(byMutableProp("showSimpleActionSheet"));
@ -66,7 +66,7 @@ export default () => {
if (!urlType) return orig.apply(this, args);
// Make clicking on theme links only work in #themes, should there be a theme proxy in the future, this can be removed.
if (urlType === "theme" && getChannel(getChannelId())?.parent_id !== THEMES_CHANNEL_ID) return orig.apply(this, args);
if (urlType === "theme" && getChannel(getChannelId())?.parent_id !== VD_THEMES_CHANNEL_ID) return orig.apply(this, args);
showConfirmationAlert({
title: Strings.HOLD_UP,

View file

@ -18,9 +18,7 @@ export default function initSettings() {
title: () => Strings.BUNNY,
icon: { uri: PyoncordIcon },
render: () => import("@core/ui/settings/pages/General"),
rawTabsConfig: {
useTrailing: () => `(${version})`
}
useTrailing: () => `(${version})`
},
{
key: "BUNNY_PLUGINS",

View file

@ -3,14 +3,13 @@ import { CardWrapper } from "@core/ui/components/AddonCard";
import { findAssetId } from "@lib/api/assets";
import { BundleUpdaterManager } from "@lib/api/native/modules";
import { useProxy } from "@lib/api/storage";
import { FontDefinition, fonts, selectFont } from "@lib/fonts";
import { FontDefinition, fonts, selectFont } from "@lib/addons/fonts";
import { lazyDestructure } from "@lib/utils/lazy";
import { ButtonColors } from "@lib/utils/types";
import { findByProps } from "@metro";
import { NavigationNative, tokens } from "@metro/common";
import { Button, Card, IconButton, Stack, Text } from "@metro/common/components";
import * as Skia from "@shopify/react-native-skia";
import { showConfirmationAlert } from "@ui/alerts";
import { showConfirmationAlert } from "@core/vendetta/alerts";
import { TextStyleSheet } from "@ui/styles";
import { useMemo } from "react";
import { View } from "react-native";
@ -104,7 +103,7 @@ export default function FontCard({ item: font }: CardWrapper<FontDefinition>) {
content: "Reload Discord to apply changes?",
confirmText: Strings.RELOAD,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
confirmColor: "red",
onConfirm: BundleUpdaterManager.reload
});
}}

View file

@ -1,8 +1,8 @@
import { formatString, Strings } from "@core/i18n";
import { findAssetId } from "@lib/api/assets";
import { createProxy, useProxy } from "@lib/api/storage";
import { FontDefinition, fonts, removeFont, saveFont, validateFont } from "@lib/fonts";
import { getCurrentTheme } from "@lib/themes";
import { FontDefinition, fonts, removeFont, saveFont, validateFont } from "@lib/addons/fonts";
import { getCurrentTheme } from "@lib/addons/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";

View file

@ -3,7 +3,7 @@ import AddonPage from "@core/ui/components/AddonPage";
import FontEditor from "@core/ui/settings/pages/Fonts/FontEditor";
import { settings } from "@lib/api/settings";
import { useProxy } from "@lib/api/storage";
import { FontDefinition, fonts } from "@lib/fonts";
import { FontDefinition, fonts } from "@lib/addons/fonts";
import { NavigationNative } from "@metro/common";
import FontCard from "./FontCard";

View file

@ -1,6 +1,6 @@
import { findAssetId } from "@lib/api/assets";
import { installPlugin, isPluginInstalled, uninstallPlugin } from "@lib/plugins";
import { BunnyPluginManifest } from "@lib/plugins/types";
import { installPlugin, isPluginInstalled, uninstallPlugin } from "@lib/addons/plugins";
import { BunnyPluginManifest } from "@lib/addons/plugins/types";
import { showToast } from "@lib/ui/toasts";
import { safeFetch } from "@lib/utils";
import { OFFICIAL_PLUGINS_REPO_URL } from "@lib/utils/constants";
@ -41,7 +41,7 @@ async function* getManifests(repoUrl: string) {
function InstallButton(props: { id: string; }) {
const [installed, setInstalled] = useState(isPluginInstalled(props.id));
const installationState = useMutation<void, Error, { install: boolean }>({
const installationState = useMutation<void, Error, { install: boolean; }>({
mutationFn: async ({ install }) => {
await (install ? installPlugin : uninstallPlugin)(props.id, true);
},
@ -63,11 +63,11 @@ function InstallButton(props: { id: string; }) {
/>;
}
function TrailingButtons(props: { id: string }) {
function TrailingButtons(props: { id: string; }) {
return <Stack spacing={8} direction="horizontal">
<IconButton
size="sm"
onPress={() => {}}
onPress={() => { }}
variant="secondary"
icon={findAssetId("CircleInformationIcon")}
/>

View file

@ -9,7 +9,6 @@ import { useProxy as useNewProxy } from "@lib/api/storage/new";
import { showToast } from "@lib/ui/toasts";
import { BUNNY_PROXY_PREFIX, VD_PROXY_PREFIX } from "@lib/utils/constants";
import { lazyDestructure } from "@lib/utils/lazy";
import { Author } from "@lib/utils/types";
import { findByProps } from "@metro";
import { NavigationNative, tokens } from "@metro/common";
import { Card, FlashList, IconButton, PressableScale, Stack, Text } from "@metro/common/components";
@ -17,9 +16,10 @@ import { ComponentProps } from "react";
import { Image, View } from "react-native";
import unifyVdPlugin from "./models/vendetta";
import { isCorePlugin, isPluginInstalled, pluginSettings, registeredPlugins } from "@lib/plugins";
import { isCorePlugin, isPluginInstalled, pluginSettings, registeredPlugins } from "@lib/addons/plugins";
import unifyBunnyPlugin from "./models/bunny";
import { createStyles } from "@lib/ui/styles";
import { Author } from "@lib/addons/types";
const useStyles = createStyles({
xButton: {
@ -33,7 +33,7 @@ export interface UnifiedPluginModel {
id: string;
name: string;
description?: string;
authors?: Array<Author | string>;
authors?: Author;
icon?: string;
isEnabled(): boolean;

View file

@ -1,6 +1,6 @@
import { useProxy } from "@lib/api/storage/new";
import { disablePlugin, enablePlugin, getId, getPluginSettingsComponent, isPluginEnabled, pluginSettings } from "@lib/plugins";
import { BunnyPluginManifest } from "@lib/plugins/types";
import { disablePlugin, enablePlugin, getId, getPluginSettingsComponent, isPluginEnabled, pluginSettings } from "@lib/addons/plugins";
import { BunnyPluginManifest } from "@lib/addons/plugins/types";
import { UnifiedPluginModel } from "..";

View file

@ -2,10 +2,9 @@ import { formatString, Strings } from "@core/i18n";
import { VdPluginManager } from "@core/vendetta/plugins";
import { findAssetId } from "@lib/api/assets";
import { purgeStorage } from "@lib/api/storage";
import { ButtonColors } from "@lib/utils/types";
import { clipboard } from "@metro/common";
import { ActionSheet, ActionSheetRow, Button, TableRow, Text } from "@metro/common/components";
import { showConfirmationAlert } from "@ui/alerts";
import { showConfirmationAlert } from "@core/vendetta/alerts";
import { hideSheet } from "@ui/sheets";
import { showToast } from "@ui/toasts";
import { ScrollView, View } from "react-native";
@ -85,7 +84,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: PluginInfo
content: formatString("ARE_YOU_SURE_TO_CLEAR_DATA", { name: plugin.name }),
confirmText: Strings.CLEAR,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
confirmColor: "red",
onConfirm: async () => {
if (vdPlugin.enabled) VdPluginManager.stopPlugin(plugin.id, false);
@ -122,7 +121,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: PluginInfo
content: formatString("ARE_YOU_SURE_TO_DELETE_PLUGIN", { name: plugin.name }),
confirmText: Strings.DELETE,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
confirmColor: "red",
onConfirm: () => {
try {
VdPluginManager.removePlugin(plugin.id);

View file

@ -3,10 +3,9 @@ import AddonCard, { CardWrapper } from "@core/ui/components/AddonCard";
import { findAssetId } from "@lib/api/assets";
import { settings } from "@lib/api/settings";
import { useProxy } from "@lib/api/storage";
import { applyTheme, fetchTheme, removeTheme, selectTheme, Theme, themes } from "@lib/themes";
import { ButtonColors } from "@lib/utils/types";
import { applyTheme, fetchTheme, removeTheme, selectTheme, Theme, themes } from "@lib/addons/themes";
import { clipboard } from "@metro/common";
import { showConfirmationAlert } from "@ui/alerts";
import { showConfirmationAlert } from "@core/vendetta/alerts";
import { showToast } from "@ui/toasts";
function selectAndApply(value: boolean, theme: Theme) {
@ -68,7 +67,7 @@ export default function ThemeCard({ item: theme }: CardWrapper<Theme>) {
content: formatString("ARE_YOU_SURE_TO_DELETE_THEME", { name: theme.data.name }),
confirmText: Strings.DELETE,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
confirmColor: "red",
onConfirm: () => {
removeTheme(theme.id).then(wasSelected => {
setRemoved(true);

View file

@ -3,9 +3,9 @@ import AddonPage from "@core/ui/components/AddonPage";
import ThemeCard from "@core/ui/settings/pages/Themes/ThemeCard";
import { settings } from "@lib/api/settings";
import { useProxy } from "@lib/api/storage";
import { installTheme, Theme, themes } from "@lib/themes";
import { Author } from "@lib/utils/types";
import { installTheme, Theme, themes } from "@lib/addons/themes";
import { Button } from "@metro/common/components";
import { Author } from "@lib/addons/types";
export default function Themes() {
useProxy(settings);

View file

@ -1,4 +1,3 @@
import { ButtonColors } from "@lib/utils/types";
import { findByPropsLazy } from "@metro/wrappers";
import InputAlert, { InputAlertProps } from "@ui/components/InputAlert";
@ -13,7 +12,7 @@ export interface ConfirmationAlertOptions {
title?: string;
content: string | JSX.Element | (string | JSX.Element)[];
confirmText?: string;
confirmColor?: ButtonColors;
confirmColor?: string;
onConfirm: () => void;
secondaryConfirmText?: string;
onConfirmSecondary?: () => void;

View file

@ -6,15 +6,15 @@ import patcher from "@lib/api/patcher";
import { loaderConfig, settings } from "@lib/api/settings";
import * as storage from "@lib/api/storage";
import { createStorage } from "@lib/api/storage";
import * as themes from "@lib/themes";
import * as themes from "@lib/addons/themes";
import * as utils from "@lib/utils";
import { cyrb64Hash } from "@lib/utils/cyrb64";
import { DiscordLogger } from "@lib/utils/logger";
import { LoggerClass } from "@lib/utils/logger";
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 alerts from "@core/vendetta/alerts";
import * as color from "@ui/color";
import * as components from "@ui/components";
import { createThemedStyleSheet } from "@ui/styles";
@ -34,7 +34,7 @@ export async function createVdPluginObject(plugin: VendettaPlugin) {
// Wrapping this with wrapSync is NOT an option.
storage: await createStorage<Record<string, any>>(storage.createMMKVBackend(plugin.id)),
},
logger: new DiscordLogger(`Bunny » ${plugin.manifest.name}`),
logger: new LoggerClass(`Bunny » ${plugin.manifest.name}`),
};
}

View file

@ -1,9 +1,9 @@
import { Author } from "@lib/addons/types";
import { settings } from "@lib/api/settings";
import { awaitStorage, createMMKVBackend, createStorage, purgeStorage, wrapSync } from "@lib/api/storage";
import { safeFetch } from "@lib/utils";
import { BUNNY_PROXY_PREFIX, VD_PROXY_PREFIX } from "@lib/utils/constants";
import { DiscordLogger, logger } from "@lib/utils/logger";
import { Author } from "@lib/utils/types";
import { LoggerClass, logger } from "@lib/utils/logger";
type EvaledPlugin = {
onLoad?(): void;
@ -39,7 +39,9 @@ export const VdPluginManager = {
plugins,
async pluginFetch(url: string) {
if (url.startsWith(VD_PROXY_PREFIX)) {
url = url.replace(VD_PROXY_PREFIX, BUNNY_PROXY_PREFIX);
url = url
.replace("https://bunny-mod.github.io/plugins-proxy", BUNNY_PROXY_PREFIX)
.replace(VD_PROXY_PREFIX, BUNNY_PROXY_PREFIX);
}
return await safeFetch(url, { cache: "no-store" });
@ -61,7 +63,7 @@ export const VdPluginManager = {
if (existingPlugin?.manifest.hash !== pluginManifest.hash) {
try {
// by polymanifest spec, plugins should always specify their main file, but just in case
// by polymanifest spec, plugins should always specify their main file, but just in case
pluginJs = await (await this.pluginFetch(id + (pluginManifest.main || "index.js"))).text();
} catch { } // Empty catch, checked below
}
@ -96,7 +98,7 @@ export const VdPluginManager = {
// Wrapping this with wrapSync is NOT an option.
storage: await createStorage<Record<string, any>>(createMMKVBackend(plugin.id)),
},
logger: new DiscordLogger(`Bunny » ${plugin.manifest.name}`),
logger: new LoggerClass(`Bunny » ${plugin.manifest.name}`),
};
const pluginString = `vendetta=>{return ${plugin.js}}\n//# sourceURL=${plugin.id}`;
@ -166,7 +168,7 @@ export const VdPluginManager = {
const allIds = Object.keys(plugins);
if (!settings.safeMode?.enabled) {
// Loop over any plugin that is enabled, update it if allowed, then start it.
// Loop over any plugin that is enabled, update it if allowed, then start it.
await Promise.allSettled(allIds.filter(pl => plugins[pl].enabled).map(async pl => (plugins[pl].update && await this.fetchPlugin(pl).catch((e: Error) => logger.error(e.message)), await this.startPlugin(pl))));
// Wait for the above to finish, then update all disabled plugins that are allowed to.
allIds.filter(pl => !plugins[pl].enabled && plugins[pl].update).forEach(pl => this.fetchPlugin(pl));

View file

@ -10,9 +10,9 @@ import { patchJSX } from "@lib/api/jsx";
import { removeFile, writeFile } from "@lib/api/native/fs";
import { isPyonLoader, isThemeSupported } from "@lib/api/native/loader";
import { FileManager } from "@lib/api/native/modules";
import { updateFonts } from "@lib/fonts";
import { initPlugins, registerCorePlugins } from "@lib/plugins";
import { initThemes, patchChatBackground } from "@lib/themes";
import { updateFonts } from "@lib/addons/fonts";
import { initPlugins, registerCorePlugins } from "@lib/addons/plugins";
import { initThemes, patchChatBackground } from "@lib/addons/themes";
import { logger } from "@lib/utils/logger";
import initSafeMode from "@ui/safeMode";
import { patchSettings } from "@ui/settings";

View file

@ -1,6 +1,6 @@
import { createStorage } from "@lib/api/storage";
import { Logger } from "@lib/utils/logger";
import { Author } from "@lib/utils/types";
import { Author } from "../types";
export interface PluginRepo {
[id: string]: {
@ -26,7 +26,7 @@ export interface BunnyPluginManifest {
readonly name: string;
readonly description: string;
readonly version: string;
readonly authors: (Author | string)[];
readonly authors: Author[];
}
export interface BunnyPluginManifestInternal extends BunnyPluginManifest {

View file

@ -8,12 +8,12 @@ import { after, before, instead } from "@lib/api/patcher";
import { awaitStorage, createFileBackend, createMMKVBackend, createStorage, wrapSync } from "@lib/api/storage";
import { findInReactTree, safeFetch } from "@lib/utils";
import { lazyDestructure, proxyLazy } from "@lib/utils/lazy";
import { Author } from "@lib/utils/types";
import { byMutableProp } from "@metro/filters";
import { createLazyModule } from "@metro/lazy";
import { findByNameLazy, findByProps, findByPropsLazy, findByStoreNameLazy } from "@metro/wrappers";
import chroma from "chroma-js";
import { ImageBackground, Platform, processColor } from "react-native";
import { Author } from "../types";
export interface ThemeData {
name: string;

4
src/lib/addons/types.ts Normal file
View file

@ -0,0 +1,4 @@
export interface Author {
name: string;
id: string;
}

View file

@ -4,6 +4,7 @@ import { getImportingModuleId, requireModule } from "@metro/internals/modules";
// TODO: Deprecate this map, make another that maps to an array of assets (Asset[]) instead
/**
* @internal
* Pitfall: Multiple assets may have the same name, this is fine if we require the asset only for display,\
* but not when used to get the registered id/index. In some condition, this would break some plugins like HideGiftButton that gets id by name for patching.\
*/

View file

@ -3,12 +3,11 @@ import { getLoaderName, getLoaderVersion, isThemeSupported } from "@lib/api/nati
import { BundleUpdaterManager, ClientInfoManager, DeviceManager } from "@lib/api/native/modules";
import { after } from "@lib/api/patcher";
import { settings } from "@lib/api/settings";
import { getThemeFromLoader, selectTheme, themes } from "@lib/themes";
import { getThemeFromLoader, selectTheme, themes } from "@lib/addons/themes";
import { logger } from "@lib/utils/logger";
import { showToast } from "@ui/toasts";
import { version } from "bunny-build-info";
import { Platform, type PlatformConstants } from "react-native";
export let socket: WebSocket;
export interface RNConstants extends PlatformConstants {
// Android
@ -44,6 +43,7 @@ export async function toggleSafeMode() {
setTimeout(BundleUpdaterManager.reload, 400);
}
let socket: WebSocket;
export function connectToDebugger(url: string) {
if (socket !== undefined && socket.readyState !== WebSocket.CLOSED) socket.close();

View file

@ -1,3 +1 @@
export * as fs from "./fs";
export * as loader from "./loader";
export * as modules from "./modules";

View file

@ -1,4 +1,4 @@
import { Theme } from "@lib/themes";
import { Theme } from "@lib/addons/themes";
// @ts-ignore
const pyonLoaderIdentity = globalThis.__PYON_LOADER__;
@ -58,7 +58,7 @@ function polyfillVendettaLoaderIdentity() {
const id = getStoredTheme()?.id;
if (!id) return null;
const { themes } = require("@lib/themes");
const { themes } = require("@lib/addons/themes");
return themes[id] ?? getStoredTheme() ?? null;
},
configurable: true

View file

@ -1,97 +0,0 @@
const nmp = window.nativeModuleProxy;
/**
* A key-value storage based upon `SharedPreferences` on Android.
*
* These types are based on Android though everything should be the same between
* platforms.
*/
export interface MMKVManager {
/**
* Get the value for the given `key`, or null
* @param key The key to fetch
*/
getItem: (key: string) => Promise<string | null>;
/**
* Deletes the value for the given `key`
* @param key The key to delete
*/
removeItem: (key: string) => void;
/**
* Sets the value of `key` to `value`
*/
setItem: (key: string, value: string) => void;
/**
* Goes through every item in storage and returns it, excluding the
* keys specified in `exclude`.
* @param exclude A list of items to exclude from result
*/
refresh: (exclude: string[]) => Promise<Record<string, string>>;
/**
* You will be murdered if you use this function.
* Clears ALL of Discord's settings.
*/
clear: () => void;
}
export interface FileManager {
/**
* @param path **Full** path to file
*/
fileExists: (path: string) => Promise<boolean>;
/**
* Allowed URI schemes on Android: `file://`, `content://` ([See here](https://developer.android.com/reference/android/content/ContentResolver#accepts-the-following-uri-schemes:_3))
*/
getSize: (uri: string) => Promise<boolean>;
/**
* @param path **Full** path to file
* @param encoding Set to `base64` in order to encode response
*/
readFile(path: string, encoding: "base64" | "utf8"): Promise<string>;
saveFileToGallery?(uri: string, fileName: string, fileType: "PNG" | "JPEG"): Promise<string>;
/**
* @param storageDir Either `cache` or `documents`.
* @param path Path in `storageDir`, parents are recursively created.
* @param data The data to write to the file
* @param encoding Set to `base64` if `data` is base64 encoded.
* @returns Promise that resolves to path of the file once it got written
*/
writeFile(storageDir: "cache" | "documents", path: string, data: string, encoding: "base64" | "utf8"): Promise<string>;
/**
* Removes a file from the path given.
* (!) On Android, this always returns false, regardless if it fails or not!
* @param storageDir Either `cache` or `documents`
* @param path Path to the file to be removed
*/
removeFile(storageDir: "cache" | "documents", path: string): Promise<unknown>;
/**
* Clear the folder from the path given
* (!) On Android, this only clears all *files* and not subdirectories!
* @param storageDir Either `cache` or `documents`
* @param path Path to the folder to be cleared
* @returns Whether the clearance succeeded
*/
clearFolder(storageDir: "cache" | "documents", path: string): Promise<boolean>;
getConstants: () => {
/**
* The path the `documents` storage dir (see {@link writeFile}) represents.
*/
DocumentsDirPath: string;
CacheDirPath: string;
};
/**
* Will apparently cease to exist some time in the future so please use {@link getConstants} instead.
* @deprecated
*/
DocumentsDirPath: string;
}
export const MMKVManager = nmp.MMKVManager as MMKVManager;
//! 173.10 renamed this to RTNFileManager.
export const FileManager = (nmp.DCDFileManager ?? nmp.RTNFileManager) as FileManager;
//! 173.13 renamed this to RTNClientInfoManager.
export const ClientInfoManager = nmp.InfoDictionaryManager ?? nmp.RTNClientInfoManager;
//! 173.14 renamed this to RTNDeviceManager.
export const DeviceManager = nmp.DCDDeviceManager ?? nmp.RTNDeviceManager;
export const { BundleUpdaterManager } = nmp;
export const ThemeManager = nmp.RTNThemeManager ?? nmp.DCDTheme;

View file

@ -0,0 +1,11 @@
import { RNModules } from "./types";
const nmp = window.nativeModuleProxy;
export const MMKVManager = nmp.MMKVManager as RNModules.MMKVManager;
export const FileManager = (nmp.DCDFileManager ?? nmp.RTNFileManager) as RNModules.FileManager;
export const ClientInfoManager = nmp.InfoDictionaryManager ?? nmp.RTNClientInfoManager;
export const DeviceManager = nmp.DCDDeviceManager ?? nmp.RTNDeviceManager;
export const { BundleUpdaterManager } = nmp;
export const ThemeManager = nmp.RTNThemeManager ?? nmp.DCDTheme;

View file

@ -0,0 +1,89 @@
export namespace RNModules {
/**
* A key-value storage based upon `SharedPreferences` on Android.
*
* These types are based on Android though everything should be the same between
* platforms.
*/
export interface MMKVManager {
/**
* Get the value for the given `key`, or null
* @param key The key to fetch
*/
getItem: (key: string) => Promise<string | null>;
/**
* Deletes the value for the given `key`
* @param key The key to delete
*/
removeItem: (key: string) => void;
/**
* Sets the value of `key` to `value`
*/
setItem: (key: string, value: string) => void;
/**
* Goes through every item in storage and returns it, excluding the
* keys specified in `exclude`.
* @param exclude A list of items to exclude from result
*/
refresh: (exclude: string[]) => Promise<Record<string, string>>;
/**
* You will be murdered if you use this function.
* Clears ALL of Discord's settings.
*/
clear: () => void;
}
export interface FileManager {
/**
* @param path **Full** path to file
*/
fileExists: (path: string) => Promise<boolean>;
/**
* Allowed URI schemes on Android: `file://`, `content://` ([See here](https://developer.android.com/reference/android/content/ContentResolver#accepts-the-following-uri-schemes:_3))
*/
getSize: (uri: string) => Promise<boolean>;
/**
* @param path **Full** path to file
* @param encoding Set to `base64` in order to encode response
*/
readFile(path: string, encoding: "base64" | "utf8"): Promise<string>;
saveFileToGallery?(uri: string, fileName: string, fileType: "PNG" | "JPEG"): Promise<string>;
/**
* @param storageDir Either `cache` or `documents`.
* @param path Path in `storageDir`, parents are recursively created.
* @param data The data to write to the file
* @param encoding Set to `base64` if `data` is base64 encoded.
* @returns Promise that resolves to path of the file once it got written
*/
writeFile(storageDir: "cache" | "documents", path: string, data: string, encoding: "base64" | "utf8"): Promise<string>;
/**
* Removes a file from the path given.
* (!) On Android, this always returns false, regardless if it fails or not!
* @param storageDir Either `cache` or `documents`
* @param path Path to the file to be removed
*/
removeFile(storageDir: "cache" | "documents", path: string): Promise<unknown>;
/**
* Clear the folder from the path given
* (!) On Android, this only clears all *files* and not subdirectories!
* @param storageDir Either `cache` or `documents`
* @param path Path to the folder to be cleared
* @returns Whether the clearance succeeded
*/
clearFolder(storageDir: "cache" | "documents", path: string): Promise<boolean>;
getConstants: () => {
/**
* The path the `documents` storage dir (see {@link writeFile}) represents.
*/
DocumentsDirPath: string;
CacheDirPath: string;
};
/**
* Will apparently cease to exist some time in the future so please use {@link getConstants} instead.
* @deprecated
*/
DocumentsDirPath: string;
}
}

View file

@ -6,7 +6,6 @@ const syncAwaitSymbol = Symbol.for("vendetta.storage.accessor");
export function createProxy(target: any = {}): { proxy: any; emitter: Emitter; } {
const emitter = new Emitter();
const parentTarget = target;
const childrens = new WeakMap<any, any>();
const proxiedChildrenSet = new WeakSet<any>();

View file

@ -2,22 +2,26 @@ import "../global.d.ts"; // eslint-disable-line import-alias/import-alias
import "../modules.d.ts"; // eslint-disable-line import-alias/import-alias
export * as api from "./api";
export * as fonts from "./fonts";
export * as plugins from "./plugins";
export * as themes from "./themes";
export * as fonts from "./addons/fonts/index.js";
export * as plugins from "./addons/plugins/index.js";
export * as themes from "./addons/themes/index.js";
export * as ui from "./ui";
export * as utils from "./utils";
export * as metro from "@metro";
import * as fonts from "./fonts";
import * as plugins from "./plugins";
import * as themes from "./themes";
import * as fonts from "./addons/fonts/index.js";
import * as plugins from "./addons/plugins/index.js";
import * as themes from "./addons/themes/index.js";
/** @internal */
export * as _jsx from "react/jsx-runtime";
import { proxyLazy } from "./utils/lazy";
/**
* @internal
* @deprecated Moved to top level (bunny.*)
*/
export const managers = proxyLazy(() => {
console.warn("bunny.managers.* is deprecated, and moved the top level (bunny.*). bunny.managers will be eventually removed soon");

View file

@ -1,4 +1,4 @@
import { color } from "@lib/themes";
import { color } from "@lib/addons/themes";
import { constants } from "@metro/common";
import { findByStoreNameLazy } from "@metro/wrappers";

View file

@ -1,10 +1,9 @@
import { Strings } from "@core/i18n";
import { Nullish } from "@lib/utils/types";
import { React } from "@metro/common";
import { Button, LegacyFormText } from "@metro/common/components";
import { Codeblock } from "@ui/components";
import { createThemedStyleSheet } from "@ui/styles";
import { ScrollView } from "react-native";
import { Falsy, ScrollView } from "react-native";
type ErrorBoundaryState = {
hasErr: false;
@ -14,7 +13,7 @@ type ErrorBoundaryState = {
};
export interface ErrorBoundaryProps {
children: JSX.Element | Nullish | (JSX.Element | Nullish)[];
children: JSX.Element | Falsy | (JSX.Element | Falsy)[];
}
const styles = createThemedStyleSheet({

View file

@ -1,4 +1,3 @@
import { ButtonColors } from "@lib/utils/types";
import { LegacyAlert, LegacyFormInput } from "@metro/common/components";
import { findByPropsLazy } from "@metro/wrappers";
@ -7,7 +6,7 @@ const Alerts = findByPropsLazy("openLazy", "close");
export interface InputAlertProps {
title?: string;
confirmText?: string;
confirmColor?: ButtonColors;
confirmColor?: string;
onConfirm: (input: string) => (void | Promise<void>);
cancelText?: string;
placeholder?: string;

View file

@ -1,3 +1,5 @@
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";

View file

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

View file

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

View file

@ -4,7 +4,6 @@ import { DeviceManager } from "@lib/api/native/modules";
import { after } from "@lib/api/patcher";
import { settings } from "@lib/api/settings";
import { lazyDestructure } from "@lib/utils/lazy";
import { ButtonColors } from "@lib/utils/types";
import { Button, CompatButton, SafeAreaView } from "@metro/common/components";
import { _lazyContextSymbol } from "@metro/lazy";
import { LazyModuleContext } from "@metro/types";
@ -91,7 +90,7 @@ export default () => after.await("render", getErrorBoundaryContext(), function (
const buttons: Button[] = [
{ text: Strings.RELOAD_DISCORD, onPress: this.handleReload },
...!settings.safeMode?.enabled ? [{ text: Strings.RELOAD_IN_SAFE_MODE, onPress: toggleSafeMode }] : [],
{ text: Strings.RETRY_RENDER, color: ButtonColors.RED, onPress: () => this.setState({ info: null, error: null }) },
{ text: Strings.RETRY_RENDER, color: "red", onPress: () => this.setState({ info: null, error: null }) },
];
return (
@ -130,7 +129,7 @@ export default () => after.await("render", getErrorBoundaryContext(), function (
return <CompatButton
text={button.text}
color={button.color ?? ButtonColors.BRAND}
color={button.color ?? "brand"}
size={button.size ?? "small"}
onPress={button.onPress}
style={{

View file

@ -12,6 +12,7 @@ export interface RowConfig {
render?: Parameters<typeof lazy>[0];
icon?: ImageURISource | number;
usePredicate?: () => boolean,
useTrailing?: () => string | JSX.Element,
rawTabsConfig?: Record<string, any>;
}

View file

@ -13,18 +13,18 @@ function SettingsSection() {
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.rawTabsConfig?.useTrailing?.() || undefined} />}
onPress={wrapOnPress(row.onPress, navigation, row.render, row.title())}
/>
{i !== arr.length - 1 && <LegacyFormDivider />}
</>
))}
{ /** 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>
))}
</>;

View file

@ -23,6 +23,7 @@ export function patchTabsUI(unpatches: (() => void | boolean)[]) {
title: row.title,
icon: row.icon,
usePredicate: row.usePredicate,
useTrailing: row.useTrailing,
onPress: wrapOnPress(row.onPress, null, row.render, row.title()),
withArrow: true,
...row.rawTabsConfig

View file

@ -1,163 +1,3 @@
import { OptionalKeys } from "@lib/utils/types";
import { LiteralUnion } from "type-fest";
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"
]>;
export type DiscordTextStyles = LiteralUnion<"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", string>;

View file

@ -2,13 +2,12 @@ export const DISCORD_SERVER = "https://discord.gg/XjYgWXHb9Q";
export const GITHUB = "https://github.com/pyoncord";
export const HTTP_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/;
export const HTTP_REGEX_MULTI = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
export const VD_PROXY_PREFIX = "https://vd-plugins.github.io/proxy";
export const BUNNY_PROXY_PREFIX = "https://bn-plugins.github.io/vd-proxy";
export const OLD_BUNNY_PROXY_PREFIX = "https://bunny-mod.github.io/plugins-proxy";
export const OFFICIAL_PLUGINS_REPO_URL = "https://bn-plugins.github.io/dist/repo.json";
// These are still Vendetta's
export const DISCORD_SERVER_ID = "1015931589865246730";
export const PLUGINS_CHANNEL_ID = "1091880384561684561";
export const THEMES_CHANNEL_ID = "1091880434939482202";
export const VD_PROXY_PREFIX = "https://vd-plugins.github.io/proxy";
export const VD_DISCORD_SERVER_ID = "1015931589865246730";
export const VD_PLUGINS_CHANNEL_ID = "1091880384561684561";
export const VD_THEMES_CHANNEL_ID = "1091880434939482202";

View file

@ -3,7 +3,7 @@
// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480
// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
export function cyrb64(str: string, seed = 0) {
export default function cyrb64(str: string, seed = 0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);

View file

@ -1,6 +1,8 @@
import { findInTree } from "@lib/utils";
import { SearchFilter } from "@lib/utils/findInTree";
export const findInReactTree = (tree: { [key: string]: any; }, filter: SearchFilter): any => findInTree(tree, filter, {
walkable: ["props", "children", "child", "sibling"],
});
export default function findInReactTree(tree: { [key: string]: any; }, filter: SearchFilter): any {
return findInTree(tree, filter, {
walkable: ["props", "children", "child", "sibling"],
});
}

View file

@ -15,7 +15,7 @@ function treeSearch(tree: SearchTree, filter: SearchFilter, opts: Required<FindI
try {
if (filter(tree)) return tree;
} catch {}
} catch { }
if (Array.isArray(tree)) {
for (const item of tree) {
@ -24,7 +24,7 @@ function treeSearch(tree: SearchTree, filter: SearchFilter, opts: Required<FindI
try {
const found = treeSearch(item, filter, opts, depth + 1);
if (found) return found;
} catch {}
} catch { }
}
} else if (typeof tree === "object") {
for (const key of Object.keys(tree)) {
@ -35,12 +35,12 @@ function treeSearch(tree: SearchTree, filter: SearchFilter, opts: Required<FindI
try {
const found = treeSearch(tree[key], filter, opts, depth + 1);
if (found) return found;
} catch {}
} catch { }
}
}
}
export const findInTree = (
export default function findInTree(
tree: SearchTree,
filter: SearchFilter,
{
@ -48,4 +48,6 @@ export const findInTree = (
ignore = [],
maxDepth = 100
}: FindInTreeOptions = {},
): any | undefined => treeSearch(tree, filter, { walkable, ignore, maxDepth }, 0);
): any | undefined {
return treeSearch(tree, filter, { walkable, ignore, maxDepth }, 0);
};

View file

@ -2,7 +2,7 @@ import { LiteralUnion } from "type-fest";
type KeyOfOrAny<P, T extends object> = P extends keyof T ? T[P] : any;
export function hookDefineProperty<
export default function hookDefineProperty<
T extends object,
P extends LiteralUnion<keyof T, PropertyKey>
>(target: T, property: LiteralUnion<keyof T, PropertyKey>, cb: (val: KeyOfOrAny<P, T>) => KeyOfOrAny<P, T>) {

View file

@ -1,10 +1,23 @@
export * as constants from "./constants";
export { cyrb64 } from "./cyrb64";
export { Emitter } from "./Emitter";
export { findInReactTree } from "./findInReactTree";
export { findInTree } from "./findInTree";
export { hookDefineProperty } from "./hookDefineProperty";
export { default as invariant } from "./invariant";
export * as lazy from "./lazy";
export * as logger from "./logger";
export { safeFetch } from "./safeFetch";
import cyrb64 from "./cyrb64";
import findInReactTree from "./findInReactTree";
import findInTree from "./findInTree";
import hookDefineProperty from "./hookDefineProperty";
import invariant from "./invariant";
import safeFetch from "./safeFetch";
import * as constants from "./constants";
import * as lazy from "./lazy";
import * as logger from "./logger";
export {
cyrb64,
findInReactTree,
findInTree,
hookDefineProperty,
invariant,
safeFetch,
constants,
lazy,
logger
};

View file

@ -1,6 +1,6 @@
import { findByNameLazy } from "@metro/wrappers";
export type LoggerFunction = (...messages: any[]) => void;
type LoggerFunction = (...messages: any[]) => void;
export interface Logger {
log: LoggerFunction;
info: LoggerFunction;
@ -11,5 +11,5 @@ export interface Logger {
verbose: LoggerFunction;
}
export const DiscordLogger = findByNameLazy("Logger");
export const logger: Logger = new DiscordLogger("Bunny");
export const LoggerClass = findByNameLazy("Logger");
export const logger: Logger = new LoggerClass("Bunny");

View file

@ -1,5 +1,5 @@
// A really basic fetch wrapper which throws on non-ok response codes
export async function safeFetch(input: RequestInfo | URL, options?: RequestInit, timeout = 10000) {
export default async function safeFetch(input: RequestInfo | URL, options?: RequestInit, timeout = 10000) {
const req = await fetch(input, {
signal: timeoutSignal(timeout),
...options

View file

@ -1,22 +0,0 @@
import { LiteralUnion } from "type-fest";
export type Nullish = null | undefined;
export type OptionalKeys<T extends Array<string>> = LiteralUnion<T[number], string>;
export interface Author {
name: string;
id?: string;
}
export enum ButtonColors {
BRAND = "brand",
RED = "red",
GREEN = "green",
PRIMARY = "primary",
TRANSPARENT = "transparent",
GREY = "grey",
LIGHTGREY = "lightgrey",
WHITE = "white",
LINK = "link"
}

View file

@ -1,4 +1,3 @@
import { Nullish } from "@lib/utils/types";
import { DiscordTextStyles } from "@ui/types";
import { MutableRefObject, ReactNode, RefObject } from "react";
import type * as RN from "react-native";
@ -104,7 +103,7 @@ interface TextInputProps extends Omit<RN.TextInputProps, "onChange" | "onChangeT
trailingIcon?: React.FC<any>;
trailingPressableProps?: PressableProps;
trailingText?: string;
value?: string | Nullish;
value?: string | RN.Falsy;
}
export type TextInput = React.FC<TextInputProps>;