P0 fixes: - Replace crypto.randomUUID() with uuid v4 (not available in RN) - Replace Platform.Version with expo-device osVersion - Fix auth navigation types, remove unused App route P1 fixes: - Push notification handler respects user preferences (useRef pattern) - Fix stale closure: use zustand subscribe + useRef for live preferences - Add retry logging for device registration failures - Replace emoji tab icons with @expo/vector-icons Ionicons - Document API integration TODOs in all local-only stores P2 fixes: - Add __DEV__ global declaration (global.d.ts) - Fix package.json main field to expo/AppEntry.js - Add retry logging for push device registration - Add z-index/elevation to LoadingOverlay - Add visual indicator to EmptyState icon P3 fixes: - Type navigation with NavigationProp<RootStackParamList> - Move getSeverityColor to theme.ts (single source of truth) - Add useMemo for SpamShield filter computations - Verified usesNonExemptEncryption: false is correct for expo-secure-store Co-Authored-By: Paperclip <noreply@paperclip.ing>
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
import { useEffect, useCallback, useRef } from 'react';
|
|
import * as Notifications from 'expo-notifications';
|
|
import { Platform } from 'react-native';
|
|
import * as Device from 'expo-device';
|
|
import { deviceService, notificationService } from '@shieldai/mobile-api-client';
|
|
import { useSettingsStore } from '@/store/settingsStore';
|
|
|
|
export function usePushNotifications() {
|
|
const preferencesRef = useRef(useSettingsStore.getState().preferences);
|
|
|
|
useEffect(() => {
|
|
const subscription = useSettingsStore.subscribe((state) => {
|
|
preferencesRef.current = state.preferences;
|
|
});
|
|
return subscription;
|
|
}, []);
|
|
|
|
Notifications.setNotificationHandler({
|
|
handleNotification: async () => {
|
|
const prefs = preferencesRef.current;
|
|
return {
|
|
shouldShowAlert: prefs.pushNotifications,
|
|
shouldPlaySound: prefs.pushNotifications,
|
|
shouldSetBadge: false,
|
|
};
|
|
},
|
|
});
|
|
|
|
const registerForPushNotifications = useCallback(async () => {
|
|
try {
|
|
const { status: existingStatus } = await Notifications.getPermissionsAsync();
|
|
let finalStatus = existingStatus;
|
|
|
|
if (existingStatus !== 'granted') {
|
|
const { status } = await Notifications.requestPermissionsAsync();
|
|
finalStatus = status;
|
|
}
|
|
|
|
if (finalStatus !== 'granted') {
|
|
return null;
|
|
}
|
|
|
|
const token = (await Notifications.getExpoPushTokenAsync({
|
|
projectId: 'shieldai-project-id',
|
|
})).data;
|
|
|
|
try {
|
|
await deviceService.registerDevice({
|
|
platform: Platform.OS === 'ios' ? 'ios' : 'android',
|
|
pushToken: token,
|
|
modelName: Platform.OS === 'ios' ? 'iPhone' : 'Android',
|
|
osVersion: Device.osVersion || '0',
|
|
appVersion: '1.0.0',
|
|
});
|
|
} catch (deviceError) {
|
|
console.warn('Device registration failed (will retry on next launch):', deviceError);
|
|
}
|
|
|
|
return token;
|
|
} catch (error) {
|
|
console.error('Failed to register for push notifications:', error);
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const subscription = Notifications.addNotificationReceivedListener((notification) => {
|
|
const type = notification.request.content.data?.type;
|
|
const prefs = preferencesRef.current;
|
|
|
|
if (type === 'darkwatch_alert' && !prefs.darkwatchAlert) return;
|
|
if (type === 'spam_blocked' && !prefs.spamBlocked) return;
|
|
if (type === 'voiceprint_analysis' && !prefs.voiceprintAnalysis) return;
|
|
});
|
|
|
|
return () => subscription.remove();
|
|
}, []);
|
|
|
|
return { registerForPushNotifications };
|
|
}
|