Sfoglia il codice sorgente

Initial commit

Generated by create-expo-app 3.2.0.
master
azri 1 settimana fa
commit
30e44c8782

+ 38
- 0
.gitignore Vedi File

@@ -0,0 +1,38 @@
1
+# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2
+
3
+# dependencies
4
+node_modules/
5
+
6
+# Expo
7
+.expo/
8
+dist/
9
+web-build/
10
+expo-env.d.ts
11
+
12
+# Native
13
+*.orig.*
14
+*.jks
15
+*.p8
16
+*.p12
17
+*.key
18
+*.mobileprovision
19
+
20
+# Metro
21
+.metro-health-check*
22
+
23
+# debug
24
+npm-debug.*
25
+yarn-debug.*
26
+yarn-error.*
27
+
28
+# macOS
29
+.DS_Store
30
+*.pem
31
+
32
+# local env files
33
+.env*.local
34
+
35
+# typescript
36
+*.tsbuildinfo
37
+
38
+app-example

+ 50
- 0
README.md Vedi File

@@ -0,0 +1,50 @@
1
+# Welcome to your Expo app 👋
2
+
3
+This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
4
+
5
+## Get started
6
+
7
+1. Install dependencies
8
+
9
+   ```bash
10
+   npm install
11
+   ```
12
+
13
+2. Start the app
14
+
15
+   ```bash
16
+    npx expo start
17
+   ```
18
+
19
+In the output, you'll find options to open the app in a
20
+
21
+- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
22
+- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
23
+- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
24
+- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
25
+
26
+You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
27
+
28
+## Get a fresh project
29
+
30
+When you're ready, run:
31
+
32
+```bash
33
+npm run reset-project
34
+```
35
+
36
+This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
37
+
38
+## Learn more
39
+
40
+To learn more about developing your project with Expo, look at the following resources:
41
+
42
+- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
43
+- [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.
44
+
45
+## Join the community
46
+
47
+Join our community of developers creating universal apps.
48
+
49
+- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
50
+- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.

+ 41
- 0
app.json Vedi File

@@ -0,0 +1,41 @@
1
+{
2
+  "expo": {
3
+    "name": "lesson01",
4
+    "slug": "lesson01",
5
+    "version": "1.0.0",
6
+    "orientation": "portrait",
7
+    "icon": "./assets/images/icon.png",
8
+    "scheme": "myapp",
9
+    "userInterfaceStyle": "automatic",
10
+    "newArchEnabled": true,
11
+    "ios": {
12
+      "supportsTablet": true
13
+    },
14
+    "android": {
15
+      "adaptiveIcon": {
16
+        "foregroundImage": "./assets/images/adaptive-icon.png",
17
+        "backgroundColor": "#ffffff"
18
+      }
19
+    },
20
+    "web": {
21
+      "bundler": "metro",
22
+      "output": "static",
23
+      "favicon": "./assets/images/favicon.png"
24
+    },
25
+    "plugins": [
26
+      "expo-router",
27
+      [
28
+        "expo-splash-screen",
29
+        {
30
+          "image": "./assets/images/splash-icon.png",
31
+          "imageWidth": 200,
32
+          "resizeMode": "contain",
33
+          "backgroundColor": "#ffffff"
34
+        }
35
+      ]
36
+    ],
37
+    "experiments": {
38
+      "typedRoutes": true
39
+    }
40
+  }
41
+}

+ 45
- 0
app/(tabs)/_layout.tsx Vedi File

@@ -0,0 +1,45 @@
1
+import { Tabs } from 'expo-router';
2
+import React from 'react';
3
+import { Platform } from 'react-native';
4
+
5
+import { HapticTab } from '@/components/HapticTab';
6
+import { IconSymbol } from '@/components/ui/IconSymbol';
7
+import TabBarBackground from '@/components/ui/TabBarBackground';
8
+import { Colors } from '@/constants/Colors';
9
+import { useColorScheme } from '@/hooks/useColorScheme';
10
+
11
+export default function TabLayout() {
12
+  const colorScheme = useColorScheme();
13
+
14
+  return (
15
+    <Tabs
16
+      screenOptions={{
17
+        tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
18
+        headerShown: false,
19
+        tabBarButton: HapticTab,
20
+        tabBarBackground: TabBarBackground,
21
+        tabBarStyle: Platform.select({
22
+          ios: {
23
+            // Use a transparent background on iOS to show the blur effect
24
+            position: 'absolute',
25
+          },
26
+          default: {},
27
+        }),
28
+      }}>
29
+      <Tabs.Screen
30
+        name="index"
31
+        options={{
32
+          title: 'Home',
33
+          tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
34
+        }}
35
+      />
36
+      <Tabs.Screen
37
+        name="explore"
38
+        options={{
39
+          title: 'Explore',
40
+          tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
41
+        }}
42
+      />
43
+    </Tabs>
44
+  );
45
+}

+ 109
- 0
app/(tabs)/explore.tsx Vedi File

@@ -0,0 +1,109 @@
1
+import { StyleSheet, Image, Platform } from 'react-native';
2
+
3
+import { Collapsible } from '@/components/Collapsible';
4
+import { ExternalLink } from '@/components/ExternalLink';
5
+import ParallaxScrollView from '@/components/ParallaxScrollView';
6
+import { ThemedText } from '@/components/ThemedText';
7
+import { ThemedView } from '@/components/ThemedView';
8
+import { IconSymbol } from '@/components/ui/IconSymbol';
9
+
10
+export default function TabTwoScreen() {
11
+  return (
12
+    <ParallaxScrollView
13
+      headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
14
+      headerImage={
15
+        <IconSymbol
16
+          size={310}
17
+          color="#808080"
18
+          name="chevron.left.forwardslash.chevron.right"
19
+          style={styles.headerImage}
20
+        />
21
+      }>
22
+      <ThemedView style={styles.titleContainer}>
23
+        <ThemedText type="title">Explore</ThemedText>
24
+      </ThemedView>
25
+      <ThemedText>This app includes example code to help you get started.</ThemedText>
26
+      <Collapsible title="File-based routing">
27
+        <ThemedText>
28
+          This app has two screens:{' '}
29
+          <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
30
+          <ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
31
+        </ThemedText>
32
+        <ThemedText>
33
+          The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
34
+          sets up the tab navigator.
35
+        </ThemedText>
36
+        <ExternalLink href="https://docs.expo.dev/router/introduction">
37
+          <ThemedText type="link">Learn more</ThemedText>
38
+        </ExternalLink>
39
+      </Collapsible>
40
+      <Collapsible title="Android, iOS, and web support">
41
+        <ThemedText>
42
+          You can open this project on Android, iOS, and the web. To open the web version, press{' '}
43
+          <ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
44
+        </ThemedText>
45
+      </Collapsible>
46
+      <Collapsible title="Images">
47
+        <ThemedText>
48
+          For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
49
+          <ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
50
+          different screen densities
51
+        </ThemedText>
52
+        <Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
53
+        <ExternalLink href="https://reactnative.dev/docs/images">
54
+          <ThemedText type="link">Learn more</ThemedText>
55
+        </ExternalLink>
56
+      </Collapsible>
57
+      <Collapsible title="Custom fonts">
58
+        <ThemedText>
59
+          Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
60
+          <ThemedText style={{ fontFamily: 'SpaceMono' }}>
61
+            custom fonts such as this one.
62
+          </ThemedText>
63
+        </ThemedText>
64
+        <ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
65
+          <ThemedText type="link">Learn more</ThemedText>
66
+        </ExternalLink>
67
+      </Collapsible>
68
+      <Collapsible title="Light and dark mode components">
69
+        <ThemedText>
70
+          This template has light and dark mode support. The{' '}
71
+          <ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
72
+          what the user's current color scheme is, and so you can adjust UI colors accordingly.
73
+        </ThemedText>
74
+        <ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
75
+          <ThemedText type="link">Learn more</ThemedText>
76
+        </ExternalLink>
77
+      </Collapsible>
78
+      <Collapsible title="Animations">
79
+        <ThemedText>
80
+          This template includes an example of an animated component. The{' '}
81
+          <ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
82
+          the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText>{' '}
83
+          library to create a waving hand animation.
84
+        </ThemedText>
85
+        {Platform.select({
86
+          ios: (
87
+            <ThemedText>
88
+              The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
89
+              component provides a parallax effect for the header image.
90
+            </ThemedText>
91
+          ),
92
+        })}
93
+      </Collapsible>
94
+    </ParallaxScrollView>
95
+  );
96
+}
97
+
98
+const styles = StyleSheet.create({
99
+  headerImage: {
100
+    color: '#808080',
101
+    bottom: -90,
102
+    left: -35,
103
+    position: 'absolute',
104
+  },
105
+  titleContainer: {
106
+    flexDirection: 'row',
107
+    gap: 8,
108
+  },
109
+});

+ 74
- 0
app/(tabs)/index.tsx Vedi File

@@ -0,0 +1,74 @@
1
+import { Image, StyleSheet, Platform } from 'react-native';
2
+
3
+import { HelloWave } from '@/components/HelloWave';
4
+import ParallaxScrollView from '@/components/ParallaxScrollView';
5
+import { ThemedText } from '@/components/ThemedText';
6
+import { ThemedView } from '@/components/ThemedView';
7
+
8
+export default function HomeScreen() {
9
+  return (
10
+    <ParallaxScrollView
11
+      headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
12
+      headerImage={
13
+        <Image
14
+          source={require('@/assets/images/partial-react-logo.png')}
15
+          style={styles.reactLogo}
16
+        />
17
+      }>
18
+      <ThemedView style={styles.titleContainer}>
19
+        <ThemedText type="title">Welcome!</ThemedText>
20
+        <HelloWave />
21
+      </ThemedView>
22
+      <ThemedView style={styles.stepContainer}>
23
+        <ThemedText type="subtitle">Step 1: Try it</ThemedText>
24
+        <ThemedText>
25
+          Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
26
+          Press{' '}
27
+          <ThemedText type="defaultSemiBold">
28
+            {Platform.select({
29
+              ios: 'cmd + d',
30
+              android: 'cmd + m',
31
+              web: 'F12'
32
+            })}
33
+          </ThemedText>{' '}
34
+          to open developer tools.
35
+        </ThemedText>
36
+      </ThemedView>
37
+      <ThemedView style={styles.stepContainer}>
38
+        <ThemedText type="subtitle">Step 2: Explore</ThemedText>
39
+        <ThemedText>
40
+          Tap the Explore tab to learn more about what's included in this starter app.
41
+        </ThemedText>
42
+      </ThemedView>
43
+      <ThemedView style={styles.stepContainer}>
44
+        <ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
45
+        <ThemedText>
46
+          When you're ready, run{' '}
47
+          <ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
48
+          <ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
49
+          <ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
50
+          <ThemedText type="defaultSemiBold">app-example</ThemedText>.
51
+        </ThemedText>
52
+      </ThemedView>
53
+    </ParallaxScrollView>
54
+  );
55
+}
56
+
57
+const styles = StyleSheet.create({
58
+  titleContainer: {
59
+    flexDirection: 'row',
60
+    alignItems: 'center',
61
+    gap: 8,
62
+  },
63
+  stepContainer: {
64
+    gap: 8,
65
+    marginBottom: 8,
66
+  },
67
+  reactLogo: {
68
+    height: 178,
69
+    width: 290,
70
+    bottom: 0,
71
+    left: 0,
72
+    position: 'absolute',
73
+  },
74
+});

+ 32
- 0
app/+not-found.tsx Vedi File

@@ -0,0 +1,32 @@
1
+import { Link, Stack } from 'expo-router';
2
+import { StyleSheet } from 'react-native';
3
+
4
+import { ThemedText } from '@/components/ThemedText';
5
+import { ThemedView } from '@/components/ThemedView';
6
+
7
+export default function NotFoundScreen() {
8
+  return (
9
+    <>
10
+      <Stack.Screen options={{ title: 'Oops!' }} />
11
+      <ThemedView style={styles.container}>
12
+        <ThemedText type="title">This screen doesn't exist.</ThemedText>
13
+        <Link href="/" style={styles.link}>
14
+          <ThemedText type="link">Go to home screen!</ThemedText>
15
+        </Link>
16
+      </ThemedView>
17
+    </>
18
+  );
19
+}
20
+
21
+const styles = StyleSheet.create({
22
+  container: {
23
+    flex: 1,
24
+    alignItems: 'center',
25
+    justifyContent: 'center',
26
+    padding: 20,
27
+  },
28
+  link: {
29
+    marginTop: 15,
30
+    paddingVertical: 15,
31
+  },
32
+});

+ 39
- 0
app/_layout.tsx Vedi File

@@ -0,0 +1,39 @@
1
+import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
2
+import { useFonts } from 'expo-font';
3
+import { Stack } from 'expo-router';
4
+import * as SplashScreen from 'expo-splash-screen';
5
+import { StatusBar } from 'expo-status-bar';
6
+import { useEffect } from 'react';
7
+import 'react-native-reanimated';
8
+
9
+import { useColorScheme } from '@/hooks/useColorScheme';
10
+
11
+// Prevent the splash screen from auto-hiding before asset loading is complete.
12
+SplashScreen.preventAutoHideAsync();
13
+
14
+export default function RootLayout() {
15
+  const colorScheme = useColorScheme();
16
+  const [loaded] = useFonts({
17
+    SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
18
+  });
19
+
20
+  useEffect(() => {
21
+    if (loaded) {
22
+      SplashScreen.hideAsync();
23
+    }
24
+  }, [loaded]);
25
+
26
+  if (!loaded) {
27
+    return null;
28
+  }
29
+
30
+  return (
31
+    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
32
+      <Stack>
33
+        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
34
+        <Stack.Screen name="+not-found" />
35
+      </Stack>
36
+      <StatusBar style="auto" />
37
+    </ThemeProvider>
38
+  );
39
+}

BIN
assets/fonts/SpaceMono-Regular.ttf Vedi File


BIN
assets/images/adaptive-icon.png Vedi File


BIN
assets/images/favicon.png Vedi File


BIN
assets/images/icon.png Vedi File


BIN
assets/images/partial-react-logo.png Vedi File


BIN
assets/images/react-logo.png Vedi File


BIN
assets/images/react-logo@2x.png Vedi File


BIN
assets/images/react-logo@3x.png Vedi File


BIN
assets/images/splash-icon.png Vedi File


+ 45
- 0
components/Collapsible.tsx Vedi File

@@ -0,0 +1,45 @@
1
+import { PropsWithChildren, useState } from 'react';
2
+import { StyleSheet, TouchableOpacity } from 'react-native';
3
+
4
+import { ThemedText } from '@/components/ThemedText';
5
+import { ThemedView } from '@/components/ThemedView';
6
+import { IconSymbol } from '@/components/ui/IconSymbol';
7
+import { Colors } from '@/constants/Colors';
8
+import { useColorScheme } from '@/hooks/useColorScheme';
9
+
10
+export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
11
+  const [isOpen, setIsOpen] = useState(false);
12
+  const theme = useColorScheme() ?? 'light';
13
+
14
+  return (
15
+    <ThemedView>
16
+      <TouchableOpacity
17
+        style={styles.heading}
18
+        onPress={() => setIsOpen((value) => !value)}
19
+        activeOpacity={0.8}>
20
+        <IconSymbol
21
+          name="chevron.right"
22
+          size={18}
23
+          weight="medium"
24
+          color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}
25
+          style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }}
26
+        />
27
+
28
+        <ThemedText type="defaultSemiBold">{title}</ThemedText>
29
+      </TouchableOpacity>
30
+      {isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
31
+    </ThemedView>
32
+  );
33
+}
34
+
35
+const styles = StyleSheet.create({
36
+  heading: {
37
+    flexDirection: 'row',
38
+    alignItems: 'center',
39
+    gap: 6,
40
+  },
41
+  content: {
42
+    marginTop: 6,
43
+    marginLeft: 24,
44
+  },
45
+});

