Initial commit
							
								
								
									
										38
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,38 @@
 | 
			
		||||
# 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
 | 
			
		||||
*.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
 | 
			
		||||
							
								
								
									
										17
									
								
								.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
			
		||||
*.jks
 | 
			
		||||
*.key
 | 
			
		||||
*.mobileprovision
 | 
			
		||||
*.p12
 | 
			
		||||
*.p8
 | 
			
		||||
.DS_Store
 | 
			
		||||
.expo
 | 
			
		||||
.npmignore
 | 
			
		||||
.vscode
 | 
			
		||||
package-lock.json
 | 
			
		||||
yarn.lock
 | 
			
		||||
yarn-error.log
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
 | 
			
		||||
# Exclude tarballs generated by `npm pack`
 | 
			
		||||
/*.tgz
 | 
			
		||||
							
								
								
									
										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.
 | 
			
		||||
							
								
								
									
										41
									
								
								app.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,41 @@
 | 
			
		||||
{
 | 
			
		||||
  "expo": {
 | 
			
		||||
    "name": "HelloWorld",
 | 
			
		||||
    "slug": "expo-template-default",
 | 
			
		||||
    "version": "1.0.0",
 | 
			
		||||
    "orientation": "portrait",
 | 
			
		||||
    "icon": "./assets/images/icon.png",
 | 
			
		||||
    "scheme": "myapp",
 | 
			
		||||
    "userInterfaceStyle": "automatic",
 | 
			
		||||
    "newArchEnabled": true,
 | 
			
		||||
    "ios": {
 | 
			
		||||
      "supportsTablet": true
 | 
			
		||||
    },
 | 
			
		||||
    "android": {
 | 
			
		||||
      "adaptiveIcon": {
 | 
			
		||||
        "foregroundImage": "./assets/images/adaptive-icon.png",
 | 
			
		||||
        "backgroundColor": "#ffffff"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										109
									
								
								app/(tabs)/explore.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,109 @@
 | 
			
		||||
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';
 | 
			
		||||
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,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										74
									
								
								app/(tabs)/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,74 @@
 | 
			
		||||
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',
 | 
			
		||||
              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 doesn't 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,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										39
									
								
								app/_layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,39 @@
 | 
			
		||||
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 { StatusBar } from 'expo-status-bar';
 | 
			
		||||
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();
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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 { 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: 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, {
 | 
			
		||||
  useSharedValue,
 | 
			
		||||
  useAnimatedStyle,
 | 
			
		||||
  withTiming,
 | 
			
		||||
  withRepeat,
 | 
			
		||||
  withSequence,
 | 
			
		||||
} 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
 | 
			
		||||
    );
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  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 { Text, type TextProps, StyleSheet } 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} />;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								components/__tests__/ThemedText-test.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import renderer from 'react-test-renderer';
 | 
			
		||||
 | 
			
		||||
import { ThemedText } from '../ThemedText';
 | 
			
		||||
 | 
			
		||||
it(`renders correctly`, () => {
 | 
			
		||||
  const tree = renderer.create(<ThemedText>Snapshot test!</ThemedText>).toJSON();
 | 
			
		||||
 | 
			
		||||
  expect(tree).toMatchSnapshot();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										24
									
								
								components/__tests__/__snapshots__/ThemedText-test.tsx.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`renders correctly 1`] = `
 | 
			
		||||
<Text
 | 
			
		||||
  style={
 | 
			
		||||
    [
 | 
			
		||||
      {
 | 
			
		||||
        "color": "#11181C",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fontSize": 16,
 | 
			
		||||
        "lineHeight": 24,
 | 
			
		||||
      },
 | 
			
		||||
      undefined,
 | 
			
		||||
      undefined,
 | 
			
		||||
      undefined,
 | 
			
		||||
      undefined,
 | 
			
		||||
      undefined,
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
>
 | 
			
		||||
  Snapshot test!
 | 
			
		||||
</Text>
 | 
			
		||||
`;
 | 
			
		||||
							
								
								
									
										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,
 | 
			
		||||
      ]}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								components/ui/IconSymbol.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,43 @@
 | 
			
		||||
// This file is a fallback for using MaterialIcons on Android and web.
 | 
			
		||||
 | 
			
		||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
 | 
			
		||||
import { SymbolWeight } from 'expo-symbols';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { OpaqueColorValue, StyleProp, ViewStyle } from 'react-native';
 | 
			
		||||
 | 
			
		||||
// Add your SFSymbol to MaterialIcons mappings here.
 | 
			
		||||
const MAPPING = {
 | 
			
		||||
  // See MaterialIcons here: https://icons.expo.fyi
 | 
			
		||||
  // See SF Symbols in the SF Symbols app on Mac.
 | 
			
		||||
  'house.fill': 'home',
 | 
			
		||||
  'paperplane.fill': 'send',
 | 
			
		||||
  'chevron.left.forwardslash.chevron.right': 'code',
 | 
			
		||||
  'chevron.right': 'chevron-right',
 | 
			
		||||
} as Partial<
 | 
			
		||||
  Record<
 | 
			
		||||
    import('expo-symbols').SymbolViewProps['name'],
 | 
			
		||||
    React.ComponentProps<typeof MaterialIcons>['name']
 | 
			
		||||
  >
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export type IconSymbolName = keyof typeof MAPPING;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An icon component that uses native SFSymbols on iOS, and MaterialIcons on Android and web. This ensures a consistent look across platforms, and optimal resource usage.
 | 
			
		||||
 *
 | 
			
		||||
 * Icon `name`s are based on SFSymbols and require manual mapping to MaterialIcons.
 | 
			
		||||
 */
 | 
			
		||||
