feat: achievement notification custom position and animations

This commit is contained in:
Zamitto
2025-05-15 19:42:23 -03:00
parent 42e8a68c08
commit 6f43da8d28
13 changed files with 502 additions and 152 deletions

View File

@@ -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";

View File

@@ -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
);

View File

@@ -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<string, UserPreferences>(
levelKeys.userPreferences,
{
valueEncoding: "json",
}
);
WindowManager.closeNotificationWindow();
if (
userPreferences.achievementNotificationsEnabled &&
userPreferences.achievementCustomNotificationsEnabled !== false
) {
WindowManager.createNotificationWindow(true);
}
};
registerEvent(
"updateAchievementCustomNotificationWindow",
updateAchievementCustomNotificationWindow
);

View File

@@ -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<string, UserPreferences>(
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);

View File

@@ -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) {

View File

@@ -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<string, UserPreferences>(
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<string, UserPreferences>(
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);