Files
eranos/src/hooks/useNativeNotifications.ts
T
Chad Curtis 3ee491a63b Add push vs persistent notification delivery option for Android
Default to push mode (no foreground service). Persistent mode with
the always-on background polling service is opt-in via the new
Delivery Method section in notification settings.

- Add notificationStyle ('push' | 'persistent') to EncryptedSettings
- Show radio group in NotificationSettings on native platforms
- Pass notificationStyle through Capacitor plugin to SharedPreferences
- DittoNotificationPlugin starts/stops foreground service on style change
- MainActivity only starts service on launch when style is persistent
- Re-enable unread polling on native when push mode is active
2026-04-07 10:54:30 -05:00

96 lines
3.4 KiB
TypeScript

import { useEffect, useMemo } from 'react';
import { Capacitor, registerPlugin } from '@capacitor/core';
import { LocalNotifications } from '@capacitor/local-notifications';
import { useCurrentUser } from './useCurrentUser';
import { useAppContext } from './useAppContext';
import { useEncryptedSettings } from './useEncryptedSettings';
import { useFollowList } from './useFollowActions';
import { getEffectiveRelays } from '@/lib/appRelays';
import { getEnabledNotificationKinds } from '@/lib/notificationKinds';
/** Interface for the native DittoNotification Capacitor plugin. */
interface DittoNotificationPlugin {
configure(options: { userPubkey?: string; relayUrls?: string[]; enabledKinds?: number[]; authors?: string[]; notificationStyle?: string }): Promise<void>;
}
const DittoNotification = registerPlugin<DittoNotificationPlugin>('DittoNotification');
/**
* Manages the native Android notification service via Capacitor.
*
* Passes user pubkey + relay URLs + enabled notification kinds + optional
* authors filter to the DittoNotification plugin so it can poll for events
* in the background. Respects the NIP-78 notificationsEnabled setting
* (defaults to on), per-type notification preferences, and the "only from
* people I follow" setting.
*
* Web Push (nostr-push) is handled separately by usePushNotifications +
* NotificationSettings — this hook is Capacitor-only.
*/
export function useNativeNotifications(): void {
const { user } = useCurrentUser();
const { config } = useAppContext();
const { settings } = useEncryptedSettings();
const { data: followData } = useFollowList();
const prefs = settings?.notificationPreferences;
const notificationsEnabled = settings?.notificationsEnabled ?? true;
const notificationStyle = settings?.notificationStyle ?? 'push';
const enabledKinds = useMemo(
() => getEnabledNotificationKinds(prefs),
[prefs],
);
// Authors filter: when onlyFollowing is set, restrict to followed pubkeys
const followedPubkeys = useMemo(
() => followData?.pubkeys ?? [],
[followData?.pubkeys],
);
const onlyFollowing = prefs?.onlyFollowing === true;
const authorsFilter = onlyFollowing && followedPubkeys.length > 0
? followedPubkeys
: undefined;
// Request native notification permission on first mount.
useEffect(() => {
if (!Capacitor.isNativePlatform()) return;
(async () => {
try {
const { display } = await LocalNotifications.checkPermissions();
if (display === 'prompt' || display === 'prompt-with-rationale') {
await LocalNotifications.requestPermissions();
}
} catch {
// Permission check failed — ignore
}
})();
}, []);
// Configure / deconfigure the native polling service.
useEffect(() => {
if (!Capacitor.isNativePlatform()) return;
if (!user || !notificationsEnabled) {
DittoNotification.configure({});
return;
}
const effectiveRelays = getEffectiveRelays(config.relayMetadata, config.useAppRelays);
const relayUrls = effectiveRelays.relays
.filter((r) => r.read)
.map((r) => r.url);
if (relayUrls.length === 0) return;
DittoNotification.configure({
userPubkey: user.pubkey,
relayUrls,
enabledKinds,
notificationStyle,
...(authorsFilter ? { authors: authorsFilter } : {}),
});
}, [user, config.relayMetadata, config.useAppRelays, notificationsEnabled, notificationStyle, enabledKinds, authorsFilter]);
}