Files
native/src/lib/app-badge.ts
T
Linus Rath 25c088383f feat: show the inbox unread count on the app icon
Ports the webmail's app-icon badge (`useAppBadge`): the icon shows the
unread count of the active account's own inbox, and a new "Unread count
on app icon" toggle (on by default, exported as the webmail's
`faviconUnreadBadge`) turns it off. Signing out of the last account
clears it.

This is iOS only. On Android expo-notifications badges through
ShortcutBadger, which few launchers honour, and implements a count of 0
as `NotificationManager.cancelAll()`, so clearing the badge would also
dismiss other accounts' mail notifications and calendar reminders.
Android launchers already badge the icon from the notifications on
display. For the same reason the Android-only background push task
does not update the badge.
2026-09-24 19:36:41 +02:00

44 lines
1.6 KiB
TypeScript

import { Platform } from 'react-native';
import * as Notifications from 'expo-notifications';
import type { Mailbox } from '../api/types';
/**
* The number on the app icon: the unread count of the active account's own
* inbox, like the webmail's tab/app-icon badge (`FaviconBadge`). `role ===
* 'inbox'` alone is not enough: shared and group inboxes sit in the same list,
* so the first match can be somebody else's. 0 (no badge) when the setting is
* off.
*/
export function appBadgeCount(mailboxes: readonly Mailbox[], enabled: boolean): number {
if (!enabled) return 0;
const inbox = mailboxes.find((m) => m.role === 'inbox' && !m.isShared);
return Math.max(0, inbox?.unreadEmails ?? 0);
}
/**
* Whether this platform can badge the app icon without side effects.
*
* iOS only. On Android expo-notifications badges through ShortcutBadger,
* which only a few launchers honour (Android 8+ launchers derive their badge
* from the notifications on display instead), and it implements a count of 0
* as `NotificationManager.cancelAll()`: clearing the badge would also dismiss
* every notification the app shows, including other accounts' new mail and
* calendar reminders.
*/
export function canBadgeAppIcon(): boolean {
return Platform.OS === 'ios';
}
/**
* Show `count` on the app icon (0 clears it). A silent no-op where the
* platform can't badge or the user denied badge permission.
*/
export async function setAppBadge(count: number): Promise<void> {
if (!canBadgeAppIcon()) return;
try {
await Notifications.setBadgeCountAsync(Math.max(0, Math.floor(count)));
} catch {
// Badging is cosmetic; never let it surface as an error.
}
}