feat: achievement sound

This commit is contained in:
2026-01-19 19:25:19 -08:00
parent b691a91c53
commit d72ca7a975
5 changed files with 84 additions and 3 deletions
+7
View File
@@ -4,6 +4,7 @@ export enum NotificationType {
PERMISSION = "permission",
CONNECTION = "connection",
TASK_START = "task_start",
ACHIEVEMENT = "achievement",
}
export interface NotificationSound {
@@ -45,4 +46,10 @@ export const NOTIFICATION_SOUNDS: Record<NotificationType, NotificationSound> =
phrase: "Working on it!",
volume: 0.6,
},
[NotificationType.ACHIEVEMENT]: {
type: NotificationType.ACHIEVEMENT,
filename: "achievement.mp3",
phrase: "Achievement Get~!",
volume: 0.8,
},
};
+19
View File
@@ -0,0 +1,19 @@
// Achievement sound player using the notification system
import { soundPlayer } from '$lib/notifications';
import { NotificationType } from '$lib/notifications/types';
export function playAchievementSound() {
// Use the soundPlayer which respects global notification settings
soundPlayer.play(NotificationType.ACHIEVEMENT);
}
// Test function for development
export function testAchievementSound() {
try {
playAchievementSound();
console.log('Achievement sound played successfully!');
} catch (error) {
console.error('Error playing achievement sound:', error);
}
}
+13 -3
View File
@@ -2,6 +2,7 @@ import { writable, derived } from 'svelte/store';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
import type { Achievement, AchievementUnlockedEvent, AchievementId } from '$lib/types/achievements';
import { playAchievementSound } from '$lib/sounds/achievement';
interface AchievementState {
achievements: Record<AchievementId, Achievement>;
@@ -304,7 +305,7 @@ function createAchievementsStore() {
return {
subscribe,
unlockAchievement: (event: AchievementUnlockedEvent) => {
unlockAchievement: (event: AchievementUnlockedEvent, playSound: boolean = true) => {
update(state => {
const achievement = state.achievements[event.achievement.id];
if (achievement && !achievement.unlocked) {
@@ -312,6 +313,15 @@ function createAchievementsStore() {
achievement.unlockedAt = event.achievement.unlocked_at ? new Date(event.achievement.unlocked_at) : new Date();
state.totalUnlocked++;
state.lastUnlocked = achievement;
// Play achievement sound only for new unlocks, not when loading saved ones
if (playSound) {
try {
playAchievementSound();
} catch (error) {
console.error('Failed to play achievement sound:', error);
}
}
}
return state;
});
@@ -386,9 +396,9 @@ export async function initAchievementsListener() {
try {
const savedAchievements = await invoke<AchievementUnlockedEvent[]>('load_saved_achievements');
// Update the store with saved achievements
// Update the store with saved achievements (don't play sounds)
for (const event of savedAchievements) {
achievementsStore.unlockAchievement(event);
achievementsStore.unlockAchievement(event, false);
}
} catch (error) {
console.error('Failed to load saved achievements:', error);
+45
View File
@@ -0,0 +1,45 @@
<script lang="ts">
import { testAchievementSound } from '$lib/sounds/achievement';
import { invoke } from '@tauri-apps/api/core';
async function testSound() {
testAchievementSound();
}
async function triggerAchievement() {
// This will trigger an achievement that hasn't been unlocked yet
try {
await invoke('check_achievements', {
eventType: 'message_sent',
data: {}
});
} catch (error) {
console.error('Failed to trigger achievement:', error);
}
}
</script>
<div class="container mx-auto p-8">
<h1 class="text-2xl font-bold mb-6">Achievement Sound Test</h1>
<div class="space-y-4">
<button
onclick={testSound}
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
>
Test Achievement Sound
</button>
<button
onclick={triggerAchievement}
class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition-colors"
>
Trigger Test Achievement
</button>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-4">
Click the first button to test just the sound effect.<br>
Click the second button to trigger a real achievement (if any are available to unlock).
</p>
</div>
</div>
Binary file not shown.