diff --git a/src/plugins/start/settings.plugins/components/PluginCard.tsx b/src/plugins/start/settings.plugins/components/PluginCard.tsx
index c593dd3..7e4ed3d 100644
--- a/src/plugins/start/settings.plugins/components/PluginCard.tsx
+++ b/src/plugins/start/settings.plugins/components/PluginCard.tsx
@@ -21,11 +21,7 @@ import {
} from '../utils/sheets'
import { PluginIcon } from './PluginIcon'
import { usePluginEnabled } from './PluginStateProvider'
-import {
- useClickOutsideTooltip,
- useEnablePluginTooltip,
- useEssentialPluginTooltip,
-} from './TooltipProvider'
+import { PluginTooltip, usePluginTooltip } from './TooltipProvider'
import type { AnyPlugin, InternalPluginMeta } from '@revenge-mod/plugins/_'
const { Card, Text, Stack, IconButton, Button, createStyles } = Design
@@ -151,14 +147,12 @@ export const InstalledPluginCard = memo(function InstalledPluginCard({
const toggleDisabled = essential || pendingUpdate
- const enableTooltip = useEnablePluginTooltip()
- const essentialTooltip = useEssentialPluginTooltip()
+ const [settingsRef, showEnableTooltip] = usePluginTooltip(
+ PluginTooltip.Enable,
+ )
- const settingsRef = useClickOutsideTooltip(useEnablePluginTooltip, () => {})
-
- const switchRef = useClickOutsideTooltip(
- useEssentialPluginTooltip,
- () => {},
+ const [switchRef, showToggleTooltip] = usePluginTooltip(
+ essential ? PluginTooltip.Essential : PluginTooltip.PendingUpdate,
)
return (
@@ -181,12 +175,7 @@ export const InstalledPluginCard = memo(function InstalledPluginCard({
{plugin.SettingsComponent && (
{
- if (!startable)
- requestAnimationFrame(() => {
- enableTooltip.targetRef.current =
- settingsRef.current
- enableTooltip.setVisible(true)
- })
+ if (!startable) showEnableTooltip()
}}
>
{
- if (toggleDisabled)
- requestAnimationFrame(() => {
- essentialTooltip.targetRef.current =
- switchRef.current
- essentialTooltip.setVisible(true)
- })
+ if (toggleDisabled) showToggleTooltip()
}}
ref={switchRef}
>
diff --git a/src/plugins/start/settings.plugins/components/PluginList.tsx b/src/plugins/start/settings.plugins/components/PluginList.tsx
index 47b0990..44e4050 100644
--- a/src/plugins/start/settings.plugins/components/PluginList.tsx
+++ b/src/plugins/start/settings.plugins/components/PluginList.tsx
@@ -9,7 +9,7 @@ import {
PLUGIN_CARD_HALF_GUTTER,
PluginCard,
} from './PluginCard'
-import { useResetTooltips } from './TooltipProvider'
+import { useHidePluginTooltips } from './TooltipProvider'
import type { AnyPlugin, InternalPluginMeta } from '@revenge-mod/plugins/_'
import type { RepoPluginListing } from '@revenge-mod/plugins/_/repositories'
import type { FlashListProps } from '@shopify/flash-list'
@@ -25,14 +25,14 @@ export function PluginFlashList({
FlashListProps,
'onContentSizeChange'
>) {
- const resetTooltips = useResetTooltips()
+ const hideTooltips = useHidePluginTooltips()
return (
plugin.manifest.id}
renderItem={({
@@ -60,7 +60,7 @@ export function InstalledPluginMasonryFlashList({
plugins: (readonly [AnyPlugin, InternalPluginMeta])[]
}) {
const numColumns = useNumColumns()
- const resetTooltips = useResetTooltips()
+ const hideTooltips = useHidePluginTooltips()
return (
plugin.manifest.id}
numColumns={numColumns}
@@ -124,7 +124,7 @@ export function BrowsePluginMasonryFlashList({
onInstall: (entry: BrowseEntry) => void
}) {
const numColumns = useNumColumns()
- const resetTooltips = useResetTooltips()
+ const hideTooltips = useHidePluginTooltips()
return (
entry.key}
numColumns={numColumns}
diff --git a/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx b/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx
index 87998c1..bd7b8ab 100644
--- a/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx
+++ b/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx
@@ -37,10 +37,9 @@ import {
import { messageOf, showErrorToast } from '../utils/repos'
import { InstalledPluginSwitch, PluginInfo } from './PluginCard'
import { usePluginEnabled, usePluginStatus } from './PluginStateProvider'
-import {
- EnablePluginTooltipProvider,
- useClickOutsideTooltip,
- useEnablePluginTooltip,
+import PluginTooltipsProvider, {
+ PluginTooltip,
+ usePluginTooltip,
} from './TooltipProvider'
import type { AnyPlugin } from '@revenge-mod/plugins/_'
@@ -61,23 +60,44 @@ export default function PluginOptionsActionSheet({
plugin,
sheetKey,
}: PluginOptionsActionSheetProps) {
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+function PluginOptions({ plugin, sheetKey }: PluginOptionsActionSheetProps) {
const enabled = usePluginEnabled(plugin)
const meta = getInternalPluginMeta(plugin)
const essential = isPluginEssential(meta)
const pendingUpdate = isPluginPendingUpdate(plugin)
const { name, author, description, icon, version } = plugin.manifest
+ const [switchRef, showPendingUpdateTooltip] = usePluginTooltip(
+ PluginTooltip.PendingUpdate,
+ )
+
return (
-
-
-
+ {
+ if (pendingUpdate) showPendingUpdateTooltip()
+ }}
+ ref={switchRef}
+ >
- )
- }
- />
-
-
- {
- ActionSheetActionCreators.hideActionSheet(
- sheetKey,
- )
- }}
- />
-
-
-
-
-
-
+
+ )
+ }
+ />
+ {
+ ActionSheetActionCreators.hideActionSheet(sheetKey)
+ }}
+ />
+
+
+
)
}
@@ -272,8 +286,9 @@ function PluginActions({
plugin: AnyPlugin
closeSheet: () => void
}) {
- const enableTooltip = useEnablePluginTooltip()
- const settingsRef = useClickOutsideTooltip(useEnablePluginTooltip, () => {})
+ const [settingsRef, showEnableTooltip] = usePluginTooltip(
+ PluginTooltip.Enable,
+ )
const meta = getInternalPluginMeta(plugin)
const startable = isPluginStartable(plugin)
const enabled = usePluginEnabled(plugin)
@@ -327,12 +342,7 @@ function PluginActions({
{plugin.SettingsComponent && (
{
- if (!startable)
- requestAnimationFrame(() => {
- enableTooltip.targetRef.current =
- settingsRef.current
- enableTooltip.setVisible(true)
- })
+ if (!startable) showEnableTooltip()
}}
>
- setVisible: (visible: boolean) => void
+export const PluginTooltip = {
+ /** Shown when an action requires the plugin to be enabled first. */
+ Enable: 'enable',
+ /** Shown when an action is blocked because the plugin is essential. */
+ Essential: 'essential',
+ /** Shown when an action is blocked because the plugin has a pending update. */
+ PendingUpdate: 'pendingUpdate',
+} as const
+
+export type PluginTooltip = (typeof PluginTooltip)[keyof typeof PluginTooltip]
+
+const Labels: Record = {
+ [PluginTooltip.Enable]: 'Plugin must be enabled first',
+ [PluginTooltip.Essential]: 'Plugin needed for Revenge to function properly',
+ [PluginTooltip.PendingUpdate]:
+ 'Plugin has a pending update, reload to apply it',
}
-const EnablePluginTooltipContext = createContext(
- null,
-)
-const EssentialPluginTooltipContext = createContext(
+interface PluginTooltipsContextValue {
+ show: (tooltip: PluginTooltip, target: View | null) => void
+ hide: () => void
+}
+
+const PluginTooltipsContext = createContext(
null,
)
-export function EnablePluginTooltipProvider({
+export default function PluginTooltipsProvider({
children,
}: {
children: React.ReactNode
}) {
- const [visible, setVisible] = useState(false)
- const targetRef = useRef(null)
+ const [visible, setVisible] = useState(null)
- useTooltip(targetRef, {
- label: 'Plugin must be enabled first',
+ // Targets are assigned by `show`, right before the tooltip becomes visible
+ const enableTarget = useRef(null)
+ const essentialTarget = useRef(null)
+ const pendingUpdateTarget = useRef(null)
+
+ useTooltip(enableTarget, {
+ label: Labels[PluginTooltip.Enable],
position: 'top',
- visible,
+ visible: visible === PluginTooltip.Enable,
})
- return (
-
- {children}
-
- )
-}
-
-export function EssentialPluginTooltipProvider({
- children,
-}: {
- children: React.ReactNode
-}) {
- const [visible, setVisible] = useState(false)
- const targetRef = useRef(null)
-
- useTooltip(targetRef, {
- label: 'Plugin needed for Revenge to function properly',
+ useTooltip(essentialTarget, {
+ label: Labels[PluginTooltip.Essential],
position: 'top',
- visible,
+ visible: visible === PluginTooltip.Essential,
})
- return (
-
- {children}
-
- )
-}
+ useTooltip(pendingUpdateTarget, {
+ label: Labels[PluginTooltip.PendingUpdate],
+ position: 'top',
+ visible: visible === PluginTooltip.PendingUpdate,
+ })
-export function useEnablePluginTooltip() {
- const context = useContext(EnablePluginTooltipContext)
- if (!context) {
- throw new Error(
- 'useEnablePluginTooltip must be used within EnablePluginTooltipProvider',
- )
- }
- return context
-}
-
-export function useEssentialPluginTooltip() {
- const context = useContext(EssentialPluginTooltipContext)
- if (!context) {
- throw new Error(
- 'useEssentialPluginTooltip must be used within EssentialPluginTooltipProvider',
- )
- }
- return context
-}
-
-export function useResetTooltips() {
- const enableTooltip = useContext(EnablePluginTooltipContext)
- const essentialTooltip = useContext(EssentialPluginTooltipContext)
-
- return () => {
- enableTooltip?.setVisible(false)
- essentialTooltip?.setVisible(false)
- }
-}
-
-export function useClickOutsideTooltip(
- contextHook: () => TooltipContextValue,
- onClickOutside: () => void,
-) {
- const { targetRef, setVisible } = contextHook()
-
- const ref = useClickOutside(() => {
- setVisible(false)
- if (targetRef.current === ref.current) {
- onClickOutside()
+ const value = useMemo(() => {
+ const targets = {
+ [PluginTooltip.Enable]: enableTarget,
+ [PluginTooltip.Essential]: essentialTarget,
+ [PluginTooltip.PendingUpdate]: pendingUpdateTarget,
}
- })
- return ref
+ return {
+ show: (tooltip, target) => {
+ targets[tooltip].current = target
+ setVisible(tooltip)
+ },
+ hide: () => setVisible(null),
+ }
+ }, [])
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+/**
+ * Binds an element to a plugin tooltip.
+ *
+ * @param tooltip The tooltip to show for the element.
+ * @returns The ref to attach to the element, and a callback showing the tooltip on it.
+ */
+export function usePluginTooltip(tooltip: PluginTooltip) {
+ const context = useContext(PluginTooltipsContext)
+ if (!context)
+ throw new Error(
+ 'usePluginTooltip must be used within PluginTooltipsProvider',
+ )
+
+ const { show, hide } = context
+
+ // Tapping anywhere else dismisses the tooltip
+ const ref = useClickOutside(hide)
+
+ const showTooltip = useCallback(() => {
+ // Tapping the element itself also counts as a tap outside of it (the
+ // press target wraps the ref), so showing waits for the next frame
+ requestAnimationFrame(() => {
+ show(tooltip, ref.current)
+ })
+ }, [show, tooltip, ref])
+
+ return [ref, showTooltip] as const
+}
+
+/** Hides the visible plugin tooltip, if any.. */
+export function useHidePluginTooltips() {
+ const context = useContext(PluginTooltipsContext)
+
+ return useCallback(() => context?.hide(), [context])
}
diff --git a/src/plugins/start/settings.plugins/screens/RevengePluginsBrowseSettingScreen.tsx b/src/plugins/start/settings.plugins/screens/RevengePluginsBrowseSettingScreen.tsx
index 6018f38..20a9625 100644
--- a/src/plugins/start/settings.plugins/screens/RevengePluginsBrowseSettingScreen.tsx
+++ b/src/plugins/start/settings.plugins/screens/RevengePluginsBrowseSettingScreen.tsx
@@ -13,13 +13,9 @@ import {
import { debounce } from '@revenge-mod/utils/callback'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { View } from 'react-native'
-import { ClickOutsideProvider } from 'react-native-click-outside'
import { BrowsePluginMasonryFlashList } from '../components/PluginList'
import PluginStatesProvider from '../components/PluginStateProvider'
-import {
- EnablePluginTooltipProvider,
- EssentialPluginTooltipProvider,
-} from '../components/TooltipProvider'
+import PluginTooltipsProvider from '../components/TooltipProvider'
import { runInstallFlow } from '../utils/repos'
import type { RepoPluginListing } from '@revenge-mod/plugins/_/repositories'
import type { BrowseSortKey } from '../components/BrowseFilterAndSortActionSheet'
@@ -34,17 +30,13 @@ const SearchDebounceTime = 100
export default function RevengePluginsBrowseSettingScreen() {
return (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)
}
diff --git a/src/plugins/start/settings.plugins/screens/RevengePluginsSettingScreen.tsx b/src/plugins/start/settings.plugins/screens/RevengePluginsSettingScreen.tsx
index 505c335..b984dcf 100644
--- a/src/plugins/start/settings.plugins/screens/RevengePluginsSettingScreen.tsx
+++ b/src/plugins/start/settings.plugins/screens/RevengePluginsSettingScreen.tsx
@@ -28,14 +28,10 @@ import {
useState,
} from 'react'
import { Image, View } from 'react-native'
-import { ClickOutsideProvider } from 'react-native-click-outside'
import RevengeIcon from '~assets/RevengeIcon'
import { InstalledPluginMasonryFlashList } from '../components/PluginList'
import PluginStatesProvider from '../components/PluginStateProvider'
-import {
- EnablePluginTooltipProvider,
- EssentialPluginTooltipProvider,
-} from '../components/TooltipProvider'
+import PluginTooltipsProvider from '../components/TooltipProvider'
import { RouteNames, Setting } from '../constants'
import type { NavigationProp, RouteProp } from '@react-navigation/core'
import type { ReactNavigationParamList } from '@revenge-mod/externals/react-navigation'
@@ -51,17 +47,13 @@ const PlusLargeIcon = getAssetIdByName('PlusLargeIcon')!
export default function RevengePluginsSettingScreen() {
return (
-
-
+
+
-
-
-
-
-
+
-
-
+
+
)
}