+ 24
- 0
components/ExternalLink.tsx Vedi File

@@ -0,0 +1,24 @@
1
+import { Link } from 'expo-router';
2
+import { openBrowserAsync } from 'expo-web-browser';
3
+import { type ComponentProps } from 'react';
4
+import { Platform } from 'react-native';
5
+
6
+type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: string };
7
+
8
+export function ExternalLink({ href, ...rest }: Props) {
9
+  return (
10
+    <Link
11
+      target="_blank"
12
+      {...rest}
13
+      href={href}
14
+      onPress={async (event) => {
15
+        if (Platform.OS !== 'web') {
16
+          // Prevent the default behavior of linking to the default browser on native.
17
+          event.preventDefault();
18
+          // Open the link in an in-app browser.
19
+          await openBrowserAsync(href);
20
+        }
21
+      }}
22
+    />
23
+  );
24
+}

+ 18
- 0
components/HapticTab.tsx Vedi File

@@ -0,0 +1,18 @@
1
+import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs';
2
+import { PlatformPressable } from '@react-navigation/elements';
3
+import * as Haptics from 'expo-haptics';
4
+
5
+export function HapticTab(props: BottomTabBarButtonProps) {
6
+  return (
7
+    <PlatformPressable
8
+      {...props}
9
+      onPressIn={(ev) => {
10
+        if (process.env.EXPO_OS === 'ios') {
11
+          // Add a soft haptic feedback when pressing down on the tabs.
12
+          Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
13
+        }
14
+        props.onPressIn?.(ev);
15
+      }}
16
+    />
17
+  );
18
+}

