basic camera functionality as well as framework for express nodejs backend

This commit is contained in:
Will Baumbach
2025-07-24 18:08:31 -05:00
parent be4a90cbef
commit 067ab0647b
35 changed files with 1921 additions and 878 deletions

View File

@@ -1,45 +1,31 @@
import Ionicons from '@expo/vector-icons/Ionicons';
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>
);
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#ffd33d',
headerStyle: {
backgroundColor: '#25292e',
},
headerShadowVisible: false,
headerTintColor: '#fff',
tabBarStyle: {
backgroundColor: '#25292e',
},
}}>
<Tabs.Screen name="index" options={{
title: 'Home', headerShown: false, tabBarIcon: ({ color, focused }) => (
<Ionicons name={focused ? 'home-sharp' : 'home-outline'} color={color} size={24} />
)
}} />
<Tabs.Screen name="login" options={{
title: 'Login', headerShown: false, tabBarIcon: ({ color, focused }) => (
<Ionicons name={focused ? 'person-circle-sharp' : 'person-circle-outline'} size={24} color={color} />
)
}} />
</Tabs>
);
}

View File

@@ -1,110 +0,0 @@
import { Image } from 'expo-image';
import { Platform, StyleSheet } from 'react-native';
import { Collapsible } from '@/components/Collapsible';
import { ExternalLink } from '@/components/ExternalLink';
import ParallaxScrollView from '@/components/ParallaxScrollView';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
import { IconSymbol } from '@/components/ui/IconSymbol';
export default function TabTwoScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
headerImage={
<IconSymbol
size={310}
color="#808080"
name="chevron.left.forwardslash.chevron.right"
style={styles.headerImage}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Explore</ThemedText>
</ThemedView>
<ThemedText>This app includes example code to help you get started.</ThemedText>
<Collapsible title="File-based routing">
<ThemedText>
This app has two screens:{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
</ThemedText>
<ThemedText>
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
sets up the tab navigator.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/router/introduction">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Android, iOS, and web support">
<ThemedText>
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
</ThemedText>
</Collapsible>
<Collapsible title="Images">
<ThemedText>
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
different screen densities
</ThemedText>
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
<ExternalLink href="https://reactnative.dev/docs/images">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Custom fonts">
<ThemedText>
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
custom fonts such as this one.
</ThemedText>
</ThemedText>
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Light and dark mode components">
<ThemedText>
This template has light and dark mode support. The{' '}
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
what the user&apos;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,
},
});

View File

@@ -1,75 +1,98 @@
import { Image } from 'expo-image';
import { Platform, StyleSheet } from 'react-native';
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useRef, useState } from 'react';
import { Button, Image, StyleSheet, Text, TouchableOpacity, View } 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 App() {
const [permission, requestPermission] = useCameraPermissions();
const [photo, setPhoto] = useState('');
const cameraRef = useRef<CameraView>(null);
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>
);
async function _takePhoto() {
if (cameraRef.current) {
const pic = await cameraRef.current.takePictureAsync()
setPhoto(pic.uri);
console.debug(photo)
}
}
if (!permission) {
// Camera permissions are still loading.
return <View />;
}
if (!permission.granted) {
// Camera permissions are not granted yet.
return (
<View style={styles.container}>
<Text style={styles.message}>We need your permission to show the camera</Text>
<Button onPress={requestPermission} title="grant permission" />
</View>
);
}
return (
<View style={styles.container}>
{photo ? (
<View style={styles.cameraContainer}>
<Image source={{ uri: photo }} style={styles.camera} />
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button} onPress={() => setPhoto('')}>
<Text style={styles.text}>Retake</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={_takePhoto}>
<Text style={styles.text}>Continue</Text>
</TouchableOpacity>
</View>
</View>
) : (
<>
<View style={styles.cameraContainer}>
<CameraView style={styles.camera} facing={'back'} ref={cameraRef}>
</CameraView>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button} onPress={_takePhoto}>
<Text style={styles.text}>Take Photo</Text>
</TouchableOpacity>
</View>
</>)}
</View>
);
}
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',
},
container: {
flex: 1,
backgroundColor: '#25292e',
justifyContent: 'center',
paddingHorizontal: 25,
paddingVertical: 200
},
cameraContainer: {
flex: 1,
},
message: {
textAlign: 'center',
paddingBottom: 10,
},
camera: {
flex: 1,
},
buttonContainer: {
flex: 0.2,
flexDirection: 'row',
backgroundColor: 'orange',
marginTop: 15,
borderRadius: 5,
},
button: {
flex: 1,
alignSelf: 'center',
alignItems: 'center',
},
text: {
fontSize: 24,
fontWeight: 'bold',
color: 'white',
},
});

66
app/(tabs)/login.tsx Normal file
View File

@@ -0,0 +1,66 @@
import React from 'react';
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
export default function LoginScreen() {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
function login(username:string, password:string) {
console.log(username, password);
}
return (
<View style={styles.container}>
<Text style={styles.text}>Login</Text>
<View style={{ width: '80%', marginTop: 20 }}>
<Text style={styles.text}>Username</Text>
<TextInput
style={styles.input}
value={username}
placeholder="Enter username"
onChangeText={setUsername}
/>
<Text style={styles.text}>Password</Text>
<TextInput
style={styles.input}
value={password}
placeholder="Enter password"
secureTextEntry
onChangeText={setPassword}
/>
<TouchableOpacity
style={styles.button}
onPress={() => login(username, password)}
>
<Text style={{ color: '#fff', fontWeight: 'bold' }}>Login</Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#25292e',
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: '#fff',
},
input: {
backgroundColor: '#fff',
borderRadius: 5,
padding: 10,
marginBottom: 15,
},
button: {
backgroundColor: '#1e90ff',
padding: 12,
borderRadius: 5,
alignItems: 'center',
}
});

View File

@@ -1,19 +1,15 @@
import { Link, Stack } from 'expo-router';
import { StyleSheet } from 'react-native';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
import { StyleSheet, View } from 'react-native';
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<ThemedView style={styles.container}>
<ThemedText type="title">This screen does not exist.</ThemedText>
<Link href="/" style={styles.link}>
<ThemedText type="link">Go to home screen!</ThemedText>
<Stack.Screen options={{ headerShown: false }} />
<View style={styles.container}>
<Link href="./" style={styles.button}>
Go back to Home screen!
</Link>
</ThemedView>
</View>
</>
);
}
@@ -21,12 +17,14 @@ export default function NotFoundScreen() {
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: '#25292e',
justifyContent: 'center',
padding: 20,
alignItems: 'center',
},
link: {
marginTop: 15,
paddingVertical: 15,
button: {
fontSize: 20,
textDecorationLine: 'underline',
color: '#fff',
},
});

View File

@@ -1,29 +1,9 @@
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';
import { useColorScheme } from '@/hooks/useColorScheme';
import { Stack } from "expo-router";
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
});
if (!loaded) {
// Async font loading only occurs in development.
return null;
}
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
);
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }}></Stack.Screen>
</Stack>
);
}