From 3bdf403264282fd6311d649a682ab1abe535fd0d Mon Sep 17 00:00:00 2001
From: amsyarasyiq <82711525+amsyarasyiq@users.noreply.github.com>
Date: Thu, 21 Dec 2023 03:30:21 +0800
Subject: [PATCH] real
---
src/index.ts | 1 +
src/ui/components/InputAlert.tsx | 2 +-
src/ui/components/Search.tsx | 68 +++++++--
src/ui/components/Summary.tsx | 11 +-
src/ui/components/index.ts | 2 +-
src/ui/quickInstall/forumPost.tsx | 125 ++++++++++++----
src/ui/safeMode.tsx | 6 +-
src/ui/settings/components/AddonPage.tsx | 45 +++++-
src/ui/settings/components/Card.tsx | 57 +++----
src/ui/settings/components/Version.tsx | 12 +-
src/ui/settings/data.tsx | 14 +-
src/ui/settings/pages/Developer.tsx | 180 ++++++++++++-----------
src/ui/settings/pages/General.tsx | 90 ++++++------
src/ui/settings/pages/Plugins.tsx | 4 +-
src/ui/settings/pages/Themes.tsx | 4 +-
15 files changed, 400 insertions(+), 221 deletions(-)
diff --git a/src/index.ts b/src/index.ts
index d32c177..e89163c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,6 +9,7 @@ import initSettings from "@ui/settings";
import initFixes from "@lib/fixes";
import logger from "@lib/logger";
import windowObject from "@lib/windowObject";
+import settings from "./lib/settings";
export default async () => {
// Load everything in parallel
diff --git a/src/ui/components/InputAlert.tsx b/src/ui/components/InputAlert.tsx
index 527c24d..75efc7d 100644
--- a/src/ui/components/InputAlert.tsx
+++ b/src/ui/components/InputAlert.tsx
@@ -42,7 +42,7 @@ export default function InputAlert({ title, confirmText, confirmColor, onConfirm
secureTextEntry={secureTextEntry}
autoFocus={true}
showBorder={true}
- style={{ paddingVertical: 5, alignSelf: "stretch", paddingHorizontal: 0 }}
+ style={{ alignSelf: "stretch" }}
/>
);
diff --git a/src/ui/components/Search.tsx b/src/ui/components/Search.tsx
index c88f6e9..3862b71 100644
--- a/src/ui/components/Search.tsx
+++ b/src/ui/components/Search.tsx
@@ -1,17 +1,61 @@
import { SearchProps } from "@types";
-import { stylesheet } from "@metro/common";
-import { findByName } from "@metro/filters";
+import { ReactNative as RN } from "@metro/common";
+import { findByProps } from "@metro/filters";
+import ErrorBoundary from "./ErrorBoundary";
+import { getAssetIDByName } from "../assets";
-const Search = findByName("StaticSearchBarContainer");
+const { FormIcon } = findByProps("FormIcon");
+const { TextInput } = findByProps("TableRow");
-const styles = stylesheet.createThemedStyleSheet({
- search: {
- margin: 0,
- padding: 0,
- borderBottomWidth: 0,
- background: "none",
- backgroundColor: "none",
+const props = {
+ "placeholder": "Search",
+ "returnKeyType": "search",
+ "size": "md",
+ "onFocus": "ƒ () {}",
+ "value": "",
+ "grow": true,
+ "autoCorrect": false,
+ "autoCapitalize": "none",
+ "accessibilityRole": "search",
+ "leadingIcon": "ƒ () {}",
+ "isClearable": true,
+ "onChangeText": "ƒ () {}",
+ "editable": true,
+ "onBlur": "ƒ () {}",
+ "style": [
+ "{padding: 12, paddingTop: 12}",
+ "{color: \"#e4e5e8\", fontFamily: \"ggsans-Medium, Noto…}",
+ "{paddingEnd: undefined, paddingStart: 36.3636360168…}",
+ "{padding: 12, paddingTop: 12}",
+ "{color: \"#e4e5e8\", fontFamily: \"ggsans-Medium, Noto…}",
+ "{paddingEnd: undefined, paddingStart: 36.3636360168…}"
+ ],
+ "placeholderTextColor": "#828391"
+};
+
+function SearchIcon() {
+ return
+}
+export default ({ onChangeText, placeholder, style }: SearchProps) => {
+ const [query, setQuery] = React.useState("");
+
+ const onChange = (value: string) => {
+ setQuery(value);
+ onChangeText?.(value);
}
-});
-export default ({ onChangeText, placeholder, style }: SearchProps) =>
\ No newline at end of file
+ return
+
+
+
+
+};
\ No newline at end of file
diff --git a/src/ui/components/Summary.tsx b/src/ui/components/Summary.tsx
index 60d3507..68c610c 100644
--- a/src/ui/components/Summary.tsx
+++ b/src/ui/components/Summary.tsx
@@ -1,17 +1,19 @@
import { SummaryProps } from "@types";
import { ReactNative as RN } from "@metro/common";
import { getAssetIDByName } from "@ui/assets";
-import { Forms } from "@ui/components";
+import { findByProps } from "@/lib/metro/filters";
+
+const { FormRow } = findByProps("FormRow");
+const { TableRow } = findByProps("TableRow");
export default function Summary({ label, icon, noPadding = false, noAnimation = false, children }: SummaryProps) {
- const { FormRow, FormDivider } = Forms;
const [hidden, setHidden] = React.useState(true);
return (
<>
- }
+ icon={icon && }
trailing={}
onPress={() => {
setHidden(!hidden);
@@ -19,7 +21,6 @@ export default function Summary({ label, icon, noPadding = false, noAnimation =
}}
/>
{!hidden && <>
-
{children}
>}
>
diff --git a/src/ui/components/index.ts b/src/ui/components/index.ts
index 496cbbf..874cc7b 100644
--- a/src/ui/components/index.ts
+++ b/src/ui/components/index.ts
@@ -8,7 +8,7 @@ export const Alert = findByDisplayName("FluxContainer(Alert)");
export const Button = findByProps("Looks", "Colors", "Sizes") as React.ComponentType & { 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 typeof RN.SafeAreaView;
+export const SafeAreaView = findByProps("useSafeAreaInsets").SafeAreaView as any;
// Vendetta
export { default as Summary } from "@ui/components/Summary";
diff --git a/src/ui/quickInstall/forumPost.tsx b/src/ui/quickInstall/forumPost.tsx
index 367789b..e383a79 100644
--- a/src/ui/quickInstall/forumPost.tsx
+++ b/src/ui/quickInstall/forumPost.tsx
@@ -1,58 +1,129 @@
import { findByName, findByProps } from "@metro/filters";
import { DISCORD_SERVER_ID, PLUGINS_CHANNEL_ID, THEMES_CHANNEL_ID, HTTP_REGEX_MULTI, PROXY_PREFIX } from "@lib/constants";
import { after } from "@lib/patcher";
-import { installPlugin } from "@lib/plugins";
-import { installTheme } from "@lib/themes";
+import { installPlugin, plugins, removePlugin } from "@lib/plugins";
+import { installTheme, removeTheme, themes } from "@lib/themes";
import { findInReactTree } from "@lib/utils";
import { getAssetIDByName } from "@ui/assets";
import { showToast } from "@ui/toasts";
-import { Forms } from "@ui/components";
+import { useProxy } from "@/lib/storage";
+
+type PostType = "Plugin" | "Theme";
const ForumPostLongPressActionSheet = findByName("ForumPostLongPressActionSheet", false);
-const { FormRow, FormIcon } = Forms;
-
+const { ActionSheetRow } = findByProps("ActionSheetRow");
+const { Button } = findByProps("TableRow");
const { useFirstForumPostMessage } = findByProps("useFirstForumPostMessage");
-const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
+const forumReactions = findByProps("MostCommonForumPostReaction");
-export default () => after("default", ForumPostLongPressActionSheet, ([{ thread }], res) => {
+const postMap = {
+ Plugin: {
+ storage: plugins,
+ urlsFilter: (url: string) => url.startsWith(PROXY_PREFIX),
+ installOrRemove: (url: string) => {
+ const isInstalled = postMap.Plugin.storage[url];
+ return isInstalled ? removePlugin(url) : installPlugin(url);
+ }
+ },
+ Theme: {
+ storage: themes,
+ urlsFilter: (url: string) => url.endsWith(".json"),
+ installOrRemove: (url: string) => {
+ const isInstalled = postMap.Theme.storage[url];
+ return isInstalled ? removeTheme(url) : installTheme(url);
+ },
+ }
+}
+
+function useExtractThreadContent(thread: any, _firstMessage = null, actionSheet = false): ([PostType, string]) | void {
if (thread.guild_id !== DISCORD_SERVER_ID) return;
// Determine what type of addon this is.
- let postType: "Plugin" | "Theme";
+ let postType: PostType;
if (thread.parent_id === PLUGINS_CHANNEL_ID) {
postType = "Plugin";
} else if (thread.parent_id === THEMES_CHANNEL_ID && window.__vendetta_loader?.features.themes) {
postType = "Theme";
} else return;
- const { firstMessage } = useFirstForumPostMessage(thread);
+ const { firstMessage } = actionSheet ? useFirstForumPostMessage(thread) : { firstMessage: _firstMessage };
- let urls = firstMessage?.content?.match(HTTP_REGEX_MULTI);
- if (!urls) return;
+ let urls = firstMessage?.content?.match(HTTP_REGEX_MULTI)?.filter(postMap[postType].urlsFilter);
+ if (!urls || !urls[0]) return;
- if (postType === "Plugin") {
- urls = urls.filter((url: string) => url.startsWith(PROXY_PREFIX));
- } else {
- urls = urls.filter((url: string) => url.endsWith(".json"));
+ // Sync with lib/plugins
+ if (postType === "Plugin" && !urls[0].endsWith("/")) urls[0] += "/";
+
+ return [postType, urls[0]];
+}
+
+function useInstaller(thread: any, firstMessage = null, actionSheet = false): [true] | [false, PostType, boolean, boolean, () => Promise] {
+ const [postType, url] = useExtractThreadContent(thread, firstMessage, actionSheet) ?? [];
+
+ useProxy(plugins);
+ useProxy(themes);
+
+ const [isInstalling, setIsInstalling] = React.useState(false);
+
+ if (!postType || !url) return [true];
+
+ const isInstalled = Boolean(postMap[postType].storage[url]);
+
+ const installOrRemove = async () => {
+ setIsInstalling(true);
+ try {
+ await postMap[postType].installOrRemove(url);
+ } catch (e: any) {
+ showToast(e.message, getAssetIDByName("Small"));
+ } finally {
+ setIsInstalling(false);
+ }
};
- const url = urls[0];
- if (!url) return;
+ return [false, postType, isInstalled, isInstalling, installOrRemove]
+}
+
+const actionSheetPatch = () => after("default", ForumPostLongPressActionSheet, ([{ thread }], res) => {
+ const [shouldReturn, postType, installed, loading, installOrRemove] = useInstaller(thread);
+ if (shouldReturn) return;
const actions = findInReactTree(res, (t) => t?.[0]?.key);
const ActionsSection = actions[0].type;
actions.unshift(
- }
- label={`Install ${postType}`}
- onPress={() =>
- (postType === "Plugin" ? installPlugin : installTheme)(url).then(() => {
- showToast(`Successfully installed ${thread.name}`, getAssetIDByName("Check"));
- }).catch((e: Error) => {
- showToast(e.message, getAssetIDByName("Small"));
- }).finally(() => hideActionSheet())
- }
+ }
+ label={`${installed ? "Uninstall" : "Install"} ${postType}`}
+ disabled={loading}
+ onPress={installOrRemove}
/>
);
});
+
+const installButtonPatch = () => after("MostCommonForumPostReaction", forumReactions, ([{ thread, firstMessage }], res) => {
+ const [shouldReturn, _, installed, loading, installOrRemove] = useInstaller(thread, firstMessage, true);
+ if (shouldReturn) return;
+
+ return <>
+ {res}
+
+ >
+});
+
+export default () => {
+ const patches = [
+ actionSheetPatch(),
+ installButtonPatch()
+ ]
+
+ return () => patches.map(p => p());
+}
diff --git a/src/ui/safeMode.tsx b/src/ui/safeMode.tsx
index c3f7806..4142be3 100644
--- a/src/ui/safeMode.tsx
+++ b/src/ui/safeMode.tsx
@@ -81,13 +81,13 @@ export default () => after("render", ErrorBoundary.prototype, function (this: an
...!settings.safeMode?.enabled ? [{ text: "Restart in Safe Mode", onPress: toggleSafeMode }] : [],
{ text: "Retry Render", color: ButtonColors.RED, onPress: () => this.setState({ info: null, error: null }) },
]
-
+
return (
<_ErrorBoundary>
-
-
+
+
{ret.props.title}
{ret.props.body}
diff --git a/src/ui/settings/components/AddonPage.tsx b/src/ui/settings/components/AddonPage.tsx
index c23d027..5ef3aa5 100644
--- a/src/ui/settings/components/AddonPage.tsx
+++ b/src/ui/settings/components/AddonPage.tsx
@@ -1,19 +1,32 @@
-import { ReactNative as RN } from "@metro/common";
+import { NavigationNative, ReactNative as RN, clipboard } from "@metro/common";
import { useProxy } from "@lib/storage";
import { HelpMessage, ErrorBoundary, Search } from "@ui/components";
import { CardWrapper } from "@ui/settings/components/Card";
import settings from "@lib/settings";
+import { findByProps } from "@/lib/metro/filters";
+import { showToast } from "@/ui/toasts";
+import { HTTP_REGEX_MULTI } from "@/lib/constants";
+import { showInputAlert } from "@/ui/alerts";
+import { getAssetIDByName } from "@/ui/assets";
interface AddonPageProps {
+ title: string;
+ fetchFunction: (url: string) => Promise;
items: Record;
safeModeMessage: string;
safeModeExtras?: JSX.Element | JSX.Element[];
card: React.ComponentType>;
}
-export default function AddonPage({ items, safeModeMessage, safeModeExtras, card: CardComponent }: AddonPageProps) {
+const reanimated = findByProps("useSharedValue");
+const { FloatingActionButton } = findByProps("FloatingActionButton");
+
+export default function AddonPage({ title, fetchFunction, items, safeModeMessage, safeModeExtras, card: CardComponent }: AddonPageProps) {
useProxy(settings)
useProxy(items);
+
+ const collapseText = reanimated.useSharedValue(0);
+ const yOffset = React.useRef(0);
const [search, setSearch] = React.useState("");
return (
@@ -26,16 +39,40 @@ export default function AddonPage({ items, safeModeMessage, safeModeExtras, c
{safeModeExtras}
}
setSearch(v.toLowerCase())}
placeholder="Search"
/>
>}
+ onScroll={e => {
+ if (e.nativeEvent.contentOffset.y <= 0) return;
+ collapseText.value = Number(e.nativeEvent.contentOffset.y > yOffset.current);
+ yOffset.current = e.nativeEvent.contentOffset.y;
+ }}
+ onMomentumScrollEnd={() => collapseText.value = 0}
style={{ paddingHorizontal: 10, paddingTop: 10 }}
- contentContainerStyle={{ paddingBottom: 20 }}
+ contentContainerStyle={{ paddingBottom: 70 }}
data={Object.values(items).filter(i => i.id?.toLowerCase().includes(search))}
renderItem={({ item, index }) => }
/>
+ {
+ // from ./InstallButton.tsx
+ clipboard.getString().then((content) =>
+ showInputAlert({
+ title: `Install ${title}`,
+ initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
+ placeholder: "https://example.com/",
+ onConfirm: (input: string) => fetchFunction(input),
+ confirmText: "Install",
+ cancelText: "Cancel",
+ })
+ )
+ }}
+ />
)
}
\ No newline at end of file
diff --git a/src/ui/settings/components/Card.tsx b/src/ui/settings/components/Card.tsx
index 9dc54a3..53eb4f4 100644
--- a/src/ui/settings/components/Card.tsx
+++ b/src/ui/settings/components/Card.tsx
@@ -4,7 +4,8 @@ import { getAssetIDByName } from "@ui/assets";
import { semanticColors } from "@ui/color";
import { Forms } from "@ui/components";
-const { FormRow, FormSwitch, FormRadio } = Forms;
+const { TableRow, TableRowIcon, TableSwitchRow, TableCheckboxRow, TableRowGroup } = findByProps("TableRow");
+const { FormSwitch, FormRadio } = Forms;
const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
@@ -12,19 +13,18 @@ const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
const styles = stylesheet.createThemedStyleSheet({
card: {
backgroundColor: semanticColors?.BACKGROUND_SECONDARY,
- borderRadius: 5,
+ borderRadius: 15,
+ overflow: "hidden"
},
header: {
padding: 0,
backgroundColor: semanticColors?.BACKGROUND_TERTIARY,
- borderTopLeftRadius: 5,
- borderTopRightRadius: 5,
},
actions: {
flexDirection: "row-reverse",
alignItems: "center",
},
- icon: {
+ actionIcon: {
width: 22,
height: 22,
marginLeft: 5,
@@ -63,29 +63,29 @@ interface CardProps {
export default function Card(props: CardProps) {
let pressableState = props.toggleValue ?? false;
- return (
+ const headerProps = {
+ style: styles.header,
+ label: props.headerLabel,
+ icon: props.headerIcon &&
+ };
+
+ return (
- }
- trailing={props.toggleType && (props.toggleType === "switch" ?
- ()
- :
- ( {
+
+ {props.toggleType && (props.toggleType === "switch" ?
+ : {
pressableState = !pressableState;
props.onToggleChange?.(pressableState)
- }}>
- {/* TODO: Look into making this respect brand color */}
-
- )
- )}
- />
- )}
+
@@ -94,24 +94,25 @@ export default function Card(props: CardProps) {
key: "CardOverflow",
header: {
title: props.overflowTitle,
- icon: props.headerIcon && ,
+ icon: props.headerIcon && ,
onClose: () => hideActionSheet(),
},
options: props.overflowActions?.map(i => ({ ...i, icon: getAssetIDByName(i.icon) })),
})}
>
-
+
}
{props.actions?.map(({ icon, onPress }) => (
-
+
))}
}
/>
+
)
}
diff --git a/src/ui/settings/components/Version.tsx b/src/ui/settings/components/Version.tsx
index 033accf..2f0e7f1 100644
--- a/src/ui/settings/components/Version.tsx
+++ b/src/ui/settings/components/Version.tsx
@@ -2,20 +2,24 @@ import { clipboard } from "@metro/common";
import { getAssetIDByName } from "@ui/assets";
import { showToast } from "@ui/toasts";
import { Forms } from "@ui/components";
+import { findByProps } from "@/lib/metro/filters";
interface VersionProps {
label: string;
version: string;
icon: string;
+ padding: boolean;
}
-const { FormRow, FormText } = Forms;
+const { TableRow } = findByProps("TableRow");
+const { FormText } = Forms;
-export default function Version({ label, version, icon }: VersionProps) {
+export default function Version({ label, version, icon, padding }: VersionProps) {
return (
- }
+ icon={}
trailing={{version}}
onPress={() => {
clipboard.setString(`${label} - ${version}`);
diff --git a/src/ui/settings/data.tsx b/src/ui/settings/data.tsx
index 8b4940e..3845a7f 100644
--- a/src/ui/settings/data.tsx
+++ b/src/ui/settings/data.tsx
@@ -14,6 +14,9 @@ import Plugins from "@ui/settings/pages/Plugins";
import Themes from "@ui/settings/pages/Themes";
import Developer from "@ui/settings/pages/Developer";
import { PROXY_PREFIX } from "@/lib/constants";
+import { findByProps } from "@/lib/metro/filters";
+
+const { useSafeAreaInsets } = findByProps("useSafeAreaInsets");
interface Screen {
[index: string]: any;
@@ -25,7 +28,13 @@ interface Screen {
render: React.ComponentType;
}
-const styles = stylesheet.createThemedStyleSheet({ container: { flex: 1, backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY } });
+const styles = stylesheet.createThemedStyleSheet({
+ container: {
+ flex: 1,
+ backgroundColor: semanticColors.BACKGROUND_MOBILE_PRIMARY
+ }
+});
+
const formatKey = (key: string, youKeys: boolean) => youKeys ? lodash.snakeCase(key).toUpperCase() : key;
// If a function is passed, it is called with the screen object, and the return value is mapped. If a string is passed, we map to the value of the property with that name on the screen. Else, just map to the given data.
// Question: Isn't this overengineered?
@@ -119,8 +128,9 @@ export const getYouData = () => {
relationships: keyMap(screens, null),
rendererConfigs: keyMap(screens, (s) => {
const WrappedComponent = React.memo(({ navigation, route }: any) => {
+ const { bottom: paddingBottom } = useSafeAreaInsets();
navigation.addListener("focus", () => navigation.setOptions(s.options));
- return
+ return
});
return {
diff --git a/src/ui/settings/pages/Developer.tsx b/src/ui/settings/pages/Developer.tsx
index bcceb62..448d0db 100644
--- a/src/ui/settings/pages/Developer.tsx
+++ b/src/ui/settings/pages/Developer.tsx
@@ -1,15 +1,26 @@
-import { ReactNative as RN, NavigationNative } from "@metro/common";
+import { ReactNative as RN, NavigationNative, stylesheet } from "@metro/common";
import { findByProps } from "@metro/filters";
import { connectToDebugger } from "@lib/debug";
import { useProxy } from "@lib/storage";
import { getAssetIDByName } from "@ui/assets";
-import { Forms, ErrorBoundary } from "@ui/components";
+import { ErrorBoundary, Forms } from "@ui/components";
import settings, { loaderConfig } from "@lib/settings";
import AssetBrowser from "@ui/settings/pages/AssetBrowser";
+import { semanticColors } from "@ui/color";
-const { FormSection, FormRow, FormSwitchRow, FormInput, FormDivider } = Forms;
+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({
+ leadingText: {
+ ...TextStyleSheet["heading-md/semibold"],
+ color: semanticColors.TEXT_MUTED,
+ marginRight: -4
+ },
+});
export default function Developer() {
const navigation = NavigationNative.useNavigation();
@@ -20,92 +31,93 @@ export default function Developer() {
return (
-
- settings.debuggerUrl = v}
+
+ ws://}
+ defaultValue={settings.debuggerUrl}
+ onChange={(v: string) => settings.debuggerUrl = v}
/>
-
- }
- onPress={() => connectToDebugger(settings.debuggerUrl)}
- />
- {window.__vendetta_rdc && <>
-
- }
- onPress={() => window.__vendetta_rdc?.connectToDevTools({
- host: settings.debuggerUrl.split(":")?.[0],
- resolveRNStyle: RN.StyleSheet.flatten,
+
+ }
+ onPress={() => connectToDebugger(settings.debuggerUrl)}
+ />
+ {window.__vendetta_rdc && <>
+ }
+ onPress={() => window.__vendetta_rdc?.connectToDevTools({
+ host: settings.debuggerUrl.split(":")?.[0],
+ resolveRNStyle: RN.StyleSheet.flatten,
+ })}
+ />
+ >}
+
+ {window.__vendetta_loader?.features.loaderConfig && <>
+
+ }
+ value={loaderConfig.customLoadUrl.enabled}
+ onValueChange={(v: boolean) => {
+ loaderConfig.customLoadUrl.enabled = v;
+ }}
+ />
+ {loaderConfig.customLoadUrl.enabled && loaderConfig.customLoadUrl.url = v}
+ placeholder="http://localhost:4040/vendetta.js"
+ label="Vendetta URL"
+ />} />}
+ {window.__vendetta_loader.features.devtools && }
+ value={loaderConfig.loadReactDevTools}
+ onValueChange={(v: boolean) => {
+ loaderConfig.loadReactDevTools = v;
+ }}
+ />}
+
+ >}
+
+ }
+ trailing={TableRow.Arrow}
+ onPress={() => navigation.push("VendettaCustomPage", {
+ title: "Asset Browser",
+ render: AssetBrowser,
})}
/>
- >}
-
- {window.__vendetta_loader?.features.loaderConfig &&
- }
- value={loaderConfig.customLoadUrl.enabled}
- onValueChange={(v: boolean) => {
- loaderConfig.customLoadUrl.enabled = v;
- }}
- />
-
- {loaderConfig.customLoadUrl.enabled && <>
- loaderConfig.customLoadUrl.url = v}
- placeholder="http://localhost:4040/vendetta.js"
- title="VENDETTA URL"
+ }
+ onPress={() => showSimpleActionSheet({
+ key: "ErrorBoundaryTools",
+ header: {
+ title: "Which ErrorBoundary do you want to trip?",
+ icon: ,
+ onClose: () => hideActionSheet(),
+ },
+ options: [
+ // @ts-expect-error
+ // Of course, to trigger an error, we need to do something incorrectly. The below will do!
+ { label: "Vendetta", onPress: () => navigation.push("VendettaCustomPage", { render: () => }) },
+ { label: "Discord", isDestructive: true, onPress: () => navigation.push("VendettaCustomPage", { noErrorBoundary: true }) },
+ ],
+ })}
/>
-
- >}
- {window.__vendetta_loader.features.devtools && }
- value={loaderConfig.loadReactDevTools}
- onValueChange={(v: boolean) => {
- loaderConfig.loadReactDevTools = v;
- }}
- />}
- }
-
- }
- trailing={FormRow.Arrow}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Asset Browser",
- render: AssetBrowser,
- })}
- />
-
- }
- trailing={FormRow.Arrow}
- onPress={() => showSimpleActionSheet({
- key: "ErrorBoundaryTools",
- header: {
- title: "Which ErrorBoundary do you want to trip?",
- icon: ,
- onClose: () => hideActionSheet(),
- },
- options: [
- // @ts-expect-error
- // Of course, to trigger an error, we need to do something incorrectly. The below will do!
- { label: "Vendetta", onPress: () => navigation.push("VendettaCustomPage", { render: () => }) },
- { label: "Discord", isDestructive: true, onPress: () => navigation.push("VendettaCustomPage", { noErrorBoundary: true }) },
- ],
- })}
- />
-
+
+
)
diff --git a/src/ui/settings/pages/General.tsx b/src/ui/settings/pages/General.tsx
index 1b94a30..ce9906f 100644
--- a/src/ui/settings/pages/General.tsx
+++ b/src/ui/settings/pages/General.tsx
@@ -1,15 +1,23 @@
-import { ReactNative as RN, url } from "@metro/common";
+import { ReactNative as RN, stylesheet, url } from "@metro/common";
import { DISCORD_SERVER, GITHUB } from "@lib/constants";
import { getDebugInfo, toggleSafeMode } from "@lib/debug";
import { useProxy } from "@lib/storage";
-import { BundleUpdaterManager } from "@lib/native";
import { getAssetIDByName } from "@ui/assets";
-import { Forms, Summary, ErrorBoundary } from "@ui/components";
+import { Summary } from "@ui/components";
import settings from "@lib/settings";
import Version from "@ui/settings/components/Version";
+import { findByProps } from "@lib/metro/filters";
+import { semanticColors } from "@ui/color";
-const { FormRow, FormSwitchRow, FormSection, FormDivider } = Forms;
-const debugInfo = getDebugInfo();
+const { Stack, TableRow, TableSwitchRow, TableRowGroup } = findByProps("TableRow");
+const debugInfo = getDebugInfo() as any;
+
+const styles = stylesheet.createThemedStyleSheet({
+ container: {
+ flex: 1,
+ backgroundColor: semanticColors.BACKGROUND_MOBILE_SECONDARY,
+ }
+})
export default function General() {
useProxy(settings);
@@ -56,7 +64,7 @@ export default function General() {
...(debugInfo.os.sdk ? [{
label: "SDK",
version: debugInfo.os.sdk,
- icon: "ic_profile_badge_verified_developer_color"
+ icon: "pencil"
}] : []),
{
label: "Manufacturer",
@@ -81,66 +89,52 @@ export default function General() {
];
return (
-
-
-
-
+
+
+ }
- trailing={FormRow.Arrow}
+ icon={}
+ trailing={TableRow.Arrow}
onPress={() => url.openDeeplink(DISCORD_SERVER)}
/>
-
- }
- trailing={FormRow.Arrow}
+ icon={}
+ trailing={TableRow.Arrow}
onPress={() => url.openURL(GITHUB)}
/>
-
-
-
+
+ }
- onPress={() => BundleUpdaterManager.reload()}
+ icon={}
+ onPress={() => RN.NativeModules.BundleUpdaterManager.reload()}
/>
-
- }
+ subLabel={`This will reload Discord ${settings.safeMode?.enabled ? "normally." : "without loading addons."}`}
+ icon={}
onPress={toggleSafeMode}
/>
-
- }
+ icon={}
value={settings.developerSettings}
onValueChange={(v: boolean) => {
settings.developerSettings = v;
}}
/>
-
-
-
- {versions.map((v, i) => (
- <>
-
- {i !== versions.length - 1 && }
- >
- ))}
+
+
+
+ {versions.map(v => )}
-
-
- {platformInfo.map((p, i) => (
- <>
-
- {i !== platformInfo.length - 1 && }
- >
- ))}
+
+ {platformInfo.map(p => )}
-
-
-
+
+
+
)
}
\ No newline at end of file
diff --git a/src/ui/settings/pages/Plugins.tsx b/src/ui/settings/pages/Plugins.tsx
index 59bf8a0..c5ac722 100644
--- a/src/ui/settings/pages/Plugins.tsx
+++ b/src/ui/settings/pages/Plugins.tsx
@@ -1,6 +1,6 @@
import { Plugin } from "@types";
import { useProxy } from "@lib/storage";
-import { plugins } from "@lib/plugins";
+import { installPlugin, plugins } from "@lib/plugins";
import settings from "@lib/settings";
import AddonPage from "@ui/settings/components/AddonPage";
import PluginCard from "@ui/settings/components/PluginCard";
@@ -10,6 +10,8 @@ export default function Plugins() {
return (
+ title="Plugin"
+ fetchFunction={installPlugin}
items={plugins}
safeModeMessage="You are in Safe Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page."
card={PluginCard}
diff --git a/src/ui/settings/pages/Themes.tsx b/src/ui/settings/pages/Themes.tsx
index a1b2489..3f9d765 100644
--- a/src/ui/settings/pages/Themes.tsx
+++ b/src/ui/settings/pages/Themes.tsx
@@ -1,6 +1,6 @@
import { Theme, ButtonColors } from "@types";
import { useProxy } from "@lib/storage";
-import { themes } from "@lib/themes";
+import { installTheme, themes } from "@lib/themes";
import { Button } from "@ui/components";
import settings from "@lib/settings";
import AddonPage from "@ui/settings/components/AddonPage";
@@ -11,6 +11,8 @@ export default function Themes() {
return (
+ title={"Themes"}
+ fetchFunction={installTheme}
items={themes}
safeModeMessage={`You are in Safe Mode, meaning themes have been temporarily disabled.${settings.safeMode?.currentThemeId ? " If a theme appears to be causing the issue, you can press below to disable it persistently." : ""}`}
safeModeExtras={settings.safeMode?.currentThemeId ?