+ 40
- 0
components/HelloWave.tsx Vedi File

@@ -0,0 +1,40 @@
1
+import { useEffect } from 'react';
2
+import { StyleSheet } from 'react-native';
3
+import Animated, {
4
+  useSharedValue,
5
+  useAnimatedStyle,
6
+  withTiming,
7
+  withRepeat,
8
+  withSequence,
9
+} from 'react-native-reanimated';
10
+
11
+import { ThemedText } from '@/components/ThemedText';
12
+
13
+export function HelloWave() {
14
+  const rotationAnimation = useSharedValue(0);
15
+
16
+  useEffect(() => {
17
+    rotationAnimation.value = withRepeat(
18
+      withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })),
19
+      4 // Run the animation 4 times
20
+    );
21
+  }, []);
22
+
23
+  const animatedStyle = useAnimatedStyle(() => ({
24
+    transform: [{ rotate: `${rotationAnimation.value}deg` }],
25
+  }));
26
+
27
+  return (
28
+    <Animated.View style={animatedStyle}>
29
+      <ThemedText style={styles.text}>👋</ThemedText>
30
+    </Animated.View>
31
+  );
32
+}
33
+
34
+const styles = StyleSheet.create({
35
+  text: {
36
+    fontSize: 28,
37
+    lineHeight: 32,
38
+    marginTop: -6,
39
+  },
40
+});

