feat(ui/plugins): overflow actions

This commit is contained in:
amsyarasyiq 2024-05-23 23:20:23 +08:00
parent d6d0801731
commit 51c6b62f5c
5 changed files with 168 additions and 4 deletions

View file

@ -2,9 +2,10 @@ import { CardWrapper } from "@core/ui/components/AddonCard";
import { requireAssetIndex } from "@lib/api/assets";
import { useProxy } from "@lib/api/storage";
import { BunnyPlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
import { showSheet } from "@lib/ui/sheets";
import { lazyDestructure } from "@lib/utils/lazy";
import { findByProps } from "@metro";
import { tokens } from "@metro/common";
import { NavigationNative, tokens } from "@metro/common";
import { Card, IconButton, Stack, TableSwitch, Text } from "@metro/common/components";
import { createContext, useContext } from "react";
import { Image, View } from "react-native";
@ -79,6 +80,7 @@ function Status() {
}
export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
const navigation = NavigationNative.useNavigation();
useProxy(plugin);
return (
@ -94,10 +96,16 @@ export default function PluginCard({ item: plugin }: CardWrapper<BunnyPlugin>) {
<View style={{ marginLeft: "auto" }}>
<Stack spacing={12} direction="horizontal">
<IconButton
onPress={() => { }}
onPress={() => {
showSheet(
"PluginInfoActionSheet",
import("./sheets/PluginInfoActionSheet"),
{ plugin, navigation }
);
}}
size="sm"
variant="secondary"
icon={requireAssetIndex("SettingsIcon")}
icon={requireAssetIndex("MoreHorizontalIcon")}
/>
<TableSwitch
value={plugin.enabled}

View file

@ -0,0 +1,135 @@
import { formatString, Strings } from "@core/i18n";
import { requireAssetIndex } from "@lib/api/assets";
import { purgeStorage } from "@lib/api/storage";
import { BunnyPlugin, fetchPlugin, getSettings, removePlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
import { showConfirmationAlert } from "@lib/ui/alerts";
import { hideSheet } from "@lib/ui/sheets";
import { showToast } from "@lib/ui/toasts";
import { ButtonColors } from "@lib/utils/types";
import { clipboard } from "@metro/common";
import { ActionSheet, IconButton, TableRow, TableRowGroup, Text } from "@metro/common/components";
import { ScrollView, View } from "react-native";
interface InfoProps {
plugin: BunnyPlugin;
navigation: any;
}
export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps) {
const settings = getSettings(plugin.id);
return <ActionSheet scrollable>
<ScrollView style={{ gap: 12 }}>
<View style={{ flexDirection: "row", alignItems: "center", paddingVertical: 24 }}>
<Text variant="heading-xl/semibold">
{plugin.manifest.name}
</Text>
<View style={{ marginLeft: "auto" }}>
{settings && <IconButton
size="md"
variant="secondary"
icon={requireAssetIndex("SettingsIcon")}
onPress={() => {
hideSheet("PluginInfoActionSheet");
navigation.push("VendettaCustomPage", {
title: plugin.manifest.name,
render: settings,
});
}}
/>}
</View>
</View>
<TableRowGroup>
<TableRow
label={Strings.REFETCH}
icon={<TableRow.Icon source={requireAssetIndex("RetryIcon")} />}
onPress={async () => {
if (plugin.enabled) stopPlugin(plugin.id, false);
try {
await fetchPlugin(plugin.id);
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, requireAssetIndex("toast_image_saved"));
} catch {
showToast(Strings.PLUGIN_REFETCH_FAILED, requireAssetIndex("Small"));
}
if (plugin.enabled) await startPlugin(plugin.id);
}}
/>
<TableRow
label={Strings.COPY_URL}
icon={<TableRow.Icon source={requireAssetIndex("copy")} />}
onPress={() => {
clipboard.setString(plugin.id);
showToast.showCopyToClipboard();
}}
/>
<TableRow
label={plugin.update ? Strings.DISABLE_UPDATES : Strings.ENABLE_UPDATES}
icon={<TableRow.Icon source={requireAssetIndex("ic_download_24px")} />}
onPress={() => {
plugin.update = !plugin.update;
showToast(formatString("TOASTS_PLUGIN_UPDATE", {
update: plugin.update,
name: plugin.manifest.name
}), requireAssetIndex("toast_image_saved"));
}}
/>
<TableRow
label={Strings.CLEAR_DATA}
icon={<TableRow.Icon source={requireAssetIndex("ic_duplicate")} />}
onPress={() => showConfirmationAlert({
title: Strings.HOLD_UP,
content: formatString("ARE_YOU_SURE_TO_CLEAR_DATA", { name: plugin.manifest.name }),
confirmText: Strings.CLEAR,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: async () => {
if (plugin.enabled) stopPlugin(plugin.id, false);
try {
await fetchPlugin(plugin.id);
showToast(Strings.PLUGIN_REFETCH_SUCCESSFUL, requireAssetIndex("toast_image_saved"));
} catch {
showToast(Strings.PLUGIN_REFETCH_FAILED, requireAssetIndex("Small"));
}
let message: any[];
try {
purgeStorage(plugin.id);
message = ["CLEAR_DATA_SUCCESSFUL", "trash"];
} catch {
message = ["CLEAR_DATA_FAILED", "Small"];
}
showToast(
formatString(message[0], { name: plugin.manifest.name }),
requireAssetIndex(message[1])
);
if (plugin.enabled) await startPlugin(plugin.id);
}
})}
/>
<TableRow
label={Strings.DELETE}
icon={<TableRow.Icon source={requireAssetIndex("ic_message_delete")} />}
onPress={() => showConfirmationAlert({
title: Strings.HOLD_UP,
content: formatString("ARE_YOU_SURE_TO_DELETE_PLUGIN", { name: plugin.manifest.name }),
confirmText: Strings.DELETE,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: () => {
try {
removePlugin(plugin.id);
} catch (e) {
showToast(String(e), requireAssetIndex("Small"));
}
}
})}
/>
</TableRowGroup>
</ScrollView>
</ActionSheet>;
}

15
src/lib/ui/sheets.ts Normal file
View file

@ -0,0 +1,15 @@
import { findByPropsProxy } from "@metro/utils";
const actionSheet = findByPropsProxy("openLazy", "hideActionSheet");
export function showSheet<T extends React.ComponentType<any>>(
key: string,
lazyImport: Promise<{ default: T; }>,
props?: React.ComponentProps<T>
) {
actionSheet.openLazy(lazyImport, key, props ?? {});
}
export function hideSheet(key: string) {
actionSheet.hideActionSheet(key);
}

View file

@ -57,7 +57,7 @@ export const SegmentedControl = findProp("SegmentedControl") as t.SegmentedContr
export const CompatSegmentedControl = findProp("CompatSegmentedControl") as t.CompatSegmentedControl;
export const FloatingActionButton = findProp("FloatingActionButton") as t.FloatingActionButton;
export const ActionSheet = findProp("ActionSheet");
export const ActionSheet = findProp("ActionSheet") as t.ActionSheet;
export const BottomSheetTitleHeader = findProp("BottomSheetTitleHeader");
const textsModule = findByPropsProxy("Text", "LegacyText");

View file

@ -122,6 +122,12 @@ interface FABProps {
export type FloatingActionButton = React.FC<FABProps>;
interface ActionSheetProps {
scrollable?: boolean;
}
export type ActionSheet = React.FC<React.PropsWithChildren<ActionSheetProps>>;
type TextProps = React.ComponentProps<typeof RN.Text> & {
variant?: DiscordTextStyles;
color?: string; // TODO: type this