diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 969ed9a4..59a01fc1 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -49,27 +49,43 @@ export const mergeAchievements = async ( }); if (newAchievements.length) { - WindowManager.mainWindow?.webContents.send( - "on-achievement-unlocked", - objectId, - shop - ); - } - - if (newAchievements.length > 0 && publishNotification) { - const achievement = newAchievements.pop()!; + const achievement = newAchievements.at(-1)!; const achievementInfo = JSON.parse( localGameAchievement?.achievements || "[]" ).find((steamAchievement) => { return achievement.name === steamAchievement.name; }); - publishNewAchievementNotification( - game.title ?? "", + WindowManager.mainWindow?.webContents.send( + "on-achievement-unlocked", + objectId, + shop, achievementInfo.displayName, - achievementInfo.icon, - newAchievements.length + achievementInfo.icon ); + + if (publishNotification) { + WindowManager.notificationWindow?.webContents.send( + "on-achievement-unlocked", + objectId, + shop, + achievementInfo.displayName, + achievementInfo.icon + ); + + WindowManager.notificationWindow?.setBounds({ y: 50 }); + + setTimeout(() => { + WindowManager.notificationWindow?.setBounds({ y: -100 }); + }, 4000); + + publishNewAchievementNotification( + game.title ?? "", + achievementInfo.displayName, + achievementInfo.icon, + newAchievements.length + ); + } } const mergedLocalAchievements = unlockedAchievements.concat(newAchievements); diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 905e4b65..40b0ae9d 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -19,6 +19,7 @@ import { HydraApi } from "./hydra-api"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; + public static notificationWindow: Electron.BrowserWindow | null = null; private static loadURL(hash = "") { // HMR for renderer base on electron-vite cli. @@ -78,7 +79,45 @@ export class WindowManager { app.quit(); } WindowManager.mainWindow?.setProgressBar(-1); + WindowManager.mainWindow = null; }); + + this.notificationWindow = new BrowserWindow({ + transparent: true, + maximizable: false, + minimizable: false, + focusable: true, + skipTaskbar: true, + frame: false, + width: 240, + height: 60, + x: 25, + y: -100, + webPreferences: { + preload: path.join(__dirname, "../preload/index.mjs"), + sandbox: false, + }, + }); + if (!app.isPackaged) + WindowManager.notificationWindow?.webContents.openDevTools(); + + this.notificationWindow.setVisibleOnAllWorkspaces(true, { + visibleOnFullScreen: true, + }); + this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); + + if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { + this.notificationWindow.loadURL( + `${process.env["ELECTRON_RENDERER_URL"]}#/achievement-notification` + ); + } else { + this.notificationWindow.loadFile( + path.join(__dirname, "../renderer/index.html"), + { + hash: "achievement-notification", + } + ); + } } public static openAuthWindow() { diff --git a/src/preload/index.ts b/src/preload/index.ts index 853284b8..ddc72721 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -51,12 +51,21 @@ contextBridge.exposeInMainWorld("electron", { getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), getGameAchievements: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameAchievements", objectId, shop), - onAchievementUnlocked: (cb: (objectId: string, shop: GameShop) => void) => { + onAchievementUnlocked: ( + cb: ( + objectId: string, + shop: GameShop, + displayName: string, + iconUrl: string + ) => void + ) => { const listener = ( _event: Electron.IpcRendererEvent, objectId: string, - shop: GameShop - ) => cb(objectId, shop); + shop: GameShop, + displayName: string, + iconUrl: string + ) => cb(objectId, shop, displayName, iconUrl); ipcRenderer.on("on-achievement-unlocked", listener); return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index fca11e74..082d48e0 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -71,7 +71,12 @@ declare global { shop: GameShop ) => Promise; onAchievementUnlocked: ( - cb: (objectId: string, shop: GameShop) => void + cb: ( + objectId: string, + shop: GameShop, + displayName: string, + iconUrl: string + ) => void ) => () => Electron.IpcRenderer; /* Library */ diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index b98d5ed9..b453b567 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -28,6 +28,7 @@ import { import { store } from "./store"; import resources from "@locales"; +import { Achievemnt } from "./pages/achievement/achievement"; Sentry.init({}); @@ -65,6 +66,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render( + diff --git a/src/renderer/src/pages/achievement/achievement.tsx b/src/renderer/src/pages/achievement/achievement.tsx new file mode 100644 index 00000000..51619524 --- /dev/null +++ b/src/renderer/src/pages/achievement/achievement.tsx @@ -0,0 +1,62 @@ +import { useEffect, useState } from "react"; + +export function Achievemnt() { + const [achievementInfo, setAchievementInfo] = useState<{ + displayName: string; + icon: string; + } | null>(null); + + const [audio, setAudio] = useState(null); + + useEffect(() => { + const unsubscribe = window.electron.onAchievementUnlocked( + (_object, _shop, displayName, icon) => { + console.log("Achievement unlocked", displayName, icon); + setAudio( + "https://us-tuna-sounds-files.voicemod.net/ade71f0d-a41b-4e3a-8097-9f1cc585745c-1646035604239.mp3" + ); + + setAchievementInfo({ + displayName, + icon, + }); + } + ); + + return () => { + unsubscribe(); + }; + }, []); + + useEffect(() => { + if (audio) { + const audioElement = new Audio(audio); + audioElement.volume = 1.0; + audioElement.play(); + setAudio(null); + } + }, [audio]); + + if (!achievementInfo) return

Nada

; + + return ( +
+ {achievementInfo.displayName} +
+

Achievement unlocked

+

{achievementInfo.displayName}

+
+
+ ); +}