+ 82
- 0
components/ParallaxScrollView.tsx Vedi File

@@ -0,0 +1,82 @@
1
+import type { PropsWithChildren, ReactElement } from 'react';
2
+import { StyleSheet } from 'react-native';
3
+import Animated, {
4
+  interpolate,
5
+  useAnimatedRef,
6
+  useAnimatedStyle,
7
+  useScrollViewOffset,
8
+} from 'react-native-reanimated';
9
+
10
+import { ThemedView } from '@/components/ThemedView';
11
+import { useBottomTabOverflow } from '@/components/ui/TabBarBackground';
12
+import { useColorScheme } from '@/hooks/useColorScheme';
13
+
14
+const HEADER_HEIGHT = 250;
15
+
16
+type Props = PropsWithChildren<{
17
+  headerImage: ReactElement;
18
+  headerBackgroundColor: { dark: string; light: string };
19
+}>;
20
+
21
+export default function ParallaxScrollView({
22
+  children,
23
+  headerImage,
24
+  headerBackgroundColor,
25
+}: Props) {
26
+  const colorScheme = useColorScheme() ?? 'light';
27
+  const scrollRef = useAnimatedRef<Animated.ScrollView>();
28
+  const scrollOffset = useScrollViewOffset(scrollRef);
29
+  const bottom = useBottomTabOverflow();
30
+  const headerAnimatedStyle = useAnimatedStyle(() => {
31
+    return {
32
+      transform: [
33
+        {
34
+          translateY: interpolate(
35
+            scrollOffset.value,
36
+            [-HEADER_HEIGHT, 0, HEADER_HEIGHT],
37
+            [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]
38
+          ),
39
+        },
40
+        {
41
+          scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),
42
+        },
43
+      ],
44
+    };
45
+  });
46
+
47
+  return (
48
+    <ThemedView style={styles.container}>
49
+      <Animated.ScrollView
50
+        ref={scrollRef}
51
+        scrollEventThrottle={16}
52
+        scrollIndicatorInsets={{ bottom }}
53
+        contentContainerStyle={{ paddingBottom: bottom }}>
54
+        <Animated.View
55
+          style={[
56
+            styles.header,
57
+            { backgroundColor: headerBackgroundColor[colorScheme] },
58
+            headerAnimatedStyle,
59
+          ]}>
60
+          {headerImage}
61
+        </Animated.View>
62
+        <ThemedView style={styles.content}>{children}</ThemedView>
63
+      </Animated.ScrollView>
64
+    </ThemedView>
65
+  );
66
+}
67
+
68
+const styles = StyleSheet.create({
69
+  container: {
70
+    flex: 1,
71
+  },
72
+  header: {
73
+    height: HEADER_HEIGHT,
74
+    overflow: 'hidden',
75
+  },
76
+  content: {
77
+    flex: 1,
78
+    padding: 32,
79
+    gap: 16,
80
+    overflow: 'hidden',
81
+  },
82
+});

