diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 660d7f48..d937e3c4 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -87,7 +87,7 @@ import "./cloud-save/upload-save-game"; import "./cloud-save/delete-game-artifact"; import "./cloud-save/select-game-backup-path"; import "./notifications/publish-new-repacks-notification"; -import "./notifications/update-achievement-notification-window-position"; +import "./notifications/update-achievement-notification-window"; import "./themes/add-custom-theme"; import "./themes/delete-custom-theme"; import "./themes/get-all-custom-themes"; diff --git a/src/main/events/notifications/update-achievement-notification-window-position.ts b/src/main/events/notifications/update-achievement-notification-window-position.ts deleted file mode 100644 index 3ba6d660..00000000 --- a/src/main/events/notifications/update-achievement-notification-window-position.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { registerEvent } from "../register-event"; -import { WindowManager } from "@main/services"; - -const updateAchievementCustomNotificationWindowPosition = async ( - _event: Electron.IpcMainInvokeEvent -) => { - const { x, y } = await WindowManager.getNotificationWindowPosition(); - - WindowManager.notificationWindow?.setPosition(x, y); - - WindowManager.notificationWindow?.webContents.send( - "on-test-achievement-notification" - ); -}; - -registerEvent( - "updateAchievementCustomNotificationWindowPosition", - updateAchievementCustomNotificationWindowPosition -); diff --git a/src/main/events/notifications/update-achievement-notification-window.ts b/src/main/events/notifications/update-achievement-notification-window.ts new file mode 100644 index 00000000..93a2feb7 --- /dev/null +++ b/src/main/events/notifications/update-achievement-notification-window.ts @@ -0,0 +1,29 @@ +import { db, levelKeys } from "@main/level"; +import { registerEvent } from "../register-event"; +import { WindowManager } from "@main/services"; +import { UserPreferences } from "@types"; + +const updateAchievementCustomNotificationWindow = async ( + _event: Electron.IpcMainInvokeEvent +) => { + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + + WindowManager.closeNotificationWindow(); + + if ( + userPreferences.achievementNotificationsEnabled && + userPreferences.achievementCustomNotificationsEnabled !== false + ) { + WindowManager.createNotificationWindow(true); + } +}; + +registerEvent( + "updateAchievementCustomNotificationWindow", + updateAchievementCustomNotificationWindow +); diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 97381743..97746ca0 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -7,11 +7,16 @@ import { findAllAchievementFiles, getAlternativeObjectIds, } from "./find-achivement-files"; -import type { AchievementFile, Game, UnlockedAchievement } from "@types"; +import type { + AchievementFile, + Game, + UnlockedAchievement, + UserPreferences, +} from "@types"; import { achievementsLogger } from "../logger"; import { Cracker } from "@shared"; import { publishCombinedNewAchievementNotification } from "../notifications"; -import { gamesSublevel } from "@main/level"; +import { db, gamesSublevel, levelKeys } from "@main/level"; import { WindowManager } from "../window-manager"; import { sleep } from "@main/helpers"; @@ -186,7 +191,7 @@ export class AchievementWatcherManager { return mergeAchievements(game, unlockedAchievements, false); } - private static preSearchAchievementsWindows = async () => { + private static async getGameAchievementFilesWindows() { const games = await gamesSublevel .values() .all() @@ -196,24 +201,24 @@ export class AchievementWatcherManager { return Promise.all( games.map((game) => { - const gameAchievementFiles: AchievementFile[] = []; + const achievementFiles: AchievementFile[] = []; for (const objectId of getAlternativeObjectIds(game.objectId)) { - gameAchievementFiles.push( + achievementFiles.push( ...(gameAchievementFilesMap.get(objectId) || []) ); - gameAchievementFiles.push( + achievementFiles.push( ...findAchievementFileInExecutableDirectory(game) ); } - return this.preProcessGameAchievementFiles(game, gameAchievementFiles); + return { game, achievementFiles }; }) ); - }; + } - private static preSearchAchievementsWithWine = async () => { + private static async getGameAchievementFilesLinux() { const games = await gamesSublevel .values() .all() @@ -221,45 +226,70 @@ export class AchievementWatcherManager { return Promise.all( games.map((game) => { - const gameAchievementFiles = findAchievementFiles(game); + const achievementFiles = findAchievementFiles(game); const achievementFileInsideDirectory = findAchievementFileInExecutableDirectory(game); - gameAchievementFiles.push(...achievementFileInsideDirectory); + achievementFiles.push(...achievementFileInsideDirectory); - return this.preProcessGameAchievementFiles(game, gameAchievementFiles); + return { game, achievementFiles }; }) ); - }; + } public static async preSearchAchievements() { - await sleep(5000); + await sleep(2000); try { - const newAchievementsCount = + const gameAchievementFiles = process.platform === "win32" - ? await this.preSearchAchievementsWindows() - : await this.preSearchAchievementsWithWine(); + ? await this.getGameAchievementFilesWindows() + : await this.getGameAchievementFilesLinux(); + + const newAchievementsCount: number[] = []; + + for (const { game, achievementFiles } of gameAchievementFiles) { + const result = await this.preProcessGameAchievementFiles( + game, + achievementFiles + ); + + newAchievementsCount.push(result); + } const totalNewGamesWithAchievements = newAchievementsCount.filter( (achievements) => achievements ).length; + const totalNewAchievements = newAchievementsCount.reduce( (acc, val) => acc + val, 0 ); if (totalNewAchievements > 0) { - WindowManager.notificationWindow?.webContents.send( - "on-combined-achievements-unlocked", - totalNewGamesWithAchievements, - totalNewAchievements + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } ); - publishCombinedNewAchievementNotification( - totalNewAchievements, - totalNewGamesWithAchievements - ); + if (userPreferences.achievementNotificationsEnabled) { + if (userPreferences.achievementCustomNotificationsEnabled !== false) { + WindowManager.notificationWindow?.webContents.send( + "on-combined-achievements-unlocked", + totalNewGamesWithAchievements, + totalNewAchievements, + userPreferences.achievementCustomNotificationPosition ?? + "top_left" + ); + } else { + publishCombinedNewAchievementNotification( + totalNewAchievements, + totalNewGamesWithAchievements + ); + } + } } } catch (err) { achievementsLogger.error("Error on preSearchAchievements", err); diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index a06d9652..761c60e6 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -106,20 +106,23 @@ export const mergeAchievements = async ( }; }); - WindowManager.notificationWindow?.webContents.send( - "on-achievement-unlocked", - game.objectId, - game.shop, - achievementsInfo - ); - - publishNewAchievementNotification({ - achievements: achievementsInfo, - unlockedAchievementCount: mergedLocalAchievements.length, - totalAchievementCount: achievementsData.length, - gameTitle: game.title, - gameIcon: game.iconUrl, - }); + if (userPreferences?.achievementCustomNotificationsEnabled !== false) { + WindowManager.notificationWindow?.webContents.send( + "on-achievement-unlocked", + game.objectId, + game.shop, + userPreferences.achievementCustomNotificationPosition ?? "top_left", + achievementsInfo + ); + } else { + publishNewAchievementNotification({ + achievements: achievementsInfo, + unlockedAchievementCount: mergedLocalAchievements.length, + totalAchievementCount: achievementsData.length, + gameTitle: game.title, + gameIcon: game.iconUrl, + }); + } } if (game.remoteId) { diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 5c080c09..807294c7 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -18,7 +18,11 @@ import { HydraApi } from "./hydra-api"; import UserAgent from "user-agents"; import { db, gamesSublevel, levelKeys } from "@main/level"; import { orderBy, slice } from "lodash-es"; -import type { ScreenState, UserPreferences } from "@types"; +import type { + AchievementCustomNotificationPosition, + ScreenState, + UserPreferences, +} from "@types"; import { AuthPage } from "@shared"; import { isStaging } from "@main/constants"; @@ -276,59 +280,44 @@ export class WindowManager { } } - private static readonly NOTIFICATION_WINDOW_WIDTH = 350; - private static readonly NOTIFICATION_WINDOW_HEIGHT = 104; - - public static async getNotificationWindowPosition() { - const userPreferences = await db.get( - levelKeys.userPreferences, - { - valueEncoding: "json", - } - ); + private static readonly NOTIFICATION_WINDOW_WIDTH = 360; + private static readonly NOTIFICATION_WINDOW_HEIGHT = 140; + private static async getNotificationWindowPosition( + position: AchievementCustomNotificationPosition | undefined + ) { const display = screen.getPrimaryDisplay(); const { width, height } = display.workAreaSize; - if ( - userPreferences?.achievementCustomNotificationPosition === "bottom_center" - ) { + if (position === "bottom_center") { return { x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "bottom_right" - ) { + if (position === "bottom_right") { return { x: width - this.NOTIFICATION_WINDOW_WIDTH, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "top_center" - ) { + if (position === "top_center") { return { x: (width - this.NOTIFICATION_WINDOW_WIDTH) / 2, y: 0, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "bottom_left" - ) { + if (position === "bottom_left") { return { x: 0, y: height - this.NOTIFICATION_WINDOW_HEIGHT, }; } - if ( - userPreferences?.achievementCustomNotificationPosition === "top_right" - ) { + if (position === "top_right") { return { x: width - this.NOTIFICATION_WINDOW_WIDTH, y: 0, @@ -341,8 +330,16 @@ export class WindowManager { }; } - public static async createNotificationWindow() { - const { x, y } = await this.getNotificationWindowPosition(); + public static async createNotificationWindow(showTestNotification = false) { + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + const { x, y } = await this.getNotificationWindowPosition( + userPreferences.achievementCustomNotificationPosition + ); this.notificationWindow = new BrowserWindow({ transparent: true, @@ -372,9 +369,25 @@ export class WindowManager { if (isStaging) { this.notificationWindow?.webContents.openDevTools(); } + + if (showTestNotification) { + setTimeout(() => { + this.notificationWindow?.webContents.send( + "on-test-achievement-notification", + userPreferences.achievementCustomNotificationPosition ?? "top_left" + ); + }, 1000); + } }); } + public static async closeNotificationWindow() { + if (this.notificationWindow) { + this.notificationWindow.close(); + this.notificationWindow = null; + } + } + public static openEditorWindow(themeId: string) { if (this.mainWindow) { const existingWindow = this.editorWindows.get(themeId); diff --git a/src/preload/index.ts b/src/preload/index.ts index 966d7f02..48e79d36 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -18,6 +18,7 @@ import type { FriendRequestSync, ShortcutLocation, ShopAssets, + AchievementCustomNotificationPosition, } from "@types"; import type { AuthPage, CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; @@ -410,39 +411,63 @@ contextBridge.exposeInMainWorld("electron", { cb: ( objectId: string, shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] + position: AchievementCustomNotificationPosition, + achievements?: { + displayName: string; + iconUrl: string; + isHidden: boolean; + isRare: boolean; + isPlatinum: boolean; + }[] ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, objectId: string, shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] - ) => cb(objectId, shop, achievements); + position: AchievementCustomNotificationPosition, + achievements?: { + displayName: string; + iconUrl: string; + isHidden: boolean; + isRare: boolean; + isPlatinum: boolean; + }[] + ) => cb(objectId, shop, position, achievements); ipcRenderer.on("on-achievement-unlocked", listener); return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); }, onCombinedAchievementsUnlocked: ( - cb: (gameCount: number, achievementsCount: number) => void + cb: ( + gameCount: number, + achievementsCount: number, + position: AchievementCustomNotificationPosition + ) => void ) => { const listener = ( _event: Electron.IpcRendererEvent, gameCount: number, - achievementCount: number - ) => cb(gameCount, achievementCount); + achievementCount: number, + position: AchievementCustomNotificationPosition + ) => cb(gameCount, achievementCount, position); ipcRenderer.on("on-combined-achievements-unlocked", listener); return () => ipcRenderer.removeListener("on-combined-achievements-unlocked", listener); }, - onTestAchievementNotification: (cb: () => void) => { - const listener = (_event: Electron.IpcRendererEvent) => cb(); + onTestAchievementNotification: ( + cb: (position: AchievementCustomNotificationPosition) => void + ) => { + const listener = ( + _event: Electron.IpcRendererEvent, + position: AchievementCustomNotificationPosition + ) => cb(position); ipcRenderer.on("on-test-achievement-notification", listener); return () => ipcRenderer.removeListener("on-test-achievement-notification", listener); }, - updateAchievementCustomNotificationWindowPosition: () => - ipcRenderer.invoke("updateAchievementCustomNotificationWindowPosition"), + updateAchievementCustomNotificationWindow: () => + ipcRenderer.invoke("updateAchievementCustomNotificationWindow"), /* Themes */ addCustomTheme: (theme: Theme) => ipcRenderer.invoke("addCustomTheme", theme), diff --git a/src/renderer/src/assets/icons/ellipses.png b/src/renderer/src/assets/icons/ellipses.png new file mode 100644 index 00000000..9c8eca1d Binary files /dev/null and b/src/renderer/src/assets/icons/ellipses.png differ diff --git a/src/renderer/src/assets/icons/trophy.png b/src/renderer/src/assets/icons/trophy.png new file mode 100644 index 00000000..73ae25e0 Binary files /dev/null and b/src/renderer/src/assets/icons/trophy.png differ diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 0f650114..ac3bc9d2 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -35,6 +35,7 @@ import type { CatalogueSearchResult, ShopAssets, ShopDetailsWithAssets, + AchievementCustomNotificationPosition, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type disk from "diskusage"; @@ -326,14 +327,27 @@ declare global { cb: ( objectId: string, shop: GameShop, - achievements?: { displayName: string; iconUrl: string }[] + position: AchievementCustomNotificationPosition, + achievements?: { + displayName: string; + iconUrl: string; + isHidden: boolean; + isRare: boolean; + isPlatinum: boolean; + }[] ) => void ) => () => Electron.IpcRenderer; onCombinedAchievementsUnlocked: ( - cb: (gameCount: number, achievementCount: number) => void + cb: ( + gameCount: number, + achievementCount: number, + position: AchievementCustomNotificationPosition + ) => void ) => () => Electron.IpcRenderer; - onTestAchievementNotification: (cb: () => void) => Electron.IpcRenderer; - updateAchievementCustomNotificationWindowPosition: () => Promise; + onTestAchievementNotification: ( + cb: (position: AchievementCustomNotificationPosition) => void + ) => Electron.IpcRenderer; + updateAchievementCustomNotificationWindow: () => Promise; /* Themes */ addCustomTheme: (theme: Theme) => Promise; diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.scss b/src/renderer/src/pages/achievements/notification/achievement-notification.scss index 51f90432..c919275f 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.scss +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.scss @@ -1,35 +1,221 @@ @use "../../../scss/globals.scss"; -@keyframes achievement-in { +$margin-horizontal: 40px; +$margin-vertical: 52px; + +@keyframes content-in { 0% { - transform: translateY(-240px); + width: 80px; + opacity: 0; + transform: scale(0); } 100% { - transform: translateY(0); + width: 80px; + opacity: 1; + transform: scale(1); } } -@keyframes achievement-out { +@keyframes content-wait { 0% { - transform: translateY(0); + width: 80px; } 100% { - transform: translateY(-240px); + width: 80px; + } +} + +@keyframes trophy-out { + 0% { + opacity: 1; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +@keyframes ellipses-stand-by { + 0% { + opacity: 1; + } + 100% { + opacity: 1; + } +} +@keyframes ellipses-out { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + scale: 1.5; + } +} + +@keyframes content-expand { + 0% { + width: 80px; + } + 100% { + width: calc(360px - $margin-horizontal); + } +} + +@keyframes title-in { + 0% { + transform: translateY(10px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes description-in { + 0% { + transform: translateY(20px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes dark-overlay { + 0% { + opacity: 0.7; + } + 50% { + opacity: 0.7; + } + 100% { + opacity: 0; + } +} + +@keyframes content-out { + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(-20px); + opacity: 0; } } .achievement-notification { - margin-top: 24px; - margin-left: 24px; - animation-duration: 1s; - height: 60px; - display: flex; - animation-name: achievement-in; - transform: translateY(0); + position: relative; + display: grid; + width: calc(360px - $margin-horizontal); + height: 140px; + overflow: hidden; + animation: + content-in 450ms ease-in-out, + content-wait 450ms ease-in-out 450ms, + content-expand 450ms ease-in-out 900ms; + + &::before { + content: ""; + position: absolute; + top: 8px; + left: 8px; + width: 64px; + height: 64px; + opacity: 0; + z-index: 1; + background: url("/src/assets/icons/ellipses.png"); + background-size: contain; + animation: ellipses-out 900ms ease-in-out; + } + + &::after { + content: ""; + position: absolute; + top: 0px; + width: 80px; + height: 80px; + opacity: 0; + background: url("/src/assets/icons/trophy.png") no-repeat center; + animation: trophy-out 900ms ease-in-out; + } + + &.top_left { + margin: $margin-vertical 0 0 $margin-horizontal; + } + + &.top_center { + margin: $margin-vertical 0 0 $margin-horizontal; + } + + &.top_right { + margin: $margin-vertical $margin-horizontal 0 0; + align-self: end; + } + + &.bottom_left { + margin: 0 0 $margin-vertical $margin-horizontal; + } + + &.bottom_center { + margin: 0 0 $margin-vertical $margin-horizontal; + } + + &.bottom_right { + margin: 0 $margin-horizontal $margin-vertical 0; + align-self: end; + } &.closing { - animation-name: achievement-out; - transform: translateY(-240px); + transform: translateY(-20px); + opacity: 0; + animation: content-out 450ms ease-in-out; + } + + &__container { + width: calc(360px - $margin-horizontal); + max-width: 100%; + border: 1px solid #ffffff1a; + display: flex; + padding: 8px 16px 8px 8px; + background: globals.$background-color; + transform: translateY(0); + box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.25); + + &.top_left { + align-self: flex-start; + justify-self: flex-start; + } + + &.top_center { + align-self: flex-start; + justify-self: center; + } + + &.top_right { + align-self: flex-start; + justify-self: flex-end; + } + + &.bottom_left { + align-self: flex-end; + justify-self: flex-start; + } + + &.bottom_center { + align-self: flex-end; + justify-self: center; + } + + &.bottom_right { + align-self: flex-end; + justify-self: flex-end; + } } &__content { @@ -37,7 +223,51 @@ flex-direction: row; gap: 8px; align-items: center; - background: globals.$background-color; - padding-right: 8px; + width: 100%; + overflow: hidden; + position: relative; + + &::after { + content: ""; + position: absolute; + width: 64px; + height: 64px; + background: #000; + opacity: 0; + animation: dark-overlay 900ms ease-in-out; + } + } + + &__icon { + min-width: 64px; + min-height: 64px; + border-radius: 2px; + flex: 1; + position: relative; + } + + &__text-container { + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; + } + + &__title { + font-size: 14px; + font-weight: 700; + white-space: nowrap; + overflow: hidden; + color: globals.$muted-color; + animation: title-in 450ms ease-in-out 900ms; + } + + &__description { + font-size: 14px; + font-weight: 400; + white-space: nowrap; + overflow: hidden; + color: globals.$body-color; + animation: description-in 450ms ease-in-out 900ms; } } diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 49f06888..271c42ea 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -3,6 +3,7 @@ import achievementSound from "@renderer/assets/audio/achievement.wav"; import { useTranslation } from "react-i18next"; import cn from "classnames"; import "./achievement-notification.scss"; +import { AchievementCustomNotificationPosition } from "@types"; interface AchievementInfo { displayName: string; @@ -16,6 +17,8 @@ export function AchievementNotification() { const [isClosing, setIsClosing] = useState(false); const [isVisible, setIsVisible] = useState(false); + const [position, setPosition] = + useState("top_left"); const [achievements, setAchievements] = useState([]); const [currentAchievement, setCurrentAchievement] = @@ -33,9 +36,11 @@ export function AchievementNotification() { useEffect(() => { const unsubscribe = window.electron.onCombinedAchievementsUnlocked( - (gameCount, achievementCount) => { + (gameCount, achievementCount, position) => { if (gameCount === 0 || achievementCount === 0) return; + setPosition(position); + setAchievements([ { displayName: t("new_achievements_unlocked", { @@ -58,9 +63,10 @@ export function AchievementNotification() { useEffect(() => { const unsubscribe = window.electron.onAchievementUnlocked( - (_object, _shop, achievements) => { + (_object, _shop, position, achievements) => { if (!achievements?.length) return; + setPosition(position); setAchievements((ach) => ach.concat(achievements)); playAudio(); @@ -73,19 +79,22 @@ export function AchievementNotification() { }, [playAudio]); useEffect(() => { - const unsubscribe = window.electron.onTestAchievementNotification(() => { - setAchievements((ach) => - ach.concat([ - { - displayName: "Test Achievement", - iconUrl: - "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fc.tenor.com%2FRwKr7hVnXREAAAAC%2Fnyan-cat.gif&f=1&nofb=1&ipt=706fd8b00cbfb5b2d2621603834d5f32c0f34cce7113de228d2fcc2247a80318", - }, - ]) - ); + const unsubscribe = window.electron.onTestAchievementNotification( + (position) => { + setPosition(position); + setAchievements((ach) => + ach.concat([ + { + displayName: "Test Achievement", + iconUrl: + "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fc.tenor.com%2FRwKr7hVnXREAAAAC%2Fnyan-cat.gif&f=1&nofb=1&ipt=706fd8b00cbfb5b2d2621603834d5f32c0f34cce7113de228d2fcc2247a80318", + }, + ]) + ); - playAudio(); - }); + playAudio(); + } + ); return () => { unsubscribe(); @@ -104,7 +113,7 @@ export function AchievementNotification() { const zero = performance.now(); closingAnimation.current = requestAnimationFrame( function animateClosing(time) { - if (time - zero <= 1000) { + if (time - zero <= 450) { closingAnimation.current = requestAnimationFrame(animateClosing); } else { setIsVisible(false); @@ -147,18 +156,30 @@ export function AchievementNotification() { return (
-
- {currentAchievement.displayName} -
-

{t("achievement_unlocked")}

-

{currentAchievement.displayName}

+
+
+ {currentAchievement.displayName} +
+

+ {t("achievement_unlocked")} +

+

+ {currentAchievement.displayName} +

+
diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index b04c57cc..8d9de622 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -119,10 +119,10 @@ export function SettingsGeneral() { const achievementCustomNotificationPositionOptions = useMemo(() => { return [ "top_left", + "top_center", "top_right", "bottom_left", "bottom_right", - "top_center", "bottom_center", ].map((position) => ({ key: position, @@ -152,7 +152,7 @@ export function SettingsGeneral() { await handleChange({ achievementCustomNotificationPosition: value }); - window.electron.updateAchievementCustomNotificationWindowPosition(); + window.electron.updateAchievementCustomNotificationWindow(); }; const handleChooseDownloadsPath = async () => { @@ -251,24 +251,28 @@ export function SettingsGeneral() { - handleChange({ + onChange={async () => { + await handleChange({ achievementNotificationsEnabled: !form.achievementNotificationsEnabled, - }) - } + }); + + window.electron.updateAchievementCustomNotificationWindow(); + }} /> - handleChange({ + onChange={async () => { + await handleChange({ achievementCustomNotificationsEnabled: !form.achievementCustomNotificationsEnabled, - }) - } + }); + + window.electron.updateAchievementCustomNotificationWindow(); + }} /> {form.achievementNotificationsEnabled &&