diff --git a/src/main/main.ts b/src/main/main.ts index 82f682c7..456d5d8f 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,9 +1,5 @@ import { DownloadManager, Ludusavi, startMainLoop } from "./services"; -import { - downloadQueueRepository, - gameRepository, - userPreferencesRepository, -} from "./repository"; +import { downloadQueueRepository, gameRepository } from "./repository"; import { UserPreferences } from "./entity"; import { RealDebridClient } from "./services/download/real-debrid"; import { HydraApi } from "./services/hydra-api"; diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 6a1eb11c..2cc7a4f4 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -144,7 +144,7 @@ const processAchievementFileDiff = async ( export class AchievementWatcherManager { private static hasFinishedMergingWithRemote = false; - public static watchAchievements = () => { + public static watchAchievements() { if (!this.hasFinishedMergingWithRemote) return; if (process.platform === "win32") { @@ -152,12 +152,12 @@ export class AchievementWatcherManager { } return watchAchievementsWithWine(); - }; + } - private static preProcessGameAchievementFiles = ( + private static preProcessGameAchievementFiles( game: Game, gameAchievementFiles: AchievementFile[] - ) => { + ) { const unlockedAchievements: UnlockedAchievement[] = []; for (const achievementFile of gameAchievementFiles) { const parsedAchievements = parseAchievementFile( @@ -185,9 +185,9 @@ export class AchievementWatcherManager { } return mergeAchievements(game, unlockedAchievements, false); - }; + } - private static preSearchAchievementsWindows = async () => { + private static async preSearchAchievementsWindows() { const games = await gameRepository.find({ where: { isDeleted: false, @@ -213,9 +213,9 @@ export class AchievementWatcherManager { return this.preProcessGameAchievementFiles(game, gameAchievementFiles); }) ); - }; + } - private static preSearchAchievementsWithWine = async () => { + private static async preSearchAchievementsWithWine() { const games = await gameRepository.find({ where: { isDeleted: false, @@ -233,9 +233,9 @@ export class AchievementWatcherManager { return this.preProcessGameAchievementFiles(game, gameAchievementFiles); }) ); - }; + } - public static preSearchAchievements = async () => { + public static async preSearchAchievements() { try { const newAchievementsCount = process.platform === "win32" @@ -261,5 +261,5 @@ export class AchievementWatcherManager { } this.hasFinishedMergingWithRemote = true; - }; + } } diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index f3e2541b..e33cd6ec 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -11,6 +11,7 @@ import { achievementSoundPath } from "@main/constants"; import icon from "@resources/icon.png?asset"; import { NotificationOptions, toXmlString } from "./xml"; import { logger } from "../logger"; +import { WindowManager } from "../window-manager"; async function downloadImage(url: string | null) { if (!url) return undefined; @@ -93,7 +94,9 @@ export const publishCombinedNewAchievementNotification = async ( toastXml: toXmlString(options), }).show(); - if (process.platform !== "linux") { + if (WindowManager.mainWindow) { + WindowManager.mainWindow.webContents.send("on-achievement-unlocked"); + } else if (process.platform !== "linux") { sound.play(achievementSoundPath); } }; @@ -140,7 +143,9 @@ export const publishNewAchievementNotification = async (info: { toastXml: toXmlString(options), }).show(); - if (process.platform !== "linux") { + if (WindowManager.mainWindow) { + WindowManager.mainWindow.webContents.send("on-achievement-unlocked"); + } else if (process.platform !== "linux") { sound.play(achievementSoundPath); } }; diff --git a/src/preload/index.ts b/src/preload/index.ts index 5f9bc02c..6a3a8758 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -148,6 +148,11 @@ contextBridge.exposeInMainWorld("electron", { return () => ipcRenderer.removeListener("on-library-batch-complete", listener); }, + onAchievementUnlocked: (cb: () => void) => { + const listener = (_event: Electron.IpcRendererEvent) => cb(); + ipcRenderer.on("on-achievement-unlocked", listener); + return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); + }, /* Hardware */ getDiskFreeSpace: (path: string) => diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 8d38b7ad..e461c0cb 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef } from "react"; - +import achievementSound from "@renderer/assets/audio/achievement.wav"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; import { @@ -233,6 +233,22 @@ export function App() { downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); }, [updateRepacks]); + const playAudio = useCallback(() => { + const audio = new Audio(achievementSound); + audio.volume = 0.2; + audio.play(); + }, []); + + useEffect(() => { + const unsubscribe = window.electron.onAchievementUnlocked(() => { + playAudio(); + }); + + return () => { + unsubscribe(); + }; + }, [playAudio]); + const handleToastClose = useCallback(() => { dispatch(closeToast()); }, [dispatch]); diff --git a/src/renderer/src/assets/audio/achievement.wav b/src/renderer/src/assets/audio/achievement.wav new file mode 100644 index 00000000..adf40d74 Binary files /dev/null and b/src/renderer/src/assets/audio/achievement.wav differ diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 31c99df5..f40f1713 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -133,6 +133,7 @@ declare global { minimized: boolean; }) => Promise; authenticateRealDebrid: (apiToken: string) => Promise; + onAchievementUnlocked: (cb: () => void) => () => Electron.IpcRenderer; /* Download sources */ putDownloadSource: (