feat: Windows taskbar notification badge counter
Adds a Tauri command (set_badge_count) that draws a red circle badge with a highlight count onto the Windows taskbar button overlay icon, matching Discord's behavior. Badge shows @mention/highlight count only (not total messages), clears to zero when all highlights are read. Frontend: useTauriNotificationBadge hook reads roomToUnreadAtom and calls set_badge_count via window.__TAURI_INTERNALS__.invoke whenever the unread map changes. No-ops silently in the browser (non-Tauri). Mounted as TauriEffects inside JotaiProvider in App.tsx. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { roomToUnreadAtom } from '../state/room/roomToUnread';
|
||||||
|
|
||||||
|
// Tauri v2 injects __TAURI_INTERNALS__ into the webview at runtime.
|
||||||
|
// We use it directly so cinny doesn't need @tauri-apps/api as a dependency.
|
||||||
|
type TauriInternals = { invoke: (cmd: string, args?: Record<string, unknown>) => Promise<unknown> };
|
||||||
|
const tauriInvoke = (): TauriInternals['invoke'] | undefined =>
|
||||||
|
(window as unknown as { __TAURI_INTERNALS__?: TauriInternals }).__TAURI_INTERNALS__?.invoke;
|
||||||
|
|
||||||
|
export function useTauriNotificationBadge() {
|
||||||
|
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const invoke = tauriInvoke();
|
||||||
|
if (!invoke) return;
|
||||||
|
|
||||||
|
let totalHighlights = 0;
|
||||||
|
roomToUnread.forEach((unread) => {
|
||||||
|
totalHighlights += unread.highlight;
|
||||||
|
});
|
||||||
|
|
||||||
|
invoke('set_badge_count', { count: totalHighlights }).catch(() => {});
|
||||||
|
}, [roomToUnread]);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import { ScreenSizeProvider, useScreenSize } from '../hooks/useScreenSize';
|
|||||||
import { useCompositionEndTracking } from '../hooks/useComposingCheck';
|
import { useCompositionEndTracking } from '../hooks/useComposingCheck';
|
||||||
import { settingsAtom } from '../state/settings';
|
import { settingsAtom } from '../state/settings';
|
||||||
import { LotusToastContainer } from '../features/toast/LotusToastContainer';
|
import { LotusToastContainer } from '../features/toast/LotusToastContainer';
|
||||||
|
import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge';
|
||||||
|
|
||||||
const FONT_MAP: Record<string, string> = {
|
const FONT_MAP: Record<string, string> = {
|
||||||
system: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
system: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
||||||
@@ -52,6 +53,11 @@ function AppearanceEffects() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TauriEffects() {
|
||||||
|
useTauriNotificationBadge();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function NightLightOverlay() {
|
function NightLightOverlay() {
|
||||||
const settings = useAtomValue(settingsAtom);
|
const settings = useAtomValue(settingsAtom);
|
||||||
if (!settings.nightLightEnabled) return null;
|
if (!settings.nightLightEnabled) return null;
|
||||||
@@ -131,6 +137,7 @@ function App() {
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<JotaiProvider>
|
<JotaiProvider>
|
||||||
<AppearanceEffects />
|
<AppearanceEffects />
|
||||||
|
<TauriEffects />
|
||||||
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
<RouterProvider router={createRouter(clientConfig, screenSize)} />
|
||||||
<NightLightOverlay />
|
<NightLightOverlay />
|
||||||
<LotusToastContainer />
|
<LotusToastContainer />
|
||||||
|
|||||||
Reference in New Issue
Block a user