revenge-bundle-next/src/plugins/start/settings/index.ts

166 lines
5.1 KiB
TypeScript

import { sRefresher, sSections } from '@revenge-mod/discord/_/modules/settings'
import { onSettingsModulesLoaded } from '@revenge-mod/discord/modules/settings'
import {
waitForModules,
waitForModuleWithImportedPath,
} from '@revenge-mod/modules/finders'
import { withName } from '@revenge-mod/modules/finders/filters'
import { instead } from '@revenge-mod/patcher'
import { InternalPluginFlags, registerPlugin } from '@revenge-mod/plugins/_'
import { PluginFlags } from '@revenge-mod/plugins/constants'
import { React } from '@revenge-mod/react'
import { asap, noop } from '@revenge-mod/utils/callback'
import { getCurrentStack } from '@revenge-mod/utils/error'
import { useReRender } from '@revenge-mod/utils/react'
import { useEffect } from 'react'
import type { FC } from 'react'
let DEBUG_patchedNavigator = false
const pluginSettings = registerPlugin(
{
id: 'revenge.settings',
name: 'Settings',
description: 'Settings UI for Revenge.',
author: 'Revenge',
icon: 'SettingsIcon',
},
{
start() {
// @as-require
import('./plugins')
onSettingsModulesLoaded(() => {
// @as-require
import('./register')
// Debug warnings
asap(() => {
if (__DEV__ && !DEBUG_patchedNavigator)
DEBUG_warnUnpatchedNavigator()
})
})
waitForModuleWithImportedPath(
'modules/user_settings/native/core/SettingsNavigator.tsx',
exports => {
patchSettingsNavigator(exports)
},
)
const unsubSOS = waitForModules(
withName('SettingsOverviewScreen'),
exports => {
unsubSOS()
patchSettingsOverviewScreen(exports)
},
{
cached: true,
returnNamespace: true,
},
)
},
},
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
sRefresher.navigator = false
}
return Reflect.apply(orig, React, args)
})
const el = Reflect.apply(orig, undefined, args)
unpatchMemo()
return el
})
DEBUG_patchedNavigator = true
}
let sectionsInst: object | undefined
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 useOverviewSettings (called by SettingsOverviewScreen):
*
* const hasPremiumSubscriptionToDisplay = useHasPremiumSubscriptionToDisplay()
* const sections = useMemo(() =>
* (...constructed sections array...),
* [hasPremiumSubscriptionToDisplay])
*/
const unpatchMemo = instead(React, 'useMemo', (args, orig) => {
if (sRefresher.overviewScreen) args[1] = undefined
const node = Reflect.apply(orig, React, args)
const sections = node.sections
// Add our custom sections here, and only do this per instance!
if (sectionsInst !== sections) {
for (const section of Object.values(sSections))
if (section.index)
sections.splice(section.index, 0, section)
else sections.unshift(section)
sectionsInst = sections
}
if (sRefresher.overviewScreen) {
node.sections = sectionsInst = [...sections]
sRefresher.overviewScreen = false
}
return node
})
const el = Reflect.apply(orig, undefined, args)
unpatchMemo()
return el
},
)
}
export default pluginSettings
/**
* Warns the developer that SettingsNavigator was not patched.
*/
function DEBUG_warnUnpatchedNavigator() {
nativeLoggingHook(
`\u001b[31mSettingsNavigator was not patched\n${getCurrentStack()}\u001b[0m`,
2,
)
}