Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

Initial commit

Generated by create-expo-app 3.1.1.

natalie 806eb314

+632
+36
.gitignore
··· 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
app.json
··· 1 + { 2 + "expo": { 3 + "name": "amethyst", 4 + "slug": "amethyst", 5 + "version": "1.0.0", 6 + "orientation": "portrait", 7 + "icon": "./assets/images/icon.png", 8 + "scheme": "myapp", 9 + "userInterfaceStyle": "automatic", 10 + "newArchEnabled": true, 11 + "splash": { 12 + "image": "./assets/images/splash-icon.png", 13 + "resizeMode": "contain", 14 + "backgroundColor": "#ffffff" 15 + }, 16 + "ios": { 17 + "supportsTablet": true 18 + }, 19 + "android": { 20 + "adaptiveIcon": { 21 + "foregroundImage": "./assets/images/adaptive-icon.png", 22 + "backgroundColor": "#ffffff" 23 + } 24 + }, 25 + "web": { 26 + "bundler": "metro", 27 + "output": "static", 28 + "favicon": "./assets/images/favicon.png" 29 + }, 30 + "plugins": [ 31 + "expo-router" 32 + ], 33 + "experiments": { 34 + "typedRoutes": true 35 + } 36 + } 37 + }
+59
app/(tabs)/_layout.tsx
··· 1 + import React from 'react'; 2 + import FontAwesome from '@expo/vector-icons/FontAwesome'; 3 + import { Link, Tabs } from 'expo-router'; 4 + import { Pressable } from 'react-native'; 5 + 6 + import Colors from '@/constants/Colors'; 7 + import { useColorScheme } from '@/components/useColorScheme'; 8 + import { useClientOnlyValue } from '@/components/useClientOnlyValue'; 9 + 10 + // You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ 11 + function TabBarIcon(props: { 12 + name: React.ComponentProps<typeof FontAwesome>['name']; 13 + color: string; 14 + }) { 15 + return <FontAwesome size={28} style={{ marginBottom: -3 }} {...props} />; 16 + } 17 + 18 + export default function TabLayout() { 19 + const colorScheme = useColorScheme(); 20 + 21 + return ( 22 + <Tabs 23 + screenOptions={{ 24 + tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint, 25 + // Disable the static render of the header on web 26 + // to prevent a hydration error in React Navigation v6. 27 + headerShown: useClientOnlyValue(false, true), 28 + }}> 29 + <Tabs.Screen 30 + name="index" 31 + options={{ 32 + title: 'Tab One', 33 + tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />, 34 + headerRight: () => ( 35 + <Link href="/modal" asChild> 36 + <Pressable> 37 + {({ pressed }) => ( 38 + <FontAwesome 39 + name="info-circle" 40 + size={25} 41 + color={Colors[colorScheme ?? 'light'].text} 42 + style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }} 43 + /> 44 + )} 45 + </Pressable> 46 + </Link> 47 + ), 48 + }} 49 + /> 50 + <Tabs.Screen 51 + name="two" 52 + options={{ 53 + title: 'Tab Two', 54 + tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />, 55 + }} 56 + /> 57 + </Tabs> 58 + ); 59 + }
+31
app/(tabs)/index.tsx
··· 1 + import { StyleSheet } from 'react-native'; 2 + 3 + import EditScreenInfo from '@/components/EditScreenInfo'; 4 + import { Text, View } from '@/components/Themed'; 5 + 6 + export default function TabOneScreen() { 7 + return ( 8 + <View style={styles.container}> 9 + <Text style={styles.title}>Tab One</Text> 10 + <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" /> 11 + <EditScreenInfo path="app/(tabs)/index.tsx" /> 12 + </View> 13 + ); 14 + } 15 + 16 + const styles = StyleSheet.create({ 17 + container: { 18 + flex: 1, 19 + alignItems: 'center', 20 + justifyContent: 'center', 21 + }, 22 + title: { 23 + fontSize: 20, 24 + fontWeight: 'bold', 25 + }, 26 + separator: { 27 + marginVertical: 30, 28 + height: 1, 29 + width: '80%', 30 + }, 31 + });
+31
app/(tabs)/two.tsx
··· 1 + import { StyleSheet } from 'react-native'; 2 + 3 + import EditScreenInfo from '@/components/EditScreenInfo'; 4 + import { Text, View } from '@/components/Themed'; 5 + 6 + export default function TabTwoScreen() { 7 + return ( 8 + <View style={styles.container}> 9 + <Text style={styles.title}>Tab Two</Text> 10 + <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" /> 11 + <EditScreenInfo path="app/(tabs)/two.tsx" /> 12 + </View> 13 + ); 14 + } 15 + 16 + const styles = StyleSheet.create({ 17 + container: { 18 + flex: 1, 19 + alignItems: 'center', 20 + justifyContent: 'center', 21 + }, 22 + title: { 23 + fontSize: 20, 24 + fontWeight: 'bold', 25 + }, 26 + separator: { 27 + marginVertical: 30, 28 + height: 1, 29 + width: '80%', 30 + }, 31 + });
+38
app/+html.tsx
··· 1 + import { ScrollViewStyleReset } from 'expo-router/html'; 2 + 3 + // This file is web-only and used to configure the root HTML for every 4 + // web page during static rendering. 5 + // The contents of this function only run in Node.js environments and 6 + // do not have access to the DOM or browser APIs. 7 + export default function Root({ children }: { children: React.ReactNode }) { 8 + return ( 9 + <html lang="en"> 10 + <head> 11 + <meta charSet="utf-8" /> 12 + <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> 13 + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 14 + 15 + {/* 16 + Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. 17 + However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. 18 + */} 19 + <ScrollViewStyleReset /> 20 + 21 + {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} 22 + <style dangerouslySetInnerHTML={{ __html: responsiveBackground }} /> 23 + {/* Add any additional <head> elements that you want globally available on web... */} 24 + </head> 25 + <body>{children}</body> 26 + </html> 27 + ); 28 + } 29 + 30 + const responsiveBackground = ` 31 + body { 32 + background-color: #fff; 33 + } 34 + @media (prefers-color-scheme: dark) { 35 + body { 36 + background-color: #000; 37 + } 38 + }`;
+40
app/+not-found.tsx
··· 1 + import { Link, Stack } from 'expo-router'; 2 + import { StyleSheet } from 'react-native'; 3 + 4 + import { Text, View } from '@/components/Themed'; 5 + 6 + export default function NotFoundScreen() { 7 + return ( 8 + <> 9 + <Stack.Screen options={{ title: 'Oops!' }} /> 10 + <View style={styles.container}> 11 + <Text style={styles.title}>This screen doesn't exist.</Text> 12 + 13 + <Link href="/" style={styles.link}> 14 + <Text style={styles.linkText}>Go to home screen!</Text> 15 + </Link> 16 + </View> 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 + title: { 29 + fontSize: 20, 30 + fontWeight: 'bold', 31 + }, 32 + link: { 33 + marginTop: 15, 34 + paddingVertical: 15, 35 + }, 36 + linkText: { 37 + fontSize: 14, 38 + color: '#2e78b7', 39 + }, 40 + });
+59
app/_layout.tsx
··· 1 + import FontAwesome from '@expo/vector-icons/FontAwesome'; 2 + import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; 3 + import { useFonts } from 'expo-font'; 4 + import { Stack } from 'expo-router'; 5 + import * as SplashScreen from 'expo-splash-screen'; 6 + import { useEffect } from 'react'; 7 + import 'react-native-reanimated'; 8 + 9 + import { useColorScheme } from '@/components/useColorScheme'; 10 + 11 + export { 12 + // Catch any errors thrown by the Layout component. 13 + ErrorBoundary, 14 + } from 'expo-router'; 15 + 16 + export const unstable_settings = { 17 + // Ensure that reloading on `/modal` keeps a back button present. 18 + initialRouteName: '(tabs)', 19 + }; 20 + 21 + // Prevent the splash screen from auto-hiding before asset loading is complete. 22 + SplashScreen.preventAutoHideAsync(); 23 + 24 + export default function RootLayout() { 25 + const [loaded, error] = useFonts({ 26 + SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), 27 + ...FontAwesome.font, 28 + }); 29 + 30 + // Expo Router uses Error Boundaries to catch errors in the navigation tree. 31 + useEffect(() => { 32 + if (error) throw error; 33 + }, [error]); 34 + 35 + useEffect(() => { 36 + if (loaded) { 37 + SplashScreen.hideAsync(); 38 + } 39 + }, [loaded]); 40 + 41 + if (!loaded) { 42 + return null; 43 + } 44 + 45 + return <RootLayoutNav />; 46 + } 47 + 48 + function RootLayoutNav() { 49 + const colorScheme = useColorScheme(); 50 + 51 + return ( 52 + <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> 53 + <Stack> 54 + <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> 55 + <Stack.Screen name="modal" options={{ presentation: 'modal' }} /> 56 + </Stack> 57 + </ThemeProvider> 58 + ); 59 + }
+35
app/modal.tsx
··· 1 + import { StatusBar } from 'expo-status-bar'; 2 + import { Platform, StyleSheet } from 'react-native'; 3 + 4 + import EditScreenInfo from '@/components/EditScreenInfo'; 5 + import { Text, View } from '@/components/Themed'; 6 + 7 + export default function ModalScreen() { 8 + return ( 9 + <View style={styles.container}> 10 + <Text style={styles.title}>Modal</Text> 11 + <View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" /> 12 + <EditScreenInfo path="app/modal.tsx" /> 13 + 14 + {/* Use a light status bar on iOS to account for the black space above the modal */} 15 + <StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} /> 16 + </View> 17 + ); 18 + } 19 + 20 + const styles = StyleSheet.create({ 21 + container: { 22 + flex: 1, 23 + alignItems: 'center', 24 + justifyContent: 'center', 25 + }, 26 + title: { 27 + fontSize: 20, 28 + fontWeight: 'bold', 29 + }, 30 + separator: { 31 + marginVertical: 30, 32 + height: 1, 33 + width: '80%', 34 + }, 35 + });
assets/fonts/SpaceMono-Regular.ttf

This is a binary file and will not be displayed.

assets/images/adaptive-icon.png

This is a binary file and will not be displayed.

assets/images/favicon.png

This is a binary file and will not be displayed.

assets/images/icon.png

This is a binary file and will not be displayed.

assets/images/splash-icon.png

This is a binary file and will not be displayed.

bun.lockb

This is a binary file and will not be displayed.

+77
components/EditScreenInfo.tsx
··· 1 + import React from 'react'; 2 + import { StyleSheet } from 'react-native'; 3 + 4 + import { ExternalLink } from './ExternalLink'; 5 + import { MonoText } from './StyledText'; 6 + import { Text, View } from './Themed'; 7 + 8 + import Colors from '@/constants/Colors'; 9 + 10 + export default function EditScreenInfo({ path }: { path: string }) { 11 + return ( 12 + <View> 13 + <View style={styles.getStartedContainer}> 14 + <Text 15 + style={styles.getStartedText} 16 + lightColor="rgba(0,0,0,0.8)" 17 + darkColor="rgba(255,255,255,0.8)"> 18 + Open up the code for this screen: 19 + </Text> 20 + 21 + <View 22 + style={[styles.codeHighlightContainer, styles.homeScreenFilename]} 23 + darkColor="rgba(255,255,255,0.05)" 24 + lightColor="rgba(0,0,0,0.05)"> 25 + <MonoText>{path}</MonoText> 26 + </View> 27 + 28 + <Text 29 + style={styles.getStartedText} 30 + lightColor="rgba(0,0,0,0.8)" 31 + darkColor="rgba(255,255,255,0.8)"> 32 + Change any of the text, save the file, and your app will automatically update. 33 + </Text> 34 + </View> 35 + 36 + <View style={styles.helpContainer}> 37 + <ExternalLink 38 + style={styles.helpLink} 39 + href="https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet"> 40 + <Text style={styles.helpLinkText} lightColor={Colors.light.tint}> 41 + Tap here if your app doesn't automatically update after making changes 42 + </Text> 43 + </ExternalLink> 44 + </View> 45 + </View> 46 + ); 47 + } 48 + 49 + const styles = StyleSheet.create({ 50 + getStartedContainer: { 51 + alignItems: 'center', 52 + marginHorizontal: 50, 53 + }, 54 + homeScreenFilename: { 55 + marginVertical: 7, 56 + }, 57 + codeHighlightContainer: { 58 + borderRadius: 3, 59 + paddingHorizontal: 4, 60 + }, 61 + getStartedText: { 62 + fontSize: 17, 63 + lineHeight: 24, 64 + textAlign: 'center', 65 + }, 66 + helpContainer: { 67 + marginTop: 15, 68 + marginHorizontal: 20, 69 + alignItems: 'center', 70 + }, 71 + helpLink: { 72 + paddingVertical: 15, 73 + }, 74 + helpLinkText: { 75 + textAlign: 'center', 76 + }, 77 + });
+25
components/ExternalLink.tsx
··· 1 + import { Link } from 'expo-router'; 2 + import * as WebBrowser from 'expo-web-browser'; 3 + import React from 'react'; 4 + import { Platform } from 'react-native'; 5 + 6 + export function ExternalLink( 7 + props: Omit<React.ComponentProps<typeof Link>, 'href'> & { href: string } 8 + ) { 9 + return ( 10 + <Link 11 + target="_blank" 12 + {...props} 13 + // @ts-expect-error: External URLs are not typed. 14 + href={props.href} 15 + onPress={(e) => { 16 + if (Platform.OS !== 'web') { 17 + // Prevent the default behavior of linking to the default browser on native. 18 + e.preventDefault(); 19 + // Open the link in an in-app browser. 20 + WebBrowser.openBrowserAsync(props.href as string); 21 + } 22 + }} 23 + /> 24 + ); 25 + }
+5
components/StyledText.tsx
··· 1 + import { Text, TextProps } from './Themed'; 2 + 3 + export function MonoText(props: TextProps) { 4 + return <Text {...props} style={[props.style, { fontFamily: 'SpaceMono' }]} />; 5 + }
+45
components/Themed.tsx
··· 1 + /** 2 + * Learn more about Light and Dark modes: 3 + * https://docs.expo.io/guides/color-schemes/ 4 + */ 5 + 6 + import { Text as DefaultText, View as DefaultView } from 'react-native'; 7 + 8 + import Colors from '@/constants/Colors'; 9 + import { useColorScheme } from './useColorScheme'; 10 + 11 + type ThemeProps = { 12 + lightColor?: string; 13 + darkColor?: string; 14 + }; 15 + 16 + export type TextProps = ThemeProps & DefaultText['props']; 17 + export type ViewProps = ThemeProps & DefaultView['props']; 18 + 19 + export function useThemeColor( 20 + props: { light?: string; dark?: string }, 21 + colorName: keyof typeof Colors.light & keyof typeof Colors.dark 22 + ) { 23 + const theme = useColorScheme() ?? 'light'; 24 + const colorFromProps = props[theme]; 25 + 26 + if (colorFromProps) { 27 + return colorFromProps; 28 + } else { 29 + return Colors[theme][colorName]; 30 + } 31 + } 32 + 33 + export function Text(props: TextProps) { 34 + const { style, lightColor, darkColor, ...otherProps } = props; 35 + const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); 36 + 37 + return <DefaultText style={[{ color }, style]} {...otherProps} />; 38 + } 39 + 40 + export function View(props: ViewProps) { 41 + const { style, lightColor, darkColor, ...otherProps } = props; 42 + const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background'); 43 + 44 + return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />; 45 + }
+10
components/__tests__/StyledText-test.js
··· 1 + import * as React from 'react'; 2 + import renderer from 'react-test-renderer'; 3 + 4 + import { MonoText } from '../StyledText'; 5 + 6 + it(`renders correctly`, () => { 7 + const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON(); 8 + 9 + expect(tree).toMatchSnapshot(); 10 + });
+4
components/useClientOnlyValue.ts
··· 1 + // This function is web-only as native doesn't currently support server (or build-time) rendering. 2 + export function useClientOnlyValue<S, C>(server: S, client: C): S | C { 3 + return client; 4 + }
+12
components/useClientOnlyValue.web.ts
··· 1 + import React from 'react'; 2 + 3 + // `useEffect` is not invoked during server rendering, meaning 4 + // we can use this to determine if we're on the server or not. 5 + export function useClientOnlyValue<S, C>(server: S, client: C): S | C { 6 + const [value, setValue] = React.useState<S | C>(server); 7 + React.useEffect(() => { 8 + setValue(client); 9 + }, [client]); 10 + 11 + return value; 12 + }
+1
components/useColorScheme.ts
··· 1 + export { useColorScheme } from 'react-native';
+8
components/useColorScheme.web.ts
··· 1 + // NOTE: The default React Native styling doesn't support server rendering. 2 + // Server rendered styles should not change between the first render of the HTML 3 + // and the first render on the client. Typically, web developers will use CSS media queries 4 + // to render different styles on the client and server, these aren't directly supported in React Native 5 + // but can be achieved using a styling library like Nativewind. 6 + export function useColorScheme() { 7 + return 'light'; 8 + }
+19
constants/Colors.ts
··· 1 + const tintColorLight = '#2f95dc'; 2 + const tintColorDark = '#fff'; 3 + 4 + export default { 5 + light: { 6 + text: '#000', 7 + background: '#fff', 8 + tint: tintColorLight, 9 + tabIconDefault: '#ccc', 10 + tabIconSelected: tintColorLight, 11 + }, 12 + dark: { 13 + text: '#fff', 14 + background: '#000', 15 + tint: tintColorDark, 16 + tabIconDefault: '#ccc', 17 + tabIconSelected: tintColorDark, 18 + }, 19 + };
+43
package.json
··· 1 + { 2 + "name": "amethyst", 3 + "main": "expo-router/entry", 4 + "version": "1.0.0", 5 + "scripts": { 6 + "start": "expo start", 7 + "android": "expo start --android", 8 + "ios": "expo start --ios", 9 + "web": "expo start --web", 10 + "test": "jest --watchAll" 11 + }, 12 + "jest": { 13 + "preset": "jest-expo" 14 + }, 15 + "dependencies": { 16 + "@expo/vector-icons": "^14.0.2", 17 + "@react-navigation/native": "^7.0.0", 18 + "expo": "~52.0.9", 19 + "expo-font": "~13.0.1", 20 + "expo-linking": "~7.0.3", 21 + "expo-router": "~4.0.7", 22 + "expo-splash-screen": "~0.29.12", 23 + "expo-status-bar": "~2.0.0", 24 + "expo-system-ui": "~4.0.3", 25 + "expo-web-browser": "~14.0.1", 26 + "react": "18.3.1", 27 + "react-dom": "18.3.1", 28 + "react-native": "0.76.2", 29 + "react-native-reanimated": "~3.16.1", 30 + "react-native-safe-area-context": "4.12.0", 31 + "react-native-screens": "~4.1.0", 32 + "react-native-web": "~0.19.13" 33 + }, 34 + "devDependencies": { 35 + "@babel/core": "^7.25.2", 36 + "@types/react": "~18.3.12", 37 + "jest": "^29.2.1", 38 + "jest-expo": "~52.0.2", 39 + "react-test-renderer": "18.3.1", 40 + "typescript": "~5.3.3" 41 + }, 42 + "private": true 43 + }
+17
tsconfig.json
··· 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 + }