Add system notifications for track changes

- Add notify-rust dependency for desktop notifications
- Create notification.rs commands module with show_track_notification and show_notification
- Register notification commands in lib.rs
- Wire up notifications in frontend for all playback scenarios:
  - Search results playback
  - Album track playback
  - Playlist track playback
  - Queue navigation and auto-advance
This commit is contained in:
vicrodh committed 2026-01-09 11:04:03 -06:00
1 parent ddbedf6ab0
commit ef8feebc45
5 files changed
+101

No files matched your search

+3
View File
@@ -52,6 +52,9 @@ symphonia = { version = "0.5", features = ["all"] }
# Media controls (MPRIS on Linux)
souvlaki = "0.7"
# Desktop notifications
notify-rust = "4"
# Storage
# sled = "0.34"
+2
View File
@@ -4,6 +4,7 @@
pub mod auth;
pub mod favorites;
pub mod notification;
pub mod playback;
pub mod playlist;
pub mod queue;
@@ -11,6 +12,7 @@ pub mod search;
pub use auth::*;
pub use favorites::*;
pub use notification::*;
pub use playback::*;
pub use playlist::*;
pub use queue::*;
+52
View File
@@ -0,0 +1,52 @@
//! System notification commands
use notify_rust::Notification;
/// Show a track change notification
#[tauri::command]
pub fn show_track_notification(
title: String,
artist: String,
album: String,
_artwork_url: Option<String>,
) -> Result<(), String> {
log::info!("Command: show_track_notification - {} by {}", title, artist);
let body = if album.is_empty() {
artist.clone()
} else {
format!("{} • {}", artist, album)
};
Notification::new()
.summary(&title)
.body(&body)
.appname("QBZ")
.timeout(3000) // 3 seconds
.show()
.map_err(|e| format!("Failed to show notification: {}", e))?;
Ok(())
}
/// Show a generic notification
#[tauri::command]
pub fn show_notification(
title: String,
body: Option<String>,
) -> Result<(), String> {
log::info!("Command: show_notification - {}", title);
let mut notification = Notification::new();
notification.summary(&title).appname("QBZ").timeout(3000);
if let Some(body_text) = body {
notification.body(&body_text);
}
notification
.show()
.map_err(|e| format!("Failed to show notification: {}", e))?;
Ok(())
}
+3
View File
@@ -124,6 +124,9 @@ pub fn run() {
commands::get_favorites,
commands::add_favorite,
commands::remove_favorite,
// Notification commands
commands::show_track_notification,
commands::show_notification,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
+41
View File
@@ -277,6 +277,14 @@
durationSecs: track.duration,
coverUrl: artwork || null
});
// Show system notification
showTrackNotification(
track.title,
track.performer?.name || 'Unknown Artist',
track.album?.title || '',
artwork || undefined
);
} catch (err) {
console.error('Failed to play track:', err);
showToast(`Playback error: ${err}`, 'error');
@@ -350,6 +358,14 @@
durationSecs: track.durationSeconds,
coverUrl: artwork || null
});
// Show system notification
showTrackNotification(
track.title,
track.artist || selectedAlbum?.artist || 'Unknown Artist',
selectedAlbum?.title || '',
artwork || undefined
);
} catch (err) {
console.error('Failed to play track:', err);
showToast(`Playback error: ${err}`, 'error');
@@ -483,6 +499,9 @@
coverUrl: track.artwork_url
});
// Show system notification
showTrackNotification(track.title, track.artist, track.album, track.artwork_url || undefined);
// Refresh queue state
await syncQueueState();
} catch (err) {
@@ -653,6 +672,14 @@
coverUrl: track.albumArt
});
// Show system notification
showTrackNotification(
track.title,
track.artist || 'Unknown Artist',
track.album || 'Playlist',
track.albumArt
);
await syncQueueState();
} catch (err) {
console.error('Failed to play track:', err);
@@ -661,6 +688,20 @@
}
}
// System Notification for track changes
async function showTrackNotification(title: string, artist: string, album: string, artworkUrl?: string) {
try {
await invoke('show_track_notification', {
title,
artist,
album,
artworkUrl: artworkUrl || null
});
} catch (err) {
console.error('Failed to show track notification:', err);
}
}
// Toast Functions
function showToast(message: string, type: 'success' | 'error' | 'info' = 'info') {
toast = { message, type };