import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; import 'react-native-reanimated'; import { useColorScheme } from '@/hooks/useColorScheme'; import React, { useState, useEffect, useRef } from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; import UserSelection from '@/components/UserSelection'; import { TouchableOpacity, Text, View } from 'react-native'; import * as Notifications from 'expo-notifications'; import * as Device from 'expo-device'; import Constants from 'expo-constants'; // Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync(); type User = { id: number; name: string; message: string; }; export default function RootLayout() { const API_KEY = process.env.EXPO_PUBLIC_API_KEY; const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; const colorScheme = useColorScheme(); const [loaded, error] = useFonts({ SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), }); const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); // Push Notifications state const [expoPushToken, setExpoPushToken] = useState(null); const notificationListener = useRef(); const responseListener = useRef(); useEffect(() => { async function prepare() { try { // Load the user const storedUser = await AsyncStorage.getItem('@user'); if (storedUser) { setUser(JSON.parse(storedUser)); } } catch (e) { console.error('Failed to load user', e); } finally { setIsLoading(false); } } prepare(); }, []); useEffect(() => { if (loaded) { SplashScreen.hideAsync(); } }, [loaded]); const handleUserSelected = async (selectedUser: User) => { setUser(selectedUser); try { await AsyncStorage.setItem('@user', JSON.stringify(selectedUser)); // Store the Push Token to your server when the user is selected (optional) if (expoPushToken) { await savePushToken(selectedUser.id, expoPushToken); // Hypothetical function to send token to server } } catch (e) { console.error('Failed to save user or push token', e); } }; const handleSwitchUser = async () => { try { await AsyncStorage.removeItem('@user'); setUser(null); } catch (e) { console.error('Failed to remove user', e); } }; /** --- PUSH NOTIFICATIONS LOGIC --- **/ useEffect(() => { // Register for push notifications and set the push token registerForPushNotificationsAsync().then(async (token) => { if (token) { setExpoPushToken(token); // Upload push token to backend when successfully received try { const storedUser = await AsyncStorage.getItem('@user'); if (storedUser) { const user = JSON.parse(storedUser); // Send the push token to your Next.js API, along with the user's ID await fetch(`${BASE_URL}/api/updatePushToken`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ apiKey: API_KEY, // Use the API key stored in the environment userId: user.id, // The logged-in user's ID pushToken: token, // The Expo push token collected }), }); console.log('Push token successfully sent to backend'); } else { console.log('No user found in AsyncStorage'); } } catch (error) { console.error('Failed to send push token to backend', error); } } }); // Listener for received notifications while the app is running notificationListener.current = Notifications.addNotificationReceivedListener(notification => { console.log('Notification Received: ', notification); }); // Listener for when the user interacts with a notification responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { console.log('User interacted with notification: ', response); }); // Clean up listeners when component unmounts return () => { if (notificationListener.current) { Notifications.removeNotificationSubscription(notificationListener.current); } if (responseListener.current) { Notifications.removeNotificationSubscription(responseListener.current); } }; }, []); if (!loaded || isLoading) { return null; // or a more elegant loading indicator } if (!user) { return ; } return ( ( Switch User ), }} /> {/* You can display the push token for debug purposes */} {expoPushToken && ( Your Push Token: {expoPushToken} )} ); } /** --- Helper functions for push notifications --- **/ // Function to get the push token and request permissions async function registerForPushNotificationsAsync() { let token; // Ensure you're running on a physical device if (Device.isDevice) { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; // Ask user for permission if not granted already if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { console.log('Failed to get push notification token because permission not granted.'); return null; } try { token = (await Notifications.getExpoPushTokenAsync()).data; console.log('Expo Push Token:', token); } catch (error) { console.log('Failed to get Expo push token:', error); return null; } } else { console.log('Must use physical device for push notifications'); } return token; } // (Optional) A function to store push tokens in your backend server async function savePushToken(userId: number, expoPushToken: string) { // You would need to implement this function to save the user's push token to your backend. // For example, you could send a POST request to your Next.js API that stores expoPushToken console.log('Saving push token for user:', userId, 'with token:', expoPushToken); }