+ 60
- 0
components/ThemedText.tsx Vedi File

@@ -0,0 +1,60 @@
1
+import { Text, type TextProps, StyleSheet } from 'react-native';
2
+
3
+import { useThemeColor } from '@/hooks/useThemeColor';
4
+
5
+export type ThemedTextProps = TextProps & {
6
+  lightColor?: string;
7
+  darkColor?: string;
8
+  type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
9
+};
10
+
11
+export function ThemedText({
12
+  style,
13
+  lightColor,
14
+  darkColor,
15
+  type = 'default',
16
+  ...rest
17
+}: ThemedTextProps) {
18
+  const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
19
+
20
+  return (
21
+    <Text
22
+      style={[
23
+        { color },
24
+        type === 'default' ? styles.default : undefined,
25
+        type === 'title' ? styles.title : undefined,
26
+        type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,
27
+        type === 'subtitle' ? styles.subtitle : undefined,
28
+        type === 'link' ? styles.link : undefined,
29
+        style,
30
+      ]}
31
+      {...rest}
32
+    />
33
+  );
34
+}
35
+
36
+const styles = StyleSheet.create({
37
+  default: {
38
+    fontSize: 16,
39
+    lineHeight: 24,
40
+  },
41
+  defaultSemiBold: {
42
+    fontSize: 16,
43
+    lineHeight: 24,
44
+    fontWeight: '600',
45
+  },
46
+  title: {
47
+    fontSize: 32,
48
+    fontWeight: 'bold',
49
+    lineHeight: 32,
50
+  },
51
+  subtitle: {
52
+    fontSize: 20,
53
+    fontWeight: 'bold',
54
+  },
55
+  link: {
56
+    lineHeight: 30,
57
+    fontSize: 16,
58
+    color: '#0a7ea4',
59
+  },
60
+});

+ 14
- 0
components/ThemedView.tsx Vedi File

@@ -0,0 +1,14 @@
1
+import { View, type ViewProps } from 'react-native';
2
+
3
+import { useThemeColor } from '@/hooks/useThemeColor';
4
+
5
+export type ThemedViewProps = ViewProps & {
6
+  lightColor?: string;
7
+  darkColor?: string;
8
+};
9
+
10
+export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
11
+  const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
12
+
13
+  return <View style={[{ backgroundColor }, style]} {...otherProps} />;
14
+}

+ 10
- 0
components/__tests__/ThemedText-test.tsx Vedi File

