Init commit. Rewrite of old project. Not done
42
app.json
@ -13,13 +13,25 @@
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
"supportsTablet": true,
|
||||
"usesAppleSignIn": true,
|
||||
"config": {
|
||||
"usesNonExemptEncryption": false
|
||||
},
|
||||
"infoPList": {
|
||||
"NSLocationWhenInUseUsageDescription": "This app uses your location in order to allow you to share your location in chat.",
|
||||
"NSCameraUsageDescription": "This app uses your camera to take photos & send them in the chat."
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"permissions": [
|
||||
"android.permission.ACCESS_COARSE_LOCATION",
|
||||
"android.permission.ACCESS_FINE_LOCATION"
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
@ -27,10 +39,32 @@
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router"
|
||||
"expo-router",
|
||||
"expo-apple-authentication",
|
||||
[
|
||||
"expo-secure-store",
|
||||
{
|
||||
"faceIDPermission": "Allow $(PRODUCT_NAME) to access your Face ID biometric data."
|
||||
}
|
||||
],
|
||||
[
|
||||
"expo-location",
|
||||
{
|
||||
"locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"router": {
|
||||
"origin": false
|
||||
},
|
||||
"eas": {
|
||||
"projectId": "c7056557-31c8-4f5c-a3e5-802df6a3bf7d"
|
||||
}
|
||||
},
|
||||
"owner": "gibbyb"
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,60 @@
|
||||
import { Tabs } from 'expo-router';
|
||||
import React from 'react';
|
||||
import { Tabs } from "expo-router";
|
||||
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import { useColorScheme } from "@/hooks/useColorScheme";
|
||||
|
||||
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
|
||||
export default function TabLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const TabLayout = () => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
|
||||
headerShown: false,
|
||||
}}>
|
||||
tabBarActiveTintColor: Colors[scheme].tint,
|
||||
//headerShown: false
|
||||
headerStyle: {
|
||||
backgroundColor: Colors[scheme].background,
|
||||
},
|
||||
headerShadowVisible: false,
|
||||
headerTintColor: Colors[scheme].tint,
|
||||
tabBarStyle: {
|
||||
backgroundColor: Colors[scheme].background,
|
||||
borderTopColor: Colors[scheme].tint,
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
name='index'
|
||||
options={{
|
||||
title: 'Home',
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name={focused ? 'home' : 'home-outline'} color={color} />
|
||||
<TabBarIcon name={focused ? 'home' : 'home-outline'} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="explore"
|
||||
|
||||
<Tabs.Screen
|
||||
name='messages'
|
||||
options={{
|
||||
title: 'Explore',
|
||||
title: 'Messages',
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name={focused ? 'code-slash' : 'code-slash-outline'} color={color} />
|
||||
<TabBarIcon name={focused ? 'chatbubbles' : 'chatbubbles-outline'} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tabs.Screen
|
||||
name='settings'
|
||||
options={{
|
||||
title: 'Settings',
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name={focused ? 'settings' : 'settings-outline'} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default TabLayout;
|
||||
|
@ -1,102 +0,0 @@
|
||||
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||
import { StyleSheet, Image, Platform } from 'react-native';
|
||||
|
||||
import { Collapsible } from '@/components/Collapsible';
|
||||
import { ExternalLink } from '@/components/ExternalLink';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
|
||||
export default function TabTwoScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
||||
headerImage={<Ionicons size={310} name="code-slash" style={styles.headerImage} />}>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Explore</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedText>This app includes example code to help you get started.</ThemedText>
|
||||
<Collapsible title="File-based routing">
|
||||
<ThemedText>
|
||||
This app has two screens:{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
||||
</ThemedText>
|
||||
<ThemedText>
|
||||
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
||||
sets up the tab navigator.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Android, iOS, and web support">
|
||||
<ThemedText>
|
||||
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
||||
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
||||
</ThemedText>
|
||||
</Collapsible>
|
||||
<Collapsible title="Images">
|
||||
<ThemedText>
|
||||
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
||||
different screen densities
|
||||
</ThemedText>
|
||||
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
|
||||
<ExternalLink href="https://reactnative.dev/docs/images">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Custom fonts">
|
||||
<ThemedText>
|
||||
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
|
||||
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
|
||||
custom fonts such as this one.
|
||||
</ThemedText>
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Light and dark mode components">
|
||||
<ThemedText>
|
||||
This template has light and dark mode support. The{' '}
|
||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
||||
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Animations">
|
||||
<ThemedText>
|
||||
This template includes an example of an animated component. The{' '}
|
||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
||||
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText> library
|
||||
to create a waving hand animation.
|
||||
</ThemedText>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<ThemedText>
|
||||
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
||||
component provides a parallax effect for the header image.
|
||||
</ThemedText>
|
||||
),
|
||||
})}
|
||||
</Collapsible>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerImage: {
|
||||
color: '#808080',
|
||||
bottom: -90,
|
||||
left: -35,
|
||||
position: 'absolute',
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
});
|
@ -1,70 +0,0 @@
|
||||
import { Image, StyleSheet, Platform } from 'react-native';
|
||||
|
||||
import { HelloWave } from '@/components/HelloWave';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
||||
headerImage={
|
||||
<Image
|
||||
source={require('@/assets/images/partial-react-logo.png')}
|
||||
style={styles.reactLogo}
|
||||
/>
|
||||
}>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Welcome!</ThemedText>
|
||||
<HelloWave />
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
|
||||
<ThemedText>
|
||||
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
|
||||
Press{' '}
|
||||
<ThemedText type="defaultSemiBold">
|
||||
{Platform.select({ ios: 'cmd + d', android: 'cmd + m' })}
|
||||
</ThemedText>{' '}
|
||||
to open developer tools.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
|
||||
<ThemedText>
|
||||
Tap the Explore tab to learn more about what's included in this starter app.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
|
||||
<ThemedText>
|
||||
When you're ready, run{' '}
|
||||
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
|
||||
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
});
|
0
app/(tabs)/messages.tsx
Normal file
0
app/(tabs)/settings.tsx
Normal file
@ -1,39 +0,0 @@
|
||||
import { ScrollViewStyleReset } from 'expo-router/html';
|
||||
import { type PropsWithChildren } from 'react';
|
||||
|
||||
/**
|
||||
* This file is web-only and used to configure the root HTML for every web page during static rendering.
|
||||
* The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs.
|
||||
*/
|
||||
export default function Root({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
|
||||
{/*
|
||||
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
|
||||
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
|
||||
*/}
|
||||
<ScrollViewStyleReset />
|
||||
|
||||
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
|
||||
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
|
||||
{/* Add any additional <head> elements that you want globally available on web... */}
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
const responsiveBackground = `
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
}`;
|
@ -1,32 +1,31 @@
|
||||
import { Link, Stack } from 'expo-router';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { ThemedView } from '@/components/theme/Theme';
|
||||
import { Link, Stack } from 'expo-router';
|
||||
import TextButton from '@/components/theme/buttons/TextButton';
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
const NotFoundScreen = () => {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||
<Stack.Screen options={{ title: 'Page not found.' }} />
|
||||
<ThemedView style={styles.container}>
|
||||
<ThemedText type="title">This screen doesn't exist.</ThemedText>
|
||||
<Link href="/" style={styles.link}>
|
||||
<ThemedText type="link">Go to home screen!</ThemedText>
|
||||
<Link href="/">
|
||||
<TextButton
|
||||
width={200}
|
||||
height={45}
|
||||
text="Go to home screen"
|
||||
fontSize={24}
|
||||
/>
|
||||
</Link>
|
||||
</ThemedView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default NotFoundScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
link: {
|
||||
marginTop: 15,
|
||||
paddingVertical: 15,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
@ -1,37 +1,21 @@
|
||||
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 { useEffect } from 'react';
|
||||
import 'react-native-reanimated';
|
||||
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
import { Stack } from "expo-router";
|
||||
import React, { useState } from "react";
|
||||
import SignInScreen from "@/components/auth/SignInScreen";
|
||||
import {
|
||||
PushNotificationManager
|
||||
} from "@/components/services/notifications/PushNotificationManager";
|
||||
|
||||
export default function RootLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const [loaded] = useFonts({
|
||||
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
const [isSignedIn, setIsSignedIn] = useState(false);
|
||||
if (!isSignedIn)
|
||||
return (<SignInScreen onSignIn={() => setIsSignedIn(true)} />);
|
||||
|
||||
return (
|
||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||
<PushNotificationManager>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
</ThemeProvider>
|
||||
</PushNotificationManager>
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 4.0 MiB |
BIN
assets/images/default-profile.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 4.0 MiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 2.6 MiB |
@ -1,37 +0,0 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
withRepeat,
|
||||
withSequence,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
|
||||
export function HelloWave() {
|
||||
const rotationAnimation = useSharedValue(0);
|
||||
|
||||
rotationAnimation.value = withRepeat(
|
||||
withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })),
|
||||
4 // Run the animation 4 times
|
||||
);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ rotate: `${rotationAnimation.value}deg` }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<Animated.View style={animatedStyle}>
|
||||
<ThemedText style={styles.text}>👋</ThemedText>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
fontSize: 28,
|
||||
lineHeight: 32,
|
||||
marginTop: -6,
|
||||
},
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import { View, type ViewProps } from 'react-native';
|
||||
|
||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
||||
|
||||
export type ThemedViewProps = ViewProps & {
|
||||
lightColor?: string;
|
||||
darkColor?: string;
|
||||
};
|
||||
|
||||
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
|
||||
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
|
||||
|
||||
return <View style={[{ backgroundColor }, style]} {...otherProps} />;
|
||||
}
|
153
components/auth/SignInScreen.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import * as AppleAuthentication from "expo-apple-authentication";
|
||||
import { StyleSheet, Alert } from "react-native";
|
||||
import { ThemedView } from "@/components/theme/Theme";
|
||||
import { useColorScheme } from "@/hooks/useColorScheme";
|
||||
import * as Notifications from "expo-notifications";
|
||||
import Constants from "expo-constants";
|
||||
import { saveUser, saveInitialData } from "@/components/services/SecureStore";
|
||||
import type { InitialData, User } from "@/constants/Types";
|
||||
|
||||
const SignInScreen({onSignIn}: {onSignIn: () => void}) => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
|
||||
const handleAppleSignIn = async () => {
|
||||
try {
|
||||
const credential = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||
],
|
||||
});
|
||||
const projectId = Constants.expoConfig?.extra?.eas?.projectId;
|
||||
if (!projectId) throw new Error('Project ID not found in eas.json');
|
||||
|
||||
const pushToken = await Notifications.getExpoPushTokenAsync({
|
||||
projectId: projectId,
|
||||
});
|
||||
console.log(
|
||||
credential.user, credential.email, credential.fullName?.givenName,
|
||||
credential.fullName?.familyName, pushToken
|
||||
);
|
||||
|
||||
const initialData = await
|
||||
fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/users/getUserByAppleId` +
|
||||
`?appleId=${credential.user}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
|
||||
},
|
||||
});
|
||||
console.log(initialData);
|
||||
if (initialData.status === 404 || !initialData.ok) {
|
||||
if (!credential.user || !credential.email ||
|
||||
!credential.fullName?.givenName || !credential.fullName?.familyName ||
|
||||
!pushToken || credential.email.length === 0) {
|
||||
Alert.alert(
|
||||
'Sign in error',
|
||||
'Unable to create an account. ' +
|
||||
'Remove App from Sign in with Apple & try again.',
|
||||
);
|
||||
throw new Error(
|
||||
'Incomplete user data. This shouldn\'t happen & means that user has ' +
|
||||
'previously signed in with Apple, but their user data did not ' +
|
||||
'save in the database.'
|
||||
);
|
||||
}
|
||||
|
||||
const userResponse = await
|
||||
fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/users/createUser`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
appleId: credential.user,
|
||||
email: credential.email,
|
||||
fullName:
|
||||
`${credential.fullName?.givenName} ${credential.fullName?.familyName}`,
|
||||
pushToken: pushToken,
|
||||
}),
|
||||
});
|
||||
if (!userResponse.ok) {
|
||||
const errorBody = await userResponse.text();
|
||||
console.error(
|
||||
'API error: No user returned: ',
|
||||
userResponse.status, errorBody
|
||||
);
|
||||
throw new Error(`Failed to create user: ${userResponse.status} ${errorBody}`);
|
||||
}
|
||||
const user: User = await userResponse.json() as User;
|
||||
await saveUser(user);
|
||||
} else if (initialData.ok) {
|
||||
const allData: InitialData = await initialData.json() as InitialData;
|
||||
console.log('Existing user found! Saving data...');
|
||||
|
||||
if (allData.user.pushToken !== pushToken.data) {
|
||||
const updatePushTokenResponse =
|
||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/users/updatePushToken`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': process.env.NEXT_PUBLIC_API_KEY ?? '',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId: allData.user.id,
|
||||
pushToken: pushToken.data,
|
||||
}),
|
||||
});
|
||||
if (!updatePushTokenResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to update push token: ${updatePushTokenResponse.status}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log('Push token is up to date.');
|
||||
}
|
||||
allData.user.pushToken = pushToken.data;
|
||||
await saveInitialData(allData);
|
||||
}
|
||||
onSignIn();
|
||||
} catch (error) {
|
||||
console.error('Error signing in:', error);
|
||||
if (error.code === 'ERR_REQUEST_CANCELLED') {
|
||||
Alert.alert('Sign in error', 'Sign in was cancelled.');
|
||||
} else {
|
||||
Alert.alert(
|
||||
'Sign in error',
|
||||
'An error occurred while signing in. Please try again or contact support.'
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<AppleAuthentication.AppleAuthenticationButton
|
||||
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
|
||||
buttonStyle={(scheme === 'light') ?
|
||||
AppleAuthentication.AppleAuthenticationButtonStyle.BLACK :
|
||||
AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
|
||||
}
|
||||
cornerRadius={5}
|
||||
style={styles.button}
|
||||
onPress={handleAppleSignIn}
|
||||
/>
|
||||
</ThemedView>
|
||||
);
|
||||
};
|
||||
export default SignInScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
button: {
|
||||
width: 200,
|
||||
height: 45,
|
||||
},
|
||||
});
|
@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import { ThemedText } from '../ThemedText';
|
||||
import { ThemedText } from '@/components/Theme';
|
||||
|
||||
it(`renders correctly`, () => {
|
||||
const tree = renderer.create(<ThemedText>Snapshot test!</ThemedText>).toJSON();
|
@ -1,5 +1,4 @@
|
||||
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
|
||||
|
||||
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||
import { type IconProps } from '@expo/vector-icons/build/createIconSet';
|
||||
import { type ComponentProps } from 'react';
|
||||
|
95
components/services/SecureStore.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import type {
|
||||
Countdown,
|
||||
InitialData,
|
||||
Relationship,
|
||||
RelationshipData,
|
||||
User,
|
||||
} from "@/constants/Types";
|
||||
|
||||
export const saveUser = async (user: User) => {
|
||||
try {
|
||||
await SecureStore.setItemAsync('user', JSON.stringify(user));
|
||||
} catch (error) {
|
||||
console.error('Error saving user to SecureStore:', error);
|
||||
}
|
||||
};
|
||||
export const getUser = async () => {
|
||||
try {
|
||||
const user = await SecureStore.getItemAsync('user');
|
||||
return user ? JSON.parse(user) as User : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting user from SecureStore:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
export const savePartner = async (partner: User) => {
|
||||
try {
|
||||
await SecureStore.setItemAsync('partner', JSON.stringify(partner));
|
||||
} catch (error) {
|
||||
console.error('Error saving partner to SecureStore:', error);
|
||||
}
|
||||
};
|
||||
export const getPartner = async () => {
|
||||
try {
|
||||
const partner = await SecureStore.getItemAsync('partner');
|
||||
return partner ? JSON.parse(partner) as User : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting partner from SecureStore:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
export const saveRelationship = async (relationship: Relationship) => {
|
||||
try {
|
||||
await SecureStore.setItemAsync('relationship', JSON.stringify(relationship));
|
||||
} catch (error) {
|
||||
console.error('Error saving relationship to SecureStore:', error);
|
||||
}
|
||||
};
|
||||
export const getRelationship = async () => {
|
||||
try {
|
||||
const relationship = await SecureStore.getItemAsync('relationship');
|
||||
return relationship ? JSON.parse(relationship) as Relationship : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting relationship from SecureStore:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
export const saveCountdown = async (countdown: Countdown) => {
|
||||
try {
|
||||
await SecureStore.setItemAsync('countdown', JSON.stringify(countdown));
|
||||
} catch (error) {
|
||||
console.error('Error saving countdown to SecureStore:', error);
|
||||
}
|
||||
};
|
||||
export const getCountdown = async () => {
|
||||
try {
|
||||
const countdown = await SecureStore.getItemAsync('countdown');
|
||||
return countdown ? JSON.parse(countdown) as Countdown : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting countdown from SecureStore:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
export const saveRelationshipData = async (relationshipData: RelationshipData) => {
|
||||
try {
|
||||
await SecureStore.setItemAsync('partner', JSON.stringify(relationshipData.Partner));
|
||||
await SecureStore.setItemAsync('relationship', JSON.stringify(relationshipData.relationship));
|
||||
} catch (error) {
|
||||
console.error('Error saving partner & relationship to SecureStore:', error);
|
||||
}
|
||||
};
|
||||
export const saveInitialData = async (initialData: InitialData) => {
|
||||
try {
|
||||
await SecureStore.setItemAsync('user', JSON.stringify(initialData.user));
|
||||
await SecureStore.setItemAsync(
|
||||
'relationship', JSON.stringify(initialData.relationshipData.relationship)
|
||||
);
|
||||
await SecureStore.setItemAsync(
|
||||
'partner', JSON.stringify(initialData.relationshipData.Partner)
|
||||
);
|
||||
await SecureStore.setItemAsync('countdown', JSON.stringify(initialData.countdown));
|
||||
} catch (error) {
|
||||
console.error('Error saving initial data to SecureStore:', error);
|
||||
}
|
||||
};
|
107
components/services/notifications/PushNotificationManager.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import * as Device from 'expo-device';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import Constants from 'expo-constants';
|
||||
import type { NotificationMessage } from '@/constants/Types';
|
||||
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
shouldShowAlert: true,
|
||||
shouldPlaySound: true,
|
||||
shouldSetBadge: true,
|
||||
}),
|
||||
});
|
||||
|
||||
export const sendPushNotification = async(expoPushToken: string, notification: NotificationMessage) => {
|
||||
const message = {
|
||||
to: expoPushToken,
|
||||
sound: notification.sound ?? 'default',
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
data: notification.data ?? {},
|
||||
};
|
||||
await fetch('https://exp.host/--/api/v2/push/send', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Accept-encoding': 'gzip, deflate',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(message),
|
||||
});
|
||||
};
|
||||
|
||||
const handleRegistrationError = (errorMessage: string) => {
|
||||
alert(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
};
|
||||
|
||||
const registerforPushNotificationsAsync = async () => {
|
||||
let token;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
await Notifications.setNotificationChannelAsync('default', {
|
||||
name: 'default',
|
||||
importance: Notifications.AndroidImportance.MAX,
|
||||
vibrationPattern: [0, 250, 250, 250],
|
||||
lightColor: '#FF231F7C',
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const projectId = Constants.expoConfig?.extra?.eas?.projectId;
|
||||
if (!projectId) {
|
||||
alert('Project ID not found in eas.json');
|
||||
return;
|
||||
}
|
||||
token = (await Notifications.getExpoPushTokenAsync({ projectId })).data;
|
||||
} else {
|
||||
alert('Must use physical device for Push Notifications');
|
||||
}
|
||||
return token;
|
||||
};
|
||||
|
||||
export const PushNotificationManager = ({children}: {children: React.ReactNode}) => {
|
||||
const [expoPushToken, setExpoPushToken] = useState('');
|
||||
const [notification, setNotification] =
|
||||
useState<Notifications.Notification | undefined>(undefined);
|
||||
const notificationListener = useRef<Notifications.Subscription>();
|
||||
const responseListener = useRef<Notifications.Subscription>();
|
||||
|
||||
useEffect(() => {
|
||||
registerforPushNotificationsAsync()
|
||||
.then(token => setExpoPushToken(token ?? ''))
|
||||
.catch((error: any) => {
|
||||
setExpoPushToken('');
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
notificationListener.current = Notifications.addNotificationReceivedListener(
|
||||
notification => {
|
||||
setNotification(notification);
|
||||
});
|
||||
responseListener.current = Notifications.addNotificationResponseReceivedListener(
|
||||
response => {
|
||||
console.log(response);
|
||||
// Handle notification response here
|
||||
});
|
||||
return () => {
|
||||
notificationListener.current &&
|
||||
Notifications.removeNotificationSubscription(notificationListener.current);
|
||||
responseListener.current &&
|
||||
Notifications.removeNotificationSubscription(responseListener.current);
|
||||
};
|
||||
}, []);
|
||||
return ( <> {children} </> );
|
||||
};
|
@ -1,13 +1,22 @@
|
||||
import { View, type ViewProps } from 'react-native';
|
||||
import { Text, type TextProps, StyleSheet } from 'react-native';
|
||||
|
||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
||||
|
||||
export type ThemedViewProps = ViewProps & {
|
||||
lightColor?: string;
|
||||
darkColor?: string;
|
||||
};
|
||||
export type ThemedTextProps = TextProps & {
|
||||
lightColor?: string;
|
||||
darkColor?: string;
|
||||
type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
|
||||
};
|
||||
|
||||
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
|
||||
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
|
||||
return <View style={[{ backgroundColor }, style]} {...otherProps} />;
|
||||
}
|
||||
|
||||
export function ThemedText({
|
||||
style,
|
||||
lightColor,
|
55
components/theme/buttons/DefaultButton.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { StyleSheet, Pressable } from "react-native";
|
||||
import { ThemedView } from "@/components/theme/Theme";
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
|
||||
const DEFAULT_WIDTH = 320;
|
||||
const DEFAULT_HEIGHT = 68;
|
||||
|
||||
type Props = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const Button = ({ width, height, children, onPress }: Props & React.ComponentProps<typeof Pressable>) => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
return (
|
||||
<ThemedView
|
||||
style={[
|
||||
styles.buttonContainer,
|
||||
{
|
||||
width: (width ?? DEFAULT_WIDTH),
|
||||
height: (height ?? DEFAULT_HEIGHT),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.button,
|
||||
{backgroundColor: Colors[scheme].text}
|
||||
]}
|
||||
onPress={onPress}
|
||||
>
|
||||
{children}
|
||||
</Pressable>
|
||||
</ThemedView>
|
||||
);
|
||||
};
|
||||
export default Button;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 3,
|
||||
},
|
||||
button: {
|
||||
borderRadius: 10,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
});
|
37
components/theme/buttons/TextButton.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import Button from '@/components/theme/buttons/DefaultButton';
|
||||
import { ThemedText } from "@/components/theme/Theme";
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
|
||||
const DEFAULT_FONT_SIZE = 16;
|
||||
|
||||
type Props = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
text: string;
|
||||
fontSize?: number;
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const TextButton = ({ width, height, text, fontSize, onPress }: Props ) => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
return (
|
||||
<Button
|
||||
width={width}
|
||||
height={height}
|
||||
onPress={onPress}
|
||||
>
|
||||
<ThemedText
|
||||
style={[
|
||||
{
|
||||
color: Colors[scheme].text,
|
||||
fontSize: fontSize ?? DEFAULT_FONT_SIZE
|
||||
}
|
||||
]}
|
||||
>
|
||||
{text}
|
||||
</ThemedText>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
export default TextButton;
|
131
constants/Types.ts
Normal file
@ -0,0 +1,131 @@
|
||||
/* Types */
|
||||
|
||||
// User Table in DB
|
||||
export type User = {
|
||||
id: number;
|
||||
appleId: string | null;
|
||||
email: string;
|
||||
fullName: string;
|
||||
pfpUrl: string | null;
|
||||
pushToken: string;
|
||||
createdAt: Date;
|
||||
metadata?: Record<string, string>;
|
||||
};
|
||||
// Relationship Table in DB
|
||||
export type Relationship = {
|
||||
id: number;
|
||||
title: string;
|
||||
requestorId: number;
|
||||
isAccepted: boolean;
|
||||
relationshipStartDate: Date;
|
||||
};
|
||||
export type UserRelationship = {
|
||||
id: number;
|
||||
userId: number;
|
||||
relationshipId: number;
|
||||
};
|
||||
// Mutated Data from Relationship
|
||||
// & UserRelationship Tables in DB
|
||||
export type RelationshipData = {
|
||||
relationship: Relationship;
|
||||
Partner: User;
|
||||
};
|
||||
// Countdown Table in DB
|
||||
export type Countdown = {
|
||||
id: number;
|
||||
relationshipId: number;
|
||||
title: string;
|
||||
date: Date;
|
||||
createdAt: Date;
|
||||
};
|
||||
// Mutated Data for Login
|
||||
// API Response
|
||||
export type InitialData = {
|
||||
user: User;
|
||||
relationshipData: RelationshipData;
|
||||
countdown: Countdown;
|
||||
};
|
||||
// Message Table in DB
|
||||
export type Message = {
|
||||
id: number;
|
||||
senderId: number;
|
||||
receiverId: number;
|
||||
text: string;
|
||||
createdAt: Date;
|
||||
isRead: boolean;
|
||||
hasLocation: boolean;
|
||||
hasMedia: boolean;
|
||||
hasQuickReply: boolean;
|
||||
};
|
||||
// MessageMedia Table in DB
|
||||
export type MessageMedia = {
|
||||
id: number;
|
||||
messageId: number;
|
||||
mediaType:
|
||||
'image' | 'video' | 'audio' | 'file';
|
||||
url: string;
|
||||
size?: number;
|
||||
metadata?: string;
|
||||
order: number;
|
||||
};
|
||||
// MessageLocation Table in DB
|
||||
export type MessageLocation = {
|
||||
id: number;
|
||||
messageId: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
// Quick Reply Table in DB
|
||||
export type QuickReply = {
|
||||
id: number;
|
||||
messageId: number;
|
||||
type: 'radio' | 'checkbox';
|
||||
keepIt: boolean;
|
||||
};
|
||||
// Quick Reply Option Table in DB
|
||||
export type QuickReplyOption = {
|
||||
id: number;
|
||||
quickReplyId: number;
|
||||
title: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type GCUser = {
|
||||
_id: number;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
};
|
||||
export type GCQuickReplies = {
|
||||
type: 'radio' | 'checkbox';
|
||||
values: GCQuickReplyOptions[];
|
||||
keepIt?: boolean;
|
||||
};
|
||||
export type GCQuickReplyOptions = {
|
||||
title: string;
|
||||
value: string;
|
||||
};
|
||||
export type GCLocation = {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
};
|
||||
export type GCMessage = {
|
||||
_id: number;
|
||||
text: string;
|
||||
createdAt: Date;
|
||||
user: GCUser;
|
||||
image?: string;
|
||||
video?: string;
|
||||
audio?: string;
|
||||
location?: GCLocation;
|
||||
system?: boolean;
|
||||
sent?: boolean;
|
||||
received?: boolean;
|
||||
pending?: boolean;
|
||||
quickReplies?: GCQuickReplies;
|
||||
};
|
||||
export type NotificationMessage = {
|
||||
sound?: string;
|
||||
title: string;
|
||||
body: string;
|
||||
data?: any;
|
||||
};
|
@ -4,5 +4,5 @@
|
||||
// to render different styles on the client and server, these aren't directly supported in React Native
|
||||
// but can be achieved using a styling library like Nativewind.
|
||||
export function useColorScheme() {
|
||||
return 'light';
|
||||
return 'dark';
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ export function useThemeColor(
|
||||
props: { light?: string; dark?: string },
|
||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
|
||||
) {
|
||||
const theme = useColorScheme() ?? 'light';
|
||||
const theme = useColorScheme() ?? 'dark';
|
||||
const colorFromProps = props[theme];
|
||||
|
||||
if (colorFromProps) {
|
||||
|
@ -4,7 +4,6 @@
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"reset-project": "node ./scripts/reset-project.js",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
@ -18,10 +17,15 @@
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"expo": "~51.0.28",
|
||||
"expo-apple-authentication": "~6.4.2",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-device": "~6.0.2",
|
||||
"expo-font": "~12.0.9",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-location": "~17.0.1",
|
||||
"expo-notifications": "~0.28.18",
|
||||
"expo-router": "~3.5.23",
|
||||
"expo-secure-store": "~13.0.2",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-system-ui": "~3.0.7",
|
||||
@ -30,6 +34,8 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.74.5",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-get-random-values": "~1.11.0",
|
||||
"react-native-gifted-chat": "^2.6.4",
|
||||
"react-native-reanimated": "~3.10.1",
|
||||
"react-native-safe-area-context": "4.10.5",
|
||||
"react-native-screens": "3.31.1",
|
||||
|
254
pnpm-lock.yaml
generated
@ -17,18 +17,33 @@ importers:
|
||||
expo:
|
||||
specifier: ~51.0.28
|
||||
version: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
expo-apple-authentication:
|
||||
specifier: ~6.4.2
|
||||
version: 6.4.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-constants:
|
||||
specifier: ~16.0.2
|
||||
version: 16.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-device:
|
||||
specifier: ~6.0.2
|
||||
version: 6.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-font:
|
||||
specifier: ~12.0.9
|
||||
version: 12.0.10(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-linking:
|
||||
specifier: ~6.3.1
|
||||
version: 6.3.1(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-location:
|
||||
specifier: ~17.0.1
|
||||
version: 17.0.1(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-notifications:
|
||||
specifier: ~0.28.18
|
||||
version: 0.28.18(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-router:
|
||||
specifier: ~3.5.23
|
||||
version: 3.5.23(k73ovpznblgccxklktj7qyihii)
|
||||
expo-secure-store:
|
||||
specifier: ~13.0.2
|
||||
version: 13.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-splash-screen:
|
||||
specifier: ~0.27.5
|
||||
version: 0.27.6(expo-modules-autolinking@1.11.3)(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
@ -53,6 +68,12 @@ importers:
|
||||
react-native-gesture-handler:
|
||||
specifier: ~2.16.1
|
||||
version: 2.16.2(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
|
||||
react-native-get-random-values:
|
||||
specifier: ~1.11.0
|
||||
version: 1.11.0(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))
|
||||
react-native-gifted-chat:
|
||||
specifier: ^2.6.4
|
||||
version: 2.6.4(react-native-get-random-values@1.11.0(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)))(react-native-reanimated@3.10.1(@babel/core@7.25.8)(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
|
||||
react-native-reanimated:
|
||||
specifier: ~3.10.1
|
||||
version: 3.10.1(@babel/core@7.25.8)(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
|
||||
@ -924,6 +945,11 @@ packages:
|
||||
peerDependencies:
|
||||
expo-modules-autolinking: '>=0.8.1'
|
||||
|
||||
'@expo/react-native-action-sheet@4.1.0':
|
||||
resolution: {integrity: sha512-RILoWhREgjMdr1NUSmZa/cHg8onV2YPDAMOy0iIP1c3H7nT9QQZf5dQNHK8ehcLM82sarVxriBJyYSSHAx7j6w==}
|
||||
peerDependencies:
|
||||
react: '>=18.0.0'
|
||||
|
||||
'@expo/rudder-sdk-node@1.1.1':
|
||||
resolution: {integrity: sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -956,6 +982,9 @@ packages:
|
||||
'@hapi/topo@5.1.0':
|
||||
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
|
||||
|
||||
'@ide/backoff@1.0.0':
|
||||
resolution: {integrity: sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@ -1344,6 +1373,9 @@ packages:
|
||||
'@types/hammerjs@2.0.46':
|
||||
resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
|
||||
|
||||
'@types/hoist-non-react-statics@3.3.5':
|
||||
resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==}
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6':
|
||||
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
|
||||
|
||||
@ -1365,6 +1397,12 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/lodash.isequal@4.5.8':
|
||||
resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==}
|
||||
|
||||
'@types/lodash@4.17.10':
|
||||
resolution: {integrity: sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==}
|
||||
|
||||
'@types/node-forge@1.3.11':
|
||||
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
|
||||
|
||||
@ -1552,6 +1590,9 @@ packages:
|
||||
asap@2.0.6:
|
||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||
|
||||
assert@2.1.0:
|
||||
resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
|
||||
|
||||
ast-types@0.15.2:
|
||||
resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==}
|
||||
engines: {node: '>=4'}
|
||||
@ -1631,6 +1672,9 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
|
||||
badgin@1.2.3:
|
||||
resolution: {integrity: sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
@ -2241,6 +2285,16 @@ packages:
|
||||
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
expo-apple-authentication@6.4.2:
|
||||
resolution: {integrity: sha512-X4u1n3Ql1hOpztXHbKNq4I1l4+Ff82gC6RmEeW43Eht7VE6E8PrQBpYKw+JJv8osrCJt7R5O1PZwed6WLN5oig==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-application@5.9.1:
|
||||
resolution: {integrity: sha512-uAfLBNZNahnDZLRU41ZFmNSKtetHUT9Ua557/q189ua0AWV7pQjoVAx49E4953feuvqc9swtU3ScZ/hN1XO/FQ==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-asset@10.0.10:
|
||||
resolution: {integrity: sha512-0qoTIihB79k+wGus9wy0JMKq7DdenziVx3iUkGvMAy2azscSgWH6bd2gJ9CGnhC6JRd3qTMFBL0ou/fx7WZl7A==}
|
||||
peerDependencies:
|
||||
@ -2251,6 +2305,11 @@ packages:
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-device@6.0.2:
|
||||
resolution: {integrity: sha512-sCt91CuTmAuMXX4SlFOn4lIos2UIr8vb0jDstDDZXys6kErcj0uynC7bQAMreU5uRUTKMAl4MAMpKt9ufCXPBw==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-file-system@17.0.1:
|
||||
resolution: {integrity: sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw==}
|
||||
peerDependencies:
|
||||
@ -2269,6 +2328,11 @@ packages:
|
||||
expo-linking@6.3.1:
|
||||
resolution: {integrity: sha512-xuZCntSBGWCD/95iZ+mTUGTwHdy8Sx+immCqbUBxdvZ2TN61P02kKg7SaLS8A4a/hLrSCwrg5tMMwu5wfKr35g==}
|
||||
|
||||
expo-location@17.0.1:
|
||||
resolution: {integrity: sha512-m+OzotzlAXO3ZZ1uqW5GC25nXW868zN+ROyBA1V4VF6jGay1ZEs4URPglCVUDzZby2F5wt24cMzqDKw2IX6nRw==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-modules-autolinking@1.11.3:
|
||||
resolution: {integrity: sha512-oYh8EZEvYF5TYppxEKUTTJmbr8j7eRRnrIxzZtMvxLTXoujThVPMFS/cbnSnf2bFm1lq50TdDNABhmEi7z0ngQ==}
|
||||
hasBin: true
|
||||
@ -2276,6 +2340,11 @@ packages:
|
||||
expo-modules-core@1.12.26:
|
||||
resolution: {integrity: sha512-y8yDWjOi+rQRdO+HY+LnUlz8qzHerUaw/LUjKPU/mX8PRXP4UUPEEp5fjAwBU44xjNmYSHWZDwet4IBBE+yQUA==}
|
||||
|
||||
expo-notifications@0.28.18:
|
||||
resolution: {integrity: sha512-oRvr8rYhbbKNhVgcO+fj5g5g6vS0umGcElpeMSWa0KudUfOOgV6nNLvv5M89393z2Ahd7wPK4bnK8lygc0nCPQ==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-router@3.5.23:
|
||||
resolution: {integrity: sha512-Re2kYcxov67hWrcjuu0+3ovsLxYn79PuX6hgtYN20MgigY5ttX79KOIBEVGTO3F3y9dxSrGHyy5Z14BcO+usGQ==}
|
||||
peerDependencies:
|
||||
@ -2296,6 +2365,11 @@ packages:
|
||||
react-native-reanimated:
|
||||
optional: true
|
||||
|
||||
expo-secure-store@13.0.2:
|
||||
resolution: {integrity: sha512-3QYgoneo8p8yeeBPBiAfokNNc2xq6+n8+Ob4fAlErEcf4H7Y72LH+K/dx0nQyWau2ZKZUXBxyyfuHFyVKrEVLg==}
|
||||
peerDependencies:
|
||||
expo: '*'
|
||||
|
||||
expo-splash-screen@0.27.5:
|
||||
resolution: {integrity: sha512-9rdZuLkFCfgJBxrheUsOEOIW6Rp+9NVlpSE0hgXQwbTCLTncf00IHSE8/L2NbFyeDLNjof1yZBppaV7tXHRUzA==}
|
||||
peerDependencies:
|
||||
@ -2326,6 +2400,9 @@ packages:
|
||||
exponential-backoff@3.1.1:
|
||||
resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
|
||||
|
||||
fast-base64-decode@1.0.0:
|
||||
resolution: {integrity: sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==}
|
||||
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
@ -2779,6 +2856,10 @@ packages:
|
||||
resolution: {integrity: sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-nan@1.3.2:
|
||||
resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-negative-zero@2.0.3:
|
||||
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -3204,6 +3285,9 @@ packages:
|
||||
lodash.debounce@4.0.8:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
|
||||
lodash.isequal@4.5.0:
|
||||
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||
|
||||
lodash.throttle@4.1.1:
|
||||
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
|
||||
|
||||
@ -3520,6 +3604,10 @@ packages:
|
||||
resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
object-is@1.1.6:
|
||||
resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
object-keys@1.1.1:
|
||||
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -3822,17 +3910,52 @@ packages:
|
||||
react-is@18.3.1:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
|
||||
react-native-communications@2.2.1:
|
||||
resolution: {integrity: sha512-5+C0X9mopI0+qxyQHzOPEi5v5rxNBQjxydPPiKMQSlX1RBIcJ8uTcqUPssQ9Mo8p6c1IKIWJUSqCj4jAmD0qVQ==}
|
||||
|
||||
react-native-gesture-handler@2.16.2:
|
||||
resolution: {integrity: sha512-vGFlrDKlmyI+BT+FemqVxmvO7nqxU33cgXVsn6IKAFishvlG3oV2Ds67D5nPkHMea8T+s1IcuMm0bF8ntZtAyg==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
react-native-get-random-values@1.11.0:
|
||||
resolution: {integrity: sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==}
|
||||
peerDependencies:
|
||||
react-native: '>=0.56'
|
||||
|
||||
react-native-gifted-chat@2.6.4:
|
||||
resolution: {integrity: sha512-Ut31I1w6g4hG/iMyC1mVNVPZCNAUfijbJaEbRmKQnTPG+nAvYFje57OHr5coe9w+IBWsJKKoNvaI4h8w9Il5aQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
react-native-get-random-values: '*'
|
||||
react-native-reanimated: '*'
|
||||
react-native-safe-area-context: '*'
|
||||
|
||||
react-native-helmet-async@2.0.4:
|
||||
resolution: {integrity: sha512-m3CkXWss6B1dd6mCMleLpzDCJJGGaHOLQsUzZv8kAASJmMfmVT4d2fx375iXKTRWT25ThBfae3dECuX5cq/8hg==}
|
||||
peerDependencies:
|
||||
react: ^16.6.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
react-native-iphone-x-helper@1.3.1:
|
||||
resolution: {integrity: sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==}
|
||||
peerDependencies:
|
||||
react-native: '>=0.42.0'
|
||||
|
||||
react-native-lightbox-v2@0.9.2:
|
||||
resolution: {integrity: sha512-+8LwINeSWvPP69YAyWhiJQyaw4Gtu8X4EMvT3PEN615NrOeDaz8jOcIw73pzYJst3z4Z0vxvFB73iH16wCPXtw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-native: '>=0.61.0'
|
||||
|
||||
react-native-parsed-text@0.0.22:
|
||||
resolution: {integrity: sha512-hfD83RDXZf9Fvth3DowR7j65fMnlqM9PpxZBGWkzVcUTFtqe6/yPcIoIAgrJbKn6YmtzkivmhWE2MCE4JKBXrQ==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
react-native-reanimated@3.10.1:
|
||||
resolution: {integrity: sha512-sfxg6vYphrDc/g4jf/7iJ7NRi+26z2+BszPmvmk0Vnrz6FL7HYljJqTf531F1x6tFmsf+FEAmuCtTUIXFLVo9w==}
|
||||
peerDependencies:
|
||||
@ -4488,6 +4611,10 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ua-parser-js@0.7.39:
|
||||
resolution: {integrity: sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==}
|
||||
hasBin: true
|
||||
|
||||
ua-parser-js@1.0.39:
|
||||
resolution: {integrity: sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==}
|
||||
hasBin: true
|
||||
@ -4584,6 +4711,10 @@ packages:
|
||||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
uuid@10.0.0:
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
hasBin: true
|
||||
|
||||
uuid@7.0.3:
|
||||
resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==}
|
||||
hasBin: true
|
||||
@ -6074,6 +6205,12 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@expo/react-native-action-sheet@4.1.0(react@18.2.0)':
|
||||
dependencies:
|
||||
'@types/hoist-non-react-statics': 3.3.5
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.2.0
|
||||
|
||||
'@expo/rudder-sdk-node@1.1.1':
|
||||
dependencies:
|
||||
'@expo/bunyan': 4.0.1
|
||||
@ -6123,6 +6260,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.3.0
|
||||
|
||||
'@ide/backoff@1.0.0': {}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
@ -6863,6 +7002,11 @@ snapshots:
|
||||
|
||||
'@types/hammerjs@2.0.46': {}
|
||||
|
||||
'@types/hoist-non-react-statics@3.3.5':
|
||||
dependencies:
|
||||
'@types/react': 18.2.79
|
||||
hoist-non-react-statics: 3.3.2
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||
|
||||
'@types/istanbul-lib-report@3.0.3':
|
||||
@ -6891,6 +7035,12 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/lodash.isequal@4.5.8':
|
||||
dependencies:
|
||||
'@types/lodash': 4.17.10
|
||||
|
||||
'@types/lodash@4.17.10': {}
|
||||
|
||||
'@types/node-forge@1.3.11':
|
||||
dependencies:
|
||||
'@types/node': 22.7.5
|
||||
@ -7073,6 +7223,14 @@ snapshots:
|
||||
|
||||
asap@2.0.6: {}
|
||||
|
||||
assert@2.1.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
is-nan: 1.3.2
|
||||
object-is: 1.1.6
|
||||
object.assign: 4.1.5
|
||||
util: 0.12.5
|
||||
|
||||
ast-types@0.15.2:
|
||||
dependencies:
|
||||
tslib: 2.7.0
|
||||
@ -7207,6 +7365,8 @@ snapshots:
|
||||
babel-plugin-jest-hoist: 29.6.3
|
||||
babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.8)
|
||||
|
||||
badgin@1.2.3: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
@ -7834,6 +7994,14 @@ snapshots:
|
||||
jest-message-util: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
|
||||
expo-apple-authentication@6.4.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
|
||||
expo-application@5.9.1(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
|
||||
expo-asset@10.0.10(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
@ -7851,6 +8019,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
expo-device@6.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
ua-parser-js: 0.7.39
|
||||
|
||||
expo-file-system@17.0.1(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
@ -7872,6 +8045,10 @@ snapshots:
|
||||
- expo
|
||||
- supports-color
|
||||
|
||||
expo-location@17.0.1(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
|
||||
expo-modules-autolinking@1.11.3:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
@ -7886,6 +8063,21 @@ snapshots:
|
||||
dependencies:
|
||||
invariant: 2.2.4
|
||||
|
||||
expo-notifications@0.28.18(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
'@expo/image-utils': 0.5.1
|
||||
'@ide/backoff': 1.0.0
|
||||
abort-controller: 3.0.0
|
||||
assert: 2.1.0
|
||||
badgin: 1.2.3
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
expo-application: 5.9.1(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
expo-constants: 16.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))
|
||||
fs-extra: 9.1.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
expo-router@3.5.23(k73ovpznblgccxklktj7qyihii):
|
||||
dependencies:
|
||||
'@expo/metro-runtime': 3.2.3(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))
|
||||
@ -7913,6 +8105,10 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
expo-secure-store@13.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))
|
||||
|
||||
expo-splash-screen@0.27.5(expo-modules-autolinking@1.11.3)(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))):
|
||||
dependencies:
|
||||
'@expo/prebuild-config': 7.0.6(expo-modules-autolinking@1.11.3)
|
||||
@ -7972,6 +8168,8 @@ snapshots:
|
||||
|
||||
exponential-backoff@3.1.1: {}
|
||||
|
||||
fast-base64-decode@1.0.0: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-glob@3.3.2:
|
||||
@ -8438,6 +8636,11 @@ snapshots:
|
||||
dependencies:
|
||||
is-glob: 2.0.1
|
||||
|
||||
is-nan@1.3.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
|
||||
is-negative-zero@2.0.3: {}
|
||||
|
||||
is-number-object@1.0.7:
|
||||
@ -9100,6 +9303,8 @@ snapshots:
|
||||
|
||||
lodash.debounce@4.0.8: {}
|
||||
|
||||
lodash.isequal@4.5.0: {}
|
||||
|
||||
lodash.throttle@4.1.1: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
@ -9489,6 +9694,11 @@ snapshots:
|
||||
|
||||
object-inspect@1.13.2: {}
|
||||
|
||||
object-is@1.1.6:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
|
||||
object-keys@1.1.1: {}
|
||||
|
||||
object.assign@4.1.5:
|
||||
@ -9787,6 +9997,8 @@ snapshots:
|
||||
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-native-communications@2.2.1: {}
|
||||
|
||||
react-native-gesture-handler@2.16.2(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
'@egjs/hammerjs': 2.0.17
|
||||
@ -9797,6 +10009,29 @@ snapshots:
|
||||
react: 18.2.0
|
||||
react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)
|
||||
|
||||
react-native-get-random-values@1.11.0(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)):
|
||||
dependencies:
|
||||
fast-base64-decode: 1.0.0
|
||||
react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)
|
||||
|
||||
react-native-gifted-chat@2.6.4(react-native-get-random-values@1.11.0(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)))(react-native-reanimated@3.10.1(@babel/core@7.25.8)(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
'@expo/react-native-action-sheet': 4.1.0(react@18.2.0)
|
||||
'@types/lodash.isequal': 4.5.8
|
||||
dayjs: 1.11.13
|
||||
lodash.isequal: 4.5.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)
|
||||
react-native-communications: 2.2.1
|
||||
react-native-get-random-values: 1.11.0(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))
|
||||
react-native-iphone-x-helper: 1.3.1(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))
|
||||
react-native-lightbox-v2: 0.9.2(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
|
||||
react-native-parsed-text: 0.0.22(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
|
||||
react-native-reanimated: 3.10.1(@babel/core@7.25.8)(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
|
||||
react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0)
|
||||
uuid: 10.0.0
|
||||
|
||||
react-native-helmet-async@2.0.4(react@18.2.0):
|
||||
dependencies:
|
||||
invariant: 2.2.4
|
||||
@ -9804,6 +10039,21 @@ snapshots:
|
||||
react-fast-compare: 3.2.2
|
||||
shallowequal: 1.1.0
|
||||
|
||||
react-native-iphone-x-helper@1.3.1(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)):
|
||||
dependencies:
|
||||
react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)
|
||||
|
||||
react-native-lightbox-v2@0.9.2(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)
|
||||
|
||||
react-native-parsed-text@0.0.22(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)
|
||||
|
||||
react-native-reanimated@3.10.1(@babel/core@7.25.8)(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
'@babel/core': 7.25.8
|
||||
@ -10557,6 +10807,8 @@ snapshots:
|
||||
|
||||
typescript@5.3.3: {}
|
||||
|
||||
ua-parser-js@0.7.39: {}
|
||||
|
||||
ua-parser-js@1.0.39: {}
|
||||
|
||||
unbox-primitive@1.0.2:
|
||||
@ -10638,6 +10890,8 @@ snapshots:
|
||||
|
||||
utils-merge@1.0.1: {}
|
||||
|
||||
uuid@10.0.0: {}
|
||||
|
||||
uuid@7.0.3: {}
|
||||
|
||||
uuid@8.3.2: {}
|
||||
|
@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script is used to reset the project to a blank state.
|
||||
* It moves the /app directory to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file.
|
||||
* You can remove the `reset-project` script from package.json and safely delete this file after running it.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = process.cwd();
|
||||
const oldDirPath = path.join(root, 'app');
|
||||
const newDirPath = path.join(root, 'app-example');
|
||||
const newAppDirPath = path.join(root, 'app');
|
||||
|
||||
const indexContent = `import { Text, View } from "react-native";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text>Edit app/index.tsx to edit this screen.</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
const layoutContent = `import { Stack } from "expo-router";
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Screen name="index" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
fs.rename(oldDirPath, newDirPath, (error) => {
|
||||
if (error) {
|
||||
return console.error(`Error renaming directory: ${error}`);
|
||||
}
|
||||
console.log('/app moved to /app-example.');
|
||||
|
||||
fs.mkdir(newAppDirPath, { recursive: true }, (error) => {
|
||||
if (error) {
|
||||
return console.error(`Error creating new app directory: ${error}`);
|
||||
}
|
||||
console.log('New /app directory created.');
|
||||
|
||||
const indexPath = path.join(newAppDirPath, 'index.tsx');
|
||||
fs.writeFile(indexPath, indexContent, (error) => {
|
||||
if (error) {
|
||||
return console.error(`Error creating index.tsx: ${error}`);
|
||||
}
|
||||
console.log('app/index.tsx created.');
|
||||
|
||||
const layoutPath = path.join(newAppDirPath, '_layout.tsx');
|
||||
fs.writeFile(layoutPath, layoutContent, (error) => {
|
||||
if (error) {
|
||||
return console.error(`Error creating _layout.tsx: ${error}`);
|
||||
}
|
||||
console.log('app/_layout.tsx created.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|