vojo/src/app/hooks/useDismissNativeCallNotifications.ts

60 lines
2.5 KiB
TypeScript

import { useEffect, useRef } from 'react';
import { useAtomValue } from 'jotai';
import { incomingCallsAtom } from '../state/incomingCalls';
import { isNativePlatform } from '../utils/capacitor';
// When the in-app incomingCallsAtom drops an entry (user accepted, declined,
// other device joined, lifetime expired), also clear the CallStyle notification
// the native service posted for that room. The AlarmManager fallback in the
// Java service handles killed-process dismiss on lifetime expiry; this hook
// covers the live-client paths where JS knows the truth sooner than the alarm.
export const useDismissNativeCallNotifications = (): void => {
const incoming = useAtomValue(incomingCallsAtom);
const prevRoomsRef = useRef<Set<string>>(new Set());
useEffect(() => {
const nextRooms = new Set<string>();
incoming.forEach((call) => nextRooms.add(call.roomId));
const dropped: string[] = [];
prevRoomsRef.current.forEach((roomId) => {
if (!nextRooms.has(roomId)) dropped.push(roomId);
});
prevRoomsRef.current = nextRooms;
if (dropped.length === 0) return;
if (!isNativePlatform()) return;
(async () => {
const { PushNotifications } = await import('@capacitor/push-notifications');
// Shape mirrors VojoFirebaseMessagingService.showIncomingCallNotification:
// tag = "call_" + roomId, id = tag.hashCode(). The Android Capacitor
// plugin reads id via JSObject.getInteger (PushNotificationsPlugin.java
// line 166), so the id must go over the bridge as a number — the TS
// type declares string, hence the cast.
const notifications = dropped.map((roomId) => {
const tag = `call_${roomId}`;
return { id: javaStringHashCode(tag), tag };
});
await PushNotifications.removeDeliveredNotifications({
notifications: notifications as unknown as Parameters<
typeof PushNotifications.removeDeliveredNotifications
>[0]['notifications'],
}).catch(() => {
/* best-effort — alarm fallback still dismisses at lifetime expiry */
});
})();
}, [incoming]);
};
// Reproduces java.lang.String#hashCode so JS-side ids match the tag ids the
// Java service computed. Must stay in sync with CallCancelReceiver / the
// tag.hashCode() call in VojoFirebaseMessagingService.
function javaStringHashCode(s: string): number {
let h = 0;
for (let i = 0; i < s.length; i += 1) {
// eslint-disable-next-line no-bitwise
h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
}
return h;
}