@@ -0,0 +1,10 @@
1
+import * as React from 'react';
2
+import renderer from 'react-test-renderer';
3
+
4
+import { ThemedText } from '../ThemedText';
5
+
6
+it(`renders correctly`, () => {
7
+  const tree = renderer.create(<ThemedText>Snapshot test!</ThemedText>).toJSON();
8
+
9
+  expect(tree).toMatchSnapshot();
10
+});

+ 24
- 0
components/__tests__/__snapshots__/ThemedText-test.tsx.snap Vedi File

@@ -0,0 +1,24 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`renders correctly 1`] = `
4
+<Text
5
+  style={
6
+    [
7
+      {
8
+        "color": "#11181C",
9
+      },
10
+      {
11
+        "fontSize": 16,
12
+        "lineHeight": 24,
13
+      },
14
+      undefined,
15
+      undefined,
16
+      undefined,
17
+      undefined,
18
+      undefined,
19
+    ]
20
+  }
21
+>
22
+  Snapshot test!
23
+</Text>
24
+`;

+ 32
- 0
components/ui/IconSymbol.ios.tsx Vedi File

@@ -0,0 +1,32 @@
1
+import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';
2
+import { StyleProp, ViewStyle } from 'react-native';
3
+
4
+export function IconSymbol({
5
+  name,
6
+  size = 24,
7
+  color,
8
+  style,
9
+  weight = 'regular',
10
+}: {
11
+  name: SymbolViewProps['name'];
12
+  size?: number;
13
+  color: string;
14
+  style?: StyleProp<ViewStyle>;
15
+  weight?: SymbolWeight;
16
+}) {
17
+  return (
18
+    <SymbolView
19
+      weight={weight}
20
+      tintColor={color}
21
+      resizeMode="scaleAspectFit"
22
+      name={name}
23
+      style={[
24
+        {
25
+          width: size,
26
+          height: size,
27
+        },
28
+        style,
29
+      ]}
30
+    />
31
+  );
32
+}

+ 43
- 0
components/ui/IconSymbol.tsx Vedi File

@@ -0,0 +1,43 @@
1
+// This file is a fallback for using MaterialIcons on Android and web.
2
+
3
+import MaterialIcons from '@expo/vector-icons/MaterialIcons';
4
+import { SymbolWeight } from 'expo-symbols';
5
+import React from 'react';
6
+import { OpaqueColorValue, StyleProp, ViewStyle } from 'react-native';
7
+
8
+// Add your SFSymbol to MaterialIcons mappings here.
9
+const MAPPING = {
10
+  // See MaterialIcons here: https://icons.expo.fyi
11
+  // See SF Symbols in the SF Symbols app on Mac.
12
+  'house.fill': 'home',
13
+  'paperplane.fill': 'send',
14
+  'chevron.left.forwardslash.chevron.right': 'code',
15
+  'chevron.right': 'chevron-right',
16
+} as Partial<
17
+  Record<
18
+    import('expo-symbols').SymbolViewProps['name'],
19
+    React.ComponentProps<typeof MaterialIcons>['name']
20
+  >
21
+>;
22
+
23
+export type IconSymbolName = keyof typeof MAPPING;
24
+
25
+/**
26
+ * 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.
27
+ *
28
+ * Icon `name`s are based on SFSymbols and require manual mapping to MaterialIcons.
29
+ */
30
+export function IconSymbol({
31
+  name,
32
+  size = 24,
33
+  color,
34
+  style,
35
+}: {
36
+  name: IconSymbolName;
37
+  size?: number;
38
+  color: string | OpaqueColorValue;
39
+  style?: StyleProp<ViewStyle>;
40
+  weight?: SymbolWeight;
41
+}) {
42
+  return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
43
+}

+ 22
- 0
components/ui/TabBarBackground.ios.tsx Vedi File

@@ -0,0 +1,22 @@
1
+import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
2
+import { BlurView } from 'expo-blur';
3
+import { StyleSheet } from 'react-native';
4
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
+
6
+export default function BlurTabBarBackground() {
7
+  return (
8
+    <BlurView
9
+      // System chrome material automatically adapts to the system's theme
10
+      // and matches the native tab bar appearance on iOS.
11
+      tint="systemChromeMaterial"
12
+      intensity={100}
13
+      style={StyleSheet.absoluteFill}
14
+    />
15
+  );
16
+}
17
+
18
+export function useBottomTabOverflow() {
19
+  const tabHeight = useBottomTabBarHeight();
20
+  const { bottom } = useSafeAreaInsets();
21
+  return tabHeight - bottom;
22
+}

+ 6
- 0
components/ui/TabBarBackground.tsx Vedi File

@@ -0,0 +1,6 @@
1
+// This is a shim for web and Android where the tab bar is generally opaque.
2
+export default undefined;
3
+
4
+export function useBottomTabOverflow() {
5
+  return 0;
6
+}

+ 26
- 0
constants/Colors.ts Vedi File