export function IconSymbol({
 | 
			
		||||
  name,
 | 
			
		||||
  size = 24,
 | 
			
		||||
  color,
 | 
			
		||||
  style,
 | 
			
		||||
}: {
 | 
			
		||||
  name: IconSymbolName;
 | 
			
		||||
  size?: number;
 | 
			
		||||
  color: string | OpaqueColorValue;
 | 
			
		||||
  style?: StyleProp<ViewStyle>;
 | 
			
		||||
  weight?: SymbolWeight;
 | 
			
		||||
}) {
 | 
			
		||||
  return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								components/ui/TabBarBackground.ios.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
			
		||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
 | 
			
		||||
import { BlurView } from 'expo-blur';
 | 
			
		||||
import { StyleSheet } from 'react-native';
 | 
			
		||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
  const tabHeight = useBottomTabBarHeight();
 | 
			
		||||
  const { bottom } = useSafeAreaInsets();
 | 
			
		||||
  return tabHeight - bottom;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										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];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "expo-template-default",
 | 
			
		||||
  "license": "0BSD",
 | 
			
		||||
  "main": "expo-router/entry",
 | 
			
		||||
  "version": "52.0.59",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "expo start",
 | 
			
		||||
    "reset-project": "node ./scripts/reset-project.js",
 | 
			
		||||
    "android": "expo start --android",
 | 
			
		||||
    "ios": "expo start --ios",
 | 
			
		||||
    "web": "expo start --web",
 | 
			
		||||
    "test": "jest --watchAll",
 | 
			
		||||
    "lint": "expo lint"
 | 
			
		||||
  },
 | 
			
		||||
  "jest": {
 | 
			
		||||
    "preset": "jest-expo"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@expo/vector-icons": "^14.0.2",
 | 
			
		||||
    "@react-navigation/bottom-tabs": "^7.2.0",
 | 
			
		||||
    "@react-navigation/native": "^7.0.14",
 | 
			
		||||
    "expo": "~52.0.28",
 | 
			
		||||
    "expo-blur": "~14.0.3",
 | 
			
		||||
    "expo-constants": "~17.0.5",
 | 
			
		||||
    "expo-font": "~13.0.3",
 | 
			
		||||
    "expo-haptics": "~14.0.1",
 | 
			
		||||
    "expo-linking": "~7.0.5",
 | 
			
		||||
    "expo-router": "~4.0.17",
 | 
			
		||||
    "expo-splash-screen": "~0.29.21",
 | 
			
		||||
    "expo-status-bar": "~2.0.1",
 | 
			
		||||
    "expo-symbols": "~0.2.1",
 | 
			
		||||
    "expo-system-ui": "~4.0.7",
 | 
			
		||||
    "expo-web-browser": "~14.0.2",
 | 
			
		||||
    "react": "18.3.1",
 | 
			
		||||
    "react-dom": "18.3.1",
 | 
			
		||||
    "react-native": "0.76.6",
 | 
			
		||||
    "react-native-gesture-handler": "~2.20.2",
 | 
			
		||||
    "react-native-reanimated": "~3.16.1",
 | 
			
		||||
    "react-native-safe-area-context": "4.12.0",
 | 
			
		||||
    "react-native-screens": "~4.4.0",
 | 
			
		||||
    "react-native-web": "~0.19.13",
 | 
			
		||||
    "react-native-webview": "13.12.5"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "^7.25.2",
 | 
			
		||||
    "@types/jest": "^29.5.12",
 | 
			
		||||
    "@types/react": "~18.3.12",
 | 
			
		||||
    "@types/react-test-renderer": "^18.3.0",
 | 
			
		||||
    "jest": "^29.2.1",
 | 
			
		||||
    "jest-expo": "~52.0.3",
 | 
			
		||||
    "react-test-renderer": "18.3.1",
 | 
			
		||||
    "typescript": "^5.3.3"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								scripts/reset-project.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						@@ -0,0 +1,84 @@
 | 
			
		||||
#!/usr/bin/env node
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This script is used to reset the project to a blank state.
 | 
			
		||||
 * It moves the /app, /components, /hooks, /scripts, and /constants directories 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 oldDirs = ["app", "components", "hooks", "constants", "scripts"];
 | 
			
		||||
const newDir = "app-example";
 | 
			
		||||
const newAppDir = "app";
 | 
			
		||||
const newDirPath = path.join(root, newDir);
 | 
			
		||||
 | 
			
		||||
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 moveDirectories = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    // Create the app-example directory
 | 
			
		||||
    await fs.promises.mkdir(newDirPath, { recursive: true });
 | 
			
		||||
    console.log(`📁 /${newDir} directory created.`);
 | 
			
		||||
 | 
			
		||||
    // Move old directories to new app-example directory
 | 
			
		||||
    for (const dir of oldDirs) {
 | 
			
		||||
      const oldDirPath = path.join(root, dir);
 | 
			
		||||
      const newDirPath = path.join(root, newDir, dir);
 | 
			
		||||
      if (fs.existsSync(oldDirPath)) {
 | 
			
		||||
        await fs.promises.rename(oldDirPath, newDirPath);
 | 
			
		||||
        console.log(`➡️ /${dir} moved to /${newDir}/${dir}.`);
 | 
			
		||||
      } 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.\n3. Delete the /app-example directory when you're done referencing it."
 | 
			
		||||
    );
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error(`Error during script execution: ${error}`);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
moveDirectories();
 | 
			
		||||
							
								
								
									
										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"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||