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} +