Initial commit
Generated by create-expo-app 3.4.2.
							
								
								
									
										39
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | ||||
| # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files | ||||
|  | ||||
| # dependencies | ||||
| node_modules/ | ||||
|  | ||||
| # Expo | ||||
| .expo/ | ||||
| dist/ | ||||
| web-build/ | ||||
| expo-env.d.ts | ||||
|  | ||||
| # Native | ||||
| .kotlin/ | ||||
| *.orig.* | ||||
| *.jks | ||||
| *.p8 | ||||
| *.p12 | ||||
| *.key | ||||
| *.mobileprovision | ||||
|  | ||||
| # Metro | ||||
| .metro-health-check* | ||||
|  | ||||
| # debug | ||||
| npm-debug.* | ||||
| yarn-debug.* | ||||
| yarn-error.* | ||||
|  | ||||
| # macOS | ||||
| .DS_Store | ||||
| *.pem | ||||
|  | ||||
| # local env files | ||||
| .env*.local | ||||
|  | ||||
| # typescript | ||||
| *.tsbuildinfo | ||||
|  | ||||
| app-example | ||||
							
								
								
									
										7
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| { | ||||
|   "editor.codeActionsOnSave": { | ||||
|     "source.fixAll": "explicit", | ||||
|     "source.organizeImports": "explicit", | ||||
|     "source.sortMembers": "explicit" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										50
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,50 @@ | ||||
| # Welcome to your Expo app 👋 | ||||
|  | ||||
| This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). | ||||
|  | ||||
| ## Get started | ||||
|  | ||||
| 1. Install dependencies | ||||
|  | ||||
|    ```bash | ||||
|    npm install | ||||
|    ``` | ||||
|  | ||||
| 2. Start the app | ||||
|  | ||||
|    ```bash | ||||
|    npx expo start | ||||
|    ``` | ||||
|  | ||||
| In the output, you'll find options to open the app in a | ||||
|  | ||||
| - [development build](https://docs.expo.dev/develop/development-builds/introduction/) | ||||
| - [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) | ||||
| - [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) | ||||
| - [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo | ||||
|  | ||||
| You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). | ||||
|  | ||||
| ## Get a fresh project | ||||
|  | ||||
| When you're ready, run: | ||||
|  | ||||
| ```bash | ||||
| npm run reset-project | ||||
| ``` | ||||
|  | ||||
| This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. | ||||
|  | ||||
| ## Learn more | ||||
|  | ||||
| To learn more about developing your project with Expo, look at the following resources: | ||||
|  | ||||
| - [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). | ||||
| - [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. | ||||
|  | ||||
| ## Join the community | ||||
|  | ||||
| Join our community of developers creating universal apps. | ||||
|  | ||||
| - [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. | ||||
| - [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. | ||||
							
								
								
									
										42
									
								
								app.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | ||||
| { | ||||
|   "expo": { | ||||
|     "name": "flexlark-template", | ||||
|     "slug": "flexlark-template", | ||||
|     "version": "1.0.0", | ||||
|     "orientation": "portrait", | ||||
|     "icon": "./assets/images/icon.png", | ||||
|     "scheme": "flexlarktemplate", | ||||
|     "userInterfaceStyle": "automatic", | ||||
|     "newArchEnabled": true, | ||||
|     "ios": { | ||||
|       "supportsTablet": true | ||||
|     }, | ||||
|     "android": { | ||||
|       "adaptiveIcon": { | ||||
|         "foregroundImage": "./assets/images/adaptive-icon.png", | ||||
|         "backgroundColor": "#ffffff" | ||||
|       }, | ||||
|       "edgeToEdgeEnabled": true | ||||
|     }, | ||||
|     "web": { | ||||
|       "bundler": "metro", | ||||
|       "output": "static", | ||||
|       "favicon": "./assets/images/favicon.png" | ||||
|     }, | ||||
|     "plugins": [ | ||||
|       "expo-router", | ||||
|       [ | ||||
|         "expo-splash-screen", | ||||
|         { | ||||
|           "image": "./assets/images/splash-icon.png", | ||||
|           "imageWidth": 200, | ||||
|           "resizeMode": "contain", | ||||
|           "backgroundColor": "#ffffff" | ||||
|         } | ||||
|       ] | ||||
|     ], | ||||
|     "experiments": { | ||||
|       "typedRoutes": true | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										45
									
								
								app/(tabs)/_layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| import { Tabs } from 'expo-router'; | ||||
| import React from 'react'; | ||||
| import { Platform } from 'react-native'; | ||||
|  | ||||
| import { HapticTab } from '@/components/HapticTab'; | ||||
| import { IconSymbol } from '@/components/ui/IconSymbol'; | ||||
| import TabBarBackground from '@/components/ui/TabBarBackground'; | ||||
| import { Colors } from '@/constants/Colors'; | ||||
| import { useColorScheme } from '@/hooks/useColorScheme'; | ||||
|  | ||||
| export default function TabLayout() { | ||||
|   const colorScheme = useColorScheme(); | ||||
|  | ||||
|   return ( | ||||
|     <Tabs | ||||
|       screenOptions={{ | ||||
|         tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint, | ||||
|         headerShown: false, | ||||
|         tabBarButton: HapticTab, | ||||
|         tabBarBackground: TabBarBackground, | ||||
|         tabBarStyle: Platform.select({ | ||||
|           ios: { | ||||
|             // Use a transparent background on iOS to show the blur effect | ||||
|             position: 'absolute', | ||||
|           }, | ||||
|           default: {}, | ||||
|         }), | ||||
|       }}> | ||||
|       <Tabs.Screen | ||||
|         name="index" | ||||
|         options={{ | ||||
|           title: 'Home', | ||||
|           tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />, | ||||
|         }} | ||||
|       /> | ||||
|       <Tabs.Screen | ||||
|         name="explore" | ||||
|         options={{ | ||||
|           title: 'Explore', | ||||
|           tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />, | ||||
|         }} | ||||
|       /> | ||||
|     </Tabs> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										110
									
								
								app/(tabs)/explore.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,110 @@ | ||||
| import { Image } from 'expo-image'; | ||||
| import { Platform, StyleSheet } 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'; | ||||
| import { IconSymbol } from '@/components/ui/IconSymbol'; | ||||
|  | ||||
| export default function TabTwoScreen() { | ||||
|   return ( | ||||
|     <ParallaxScrollView | ||||
|       headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }} | ||||
|       headerImage={ | ||||
|         <IconSymbol | ||||
|           size={310} | ||||
|           color="#808080" | ||||
|           name="chevron.left.forwardslash.chevron.right" | ||||
|           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, | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										75
									
								
								app/(tabs)/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,75 @@ | ||||
| import { Image } from 'expo-image'; | ||||
| import { Platform, StyleSheet } 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', | ||||
|               web: 'F12', | ||||
|             })} | ||||
|           </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', | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										32
									
								
								app/+not-found.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,32 @@ | ||||
| import { Link, Stack } from 'expo-router'; | ||||
| import { StyleSheet } from 'react-native'; | ||||
|  | ||||
| import { ThemedText } from '@/components/ThemedText'; | ||||
| import { ThemedView } from '@/components/ThemedView'; | ||||
|  | ||||
| export default function NotFoundScreen() { | ||||
|   return ( | ||||
|     <> | ||||
|       <Stack.Screen options={{ title: 'Oops!' }} /> | ||||
|       <ThemedView style={styles.container}> | ||||
|         <ThemedText type="title">This screen does not exist.</ThemedText> | ||||
|         <Link href="/" style={styles.link}> | ||||
|           <ThemedText type="link">Go to home screen!</ThemedText> | ||||
|         </Link> | ||||
|       </ThemedView> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   container: { | ||||
|     flex: 1, | ||||
|     alignItems: 'center', | ||||
|     justifyContent: 'center', | ||||
|     padding: 20, | ||||
|   }, | ||||
|   link: { | ||||
|     marginTop: 15, | ||||
|     paddingVertical: 15, | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										29
									
								
								app/_layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | ||||
| import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; | ||||
| import { useFonts } from 'expo-font'; | ||||
| import { Stack } from 'expo-router'; | ||||
| import { StatusBar } from 'expo-status-bar'; | ||||
| import 'react-native-reanimated'; | ||||
|  | ||||
| import { useColorScheme } from '@/hooks/useColorScheme'; | ||||
|  | ||||
| export default function RootLayout() { | ||||
|   const colorScheme = useColorScheme(); | ||||
|   const [loaded] = useFonts({ | ||||
|     SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), | ||||
|   }); | ||||
|  | ||||
|   if (!loaded) { | ||||
|     // Async font loading only occurs in development. | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> | ||||
|       <Stack> | ||||
|         <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> | ||||
|         <Stack.Screen name="+not-found" /> | ||||
|       </Stack> | ||||
|       <StatusBar style="auto" /> | ||||
|     </ThemeProvider> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/fonts/SpaceMono-Regular.ttf
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								assets/images/adaptive-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 22 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/partial-react-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/react-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/react-logo@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/react-logo@3x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 21 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/splash-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										45
									
								
								components/Collapsible.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| import { PropsWithChildren, useState } from 'react'; | ||||
| import { StyleSheet, TouchableOpacity } from 'react-native'; | ||||
|  | ||||
| import { ThemedText } from '@/components/ThemedText'; | ||||
| import { ThemedView } from '@/components/ThemedView'; | ||||
| import { IconSymbol } from '@/components/ui/IconSymbol'; | ||||
| import { Colors } from '@/constants/Colors'; | ||||
| import { useColorScheme } from '@/hooks/useColorScheme'; | ||||
|  | ||||
| export function Collapsible({ children, title }: PropsWithChildren & { title: string }) { | ||||
|   const [isOpen, setIsOpen] = useState(false); | ||||
|   const theme = useColorScheme() ?? 'light'; | ||||
|  | ||||
|   return ( | ||||
|     <ThemedView> | ||||
|       <TouchableOpacity | ||||
|         style={styles.heading} | ||||
|         onPress={() => setIsOpen((value) => !value)} | ||||
|         activeOpacity={0.8}> | ||||
|         <IconSymbol | ||||
|           name="chevron.right" | ||||
|           size={18} | ||||
|           weight="medium" | ||||
|           color={theme === 'light' ? Colors.light.icon : Colors.dark.icon} | ||||
|           style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }} | ||||
|         /> | ||||
|  | ||||
|         <ThemedText type="defaultSemiBold">{title}</ThemedText> | ||||
|       </TouchableOpacity> | ||||
|       {isOpen && <ThemedView style={styles.content}>{children}</ThemedView>} | ||||
|     </ThemedView> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   heading: { | ||||
|     flexDirection: 'row', | ||||
|     alignItems: 'center', | ||||
|     gap: 6, | ||||
|   }, | ||||
|   content: { | ||||
|     marginTop: 6, | ||||
|     marginLeft: 24, | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										24
									
								
								components/ExternalLink.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | ||||
| import { Href, Link } from 'expo-router'; | ||||
| import { openBrowserAsync } from 'expo-web-browser'; | ||||
| import { type ComponentProps } from 'react'; | ||||
| import { Platform } from 'react-native'; | ||||
|  | ||||
| type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & string }; | ||||
|  | ||||
| export function ExternalLink({ href, ...rest }: Props) { | ||||
|   return ( | ||||
|     <Link | ||||
|       target="_blank" | ||||
|       {...rest} | ||||
|       href={href} | ||||
|       onPress={async (event) => { | ||||
|         if (Platform.OS !== 'web') { | ||||
|           // Prevent the default behavior of linking to the default browser on native. | ||||
|           event.preventDefault(); | ||||
|           // Open the link in an in-app browser. | ||||
|           await openBrowserAsync(href); | ||||
|         } | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										18
									
								
								components/HapticTab.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | ||||
| import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'; | ||||
| import { PlatformPressable } from '@react-navigation/elements'; | ||||
| import * as Haptics from 'expo-haptics'; | ||||
|  | ||||
| export function HapticTab(props: BottomTabBarButtonProps) { | ||||
|   return ( | ||||
|     <PlatformPressable | ||||
|       {...props} | ||||
|       onPressIn={(ev) => { | ||||
|         if (process.env.EXPO_OS === 'ios') { | ||||
|           // Add a soft haptic feedback when pressing down on the tabs. | ||||
|           Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); | ||||
|         } | ||||
|         props.onPressIn?.(ev); | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										40
									
								
								components/HelloWave.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | ||||
| import { useEffect } from 'react'; | ||||
| import { StyleSheet } from 'react-native'; | ||||
| import Animated, { | ||||
|   useAnimatedStyle, | ||||
|   useSharedValue, | ||||
|   withRepeat, | ||||
|   withSequence, | ||||
|   withTiming, | ||||
| } from 'react-native-reanimated'; | ||||
|  | ||||
| import { ThemedText } from '@/components/ThemedText'; | ||||
|  | ||||
| export function HelloWave() { | ||||
|   const rotationAnimation = useSharedValue(0); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     rotationAnimation.value = withRepeat( | ||||
|       withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })), | ||||
|       4 // Run the animation 4 times | ||||
|     ); | ||||
|   }, [rotationAnimation]); | ||||
|  | ||||
|   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, | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										82
									
								
								components/ParallaxScrollView.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,82 @@ | ||||
| import type { PropsWithChildren, ReactElement } from 'react'; | ||||
| import { StyleSheet } from 'react-native'; | ||||
| import Animated, { | ||||
|   interpolate, | ||||
|   useAnimatedRef, | ||||
|   useAnimatedStyle, | ||||
|   useScrollViewOffset, | ||||
| } from 'react-native-reanimated'; | ||||
|  | ||||
| import { ThemedView } from '@/components/ThemedView'; | ||||
| import { useBottomTabOverflow } from '@/components/ui/TabBarBackground'; | ||||
| import { useColorScheme } from '@/hooks/useColorScheme'; | ||||
|  | ||||
| const HEADER_HEIGHT = 250; | ||||
|  | ||||
| type Props = PropsWithChildren<{ | ||||
|   headerImage: ReactElement; | ||||
|   headerBackgroundColor: { dark: string; light: string }; | ||||
| }>; | ||||
|  | ||||
| export default function ParallaxScrollView({ | ||||
|   children, | ||||
|   headerImage, | ||||
|   headerBackgroundColor, | ||||
| }: Props) { | ||||
|   const colorScheme = useColorScheme() ?? 'light'; | ||||
|   const scrollRef = useAnimatedRef<Animated.ScrollView>(); | ||||
|   const scrollOffset = useScrollViewOffset(scrollRef); | ||||
|   const bottom = useBottomTabOverflow(); | ||||
|   const headerAnimatedStyle = useAnimatedStyle(() => { | ||||
|     return { | ||||
|       transform: [ | ||||
|         { | ||||
|           translateY: interpolate( | ||||
|             scrollOffset.value, | ||||
|             [-HEADER_HEIGHT, 0, HEADER_HEIGHT], | ||||
|             [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75] | ||||
|           ), | ||||
|         }, | ||||
|         { | ||||
|           scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]), | ||||
|         }, | ||||
|       ], | ||||
|     }; | ||||
|   }); | ||||
|  | ||||
|   return ( | ||||
|     <ThemedView style={styles.container}> | ||||
|       <Animated.ScrollView | ||||
|         ref={scrollRef} | ||||
|         scrollEventThrottle={16} | ||||
|         scrollIndicatorInsets={{ bottom }} | ||||
|         contentContainerStyle={{ paddingBottom: bottom }}> | ||||
|         <Animated.View | ||||
|           style={[ | ||||
|             styles.header, | ||||
|             { backgroundColor: headerBackgroundColor[colorScheme] }, | ||||
|             headerAnimatedStyle, | ||||
|           ]}> | ||||
|           {headerImage} | ||||
|         </Animated.View> | ||||
|         <ThemedView style={styles.content}>{children}</ThemedView> | ||||
|       </Animated.ScrollView> | ||||
|     </ThemedView> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   container: { | ||||
|     flex: 1, | ||||
|   }, | ||||
|   header: { | ||||
|     height: HEADER_HEIGHT, | ||||
|     overflow: 'hidden', | ||||
|   }, | ||||
|   content: { | ||||
|     flex: 1, | ||||
|     padding: 32, | ||||
|     gap: 16, | ||||
|     overflow: 'hidden', | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										60
									
								
								components/ThemedText.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | ||||
| import { StyleSheet, Text, type TextProps } from 'react-native'; | ||||
|  | ||||
| import { useThemeColor } from '@/hooks/useThemeColor'; | ||||
|  | ||||
| export type ThemedTextProps = TextProps & { | ||||
|   lightColor?: string; | ||||
|   darkColor?: string; | ||||
|   type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'; | ||||
| }; | ||||
|  | ||||
| export function ThemedText({ | ||||
|   style, | ||||
|   lightColor, | ||||
|   darkColor, | ||||
|   type = 'default', | ||||
|   ...rest | ||||
| }: ThemedTextProps) { | ||||
|   const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); | ||||
|  | ||||
|   return ( | ||||
|     <Text | ||||
|       style={[ | ||||
|         { color }, | ||||
|         type === 'default' ? styles.default : undefined, | ||||
|         type === 'title' ? styles.title : undefined, | ||||
|         type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined, | ||||
|         type === 'subtitle' ? styles.subtitle : undefined, | ||||
|         type === 'link' ? styles.link : undefined, | ||||
|         style, | ||||
|       ]} | ||||
|       {...rest} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   default: { | ||||
|     fontSize: 16, | ||||
|     lineHeight: 24, | ||||
|   }, | ||||
|   defaultSemiBold: { | ||||
|     fontSize: 16, | ||||
|     lineHeight: 24, | ||||
|     fontWeight: '600', | ||||
|   }, | ||||
|   title: { | ||||
|     fontSize: 32, | ||||
|     fontWeight: 'bold', | ||||
|     lineHeight: 32, | ||||
|   }, | ||||
|   subtitle: { | ||||
|     fontSize: 20, | ||||
|     fontWeight: 'bold', | ||||
|   }, | ||||
|   link: { | ||||
|     lineHeight: 30, | ||||
|     fontSize: 16, | ||||
|     color: '#0a7ea4', | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										14
									
								
								components/ThemedView.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | ||||
| 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} />; | ||||
| } | ||||
							
								
								
									
										32
									
								
								components/ui/IconSymbol.ios.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,32 @@ | ||||
| import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'; | ||||
| import { StyleProp, ViewStyle } from 'react-native'; | ||||
|  | ||||
| export function IconSymbol({ | ||||
|   name, | ||||
|   size = 24, | ||||
|   color, | ||||
|   style, | ||||
|   weight = 'regular', | ||||
| }: { | ||||
|   name: SymbolViewProps['name']; | ||||
|   size?: number; | ||||
|   color: string; | ||||
|   style?: StyleProp<ViewStyle>; | ||||
|   weight?: SymbolWeight; | ||||
| }) { | ||||
|   return ( | ||||
|     <SymbolView | ||||
|       weight={weight} | ||||
|       tintColor={color} | ||||
|       resizeMode="scaleAspectFit" | ||||
|       name={name} | ||||
|       style={[ | ||||
|         { | ||||
|           width: size, | ||||
|           height: size, | ||||
|         }, | ||||
|         style, | ||||
|       ]} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										41
									
								
								components/ui/IconSymbol.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | ||||
| // Fallback for using MaterialIcons on Android and web. | ||||
|  | ||||
| import MaterialIcons from '@expo/vector-icons/MaterialIcons'; | ||||
| import { SymbolWeight, SymbolViewProps } from 'expo-symbols'; | ||||
| import { ComponentProps } from 'react'; | ||||
| import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'; | ||||
|  | ||||
| type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>; | ||||
| type IconSymbolName = keyof typeof MAPPING; | ||||
|  | ||||
| /** | ||||
|  * Add your SF Symbols to Material Icons mappings here. | ||||
|  * - see Material Icons in the [Icons Directory](https://icons.expo.fyi). | ||||
|  * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app. | ||||
|  */ | ||||
| const MAPPING = { | ||||
|   'house.fill': 'home', | ||||
|   'paperplane.fill': 'send', | ||||
|   'chevron.left.forwardslash.chevron.right': 'code', | ||||
|   'chevron.right': 'chevron-right', | ||||
| } as IconMapping; | ||||
|  | ||||
| /** | ||||
|  * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web. | ||||
|  * This ensures a consistent look across platforms, and optimal resource usage. | ||||
|  * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons. | ||||
|  */ | ||||
| export function IconSymbol({ | ||||
|   name, | ||||
|   size = 24, | ||||
|   color, | ||||
|   style, | ||||
| }: { | ||||
|   name: IconSymbolName; | ||||
|   size?: number; | ||||
|   color: string | OpaqueColorValue; | ||||
|   style?: StyleProp<TextStyle>; | ||||
|   weight?: SymbolWeight; | ||||
| }) { | ||||
|   return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />; | ||||
| } | ||||
							
								
								
									
										19
									
								
								components/ui/TabBarBackground.ios.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; | ||||
| import { BlurView } from 'expo-blur'; | ||||
| import { StyleSheet } from 'react-native'; | ||||
|  | ||||
| export default function BlurTabBarBackground() { | ||||
|   return ( | ||||
|     <BlurView | ||||
|       // System chrome material automatically adapts to the system's theme | ||||
|       // and matches the native tab bar appearance on iOS. | ||||
|       tint="systemChromeMaterial" | ||||
|       intensity={100} | ||||
|       style={StyleSheet.absoluteFill} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| export function useBottomTabOverflow() { | ||||
|   return useBottomTabBarHeight(); | ||||
| } | ||||
							
								
								
									
										6
									
								
								components/ui/TabBarBackground.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| // This is a shim for web and Android where the tab bar is generally opaque. | ||||
| export default undefined; | ||||
|  | ||||
| export function useBottomTabOverflow() { | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										26
									
								
								constants/Colors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | ||||
| /** | ||||
|  * Below are the colors that are used in the app. The colors are defined in the light and dark mode. | ||||
|  * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. | ||||
|  */ | ||||
|  | ||||
| const tintColorLight = '#0a7ea4'; | ||||
| const tintColorDark = '#fff'; | ||||
|  | ||||
| export const Colors = { | ||||
|   light: { | ||||
|     text: '#11181C', | ||||
|     background: '#fff', | ||||
|     tint: tintColorLight, | ||||
|     icon: '#687076', | ||||
|     tabIconDefault: '#687076', | ||||
|     tabIconSelected: tintColorLight, | ||||
|   }, | ||||
|   dark: { | ||||
|     text: '#ECEDEE', | ||||
|     background: '#151718', | ||||
|     tint: tintColorDark, | ||||
|     icon: '#9BA1A6', | ||||
|     tabIconDefault: '#9BA1A6', | ||||
|     tabIconSelected: tintColorDark, | ||||
|   }, | ||||
| }; | ||||
							
								
								
									
										10
									
								
								eslint.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| // https://docs.expo.dev/guides/using-eslint/ | ||||
| const { defineConfig } = require('eslint/config'); | ||||
| const expoConfig = require('eslint-config-expo/flat'); | ||||
|  | ||||
| module.exports = defineConfig([ | ||||
|   expoConfig, | ||||
|   { | ||||
|     ignores: ['dist/*'], | ||||
|   }, | ||||
| ]); | ||||
							
								
								
									
										1
									
								
								hooks/useColorScheme.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| export { useColorScheme } from 'react-native'; | ||||
							
								
								
									
										21
									
								
								hooks/useColorScheme.web.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| import { useEffect, useState } from 'react'; | ||||
| import { useColorScheme as useRNColorScheme } from 'react-native'; | ||||
|  | ||||
| /** | ||||
|  * To support static rendering, this value needs to be re-calculated on the client side for web | ||||
|  */ | ||||
| export function useColorScheme() { | ||||
|   const [hasHydrated, setHasHydrated] = useState(false); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     setHasHydrated(true); | ||||
|   }, []); | ||||
|  | ||||
|   const colorScheme = useRNColorScheme(); | ||||
|  | ||||
|   if (hasHydrated) { | ||||
|     return colorScheme; | ||||
|   } | ||||
|  | ||||
|   return 'light'; | ||||
| } | ||||
							
								
								
									
										21
									
								
								hooks/useThemeColor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| /** | ||||
|  * Learn more about light and dark modes: | ||||
|  * https://docs.expo.dev/guides/color-schemes/ | ||||
|  */ | ||||
|  | ||||
| import { Colors } from '@/constants/Colors'; | ||||
| import { useColorScheme } from '@/hooks/useColorScheme'; | ||||
|  | ||||
| export function useThemeColor( | ||||
|   props: { light?: string; dark?: string }, | ||||
|   colorName: keyof typeof Colors.light & keyof typeof Colors.dark | ||||
| ) { | ||||
|   const theme = useColorScheme() ?? 'light'; | ||||
|   const colorFromProps = props[theme]; | ||||
|  | ||||
|   if (colorFromProps) { | ||||
|     return colorFromProps; | ||||
|   } else { | ||||
|     return Colors[theme][colorName]; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										49
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,49 @@ | ||||
| { | ||||
|   "name": "flexlark-template", | ||||
|   "main": "expo-router/entry", | ||||
|   "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", | ||||
|     "lint": "expo lint" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@expo/vector-icons": "^14.1.0", | ||||
|     "@react-navigation/bottom-tabs": "^7.3.10", | ||||
|     "@react-navigation/elements": "^2.3.8", | ||||
|     "@react-navigation/native": "^7.1.6", | ||||
|     "expo": "~53.0.20", | ||||
|     "expo-blur": "~14.1.5", | ||||
|     "expo-constants": "~17.1.7", | ||||
|     "expo-font": "~13.3.2", | ||||
|     "expo-haptics": "~14.1.4", | ||||
|     "expo-image": "~2.4.0", | ||||
|     "expo-linking": "~7.1.7", | ||||
|     "expo-router": "~5.1.4", | ||||
|     "expo-splash-screen": "~0.30.10", | ||||
|     "expo-status-bar": "~2.2.3", | ||||
|     "expo-symbols": "~0.4.5", | ||||
|     "expo-system-ui": "~5.0.10", | ||||
|     "expo-web-browser": "~14.2.0", | ||||
|     "react": "19.0.0", | ||||
|     "react-dom": "19.0.0", | ||||
|     "react-native": "0.79.5", | ||||
|     "react-native-gesture-handler": "~2.24.0", | ||||
|     "react-native-reanimated": "~3.17.4", | ||||
|     "react-native-safe-area-context": "5.4.0", | ||||
|     "react-native-screens": "~4.11.1", | ||||
|     "react-native-web": "~0.20.0", | ||||
|     "react-native-webview": "13.13.5" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@babel/core": "^7.25.2", | ||||
|     "@types/react": "~19.0.10", | ||||
|     "typescript": "~5.8.3", | ||||
|     "eslint": "^9.25.0", | ||||
|     "eslint-config-expo": "~9.2.0" | ||||
|   }, | ||||
|   "private": true | ||||
| } | ||||
							
								
								
									
										8593
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								pnpm-workspace.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| nodeLinker: hoisted | ||||
							
								
								
									
										112
									
								
								scripts/reset-project.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,112 @@ | ||||
| #!/usr/bin/env node | ||||
|  | ||||
| /** | ||||
|  * This script is used to reset the project to a blank state. | ||||
|  * It deletes or moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example based on user input 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 readline = require("readline"); | ||||
|  | ||||
| const root = process.cwd(); | ||||
| const oldDirs = ["app", "components", "hooks", "constants", "scripts"]; | ||||
| const exampleDir = "app-example"; | ||||
| const newAppDir = "app"; | ||||
| const exampleDirPath = path.join(root, exampleDir); | ||||
|  | ||||
| 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 />; | ||||
| } | ||||
| `; | ||||
|  | ||||
| const rl = readline.createInterface({ | ||||
|   input: process.stdin, | ||||
|   output: process.stdout, | ||||
| }); | ||||
|  | ||||
| const moveDirectories = async (userInput) => { | ||||
|   try { | ||||
|     if (userInput === "y") { | ||||
|       // Create the app-example directory | ||||
|       await fs.promises.mkdir(exampleDirPath, { recursive: true }); | ||||
|       console.log(`📁 /${exampleDir} directory created.`); | ||||
|     } | ||||
|  | ||||
|     // Move old directories to new app-example directory or delete them | ||||
|     for (const dir of oldDirs) { | ||||
|       const oldDirPath = path.join(root, dir); | ||||
|       if (fs.existsSync(oldDirPath)) { | ||||
|         if (userInput === "y") { | ||||
|           const newDirPath = path.join(root, exampleDir, dir); | ||||
|           await fs.promises.rename(oldDirPath, newDirPath); | ||||
|           console.log(`➡️ /${dir} moved to /${exampleDir}/${dir}.`); | ||||
|         } else { | ||||
|           await fs.promises.rm(oldDirPath, { recursive: true, force: true }); | ||||
|           console.log(`❌ /${dir} deleted.`); | ||||
|         } | ||||
|       } else { | ||||
|         console.log(`➡️ /${dir} does not exist, skipping.`); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Create new /app directory | ||||
|     const newAppDirPath = path.join(root, newAppDir); | ||||
|     await fs.promises.mkdir(newAppDirPath, { recursive: true }); | ||||
|     console.log("\n📁 New /app directory created."); | ||||
|  | ||||
|     // Create index.tsx | ||||
|     const indexPath = path.join(newAppDirPath, "index.tsx"); | ||||
|     await fs.promises.writeFile(indexPath, indexContent); | ||||
|     console.log("📄 app/index.tsx created."); | ||||
|  | ||||
|     // Create _layout.tsx | ||||
|     const layoutPath = path.join(newAppDirPath, "_layout.tsx"); | ||||
|     await fs.promises.writeFile(layoutPath, layoutContent); | ||||
|     console.log("📄 app/_layout.tsx created."); | ||||
|  | ||||
|     console.log("\n✅ Project reset complete. Next steps:"); | ||||
|     console.log( | ||||
|       `1. Run \`npx expo start\` to start a development server.\n2. Edit app/index.tsx to edit the main screen.${ | ||||
|         userInput === "y" | ||||
|           ? `\n3. Delete the /${exampleDir} directory when you're done referencing it.` | ||||
|           : "" | ||||
|       }` | ||||
|     ); | ||||
|   } catch (error) { | ||||
|     console.error(`❌ Error during script execution: ${error.message}`); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| rl.question( | ||||
|   "Do you want to move existing files to /app-example instead of deleting them? (Y/n): ", | ||||
|   (answer) => { | ||||
|     const userInput = answer.trim().toLowerCase() || "y"; | ||||
|     if (userInput === "y" || userInput === "n") { | ||||
|       moveDirectories(userInput).finally(() => rl.close()); | ||||
|     } else { | ||||
|       console.log("❌ Invalid input. Please enter 'Y' or 'N'."); | ||||
|       rl.close(); | ||||
|     } | ||||
|   } | ||||
| ); | ||||
							
								
								
									
										17
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | ||||
| { | ||||
|   "extends": "expo/tsconfig.base", | ||||
|   "compilerOptions": { | ||||
|     "strict": true, | ||||
|     "paths": { | ||||
|       "@/*": [ | ||||
|         "./*" | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "include": [ | ||||
|     "**/*.ts", | ||||
|     "**/*.tsx", | ||||
|     ".expo/types/**/*.ts", | ||||
|     "expo-env.d.ts" | ||||
|   ] | ||||
| } | ||||