diff --git a/src/plugins/start/settings/components/PluginCard.tsx b/src/plugins/start/settings/components/PluginCard.tsx
index 9c70243..e51bd2d 100644
--- a/src/plugins/start/settings/components/PluginCard.tsx
+++ b/src/plugins/start/settings/components/PluginCard.tsx
@@ -1,27 +1,15 @@
-import { useNavigation } from '@react-navigation/native'
import { getAssetIdByName } from '@revenge-mod/assets'
import { styles } from '@revenge-mod/components/_'
import FormSwitch from '@revenge-mod/components/FormSwitch'
-import { AlertActionCreators } from '@revenge-mod/discord/actions'
import { Tokens } from '@revenge-mod/discord/common'
import { Design } from '@revenge-mod/discord/design'
-import {
- enablePlugin,
- getPluginDependencies,
- isPluginEnabled,
- isPluginEssential,
- runPluginLate,
-} from '@revenge-mod/plugins/_'
+import { isPluginEssential } from '@revenge-mod/plugins/_'
import { memo } from 'react'
-import {
- Image,
- PixelRatio,
- Pressable,
- useWindowDimensions,
- View,
-} from 'react-native'
-import { PluginFlashList } from './PluginList'
-import PluginStatesProvider, { usePluginEnabled } from './PluginStateProvider'
+import { Image, Pressable } from 'react-native'
+import { handleDisablePlugin, handleEnablePlugin } from '../utils/actions'
+import { openPluginSettings } from '../utils/alerts'
+import { showPluginOptionsActionSheet } from '../utils/sheets'
+import { usePluginEnabled } from './PluginStateProvider'
import {
enableTooltipTarget,
essentialTooltipTarget,
@@ -35,6 +23,7 @@ const { Card, Text, Stack, IconButton, createStyles } = Design
const PuzzlePieceIcon = getAssetIdByName('PuzzlePieceIcon', 'png')!
const SettingsIcon = getAssetIdByName('SettingsIcon', 'png')!
+const MoreVerticalIcon = getAssetIdByName('MoreVerticalIcon', 'png')!
export const PLUGIN_CARD_ESTIMATED_SIZE = 116
@@ -66,6 +55,37 @@ export const PluginCard = memo(function PluginCard({
rowEnd && styles_.rowEnd,
]}
>
+
+
+ )
+})
+
+export const PluginInfo = memo(function PluginInfo({
+ name,
+ description,
+ author,
+ icon,
+ actions,
+ aligned,
+}: {
+ name: string
+ description: string
+ author: string
+ icon?: string
+ actions?: React.ReactNode
+ aligned?: boolean
+}) {
+ const styles_ = usePluginCardStyles()
+
+ return (
+
{actions}
-
+
-
+
)
})
@@ -116,16 +139,13 @@ export const InstalledPluginCard = memo(function InstalledPluginCard({
rowEnd?: boolean
columnEnd?: boolean
}) {
- const navigation = useNavigation()
const enabled = usePluginEnabled(plugin)
const {
manifest: { name, description, author, icon },
} = plugin
- const dependencies = getPluginDependencies(plugin)
const essential = isPluginEssential(meta)
- const { dependents } = meta
const settingsRef = useClickOutsideTooltip(enableTooltipTarget, () => {
setEnablePluginTooltipVisible?.(false)
@@ -145,6 +165,14 @@ export const InstalledPluginCard = memo(function InstalledPluginCard({
columnEnd={columnEnd}
actions={
<>
+ {
+ showPluginOptionsActionSheet(plugin)
+ }}
+ />
{plugin.SettingsComponent && (
{
@@ -165,7 +193,7 @@ export const InstalledPluginCard = memo(function InstalledPluginCard({
variant="secondary"
icon={SettingsIcon}
onPress={() => {
- navigation.navigate(plugin.manifest.id)
+ openPluginSettings(plugin)
}}
disabled={!enabled}
/>
@@ -186,20 +214,12 @@ export const InstalledPluginCard = memo(function InstalledPluginCard({
ref={switchRef}
>
{
- if (enabled)
- return handleEnablePlugin(
- plugin,
- dependencies,
- )
- else
- return handleDisablePlugin(
- plugin,
- dependents,
- )
+ if (enabled) handleEnablePlugin(plugin)
+ else handleDisablePlugin(plugin)
}}
value={enabled}
/>
@@ -210,162 +230,6 @@ export const InstalledPluginCard = memo(function InstalledPluginCard({
)
})
-async function handleEnablePlugin(
- plugin: AnyPlugin,
- dependencies: AnyPlugin[],
-) {
- const disabledDeps = dependencies.filter(dep => !isPluginEnabled(dep))
-
- async function action() {
- await enablePlugin(plugin)
- await runPluginLate(plugin)
- }
-
- if (disabledDeps.length)
- AlertActionCreators.openAlert(
- 'plugin-has-dependencies',
-
-
- ,
- )
- else await action()
-}
-
-async function handleDisablePlugin(plugin: AnyPlugin, dependents: AnyPlugin[]) {
- const action = plugin.disable
-
- const enabledDeps = dependents.filter(isPluginEnabled)
-
- if (enabledDeps.length)
- AlertActionCreators.openAlert(
- 'plugin-has-dependents',
-
-
- ,
- )
- else await action()
-}
-
-function PluginHasDependenciesAlert({
- plugin,
- dependencies,
- action,
-}: {
- plugin: AnyPlugin
- dependencies: AnyPlugin[]
- action: () => Promise
-}) {
- const { height: windowHeight } = useWindowDimensions()
- const maxHeight = PixelRatio.get() * windowHeight * 0.35 - 64
-
- return (
-
- Plugin{' '}
-
- {plugin.manifest.name}
- {' '}
- depends on other plugins to function.
- {'\n'}
- Enabling it will also enable the following plugins:
-
- }
- extraContent={
-
-
-
- }
- actions={
- <>
-
-
- >
- }
- />
- )
-}
-
-function PluginHasDependentsAlert({
- plugin,
- dependents,
- action,
-}: {
- plugin: AnyPlugin
- dependents: AnyPlugin[]
- action: () => Promise
-}) {
- const { height: windowHeight } = useWindowDimensions()
- const maxHeight = PixelRatio.get() * windowHeight * 0.35 - 64
-
- return (
-
- Other plugins depend on{' '}
-
- {plugin.manifest.name}
- {' '}
- to function.
- {'\n'}
- Disabling it will also disable the following plugins:
-
- }
- extraContent={
-
-
-
- }
- actions={
- <>
-
-
- >
- }
- />
- )
-}
-
const usePluginCardStyles = createStyles({
icon: {
width: 20,
diff --git a/src/plugins/start/settings/components/PluginClearDataConfirmationAlert.tsx b/src/plugins/start/settings/components/PluginClearDataConfirmationAlert.tsx
new file mode 100644
index 0000000..fe3479f
--- /dev/null
+++ b/src/plugins/start/settings/components/PluginClearDataConfirmationAlert.tsx
@@ -0,0 +1,37 @@
+import { Design } from '@revenge-mod/discord/design'
+import type { AnyPlugin } from '@revenge-mod/plugins/_'
+
+const { AlertModal, AlertActionButton, Text } = Design
+
+export default function PluginClearDataConfirmationAlert({
+ plugin,
+ action,
+}: {
+ plugin: AnyPlugin
+ action: () => Promise
+}) {
+ return (
+
+ All data, including files and settings for{' '}
+
+ {plugin.manifest.name}
+ {' '}
+ will be deleted
+
+ }
+ actions={
+ <>
+
+
+ >
+ }
+ />
+ )
+}
diff --git a/src/plugins/start/settings/components/PluginHasDependenciesAlert.tsx b/src/plugins/start/settings/components/PluginHasDependenciesAlert.tsx
new file mode 100644
index 0000000..b008f18
--- /dev/null
+++ b/src/plugins/start/settings/components/PluginHasDependenciesAlert.tsx
@@ -0,0 +1,60 @@
+import { Design } from '@revenge-mod/discord/design'
+import { PixelRatio, useWindowDimensions, View } from 'react-native'
+import { PLUGIN_CARD_ESTIMATED_SIZE } from './PluginCard'
+import { PluginFlashList } from './PluginList'
+import type { AnyPlugin } from '@revenge-mod/plugins/_'
+
+const { AlertModal, AlertActionButton, Text } = Design
+
+export default function PluginHasDependenciesAlert({
+ plugin,
+ dependencies,
+ action,
+}: {
+ plugin: AnyPlugin
+ dependencies: AnyPlugin[]
+ action: () => Promise
+}) {
+ const { height: windowHeight } = useWindowDimensions()
+ const maxHeight = PixelRatio.get() * windowHeight * 0.35 - 64
+
+ return (
+
+ Plugin{' '}
+
+ {plugin.manifest.name}
+ {' '}
+ depends on other plugins to function.
+ {'\n'}
+ Enabling it will also enable the following plugins:
+
+ }
+ extraContent={
+
+
+
+ }
+ actions={
+ <>
+
+
+ >
+ }
+ />
+ )
+}
diff --git a/src/plugins/start/settings/components/PluginHasDependentsAlert.tsx b/src/plugins/start/settings/components/PluginHasDependentsAlert.tsx
new file mode 100644
index 0000000..066066b
--- /dev/null
+++ b/src/plugins/start/settings/components/PluginHasDependentsAlert.tsx
@@ -0,0 +1,60 @@
+import { Design } from '@revenge-mod/discord/design'
+import { PixelRatio, useWindowDimensions, View } from 'react-native'
+import { PLUGIN_CARD_ESTIMATED_SIZE } from './PluginCard'
+import { PluginFlashList } from './PluginList'
+import type { AnyPlugin } from '@revenge-mod/plugins/_'
+
+const { AlertModal, AlertActionButton, Text } = Design
+
+export default function PluginHasDependentsAlert({
+ plugin,
+ dependents,
+ action,
+}: {
+ plugin: AnyPlugin
+ dependents: AnyPlugin[]
+ action: () => Promise
+}) {
+ const { height: windowHeight } = useWindowDimensions()
+ const maxHeight = PixelRatio.get() * windowHeight * 0.35 - 64
+
+ return (
+
+ Other plugins depend on{' '}
+
+ {plugin.manifest.name}
+ {' '}
+ to function.
+ {'\n'}
+ Disabling it will also disable the following plugins:
+
+ }
+ extraContent={
+
+
+
+ }
+ actions={
+ <>
+
+
+ >
+ }
+ />
+ )
+}
diff --git a/src/plugins/start/settings/components/PluginOptionsActionSheet.tsx b/src/plugins/start/settings/components/PluginOptionsActionSheet.tsx
new file mode 100644
index 0000000..2dedf65
--- /dev/null
+++ b/src/plugins/start/settings/components/PluginOptionsActionSheet.tsx
@@ -0,0 +1,152 @@
+import { getAssetIdByName } from '@revenge-mod/assets'
+import { FormSwitch } from '@revenge-mod/components'
+import {
+ ActionSheetActionCreators,
+ ToastActionCreators,
+} from '@revenge-mod/discord/actions'
+import { Design } from '@revenge-mod/discord/design'
+import { Clipboard } from '@revenge-mod/externals/react-native-clipboard'
+import {
+ getInternalPluginMeta,
+ isPluginEssential,
+} from '@revenge-mod/plugins/_'
+import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
+import { getErrorStack } from '@revenge-mod/utils/error'
+import { handleDisablePlugin, handleEnablePlugin } from '../utils/actions'
+import {
+ openPluginSettings,
+ showPluginClearDataConfirmation,
+} from '../utils/alerts'
+import { PluginInfo } from './PluginCard'
+import { usePluginEnabled } from './PluginStateProvider'
+import type { AnyPlugin } from '@revenge-mod/plugins/_'
+
+export interface PluginOptionsActionSheetProps {
+ plugin: AnyPlugin
+ sheetKey: string
+}
+
+const { ActionSheet, IconButton, TableRowGroup, TableRow, Stack } = Design
+
+const FileWarningIcon = getAssetIdByName('FileWarningIcon', 'png')!
+const SettingsIcon = getAssetIdByName('SettingsIcon', 'png')!
+
+export default function PluginOptionsActionSheet({
+ plugin,
+ sheetKey,
+}: PluginOptionsActionSheetProps) {
+ const enabled = usePluginEnabled(plugin)
+ const meta = getInternalPluginMeta(plugin)
+ const { dependencies, dependents } = meta!
+ const essential = isPluginEssential(meta)
+ const {
+ manifest: { id, name, author, description, icon },
+ errors,
+ flags,
+ } = plugin
+
+ const closeActionSheet = () => {
+ ActionSheetActionCreators.hideActionSheet(sheetKey)
+ }
+
+ return (
+
+
+ {
+ if (v) handleEnablePlugin(plugin)
+ else handleDisablePlugin(plugin)
+ }}
+ />
+ )
+ }
+ />
+
+ {
+ showPluginClearDataConfirmation(
+ plugin,
+ closeActionSheet,
+ )
+ }}
+ />
+ {plugin.SettingsComponent && (
+ {
+ openPluginSettings(plugin)
+ closeActionSheet()
+ }}
+ />
+ )}
+
+
+
+ {errors.length && (
+ {
+ Clipboard.setString(
+ errors.map(getErrorStack).join('\n'),
+ )
+ showCopiedToClipboardToast()
+ }}
+ />
+ )}
+
+
+ {
+ Clipboard.setString(id)
+ showCopiedToClipboardToast()
+ }}
+ />
+ {dependencies?.length && (
+
+ )}
+ {dependents?.length && (
+
+ )}
+
+
+
+ )
+}
+
+const CopyIcon = lookupGeneratedIconComponent('CopyIcon')!
+
+function showCopiedToClipboardToast() {
+ ToastActionCreators.open({
+ key: 'REVENGE_PLUGIN_SETTINGS_COPIED',
+ content: 'Copied to clipboard',
+ IconComponent: CopyIcon,
+ })
+}
diff --git a/src/plugins/start/settings/utils/actions.tsx b/src/plugins/start/settings/utils/actions.tsx
new file mode 100644
index 0000000..1ae26ea
--- /dev/null
+++ b/src/plugins/start/settings/utils/actions.tsx
@@ -0,0 +1,38 @@
+import {
+ disablePlugin,
+ enablePlugin,
+ getInternalPluginMeta,
+ getPluginDependencies,
+ isPluginEnabled,
+ runPluginLate,
+} from '@revenge-mod/plugins/_'
+import {
+ showPluginHasDependenciesAlert,
+ showPluginHasDependentsAlert,
+} from './alerts'
+import type { AnyPlugin } from '@revenge-mod/plugins/_'
+
+export async function handleEnablePlugin(plugin: AnyPlugin) {
+ const dependencies = getPluginDependencies(plugin)!
+ const disabledDeps = dependencies?.filter(dep => !isPluginEnabled(dep))
+
+ async function action() {
+ await enablePlugin(plugin)
+ await runPluginLate(plugin)
+ }
+
+ if (disabledDeps?.length)
+ showPluginHasDependenciesAlert(plugin, disabledDeps, action)
+ else await action()
+}
+
+export async function handleDisablePlugin(plugin: AnyPlugin) {
+ const { dependents } = getInternalPluginMeta(plugin)!
+ const action = () => disablePlugin(plugin)
+
+ const enabledDeps = dependents.filter(isPluginEnabled)
+
+ if (enabledDeps.length)
+ showPluginHasDependentsAlert(plugin, enabledDeps, action)
+ else await action()
+}
diff --git a/src/plugins/start/settings/utils/alerts.tsx b/src/plugins/start/settings/utils/alerts.tsx
new file mode 100644
index 0000000..29810d7
--- /dev/null
+++ b/src/plugins/start/settings/utils/alerts.tsx
@@ -0,0 +1,73 @@
+import { AlertActionCreators } from '@revenge-mod/discord/actions'
+import { RootNavigationRef } from '@revenge-mod/discord/modules/main_tabs_v2'
+import PluginClearDataConfirmationAlert from '../components/PluginClearDataConfirmationAlert'
+import PluginHasDependenciesAlert from '../components/PluginHasDependenciesAlert'
+import PluginHasDependentsAlert from '../components/PluginHasDependentsAlert'
+import PluginStatesProvider from '../components/PluginStateProvider'
+import type { AnyPlugin } from '@revenge-mod/plugins/_'
+import type { InitPluginApi } from '@revenge-mod/plugins/types'
+import type { AnyObject } from '@revenge-mod/utils/types'
+
+export function showPluginClearDataConfirmation(
+ plugin: AnyPlugin,
+ callback: () => void,
+) {
+ const KEY = 'plugin-clear-data-confirmation'
+
+ async function action() {
+ const { storage } = plugin.api as InitPluginApi<{ storage: AnyObject }>
+ if (await storage.exists()) await storage.delete()
+ callback()
+ }
+
+ AlertActionCreators.openAlert(
+ KEY,
+ ,
+ )
+}
+
+export function openPluginSettings(plugin: AnyPlugin) {
+ if (!plugin.SettingsComponent) return
+
+ const navigation = RootNavigationRef.getRootNavigationRef()
+ if (navigation.isReady()) navigation.navigate(plugin.manifest.id)
+ else
+ navigation.addListener('ready', function self() {
+ navigation.navigate(plugin.manifest.id)
+ navigation.removeListener('ready', self)
+ })
+}
+
+export function showPluginHasDependenciesAlert(
+ plugin: AnyPlugin,
+ dependencies: AnyPlugin[],
+ action: () => Promise,
+) {
+ AlertActionCreators.openAlert(
+ 'plugin-has-dependencies',
+
+
+ ,
+ )
+}
+
+export function showPluginHasDependentsAlert(
+ plugin: AnyPlugin,
+ dependents: AnyPlugin[],
+ action: () => Promise,
+) {
+ AlertActionCreators.openAlert(
+ 'plugin-has-dependents',
+
+
+ ,
+ )
+}
diff --git a/src/plugins/start/settings/utils/sheets.tsx b/src/plugins/start/settings/utils/sheets.tsx
new file mode 100644
index 0000000..fcda7f5
--- /dev/null
+++ b/src/plugins/start/settings/utils/sheets.tsx
@@ -0,0 +1,20 @@
+import { ActionSheetActionCreators } from '@revenge-mod/discord/actions'
+import PluginStatesProvider from '../components/PluginStateProvider'
+import type { AnyPlugin } from '@revenge-mod/plugins/_'
+import type { PluginOptionsActionSheetProps } from '../components/PluginOptionsActionSheet'
+
+export function showPluginOptionsActionSheet(plugin: AnyPlugin) {
+ const KEY = 'plugin-options-action-sheet'
+
+ ActionSheetActionCreators.openLazy(
+ import('../components/PluginOptionsActionSheet').then(m => ({
+ default: (props: PluginOptionsActionSheetProps) => (
+
+
+
+ ),
+ })),
+ KEY,
+ { plugin, sheetKey: KEY },
+ )
+}