From 2cf998542f64683882b098f043e6f111384df571 Mon Sep 17 00:00:00 2001 From: Rosie Val Date: Sun, 9 Aug 2026 00:41:31 -0400 Subject: [PATCH] Add per-plugin Check for updates row --- .../components/PluginOptionsActionSheet.tsx | 91 ++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx b/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx index 0459dcb..bfa09b7 100644 --- a/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx +++ b/src/plugins/start/settings.plugins/components/PluginOptionsActionSheet.tsx @@ -22,7 +22,14 @@ import { runPluginLate, stopPlugin, } from '@revenge-mod/plugins/_' -import { listRepos } from '@revenge-mod/plugins/_/repositories' +import { + installFromRepo, + listRepos, + listUpdates, + planInstall, + refreshRepo, +} from '@revenge-mod/plugins/_/repositories' +import type { RepoUpdate } from '@revenge-mod/plugins/_/repositories' import { PluginStatus } from '@revenge-mod/plugins/constants' import { formatVersion } from '@revenge-mod/plugins/utils' import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord' @@ -34,7 +41,7 @@ import { showPluginClearDataConfirmation, showPluginUninstallConfirmation, } from '../utils/alerts' -import { messageOf, showErrorToast } from '../utils/repos' +import { confirmPlan, messageOf, showErrorToast } from '../utils/repos' import { InstalledPluginSwitch, PluginInfo } from './PluginCard' import { usePluginEnabled, usePluginStatus } from './PluginStateProvider' import { @@ -143,6 +150,85 @@ function StatusSection({ plugin }: { plugin: AnyPlugin }) { ) } +function UpdateRow({ plugin }: { plugin: AnyPlugin }) { + const [busy, setBusy] = useState(false) + const [checked, setChecked] = useState(false) + const [update, setUpdate] = useState(null) + + const meta = getInternalPluginMeta(plugin) + const repoUrl = meta.source?.repo + if (isPluginInternal(meta) || !repoUrl) return null + + const check = async () => { + setBusy(true) + try { + await refreshRepo(repoUrl) + const updates = await listUpdates(repoUrl) + const found = + updates.find(u => u.id === plugin.manifest.id) ?? null + setUpdate(found) + setChecked(true) + } catch (e) { + showErrorToast(messageOf(e)) + } finally { + setBusy(false) + } + } + + const doUpdate = async () => { + if (!update) return + setBusy(true) + try { + const plan = await planInstall( + update.id, + undefined, + update.channel, + ) + const accepted = await confirmPlan(plan) + if (!accepted) return + await installFromRepo(plan) + setUpdate(null) + setChecked(true) + ToastActionCreators.open({ + key: 'revenge-plugin-update-pending', + content: 'Update downloaded. Reload to apply.', + }) + } catch (e) { + showErrorToast(messageOf(e)) + } finally { + setBusy(false) + } + } + + const icon = update + ? 'DownloadIcon' + : checked + ? 'CircleCheckIcon' + : 'RefreshIcon' + + const label = update + ? 'Update' + : checked + ? 'Up to date' + : 'Check for updates' + + const subLabel = update + ? `${update.installed} → ${update.available} (${update.channel})` + : checked + ? 'This plugin is on the latest available version' + : 'Tap to refresh this plugin\'s repository' + + return ( + } + label={label} + subLabel={subLabel} + onPress={update ? doUpdate : check} + disabled={busy} + /> + ) +} + function AdvancedSection({ plugin }: { plugin: AnyPlugin }) { const meta = getInternalPluginMeta(plugin) const dependents = getPluginDependents(plugin, true) @@ -152,6 +238,7 @@ function AdvancedSection({ plugin }: { plugin: AnyPlugin }) { return ( +