@@ -0,0 +1,26 @@
1
+/**
2
+ * Below are the colors that are used in the app. The colors are defined in the light and dark mode.
3
+ * 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.
4
+ */
5
+
6
+const tintColorLight = '#0a7ea4';
7
+const tintColorDark = '#fff';
8
+
9
+export const Colors = {
10
+  light: {
11
+    text: '#11181C',
12
+    background: '#fff',
13
+    tint: tintColorLight,
14
+    icon: '#687076',
15
+    tabIconDefault: '#687076',
16
+    tabIconSelected: tintColorLight,
17
+  },
18
+  dark: {
19
+    text: '#ECEDEE',
20
+    background: '#151718',
21
+    tint: tintColorDark,
22
+    icon: '#9BA1A6',
23
+    tabIconDefault: '#9BA1A6',
24
+    tabIconSelected: tintColorDark,
25
+  },
26
+};

+ 1
- 0
hooks/useColorScheme.ts Vedi File

@@ -0,0 +1 @@
1
+export { useColorScheme } from 'react-native';

+ 21
- 0
hooks/useColorScheme.web.ts Vedi File

@@ -0,0 +1,21 @@
1
+import { useEffect, useState } from 'react';
2
+import { useColorScheme as useRNColorScheme } from 'react-native';
3
+
4
+/**
5
+ * To support static rendering, this value needs to be re-calculated on the client side for web
6
+ */
7
+export function useColorScheme() {
8
+  const [hasHydrated, setHasHydrated] = useState(false);
9
+
10
+  useEffect(() => {
11
+    setHasHydrated(true);
12
+  }, []);
13
+
14
+  const colorScheme = useRNColorScheme();
15
+
16
+  if (hasHydrated) {
17
+    return colorScheme;
18
+  }
19
+
20
+  return 'light';
21
+}

+ 21
- 0
hooks/useThemeColor.ts Vedi File

@@ -0,0 +1,21 @@
1
+/**
2
+ * Learn more about light and dark modes:
3
+ * https://docs.expo.dev/guides/color-schemes/
4
+ */
5
+
6
+import { Colors } from '@/constants/Colors';
7
+import { useColorScheme } from '@/hooks/useColorScheme';
8
+
9
+export function useThemeColor(
10
+  props: { light?: string; dark?: string },
11
+  colorName: keyof typeof Colors.light & keyof typeof Colors.dark
12
+) {
13
+  const theme = useColorScheme() ?? 'light';
14
+  const colorFromProps = props[theme];
15
+
16
+  if (colorFromProps) {
17
+    return colorFromProps;
18
+  } else {
19
+    return Colors[theme][colorName];
20
+  }
21
+}

+ 14386
- 0
package-lock.json
File diff soppresso perché troppo grande
Vedi File


+ 54
- 0
package.json Vedi File

@@ -0,0 +1,54 @@
1
+{
2
+  "name": "lesson01",
3
+  "main": "expo-router/entry",
4
+  "version": "1.0.0",
5
+  "scripts": {
6
+    "start": "expo start",
7
+    "reset-project": "node ./scripts/reset-project.js",
8
+    "android": "expo start --android",
9
+    "ios": "expo start --ios",
10
+    "web": "expo start --web",
11
+    "test": "jest --watchAll",
12
+    "lint": "expo lint"
13
+  },
14
+  "jest": {
15
+    "preset": "jest-expo"
16
+  },
17
+  "dependencies": {
18
+    "@expo/vector-icons": "^14.0.2",
19
+    "@react-navigation/bottom-tabs": "^7.2.0",
20
+    "@react-navigation/native": "^7.0.14",
21
+    "expo": "~52.0.40",
22
+    "expo-blur": "~14.0.3",
23
+    "expo-constants": "~17.0.8",
24
+    "expo-font": "~13.0.4",
25
+    "expo-haptics": "~14.0.1",
26
+    "expo-linking": "~7.0.5",
27
+    "expo-router": "~4.0.19",
28
+    "expo-splash-screen": "~0.29.22",
29
+    "expo-status-bar": "~2.0.1",
30
+    "expo-symbols": "~0.2.2",
31
+    "expo-system-ui": "~4.0.8",
32
+    "expo-web-browser": "~14.0.2",
33
+    "react": "18.3.1",
34
+    "react-dom": "18.3.1",
35
+    "react-native": "0.76.7",
36
+    "react-native-gesture-handler": "~2.20.2",
37
+    "react-native-reanimated": "~3.16.1",
38
+    "react-native-safe-area-context": "4.12.0",
39
+    "react-native-screens": "~4.4.0",
40
+    "react-native-web": "~0.19.13",
41
+    "react-native-webview": "13.12.5"
42
+  },
43
+  "devDependencies": {
44
+    "@babel/core": "^7.25.2",
45
+    "@types/jest": "^29.5.12",
46
+    "@types/react": "~18.3.12",
47
+    "@types/react-test-renderer": "^18.3.0",
48
+    "jest": "^29.2.1",
49
+    "jest-expo": "~52.0.6",
50
+    "react-test-renderer": "18.3.1",
51
+    "typescript": "^5.3.3"
52
+  },
53
+  "private": true
54
+}

