-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8400a47
commit 0dff40a
Showing
25 changed files
with
757 additions
and
147 deletions.
There are no files selected for viewing
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
); | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
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'; | ||
import { Provider } from 'react-redux'; | ||
import { store } from '@/store'; | ||
|
||
// 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 ( | ||
<Provider store={store}> | ||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> | ||
<Stack> | ||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} /> | ||
<Stack.Screen name="+not-found" /> | ||
</Stack> | ||
<StatusBar style="auto" /> | ||
</ThemeProvider> | ||
</Provider> | ||
|
||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
}} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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', | ||
}, | ||
}); |
Oops, something went wrong.