import { StatusBar } from 'expo-status-bar'; import React, { useState, useRef, useEffect } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Provider as PaperProvider, DefaultTheme, Appbar, Button, Text } from 'react-native-paper'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import Login from "./Views/Login.js" import Feed from "./Views/Feed.js" import Profile from "./Views/Profile.js" import Tags from "./Views/Tags.js" import Search from './Views/Search.js'; import Groups from './Views/Groups.js'; import Courses from './Views/Courses.js'; import NotificationsView from './Views/NotificationsView.js'; import SinglePost from './Views/SinglePost.js' import * as Device from 'expo-device'; import * as Notifications from 'expo-notifications'; import API from './API.js'; import i18n from "./i18nMessages.js"; import NewPostView from './Views/NewPost.js'; import { TouchableOpacity, View, Image, KeyboardAvoidingView } from 'react-native'; import MenuView from './Views/Menu.js'; import ProfileSettings from './Views/ProfileSettings.js'; import InviteView from './Views/Invite.js'; import MediaView from './components/MediaView.js'; import { useSnapshot } from 'valtio'; import GlobalState from './contexts/GlobalState.js'; import NewGroup from './Views/NewGroup.js'; import Slideshow from './Views/Slideshow.js'; import SongPlayer from './Views/SongPlayer.js'; import GlobalChat from './Views/GlobalChat.js'; import BiblePicker from './Views/BiblePicker.js'; import BibleChapterView from './Views/BibleChapterView.js'; import { Platform } from 'react-native'; import { PostHogProvider } from 'posthog-react-native' import * as Updates from 'expo-updates'; import { useNavigation } from '@react-navigation/native'; import * as Linking from 'expo-linking'; const Tab = createBottomTabNavigator(); const Stack = createNativeStackNavigator(); const theme = { ...DefaultTheme, roundness: 2, colors: { ...DefaultTheme.colors, primary: '#000000', accent: '#0d6efd', background: "#edf2f7", }, }; Notifications.setNotificationHandler({ handleNotification: async () => { //console.log("notif background"); return { shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: true, }; }, }); const parseTokenLoginUrl = (url = "") => { const parsed = Linking.parse(url); const token = typeof parsed?.queryParams?.token === "string" ? parsed.queryParams.token.trim() : ""; if (!token) return null; const hostOrPath = `${parsed?.hostname || ""}/${parsed?.path || ""}`.toLowerCase(); if (!hostOrPath.includes("token-login")) return null; return token; }; async function registerForPushNotificationsAsync() { if (Platform.OS === 'android') { Notifications.setNotificationChannelAsync('default', { name: 'default', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], lightColor: '#FF231F7C', }); } let token; if (Device.isDevice) { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { //alert('Failed to get push token for push notification!'); return; } token = (await Notifications.getExpoPushTokenAsync({ projectId: "c2bb4d4e-4d4d-4f34-a873-7cad78c6023c", })).data; } else { //alert('Must use physical device for Push Notifications'); } return token; } const MainNavigation = ({ route }) => { const gState = useSnapshot(GlobalState); const viewer = gState.me; const [expoPushToken, setExpoPushToken] = useState(''); const [notification, setNotification] = useState(false); const notificationListener = useRef(); const responseListener = useRef(); const mainNavigation = useNavigation(); useEffect(() => { registerForPushNotificationsAsync().then(async (token) => { let isLoggedIn = await API.isLoggedIn(); if (!isLoggedIn) return false; if (!token) return false; API.registerToken(token); return setExpoPushToken(token); }); // This listener is fired whenever a notification is received while the app is foregrounded notificationListener.current = Notifications.addNotificationReceivedListener(notification => { //console.log("got notif", notification); setNotification(notification); }); // This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed) responseListener.current = Notifications.addNotificationResponseReceivedListener(response => { const data = response.notification.request.content.data; if (data && Object.keys(data).length > 0) { try { if (data.profile_id) { mainNavigation.navigate("Profile", { profileid: data.profile_id }); } if (data.post_id) { mainNavigation.navigate("SinglePost", { postid: data.post_id }); } if (data.type === "chat") { mainNavigation.navigate("GlobalChat"); } } catch (error) { alert("Error: " + error); } } else { //alert("Notification clicked but no data found."); } }); const interval = setInterval(async () => { if (await API.isLoggedIn()) { let me = await API.getMe(); //console.log(JSON.stringify(viewer), JSON.stringify(me)) if (JSON.stringify(viewer) !== JSON.stringify(me)) { console.log("Updating me") GlobalState.me = me; } } }, 30000); return () => { Notifications.removeNotificationSubscription(notificationListener.current); Notifications.removeNotificationSubscription(responseListener.current); clearInterval(interval); }; }, []); return ( <> ( ), header: () => { <> }, }} listeners={({ navigation, route }) => ({ tabPress: e => { navigation.navigate('Feed', { reRender: Math.random() }); }, })} /> ( ), header: () => { <> }, }} /> ( ), tabBarButton: (props) => ( {props.children} ), header: () => { <> }, }} listeners={({ navigation, route }) => ({ tabPress: e => { //console.log("listener", route) if (route.name !== "NewPost") { // Target current profile if one in route navigation.navigate('NewPost', { toProfile: route.params?.profileid }); } else { //Send function on child navigation.navigate('NewPost', { toProfile: route.params?.profileid, sendNow: true }); } }, })} /> ( ), header: () => { <> }, }} /> ( ), header: () => { <> }, }} listeners={({ navigation, route }) => ({ tabPress: e => { navigation.navigate('MyProfile', { profileid: viewer._id }); }, })} /> {/* ( ), header: () => { <> }, }} />*/} ) } export default function App() { const appState = useSnapshot(GlobalState); const viewer = appState.me || {}; const navigationRef = useRef(null); const pendingTokenRef = useRef(null); const hasUnviewedNotifications = Array.isArray(viewer?.notifications) ? viewer.notifications.some((n) => n && n.viewed !== true) : false; useEffect(() => { const routeTokenLogin = (url) => { const token = parseTokenLoginUrl(url); if (!token) return; if (!navigationRef.current) { pendingTokenRef.current = token; return; } navigationRef.current.navigate("Login", { token }); }; Linking.getInitialURL().then((url) => { if (!url) return; routeTokenLogin(url); }); const sub = Linking.addEventListener("url", ({ url }) => { routeTokenLogin(url); }); return () => { sub.remove(); }; }, []); return ( , }} theme={theme}> { if (!pendingTokenRef.current) return; const token = pendingTokenRef.current; pendingTokenRef.current = null; navigationRef.current?.navigate("Login", { token }); }} > { return ( {props.navigation.canGoBack() ? { props.navigation.goBack(); }} /> : { props.navigation.navigate('Menu'); }} />} { props.navigation.navigate("GlobalChat"); }} onLongPress={() => { props.navigation.navigate("SongPlayer"); }} /> { props.navigation.navigate("Search") }} /> ( {hasUnviewedNotifications ? ( ) : <>} )} onPress={() => { props.navigation.navigate("Notifications") }} /> ) }, }}> ); }