+ 112
- 0
scripts/reset-project.js Vedi File

@@ -0,0 +1,112 @@
1
+#!/usr/bin/env node
2
+
3
+/**
4
+ * This script is used to reset the project to a blank state.
5
+ * 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.
6
+ * You can remove the `reset-project` script from package.json and safely delete this file after running it.
7
+ */
8
+
9
+const fs = require("fs");
10
+const path = require("path");
11
+const readline = require("readline");
12
+
13
+const root = process.cwd();
14
+const oldDirs = ["app", "components", "hooks", "constants", "scripts"];
15
+const exampleDir = "app-example";
16
+const newAppDir = "app";
17
+const exampleDirPath = path.join(root, exampleDir);
18
+
19
+const indexContent = `import { Text, View } from "react-native";
20
+
21
+export default function Index() {
22
+  return (
23
+    <View
24
+      style={{
25
+        flex: 1,
26
+        justifyContent: "center",
27
+        alignItems: "center",
28
+      }}
29
+    >
30
+      <Text>Edit app/index.tsx to edit this screen.</Text>
31
+    </View>
32
+  );
33
+}
34
+`;
35
+
36
+const layoutContent = `import { Stack } from "expo-router";
37
+
38
+export default function RootLayout() {
39
+  return <Stack />;
40
+}
41
+`;
42
+
43
+const rl = readline.createInterface({
44
+  input: process.stdin,
45
+  output: process.stdout,
46
+});
47
+
48
+const moveDirectories = async (userInput) => {
49
+  try {
50
+    if (userInput === "y") {
51
+      // Create the app-example directory
52
+      await fs.promises.mkdir(exampleDirPath, { recursive: true });
53
+      console.log(`📁 /${exampleDir} directory created.`);
54
+    }
55
+
56
+    // Move old directories to new app-example directory or delete them
57
+    for (const dir of oldDirs) {
58
+      const oldDirPath = path.join(root, dir);
59
+      if (fs.existsSync(oldDirPath)) {
60
+        if (userInput === "y") {
61
+          const newDirPath = path.join(root, exampleDir, dir);
62
+          await fs.promises.rename(oldDirPath, newDirPath);
63
+          console.log(`➡️ /${dir} moved to /${exampleDir}/${dir}.`);
64
+        } else {
65
+          await fs.promises.rm(oldDirPath, { recursive: true, force: true });
66
+          console.log(`❌ /${dir} deleted.`);
67
+        }
68
+      } else {
69
+        console.log(`➡️ /${dir} does not exist, skipping.`);
70
+      }
71
+    }
72
+
73
+    // Create new /app directory
74
+    const newAppDirPath = path.join(root, newAppDir);
75
+    await fs.promises.mkdir(newAppDirPath, { recursive: true });
76
+    console.log("\n📁 New /app directory created.");
77
+
78
+    // Create index.tsx
79
+    const indexPath = path.join(newAppDirPath, "index.tsx");
80
+    await fs.promises.writeFile(indexPath, indexContent);
81
+    console.log("📄 app/index.tsx created.");
82
+
83
+    // Create _layout.tsx
84
+    const layoutPath = path.join(newAppDirPath, "_layout.tsx");
85
+    await fs.promises.writeFile(layoutPath, layoutContent);
86
+    console.log("📄 app/_layout.tsx created.");
87
+
88
+    console.log("\n✅ Project reset complete. Next steps:");
89
+    console.log(
90
+      `1. Run \`npx expo start\` to start a development server.\n2. Edit app/index.tsx to edit the main screen.${
91
+        userInput === "y"
92
+          ? `\n3. Delete the /${exampleDir} directory when you're done referencing it.`
93
+          : ""
94
+      }`
95
+    );
96
+  } catch (error) {
97
+    console.error(`❌ Error during script execution: ${error.message}`);
98
+  }
99
+};
100
+
101
+rl.question(
102
+  "Do you want to move existing files to /app-example instead of deleting them? (Y/n): ",
103
+  (answer) => {
104
+    const userInput = answer.trim().toLowerCase() || "y";
105
+    if (userInput === "y" || userInput === "n") {
106
+      moveDirectories(userInput).finally(() => rl.close());
107
+    } else {
108
+      console.log("❌ Invalid input. Please enter 'Y' or 'N'.");
109
+      rl.close();
110
+    }
111
+  }
112
+);

+ 17
- 0
tsconfig.json Vedi File

@@ -0,0 +1,17 @@
1
+{
2
+  "extends": "expo/tsconfig.base",
3
+  "compilerOptions": {
4
+    "strict": true,
5
+    "paths": {
6
+      "@/*": [
7
+        "./*"
8
+      ]
9
+    }
10
+  },
11
+  "include": [
12
+    "**/*.ts",
13
+    "**/*.tsx",
14
+    ".expo/types/**/*.ts",
15
+    "expo-env.d.ts"
16
+  ]
17
+}

Loading…
Annulla
Salva