diff --git a/lib/discord/src/modules/settings/_internal.ts b/lib/discord/src/modules/settings/_internal.ts index 0d9f252..ff4bf26 100644 --- a/lib/discord/src/modules/settings/_internal.ts +++ b/lib/discord/src/modules/settings/_internal.ts @@ -1,3 +1,4 @@ +import { noop } from '@revenge-mod/utils/callback' import type { SettingsItem, SettingsModulesLoadedSubscription, @@ -10,3 +11,10 @@ export const sSections: Record = {} export const sConfig: Record = {} export const sSubscriptions = new Set() + +export const sRefresher = { + navigator: false, + overviewScreen: false, + callNavigator: noop, + callOverviewScreen: noop, +} diff --git a/lib/discord/src/modules/settings/index.ts b/lib/discord/src/modules/settings/index.ts index 3142225..0d0f0ba 100644 --- a/lib/discord/src/modules/settings/index.ts +++ b/lib/discord/src/modules/settings/index.ts @@ -1,11 +1,6 @@ -import { ReactNavigationNative } from '@revenge-mod/externals/react-navigation' import { noop } from '@revenge-mod/utils/callback' -import { findInTree } from '@revenge-mod/utils/tree' -import { Constants } from '../../common' import { sLoaded } from '../../start' -import { RootNavigationRef } from '../main_tabs_v2' -import { sConfig, sSections, sSubscriptions } from './_internal' -import type { NavigationState, PartialState } from '@react-navigation/core' +import { sConfig, sRefresher, sSections, sSubscriptions } from './_internal' import type { DiscordModules } from '../../types' export type SettingsItem = DiscordModules.Modules.Settings.SettingsItem @@ -101,71 +96,18 @@ export function addSettingsItemToSection(key: string, item: string) { } } -const { CommonActions, StackActions } = ReactNavigationNative +/** + * Refreshes the SettingsOverviewScreen. + */ +export function refreshSettingsOverviewScreen() { + sRefresher.overviewScreen = true + sRefresher.callOverviewScreen() +} /** - * Refreshes the SettingsOverviewScreen, applying any changes made to settings modules. - * - * @param renavigate Whether to renavigate instead of replacing the screen in the stack. - * @returns Whether the SettingsOverviewScreen was refreshed. + * Refreshes the settings navigator. */ -export async function refreshSettingsOverviewScreen(renavigate?: boolean) { - const navigation = RootNavigationRef.getRootNavigationRef() - if (!navigation.isReady()) return - - let state = navigation.getRootState() - // State with SettingsOverviewScreen - let settingsState = findInTree(state, isNavigationSettingsState) - // We're currently not on the settings screen, so we don't need to reset - if (!settingsState) return - - if (renavigate) { - const mainState = findInTree(state, isNavigationMainState) - if (!mainState) return - - navigation.dispatch({ - ...CommonActions.goBack(), - target: mainState.key, - }) - - navigation.navigate(mainState.routes[mainState.index].name) - - // Wait for navigation to complete and reset to the settings state - requestAnimationFrame(() => { - navigation.reset({ - index: settingsState!.routes.length - 1, - routes: settingsState!.routes, - } as PartialState>) - }) - } else { - requestAnimationFrame(() => { - // Get updated state - state = navigation.getRootState() - settingsState = findInTree(state, isNavigationSettingsState) - if (!settingsState) return - - const { - key: target, - routes: [{ name, key: source }], - } = settingsState - - navigation.dispatch({ - ...StackActions.replace(name), - source, - target, - }) - }) - } -} - -function isNavigationMainState(state: any): state is NavigationState { - return Array.isArray(state.routes) && state.routes.length > 1 -} - -function isNavigationSettingsState(state: any): state is NavigationState { - return ( - Array.isArray(state.routes) && - state.routes[0]?.name === - (Constants.UserSettingsSections as Record).OVERVIEW - ) +export function refreshSettingsNavigator() { + sRefresher.navigator = true + sRefresher.callNavigator() } diff --git a/src/plugins/init/settings/index.ts b/src/plugins/init/settings/index.ts index ea56c44..cb2468e 100644 --- a/src/plugins/init/settings/index.ts +++ b/src/plugins/init/settings/index.ts @@ -1,20 +1,22 @@ -import { sSections } from '@revenge-mod/discord/_/modules/settings' +import { sRefresher, sSections } from '@revenge-mod/discord/_/modules/settings' import { onSettingsModulesLoaded } from '@revenge-mod/discord/modules/settings' import { byName } from '@revenge-mod/modules/finders/filters' -import { waitForModules } from '@revenge-mod/modules/finders/wait' -import { after } from '@revenge-mod/patcher' +import { + waitForModuleByImportedPath, + waitForModules, +} from '@revenge-mod/modules/finders/wait' +import { instead } from '@revenge-mod/patcher' import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_' import { PluginFlags } from '@revenge-mod/plugins/constants' +import { useEffect } from 'react' import type { FC } from 'react' import './register-routes' +import { React } from '@revenge-mod/react' +import { noop } from '@revenge-mod/utils/callback' +import { useReRender } from '@revenge-mod/utils/react' -// Always register before so setting modules that depend on the Revenge section can be registered properly -onSettingsModulesLoaded(() => { - require('./register') -}) - -registerPlugin( +const pluginSettings = registerPlugin( { id: 'revenge.settings', name: 'Settings', @@ -24,43 +26,22 @@ registerPlugin( }, { start() { + onSettingsModulesLoaded(() => { + require('./register') + }) + + waitForModuleByImportedPath( + 'modules/main_tabs_v2/native/settings/SettingsNavigator.tsx', + exports => { + patchSettingsNavigator(exports) + }, + ) + const unsubSOS = waitForModules( byName('SettingsOverviewScreen'), exports => { unsubSOS() - - const customSections = sSections - - after(exports as { default: FC }, 'default', tree => { - const { - props: { sections }, - } = tree as { - props: { - sections: Array<{ - label: string - settings: string[] - }> - } - } - - // Check if we even have custom sections - const firstCustomSection = - customSections[Object.keys(customSections)[0]] - if (!firstCustomSection) return tree - - // Check if sections are already spliced - const [firstCustomItem] = firstCustomSection.settings - if ( - sections.findIndex(section => - section.settings.includes(firstCustomItem), - ) < 0 - ) - for (const section of Object.values(customSections)) - if (!section.index) sections.unshift(section) - else sections.splice(section.index, 0, section) - - return tree - }) + patchSettingsOverviewScreen(exports) }, { returnNamespace: true, @@ -71,3 +52,69 @@ registerPlugin( PluginFlags.Enabled, InternalPluginFlags.Internal | InternalPluginFlags.Essential, ) + +function patchSettingsNavigator(exports: any) { + instead(exports.default, 'type', (args, orig) => { + const reRender = useReRender() + useEffect(() => { + sRefresher.callNavigator = reRender + return () => { + sRefresher.navigator = false + sRefresher.callNavigator = noop + } + }, [reRender]) + + // useMemo(() => getSettingScreens(), []) + const unpatchMemo = instead(React, 'useMemo', (args, orig) => { + if (!args[1]?.length && sRefresher.navigator) args[1] = undefined + return Reflect.apply(orig, React, args) + }) + + const fiber = Reflect.apply(orig, undefined, args) + unpatchMemo() + return fiber + }) +} + +function patchSettingsOverviewScreen(exports: any) { + instead( + exports as { + default: FC + }, + 'default', + (args, orig) => { + const reRender = useReRender() + useEffect(() => { + sRefresher.callOverviewScreen = reRender + return () => { + sRefresher.overviewScreen = false + sRefresher.callOverviewScreen = noop + } + }, [reRender]) + + // In: SearchableSettingsList + // useMemo(() => { + // return doSettingsBlocklistFiltering(props.sections, canShowNitroSettings) + // }, [canShowNitroSettings]) + const unpatchMemo = instead(React, 'useMemo', (args, orig) => { + if (sRefresher.overviewScreen) args[1] = undefined + + const sections = Reflect.apply(orig, React, args) + + // Add our custom sections here + for (const section of Object.values(sSections)) + if (section.index) + sections.splice(section.index, 0, section) + else sections.unshift(section) + + return sections + }) + + const tree = Reflect.apply(orig, undefined, args) + unpatchMemo() + return tree + }, + ) +} + +export default pluginSettings