diff --git a/src/def.d.ts b/src/def.d.ts index a3b489f..c25d151 100644 --- a/src/def.d.ts +++ b/src/def.d.ts @@ -37,6 +37,22 @@ interface Assets { [id: string]: Asset; } +interface PluginManifest { + name: string; + description: string; + icon?: string; + author: string; +} + +interface Plugin { + id: string; + manifest: PluginManifest; + enabled: boolean; + js: string; +} + +type Indexable = { [index: string]: Type } + interface VendettaObject { patcher: { after: typeof _spitroast.after; @@ -91,9 +107,11 @@ interface VendettaObject { } declare global { + type React = typeof _React; interface Window { [key: PropertyKey]: any; modules: MetroModules; vendetta: VendettaObject; + React: typeof _React; } } diff --git a/src/lib/plugins.ts b/src/lib/plugins.ts new file mode 100644 index 0000000..e80b4a9 --- /dev/null +++ b/src/lib/plugins.ts @@ -0,0 +1,24 @@ +import { Indexable, PluginManifest, Plugin } from "@types"; + +export const plugins: Indexable = {}; + +export async function fetchPlugin(url: string) { + if (!url.endsWith("/")) url += "/"; + + if (plugins[url]) throw new Error(`That plugin is already installed!`); + + let pluginManifest: PluginManifest; + + try { + pluginManifest = await (await fetch(new URL("manifest.json", url), { cache: "no-store" })).json(); + } catch { + throw new Error(`Failed to fetch manifest for ${url}`); + } + + plugins[url] = { + id: url.split("://")[1], + manifest: pluginManifest, + enabled: true, + js: "", + }; +} \ No newline at end of file diff --git a/src/ui/settings/components/PluginCard.tsx b/src/ui/settings/components/PluginCard.tsx new file mode 100644 index 0000000..13df03a --- /dev/null +++ b/src/ui/settings/components/PluginCard.tsx @@ -0,0 +1,49 @@ +import { ReactNative as RN, stylesheet } from "@metro/common"; +import { Forms } from "@ui/components"; +import { Plugin } from "@types"; +import { getAssetIDByName } from "@/ui/assets"; + +const { FormRow, FormText, FormSwitch } = Forms; + +const styles = stylesheet.createThemedStyleSheet({ + card: { + backgroundColor: stylesheet.ThemeColorMap.BACKGROUND_SECONDARY, + borderRadius: 5, + margin: 10, + }, + header: { + backgroundColor: stylesheet.ThemeColorMap.BACKGROUND_TERTIARY, + borderTopLeftRadius: 5, + borderTopRightRadius: 5, + } +}) + +interface PluginCardProps { + plugin: Plugin; +} + +export default function PluginCard({ plugin }: PluginCardProps) { + const [enabled, setEnabled] = React.useState(plugin.enabled); + + return ( + + } + trailing={ + { + setEnabled(v); + plugin.enabled = enabled; + }} + /> + } + /> + + + ) +} \ No newline at end of file diff --git a/src/ui/settings/components/SettingsSection.tsx b/src/ui/settings/components/SettingsSection.tsx index 1b572c4..886404c 100644 --- a/src/ui/settings/components/SettingsSection.tsx +++ b/src/ui/settings/components/SettingsSection.tsx @@ -16,6 +16,12 @@ export default function SettingsSection({ navigation }: SettingsSectionProps) { trailing={FormRow.Arrow} onPress={() => navigation.push("VendettaSettings")} /> + } + trailing={FormRow.Arrow} + onPress={() => navigation.push("VendettaPlugins")} + /> } diff --git a/src/ui/settings/index.tsx b/src/ui/settings/index.tsx index 3c112d4..87b12ff 100644 --- a/src/ui/settings/index.tsx +++ b/src/ui/settings/index.tsx @@ -18,6 +18,10 @@ export default function initSettings() { title: "Vendetta", render: General, }, + VendettaPlugins: { + title: "Plugins", + render: Plugins + }, VendettaAssetBrowser: { title: "Asset Browser", render: AssetBrowser, diff --git a/src/ui/settings/pages/Plugins.tsx b/src/ui/settings/pages/Plugins.tsx new file mode 100644 index 0000000..c866b1e --- /dev/null +++ b/src/ui/settings/pages/Plugins.tsx @@ -0,0 +1,56 @@ +import { ReactNative as RN, stylesheet } from "@metro/common"; +import { Forms } from "@ui/components"; +import { showToast } from "@ui/toasts"; +import { getAssetIDByName } from "@ui/assets"; +import { fetchPlugin, plugins } from "@lib/plugins"; +import PluginCard from "@ui/settings/components/PluginCard"; + +const { FormInput, FormRow, FormText } = Forms; + +const styles = stylesheet.createThemedStyleSheet({ + disclaimer: { + backgroundColor: stylesheet.ThemeColorMap.BACKGROUND_SECONDARY, + padding: 10 + }, + disclaimerText: { + textAlign: "center" + } +}) + +export default function Plugins() { + const [pluginUrl, setPluginUrl] = React.useState(""); + const [pluginList, setPluginList] = React.useState(plugins); + + + return ( + <> + setPluginUrl(v)} + title="PLUGIN URL" + /> + } + trailing={FormRow.Arrow} + onPress={() => { + fetchPlugin(pluginUrl).then(() => { + setPluginUrl(""); + setPluginList(plugins); + }).catch((e: Error) => { + showToast(e.message, getAssetIDByName("Small")); + }); + } + } + /> + } + keyExtractor={item => item.id} + /> + + Plugins are currently non-functional, but most of the infrastructure and UI is in place. + + + ) +} \ No newline at end of file