From 348b1a5ed0488366c993267b822c681c3585a53b Mon Sep 17 00:00:00 2001 From: Tickbase Date: Thu, 30 Apr 2026 21:00:09 +0200 Subject: [PATCH] hook up #93 --- src/App.tsx | 35 ++++++++-- src/contexts/AppContext.tsx | 12 +++- src/contexts/AppProvider.tsx | 131 ++++++++++++++++++++++++++++++++++- 3 files changed, 169 insertions(+), 9 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ca09e08..e643923 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,7 @@ import { } from '@/components/dialogs' // Game components -import { GameList } from '@/components/games' +import { GameList, EpicGameList } from '@/components/games' /** * Main application component @@ -71,11 +71,25 @@ function App() { handleSelectCreamLinux, handleSelectSmokeAPI, closeUnlockerDialog, + epicGames, + epicLoading, + epicInstallingId, + loadEpicGames, + handleEpicInstall, + handleEpicUninstallScream, + handleEpicUninstallKoaloader, + handleEpicSettings, } = useAppContext() // Conflict detection - const { conflicts, showDialog, resolveConflict, closeDialog } = - useConflictDetection(games) + const { conflicts, showDialog, resolveConflict, closeDialog } = useConflictDetection(games) + + const handleSetFilter = async (f: string) => { + setFilter(f) + if (f === 'epic' && epicGames.length === 0 && !epicLoading) { + await loadEpicGames() + } + } // Handle conflict resolution const handleConflictResolve = async ( @@ -126,13 +140,22 @@ function App() {
{/* Sidebar for filtering */} - {/* Show error or game list */} - {error ? ( + {filter === 'epic' ? ( + + ) : error ? (

Error Loading Games

{error}

diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx index 342cf95..6cb97be 100644 --- a/src/contexts/AppContext.tsx +++ b/src/contexts/AppContext.tsx @@ -1,5 +1,5 @@ import { createContext } from 'react' -import { Game, DlcInfo } from '@/types' +import { Game, DlcInfo, EpicGame } from '@/types' import { ActionType } from '@/components/buttons/ActionButton' import { DlcDialogState } from '@/hooks/useDlcManager' @@ -49,6 +49,16 @@ export interface AppContextType { handleDlcDialogClose: () => void handleUpdateDlcs: (gameId: string) => Promise + // Epic Games + epicGames: EpicGame[] + epicLoading: boolean + epicInstallingId: string | null + loadEpicGames: () => Promise + handleEpicInstall: (game: EpicGame) => void + handleEpicUninstallScream: (game: EpicGame) => void + handleEpicUninstallKoaloader: (game: EpicGame) => void + handleEpicSettings: (game: EpicGame) => void + // Game actions progressDialog: ProgressDialogState handleGameAction: (gameId: string, action: ActionType) => Promise diff --git a/src/contexts/AppProvider.tsx b/src/contexts/AppProvider.tsx index a7ecb4a..73c5235 100644 --- a/src/contexts/AppProvider.tsx +++ b/src/contexts/AppProvider.tsx @@ -1,11 +1,12 @@ import { ReactNode, useState, useEffect } from 'react' import { AppContext, AppContextType } from './AppContext' import { useGames, useDlcManager, useGameActions, useToasts } from '@/hooks' -import { DlcInfo, Config } from '@/types' +import { DlcInfo, Config, EpicGame } from '@/types' import { ActionType } from '@/components/buttons/ActionButton' import { ToastContainer } from '@/components/notifications' -import { SmokeAPISettingsDialog, OptInDialog, RatingDialog, SmokeAPIVotesDialog } from '@/components/dialogs' +import { SmokeAPISettingsDialog, OptInDialog, RatingDialog, SmokeAPIVotesDialog, EpicUnlockerSelectionDialog, ScreamAPISettingsDialog } from '@/components/dialogs' import { invoke } from '@tauri-apps/api/core' +import { listen } from '@tauri-apps/api/event' // Context provider component interface AppProviderProps { @@ -43,6 +44,20 @@ export const AppProvider = ({ children }: AppProviderProps) => { // Settings dialog state const [settingsDialog, setSettingsDialog] = useState({ visible: false }) + const [epicGames, setEpicGames] = useState([]) + const [epicLoading, setEpicLoading] = useState(false) + const [epicInstallingId, setEpicInstallingId] = useState(null) + + const [epicUnlockerDialog, setEpicUnlockerDialog] = useState<{ + visible: boolean + game: EpicGame | null + }>({ visible: false, game: null }) + + const [screamSettingsDialog, setScreamSettingsDialog] = useState<{ + visible: boolean + game: EpicGame | null + }>({ visible: false, game: null }) + // SmokeAPI settings dialog state const [smokeAPISettingsDialog, setSmokeAPISettingsDialog] = useState<{ visible: boolean @@ -95,6 +110,91 @@ export const AppProvider = ({ children }: AppProviderProps) => { .catch((err) => console.error('Failed to load config for reporting check:', err)) }, []) + useEffect(() => { + let unlisten: (() => void) | undefined + listen('epic-game-updated', (event) => { + const updated = event.payload + const prev = epicGames.find((g) => g.app_name === updated.app_name) + + setEpicGames((games) => + games.map((g) => (g.app_name === updated.app_name ? updated : g)) + ) + setEpicInstallingId(null) + + // Determine what changed and show appropriate toast + if (prev) { + const installedScream = !prev.scream_installed && updated.scream_installed + const uninstalledScream = prev.scream_installed && !updated.scream_installed + const installedKoa = !prev.koaloader_installed && updated.koaloader_installed + const uninstalledKoa = prev.koaloader_installed && !updated.koaloader_installed + + if (installedScream) { + success(`ScreamAPI installed for ${updated.title}`) + } else if (uninstalledScream) { + info(`ScreamAPI removed from ${updated.title}`) + } else if (installedKoa) { + success(`Koaloader installed for ${updated.title}`) + } else if (uninstalledKoa) { + info(`Koaloader removed from ${updated.title}`) + } + + if (updated.proxy_fallback_used) { + warning( + 'No compatible proxy import found - installed using version.dll as a fallback. ' + + 'If the game has issues, try the direct ScreamAPI method instead.' + ) + } + } + }).then((fn) => { unlisten = fn }) + return () => { unlisten?.() } + }, [epicGames, success, info, warning]) + + const loadEpicGames = async () => { + setEpicLoading(true) + try { + const games = await invoke('scan_epic_games') + setEpicGames(games) + } catch (e) { + showError(`Failed to scan Epic games: ${e}`) + } finally { + setEpicLoading(false) + } + } + + const runEpicAction = async (game: EpicGame, action: string) => { + setEpicInstallingId(game.app_name) + try { + await invoke('process_epic_action', { epicAction: { game, action } }) + // state updated via epic-game-updated event listener + } catch (e) { + showError(`Action failed: ${e}`) + setEpicInstallingId(null) + } + } + + const handleEpicInstall = (game: EpicGame) => { + setEpicUnlockerDialog({ visible: true, game }) + } + + const handleEpicUninstallScream = (game: EpicGame) => runEpicAction(game, 'uninstall_scream') + const handleEpicUninstallKoaloader = (game: EpicGame) => runEpicAction(game, 'uninstall_koaloader') + + const handleEpicSettings = (game: EpicGame) => { + setScreamSettingsDialog({ visible: true, game }) + } + + const handleSelectScreamAPI = () => { + const game = epicUnlockerDialog.game + setEpicUnlockerDialog({ visible: false, game: null }) + if (game) runEpicAction(game, 'install_scream') + } + + const handleSelectKoaloader = () => { + const game = epicUnlockerDialog.game + setEpicUnlockerDialog({ visible: false, game: null }) + if (game) runEpicAction(game, 'install_koaloader') + } + // Settings handlers const handleSettingsOpen = () => { setSettingsDialog({ visible: true }) @@ -366,6 +466,16 @@ export const AppProvider = ({ children }: AppProviderProps) => { handleDlcDialogClose: closeDlcDialog, handleUpdateDlcs: (gameId: string) => handleUpdateDlcs(gameId), + // Epic games + epicGames, + epicLoading, + epicInstallingId, + loadEpicGames, + handleEpicInstall, + handleEpicUninstallScream, + handleEpicUninstallKoaloader, + handleEpicSettings, + // Game actions progressDialog, handleGameAction, @@ -458,6 +568,23 @@ export const AppProvider = ({ children }: AppProviderProps) => { gameTitle={smokeAPISettingsDialog.gameTitle} /> + {/* Epic Unlocker Selection Dialog */} + setEpicUnlockerDialog({ visible: false, game: null })} + onSelectScreamAPI={handleSelectScreamAPI} + onSelectKoaloader={handleSelectKoaloader} + /> + + {/* ScreamAPI Settings Dialog */} + setScreamSettingsDialog({ visible: false, game: null })} + gamePath={screamSettingsDialog.game?.install_path ?? ''} + gameTitle={screamSettingsDialog.game?.title ?? ''} + /> + {/* SmokeAPI Votes Dialog */}