Hopefully feature-complete Android Bluesky client written in Expo
atproto bluesky

Prototype

+2 -1
app.json
··· 38 38 "backgroundColor": "#000000" 39 39 } 40 40 } 41 - ] 41 + ], 42 + "expo-video" 42 43 ], 43 44 "experiments": { 44 45 "typedRoutes": true,
-35
app/(tabs)/_layout.tsx
··· 1 - import { Tabs } from 'expo-router'; 2 - import React from 'react'; 3 - 4 - import { HapticTab } from '@/components/haptic-tab'; 5 - import { IconSymbol } from '@/components/ui/icon-symbol'; 6 - import { Colors } from '@/constants/theme'; 7 - import { useColorScheme } from '@/hooks/use-color-scheme'; 8 - 9 - export default function TabLayout() { 10 - const colorScheme = useColorScheme(); 11 - 12 - return ( 13 - <Tabs 14 - screenOptions={{ 15 - tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint, 16 - headerShown: false, 17 - tabBarButton: HapticTab, 18 - }}> 19 - <Tabs.Screen 20 - name="index" 21 - options={{ 22 - title: 'Home', 23 - tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />, 24 - }} 25 - /> 26 - <Tabs.Screen 27 - name="explore" 28 - options={{ 29 - title: 'Explore', 30 - tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />, 31 - }} 32 - /> 33 - </Tabs> 34 - ); 35 - }
-112
app/(tabs)/explore.tsx
··· 1 - import { Image } from 'expo-image'; 2 - import { Platform, StyleSheet } from 'react-native'; 3 - 4 - import { Collapsible } from '@/components/ui/collapsible'; 5 - import { ExternalLink } from '@/components/external-link'; 6 - import ParallaxScrollView from '@/components/parallax-scroll-view'; 7 - import { ThemedText } from '@/components/themed-text'; 8 - import { ThemedView } from '@/components/themed-view'; 9 - import { IconSymbol } from '@/components/ui/icon-symbol'; 10 - import { Fonts } from '@/constants/theme'; 11 - 12 - export default function TabTwoScreen() { 13 - return ( 14 - <ParallaxScrollView 15 - headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }} 16 - headerImage={ 17 - <IconSymbol 18 - size={310} 19 - color="#808080" 20 - name="chevron.left.forwardslash.chevron.right" 21 - style={styles.headerImage} 22 - /> 23 - }> 24 - <ThemedView style={styles.titleContainer}> 25 - <ThemedText 26 - type="title" 27 - style={{ 28 - fontFamily: Fonts.rounded, 29 - }}> 30 - Explore 31 - </ThemedText> 32 - </ThemedView> 33 - <ThemedText>This app includes example code to help you get started.</ThemedText> 34 - <Collapsible title="File-based routing"> 35 - <ThemedText> 36 - This app has two screens:{' '} 37 - <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '} 38 - <ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText> 39 - </ThemedText> 40 - <ThemedText> 41 - The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '} 42 - sets up the tab navigator. 43 - </ThemedText> 44 - <ExternalLink href="https://docs.expo.dev/router/introduction"> 45 - <ThemedText type="link">Learn more</ThemedText> 46 - </ExternalLink> 47 - </Collapsible> 48 - <Collapsible title="Android, iOS, and web support"> 49 - <ThemedText> 50 - You can open this project on Android, iOS, and the web. To open the web version, press{' '} 51 - <ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project. 52 - </ThemedText> 53 - </Collapsible> 54 - <Collapsible title="Images"> 55 - <ThemedText> 56 - For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '} 57 - <ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for 58 - different screen densities 59 - </ThemedText> 60 - <Image 61 - source={require('@/assets/images/react-logo.png')} 62 - style={{ width: 100, height: 100, alignSelf: 'center' }} 63 - /> 64 - <ExternalLink href="https://reactnative.dev/docs/images"> 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&apos;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{' '} 83 - <ThemedText type="defaultSemiBold" style={{ fontFamily: Fonts.mono }}> 84 - react-native-reanimated 85 - </ThemedText>{' '} 86 - library to create a waving hand animation. 87 - </ThemedText> 88 - {Platform.select({ 89 - ios: ( 90 - <ThemedText> 91 - The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '} 92 - component provides a parallax effect for the header image. 93 - </ThemedText> 94 - ), 95 - })} 96 - </Collapsible> 97 - </ParallaxScrollView> 98 - ); 99 - } 100 - 101 - const styles = StyleSheet.create({ 102 - headerImage: { 103 - color: '#808080', 104 - bottom: -90, 105 - left: -35, 106 - position: 'absolute', 107 - }, 108 - titleContainer: { 109 - flexDirection: 'row', 110 - gap: 8, 111 - }, 112 - });
-98
app/(tabs)/index.tsx
··· 1 - import { Image } from 'expo-image'; 2 - import { Platform, StyleSheet } from 'react-native'; 3 - 4 - import { HelloWave } from '@/components/hello-wave'; 5 - import ParallaxScrollView from '@/components/parallax-scroll-view'; 6 - import { ThemedText } from '@/components/themed-text'; 7 - import { ThemedView } from '@/components/themed-view'; 8 - import { Link } from 'expo-router'; 9 - 10 - export default function HomeScreen() { 11 - return ( 12 - <ParallaxScrollView 13 - headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }} 14 - headerImage={ 15 - <Image 16 - source={require('@/assets/images/partial-react-logo.png')} 17 - style={styles.reactLogo} 18 - /> 19 - }> 20 - <ThemedView style={styles.titleContainer}> 21 - <ThemedText type="title">Welcome!</ThemedText> 22 - <HelloWave /> 23 - </ThemedView> 24 - <ThemedView style={styles.stepContainer}> 25 - <ThemedText type="subtitle">Step 1: Try it</ThemedText> 26 - <ThemedText> 27 - Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes. 28 - Press{' '} 29 - <ThemedText type="defaultSemiBold"> 30 - {Platform.select({ 31 - ios: 'cmd + d', 32 - android: 'cmd + m', 33 - web: 'F12', 34 - })} 35 - </ThemedText>{' '} 36 - to open developer tools. 37 - </ThemedText> 38 - </ThemedView> 39 - <ThemedView style={styles.stepContainer}> 40 - <Link href="/modal"> 41 - <Link.Trigger> 42 - <ThemedText type="subtitle">Step 2: Explore</ThemedText> 43 - </Link.Trigger> 44 - <Link.Preview /> 45 - <Link.Menu> 46 - <Link.MenuAction title="Action" icon="cube" onPress={() => alert('Action pressed')} /> 47 - <Link.MenuAction 48 - title="Share" 49 - icon="square.and.arrow.up" 50 - onPress={() => alert('Share pressed')} 51 - /> 52 - <Link.Menu title="More" icon="ellipsis"> 53 - <Link.MenuAction 54 - title="Delete" 55 - icon="trash" 56 - destructive 57 - onPress={() => alert('Delete pressed')} 58 - /> 59 - </Link.Menu> 60 - </Link.Menu> 61 - </Link> 62 - 63 - <ThemedText> 64 - {`Tap the Explore tab to learn more about what's included in this starter app.`} 65 - </ThemedText> 66 - </ThemedView> 67 - <ThemedView style={styles.stepContainer}> 68 - <ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText> 69 - <ThemedText> 70 - {`When you're ready, run `} 71 - <ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '} 72 - <ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '} 73 - <ThemedText type="defaultSemiBold">app</ThemedText> to{' '} 74 - <ThemedText type="defaultSemiBold">app-example</ThemedText>. 75 - </ThemedText> 76 - </ThemedView> 77 - </ParallaxScrollView> 78 - ); 79 - } 80 - 81 - const styles = StyleSheet.create({ 82 - titleContainer: { 83 - flexDirection: 'row', 84 - alignItems: 'center', 85 - gap: 8, 86 - }, 87 - stepContainer: { 88 - gap: 8, 89 - marginBottom: 8, 90 - }, 91 - reactLogo: { 92 - height: 178, 93 - width: 290, 94 - bottom: 0, 95 - left: 0, 96 - position: 'absolute', 97 - }, 98 - });
-24
app/_layout.tsx
··· 1 - import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; 2 - import { Stack } from 'expo-router'; 3 - import { StatusBar } from 'expo-status-bar'; 4 - import 'react-native-reanimated'; 5 - 6 - import { useColorScheme } from '@/hooks/use-color-scheme'; 7 - 8 - export const unstable_settings = { 9 - anchor: '(tabs)', 10 - }; 11 - 12 - export default function RootLayout() { 13 - const colorScheme = useColorScheme(); 14 - 15 - return ( 16 - <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> 17 - <Stack> 18 - <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> 19 - <Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} /> 20 - </Stack> 21 - <StatusBar style="auto" /> 22 - </ThemeProvider> 23 - ); 24 - }
-29
app/modal.tsx
··· 1 - import { Link } from 'expo-router'; 2 - import { StyleSheet } from 'react-native'; 3 - 4 - import { ThemedText } from '@/components/themed-text'; 5 - import { ThemedView } from '@/components/themed-view'; 6 - 7 - export default function ModalScreen() { 8 - return ( 9 - <ThemedView style={styles.container}> 10 - <ThemedText type="title">This is a modal</ThemedText> 11 - <Link href="/" dismissTo style={styles.link}> 12 - <ThemedText type="link">Go to home screen</ThemedText> 13 - </Link> 14 - </ThemedView> 15 - ); 16 - } 17 - 18 - const styles = StyleSheet.create({ 19 - container: { 20 - flex: 1, 21 - alignItems: 'center', 22 - justifyContent: 'center', 23 - padding: 20, 24 - }, 25 - link: { 26 - marginTop: 15, 27 - paddingVertical: 15, 28 - }, 29 - });
+122 -5
bun.lock
··· 5 5 "": { 6 6 "name": "dusksky", 7 7 "dependencies": { 8 + "@atcute/bluesky": "^3.2.14", 9 + "@atcute/bluesky-richtext-segmenter": "^2.0.4", 10 + "@atcute/client": "^4.1.1", 8 11 "@expo/vector-icons": "^15.0.3", 12 + "@formatjs/intl-segmenter": "^12.0.5", 13 + "@pchmn/expo-material3-theme": "^1.3.2", 9 14 "@react-navigation/bottom-tabs": "^7.4.0", 10 15 "@react-navigation/elements": "^2.6.3", 11 16 "@react-navigation/native": "^7.1.8", 17 + "@shopify/flash-list": "2.0.2", 18 + "@tanstack/react-query": "^5.90.12", 12 19 "expo": "~54.0.30", 13 20 "expo-constants": "~18.0.12", 14 21 "expo-font": "~14.0.10", ··· 20 27 "expo-status-bar": "~3.0.9", 21 28 "expo-symbols": "~1.0.8", 22 29 "expo-system-ui": "~6.0.9", 30 + "expo-video": "~3.0.15", 23 31 "expo-web-browser": "~15.0.10", 24 32 "react": "19.1.0", 25 33 "react-dom": "19.1.0", ··· 35 43 "@types/react": "~19.1.0", 36 44 "eslint": "^9.25.0", 37 45 "eslint-config-expo": "~10.0.0", 46 + "expo-atlas": "^0.4.0", 38 47 "typescript": "~5.9.2", 39 48 }, 40 49 }, 41 50 }, 42 51 "packages": { 43 52 "@0no-co/graphql.web": ["@0no-co/graphql.web@1.2.0", "", { "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "optionalPeers": ["graphql"] }, "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw=="], 53 + 54 + "@atcute/atproto": ["@atcute/atproto@3.1.9", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w=="], 55 + 56 + "@atcute/bluesky": ["@atcute/bluesky@3.2.14", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.5" } }, "sha512-XlVuF55AYIyplmKvlGLlj+cUvk9ggxNRPczkTPIY991xJ4qDxDHpBJ39ekAV4dWcuBoRo2o9JynzpafPu2ljDA=="], 57 + 58 + "@atcute/bluesky-richtext-segmenter": ["@atcute/bluesky-richtext-segmenter@2.0.4", "", { "dependencies": { "@atcute/bluesky": "^3.2.5", "@atcute/lexicons": "^1.2.2" } }, "sha512-6m5QEAv4lU3qTy5MeJXJRRG33acipYJnMW1T7W/KrMyThGhQ7jSTTh8Z48quElgivgX7MDj6o/ow1oLUsjsCKw=="], 59 + 60 + "@atcute/client": ["@atcute/client@4.1.1", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.5" } }, "sha512-FROCbTTCeL5u4tO/n72jDEKyKqjdlXMB56Ehve3W/gnnLGCYWvN42sS7tvL1Mgu6sbO3yZwsXKDrmM2No4XpjA=="], 61 + 62 + "@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="], 63 + 64 + "@atcute/lexicons": ["@atcute/lexicons@1.2.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "esm-env": "^1.2.2" } }, "sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q=="], 44 65 45 66 "@babel/code-frame": ["@babel/code-frame@7.10.4", "", { "dependencies": { "@babel/highlight": "^7.10.4" } }, "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg=="], 46 67 ··· 230 251 231 252 "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], 232 253 254 + "@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="], 255 + 233 256 "@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="], 234 257 235 258 "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], ··· 296 319 297 320 "@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="], 298 321 322 + "@expo/server": ["@expo/server@0.5.3", "", { "dependencies": { "abort-controller": "^3.0.0", "debug": "^4.3.4", "source-map-support": "~0.5.21", "undici": "^6.18.2" } }, "sha512-WXsWzeBs5v/h0PUfHyNLLz07rwwO5myQ1A5DGYewyyGLmsyl61yVCe8AgAlp1wkiMsqhj2hZqI2u3K10QnCMrQ=="], 323 + 299 324 "@expo/spawn-async": ["@expo/spawn-async@1.7.2", "", { "dependencies": { "cross-spawn": "^7.0.3" } }, "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew=="], 300 325 301 326 "@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="], ··· 306 331 307 332 "@expo/xcpretty": ["@expo/xcpretty@4.3.2", "", { "dependencies": { "@babel/code-frame": "7.10.4", "chalk": "^4.1.0", "find-up": "^5.0.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-ReZxZ8pdnoI3tP/dNnJdnmAk7uLT4FjsKDGW7YeDdvdOMz2XCQSmSCM9IWlrXuWtMF9zeSB6WJtEhCQ41gQOfw=="], 308 333 334 + "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@3.0.5", "", { "dependencies": { "@formatjs/fast-memoize": "3.0.1", "@formatjs/intl-localematcher": "0.7.3", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-TF0uoOhPhbzzAuKgOA3s8M20wZm5f6IWDq6dBVkl8gKvS7vq84AkzR9ts0oLN0pbDy6TDx0pESUgyJgMJQItDg=="], 335 + 336 + "@formatjs/fast-memoize": ["@formatjs/fast-memoize@3.0.1", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-kzk635kEmsxrrEWQXY7uKRocFCVXR4es5OQqcqCGg2NPtQztG/OBkE9THHu6UOTxpfyIkZhh6DjPBZGRp7y3og=="], 337 + 338 + "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.7.3", "", { "dependencies": { "@formatjs/fast-memoize": "3.0.1", "tslib": "^2.8.0" } }, "sha512-NaeABectKdTCOnlH9VFGmMS3K0JuR7Soc2t5R2MCkBrM3H/hlKVYh0XSrcjjPkbjIdrF7L/Bzx9JtGuVaSfYlA=="], 339 + 340 + "@formatjs/intl-segmenter": ["@formatjs/intl-segmenter@12.0.5", "", { "dependencies": { "@formatjs/ecma402-abstract": "3.0.5", "@formatjs/intl-localematcher": "0.7.3", "tslib": "^2.8.0" } }, "sha512-Xb6Bs9qMGwG+Jh5ADKfujlm2+nh4kLybD/YVwyHBDeyiY78wffs2KfXHM7xBTM3higjV8LodNWwAtOXgZud0Sg=="], 341 + 309 342 "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 310 343 311 344 "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], ··· 350 383 351 384 "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 352 385 386 + "@material/material-color-utilities": ["@material/material-color-utilities@0.2.7", "", {}, "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ=="], 387 + 353 388 "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], 354 389 355 390 "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], 391 + 392 + "@pchmn/expo-material3-theme": ["@pchmn/expo-material3-theme@1.3.2", "", { "dependencies": { "@material/material-color-utilities": "^0.2.7", "color": "^4.2.3" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-LT0A0qwdOg2QUKDUxLp1Wk6tj9Qk4x0HAUjJd3IBko01CbmWrLYiq0W01JTBnWczYyj2X4+1j/X3aFYrPDsRag=="], 356 393 357 394 "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], 358 395 ··· 432 469 433 470 "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], 434 471 472 + "@shopify/flash-list": ["@shopify/flash-list@2.0.2", "", { "dependencies": { "tslib": "2.8.1" }, "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w=="], 473 + 435 474 "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], 436 475 437 476 "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], 438 477 439 478 "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], 440 479 480 + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 481 + 482 + "@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="], 483 + 484 + "@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="], 485 + 441 486 "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 442 487 443 488 "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], ··· 572 617 573 618 "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], 574 619 620 + "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], 621 + 575 622 "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], 576 623 577 624 "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], ··· 626 673 627 674 "baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="], 628 675 676 + "basic-auth": ["basic-auth@2.0.1", "", { "dependencies": { "safe-buffer": "5.1.2" } }, "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg=="], 677 + 629 678 "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="], 630 679 631 680 "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], 681 + 682 + "body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="], 632 683 633 684 "bplist-creator": ["bplist-creator@0.1.0", "", { "dependencies": { "stream-buffers": "2.2.x" } }, "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg=="], 634 685 ··· 698 749 699 750 "connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="], 700 751 752 + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], 753 + 754 + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], 755 + 701 756 "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 702 757 758 + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 759 + 760 + "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], 761 + 703 762 "core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], 704 763 705 764 "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], ··· 719 778 "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], 720 779 721 780 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 781 + 782 + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], 722 783 723 784 "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="], 724 785 ··· 808 869 809 870 "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 810 871 872 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 873 + 811 874 "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 812 875 813 876 "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], ··· 830 893 831 894 "expo-asset": ["expo-asset@12.0.12", "", { "dependencies": { "@expo/image-utils": "^0.8.8", "expo-constants": "~18.0.12" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ=="], 832 895 896 + "expo-atlas": ["expo-atlas@0.4.3", "", { "dependencies": { "@expo/server": "^0.5.0", "arg": "^5.0.2", "chalk": "^4.1.2", "compression": "^1.7.4", "connect": "^3.7.0", "express": "^4.19.2", "freeport-async": "^2.0.0", "getenv": "^2.0.0", "morgan": "^1.10.0", "open": "^8.4.2", "serve-static": "^1.15.0", "stream-json": "^1.8.0" }, "peerDependencies": { "expo": "*" }, "bin": { "expo-atlas": "build/src/cli/bin.js" } }, "sha512-nN2bouxFvMsqZqLl0ka+eI9Ofida0PcuoE4v+7fHlgyp95X2cCL8Acf0nRKXmmIBEYazdw0d7BAOZfC0b41oxA=="], 897 + 833 898 "expo-constants": ["expo-constants@18.0.12", "", { "dependencies": { "@expo/config": "~12.0.12", "@expo/env": "~2.0.8" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-WzcKYMVNRRu4NcSzfIVRD5aUQFnSpTZgXFrlWmm19xJoDa4S3/PQNi6PNTBRc49xz9h8FT7HMxRKaC8lr0gflA=="], 834 899 835 900 "expo-file-system": ["expo-file-system@19.0.21", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg=="], ··· 860 925 861 926 "expo-system-ui": ["expo-system-ui@6.0.9", "", { "dependencies": { "@react-native/normalize-colors": "0.81.5", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-eQTYGzw1V4RYiYHL9xDLYID3Wsec2aZS+ypEssmF64D38aDrqbDgz1a2MSlHLQp2jHXSs3FvojhZ9FVela1Zcg=="], 862 927 928 + "expo-video": ["expo-video@3.0.15", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-KmxHVCtOBb1fnxXL6DpgLbEe7Qlv/vHNGTLfz0u/eY8fBC9s5cncD2BhPunEffrGvNMftBzYMYDaO86x+IYpnA=="], 929 + 863 930 "expo-web-browser": ["expo-web-browser@15.0.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg=="], 864 931 865 932 "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], 866 933 934 + "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], 935 + 867 936 "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 868 937 869 938 "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], ··· 897 966 "fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="], 898 967 899 968 "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], 969 + 970 + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], 900 971 901 972 "freeport-async": ["freeport-async@2.0.0", "", {}, "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ=="], 902 973 ··· 974 1045 975 1046 "hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="], 976 1047 1048 + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], 1049 + 977 1050 "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 978 1051 979 1052 "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], ··· 995 1068 "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], 996 1069 997 1070 "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], 1071 + 1072 + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], 998 1073 999 1074 "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], 1000 1075 ··· 1162 1237 1163 1238 "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 1164 1239 1240 + "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], 1241 + 1165 1242 "memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="], 1166 1243 1244 + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], 1245 + 1167 1246 "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], 1168 1247 1248 + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], 1249 + 1169 1250 "metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="], 1170 1251 1171 1252 "metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="], ··· 1214 1295 1215 1296 "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], 1216 1297 1298 + "morgan": ["morgan@1.10.1", "", { "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.1.0" } }, "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A=="], 1299 + 1217 1300 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 1218 1301 1219 1302 "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], ··· 1224 1307 1225 1308 "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 1226 1309 1227 - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], 1310 + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], 1228 1311 1229 1312 "nested-error-stacks": ["nested-error-stacks@2.0.1", "", {}, "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A=="], 1230 1313 ··· 1268 1351 1269 1352 "onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="], 1270 1353 1271 - "open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], 1354 + "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], 1272 1355 1273 1356 "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 1274 1357 ··· 1298 1381 1299 1382 "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], 1300 1383 1384 + "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], 1385 + 1301 1386 "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 1302 1387 1303 1388 "picomatch": ["picomatch@3.0.1", "", {}, "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag=="], ··· 1330 1415 1331 1416 "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], 1332 1417 1418 + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], 1419 + 1333 1420 "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 1334 1421 1335 1422 "qrcode-terminal": ["qrcode-terminal@0.11.0", "", { "bin": { "qrcode-terminal": "./bin/qrcode-terminal.js" } }, "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ=="], 1336 1423 1424 + "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], 1425 + 1337 1426 "query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="], 1338 1427 1339 1428 "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="], 1340 1429 1341 1430 "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], 1431 + 1432 + "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], 1342 1433 1343 1434 "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], 1344 1435 ··· 1424 1515 1425 1516 "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], 1426 1517 1518 + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 1519 + 1427 1520 "sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], 1428 1521 1429 1522 "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], ··· 1502 1595 1503 1596 "stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="], 1504 1597 1598 + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], 1599 + 1600 + "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], 1601 + 1505 1602 "strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="], 1506 1603 1507 1604 "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], ··· 1573 1670 "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], 1574 1671 1575 1672 "type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="], 1673 + 1674 + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], 1576 1675 1577 1676 "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], 1578 1677 ··· 1760 1859 1761 1860 "@react-native/community-cli-plugin/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 1762 1861 1862 + "@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], 1863 + 1763 1864 "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 1764 1865 1765 1866 "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 1766 1867 1767 1868 "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 1768 1869 1870 + "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], 1871 + 1769 1872 "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], 1770 1873 1771 1874 "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 1772 1875 1773 1876 "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 1774 1877 1775 - "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], 1878 + "basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], 1879 + 1880 + "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 1776 1881 1777 1882 "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 1778 1883 1779 1884 "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 1780 1885 1781 - "compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], 1782 - 1783 1886 "connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 1784 1887 1785 1888 "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], ··· 1795 1898 "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 1796 1899 1797 1900 "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], 1901 + 1902 + "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 1903 + 1904 + "express/finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], 1798 1905 1799 1906 "fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="], 1800 1907 ··· 1840 1947 1841 1948 "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 1842 1949 1950 + "morgan/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 1951 + 1952 + "morgan/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="], 1953 + 1843 1954 "npm-package-arg/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 1844 1955 1845 1956 "ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], ··· 1922 2033 1923 2034 "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 1924 2035 2036 + "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 2037 + 1925 2038 "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 1926 2039 1927 2040 "connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 2041 + 2042 + "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 1928 2043 1929 2044 "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 1930 2045 ··· 1939 2054 "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], 1940 2055 1941 2056 "metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], 2057 + 2058 + "morgan/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 1942 2059 1943 2060 "ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], 1944 2061
-25
components/external-link.tsx
··· 1 - import { Href, Link } from 'expo-router'; 2 - import { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser'; 3 - import { type ComponentProps } from 'react'; 4 - 5 - type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & string }; 6 - 7 - export function ExternalLink({ href, ...rest }: Props) { 8 - return ( 9 - <Link 10 - target="_blank" 11 - {...rest} 12 - href={href} 13 - onPress={async (event) => { 14 - if (process.env.EXPO_OS !== 'web') { 15 - // Prevent the default behavior of linking to the default browser on native. 16 - event.preventDefault(); 17 - // Open the link in an in-app browser. 18 - await openBrowserAsync(href, { 19 - presentationStyle: WebBrowserPresentationStyle.AUTOMATIC, 20 - }); 21 - } 22 - }} 23 - /> 24 - ); 25 - }
-18
components/haptic-tab.tsx
··· 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 - }
-19
components/hello-wave.tsx
··· 1 - import Animated from 'react-native-reanimated'; 2 - 3 - export function HelloWave() { 4 - return ( 5 - <Animated.Text 6 - style={{ 7 - fontSize: 28, 8 - lineHeight: 32, 9 - marginTop: -6, 10 - animationName: { 11 - '50%': { transform: [{ rotate: '25deg' }] }, 12 - }, 13 - animationIterationCount: 4, 14 - animationDuration: '300ms', 15 - }}> 16 - 👋 17 - </Animated.Text> 18 - ); 19 - }
-79
components/parallax-scroll-view.tsx
··· 1 - import type { PropsWithChildren, ReactElement } from 'react'; 2 - import { StyleSheet } from 'react-native'; 3 - import Animated, { 4 - interpolate, 5 - useAnimatedRef, 6 - useAnimatedStyle, 7 - useScrollOffset, 8 - } from 'react-native-reanimated'; 9 - 10 - import { ThemedView } from '@/components/themed-view'; 11 - import { useColorScheme } from '@/hooks/use-color-scheme'; 12 - import { useThemeColor } from '@/hooks/use-theme-color'; 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 backgroundColor = useThemeColor({}, 'background'); 27 - const colorScheme = useColorScheme() ?? 'light'; 28 - const scrollRef = useAnimatedRef<Animated.ScrollView>(); 29 - const scrollOffset = useScrollOffset(scrollRef); 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 - <Animated.ScrollView 49 - ref={scrollRef} 50 - style={{ backgroundColor, flex: 1 }} 51 - scrollEventThrottle={16}> 52 - <Animated.View 53 - style={[ 54 - styles.header, 55 - { backgroundColor: headerBackgroundColor[colorScheme] }, 56 - headerAnimatedStyle, 57 - ]}> 58 - {headerImage} 59 - </Animated.View> 60 - <ThemedView style={styles.content}>{children}</ThemedView> 61 - </Animated.ScrollView> 62 - ); 63 - } 64 - 65 - const styles = StyleSheet.create({ 66 - container: { 67 - flex: 1, 68 - }, 69 - header: { 70 - height: HEADER_HEIGHT, 71 - overflow: 'hidden', 72 - }, 73 - content: { 74 - flex: 1, 75 - padding: 32, 76 - gap: 16, 77 - overflow: 'hidden', 78 - }, 79 - });
-60
components/themed-text.tsx
··· 1 - import { StyleSheet, Text, type TextProps } from 'react-native'; 2 - 3 - import { useThemeColor } from '@/hooks/use-theme-color'; 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
components/themed-view.tsx
··· 1 - import { View, type ViewProps } from 'react-native'; 2 - 3 - import { useThemeColor } from '@/hooks/use-theme-color'; 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 - }
-45
components/ui/collapsible.tsx
··· 1 - import { PropsWithChildren, useState } from 'react'; 2 - import { StyleSheet, TouchableOpacity } from 'react-native'; 3 - 4 - import { ThemedText } from '@/components/themed-text'; 5 - import { ThemedView } from '@/components/themed-view'; 6 - import { IconSymbol } from '@/components/ui/icon-symbol'; 7 - import { Colors } from '@/constants/theme'; 8 - import { useColorScheme } from '@/hooks/use-color-scheme'; 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 - });
-32
components/ui/icon-symbol.ios.tsx
··· 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 - }
-41
components/ui/icon-symbol.tsx
··· 1 - // Fallback for using MaterialIcons on Android and web. 2 - 3 - import MaterialIcons from '@expo/vector-icons/MaterialIcons'; 4 - import { SymbolWeight, SymbolViewProps } from 'expo-symbols'; 5 - import { ComponentProps } from 'react'; 6 - import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'; 7 - 8 - type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>; 9 - type IconSymbolName = keyof typeof MAPPING; 10 - 11 - /** 12 - * Add your SF Symbols to Material Icons mappings here. 13 - * - see Material Icons in the [Icons Directory](https://icons.expo.fyi). 14 - * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app. 15 - */ 16 - const MAPPING = { 17 - 'house.fill': 'home', 18 - 'paperplane.fill': 'send', 19 - 'chevron.left.forwardslash.chevron.right': 'code', 20 - 'chevron.right': 'chevron-right', 21 - } as IconMapping; 22 - 23 - /** 24 - * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web. 25 - * This ensures a consistent look across platforms, and optimal resource usage. 26 - * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons. 27 - */ 28 - export function IconSymbol({ 29 - name, 30 - size = 24, 31 - color, 32 - style, 33 - }: { 34 - name: IconSymbolName; 35 - size?: number; 36 - color: string | OpaqueColorValue; 37 - style?: StyleProp<TextStyle>; 38 - weight?: SymbolWeight; 39 - }) { 40 - return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />; 41 - }
-53
constants/theme.ts
··· 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 - import { Platform } from 'react-native'; 7 - 8 - const tintColorLight = '#0a7ea4'; 9 - const tintColorDark = '#fff'; 10 - 11 - export const Colors = { 12 - light: { 13 - text: '#11181C', 14 - background: '#fff', 15 - tint: tintColorLight, 16 - icon: '#687076', 17 - tabIconDefault: '#687076', 18 - tabIconSelected: tintColorLight, 19 - }, 20 - dark: { 21 - text: '#ECEDEE', 22 - background: '#151718', 23 - tint: tintColorDark, 24 - icon: '#9BA1A6', 25 - tabIconDefault: '#9BA1A6', 26 - tabIconSelected: tintColorDark, 27 - }, 28 - }; 29 - 30 - export const Fonts = Platform.select({ 31 - ios: { 32 - /** iOS `UIFontDescriptorSystemDesignDefault` */ 33 - sans: 'system-ui', 34 - /** iOS `UIFontDescriptorSystemDesignSerif` */ 35 - serif: 'ui-serif', 36 - /** iOS `UIFontDescriptorSystemDesignRounded` */ 37 - rounded: 'ui-rounded', 38 - /** iOS `UIFontDescriptorSystemDesignMonospaced` */ 39 - mono: 'ui-monospace', 40 - }, 41 - default: { 42 - sans: 'normal', 43 - serif: 'serif', 44 - rounded: 'normal', 45 - mono: 'monospace', 46 - }, 47 - web: { 48 - sans: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", 49 - serif: "Georgia, 'Times New Roman', serif", 50 - rounded: "'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif", 51 - mono: "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", 52 - }, 53 - });
-1
hooks/use-color-scheme.ts
··· 1 - export { useColorScheme } from 'react-native';
-21
hooks/use-color-scheme.web.ts
··· 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
hooks/use-theme-color.ts
··· 1 - /** 2 - * Learn more about light and dark modes: 3 - * https://docs.expo.dev/guides/color-schemes/ 4 - */ 5 - 6 - import { Colors } from '@/constants/theme'; 7 - import { useColorScheme } from '@/hooks/use-color-scheme'; 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 - }
+2
index.js
··· 1 + import "@formatjs/intl-segmenter/polyfill.js"; 2 + import "expo-router/entry";
+14 -5
package.json
··· 1 1 { 2 2 "name": "dusksky", 3 - "main": "expo-router/entry", 3 + "main": "./index", 4 4 "version": "1.0.0", 5 5 "scripts": { 6 6 "start": "expo start", ··· 11 11 "lint": "expo lint" 12 12 }, 13 13 "dependencies": { 14 + "@atcute/bluesky": "^3.2.14", 15 + "@atcute/bluesky-richtext-segmenter": "^2.0.4", 16 + "@atcute/client": "^4.1.1", 14 17 "@expo/vector-icons": "^15.0.3", 18 + "@formatjs/intl-segmenter": "^12.0.5", 19 + "@pchmn/expo-material3-theme": "^1.3.2", 15 20 "@react-navigation/bottom-tabs": "^7.4.0", 16 21 "@react-navigation/elements": "^2.6.3", 17 22 "@react-navigation/native": "^7.1.8", 23 + "@shopify/flash-list": "2.0.2", 24 + "@tanstack/react-query": "^5.90.12", 18 25 "expo": "~54.0.30", 19 26 "expo-constants": "~18.0.12", 20 27 "expo-font": "~14.0.10", ··· 26 33 "expo-status-bar": "~3.0.9", 27 34 "expo-symbols": "~1.0.8", 28 35 "expo-system-ui": "~6.0.9", 36 + "expo-video": "~3.0.15", 29 37 "expo-web-browser": "~15.0.10", 30 38 "react": "19.1.0", 31 39 "react-dom": "19.1.0", 32 40 "react-native": "0.81.5", 33 41 "react-native-gesture-handler": "~2.28.0", 34 - "react-native-worklets": "0.5.1", 35 42 "react-native-reanimated": "~4.1.1", 36 43 "react-native-safe-area-context": "~5.6.0", 37 44 "react-native-screens": "~4.16.0", 38 - "react-native-web": "~0.21.0" 45 + "react-native-web": "~0.21.0", 46 + "react-native-worklets": "0.5.1" 39 47 }, 40 48 "devDependencies": { 41 49 "@types/react": "~19.1.0", 42 - "typescript": "~5.9.2", 43 50 "eslint": "^9.25.0", 44 - "eslint-config-expo": "~10.0.0" 51 + "eslint-config-expo": "~10.0.0", 52 + "expo-atlas": "^0.4.0", 53 + "typescript": "~5.9.2" 45 54 }, 46 55 "private": true 47 56 }
-112
scripts/reset-project.js
··· 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 - );
+117
src/app/(tabs)/_layout.tsx
··· 1 + import { useInfiniteQuery } from "@tanstack/react-query"; 2 + import { RefreshControl, Text, View, useColorScheme } from "react-native"; 3 + import { SafeAreaView } from "react-native-safe-area-context"; 4 + import { Client, ok, simpleFetchHandler } from "@atcute/client"; 5 + import { FlashList } from "@shopify/flash-list"; 6 + import { AppBskyFeedPost, AppBskyRichtextFacet } from "@atcute/bluesky"; 7 + import { StatusBar } from "expo-status-bar"; 8 + import EmbedView from "@/src/components/EmbedView"; 9 + import { Image } from "expo-image"; 10 + import { segmentize } from "@atcute/bluesky-richtext-segmenter"; 11 + import { Link } from "expo-router"; 12 + import { useMaterial3Theme } from "@pchmn/expo-material3-theme"; 13 + 14 + export default function TabLayout() { 15 + const colorScheme = useColorScheme(); 16 + const { theme } = useMaterial3Theme({ sourceColor: "#f4983c" }); 17 + 18 + const feedQuery = useInfiniteQuery({ 19 + queryKey: ["feed"], 20 + initialPageParam: "", 21 + queryFn: async ({ signal, pageParam }) => { 22 + const client = new Client({ handler: simpleFetchHandler({ service: "https://api.bsky.app" }) }); 23 + const res = await ok( 24 + client.get("app.bsky.feed.getAuthorFeed", { 25 + signal, 26 + params: { actor: "did:plc:irx36xprktslecsbopbwnh5w", cursor: pageParam, filter: "posts_no_replies" }, 27 + }) 28 + ); 29 + 30 + return res; 31 + }, 32 + getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPage.cursor, 33 + getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) => firstPage.cursor, 34 + }); 35 + 36 + return ( 37 + <> 38 + <SafeAreaView style={{ backgroundColor: theme[colorScheme!].background }}> 39 + <Text style={{ color: colorScheme === "light" ? "black" : "white" }}>{feedQuery.status}</Text> 40 + {feedQuery.isError && <Text>{JSON.stringify(feedQuery.error)}</Text>} 41 + {feedQuery.isSuccess && ( 42 + <View style={{ height: "100%" }}> 43 + <FlashList 44 + decelerationRate={"normal"} 45 + snapToEnd={false} 46 + snapToStart={false} 47 + pagingEnabled={false} 48 + data={feedQuery.data.pages.flatMap((val) => val.feed)} 49 + onEndReached={() => feedQuery.fetchNextPage()} 50 + contentContainerStyle={{ gap: 8 }} 51 + refreshControl={<RefreshControl refreshing={feedQuery.isRefetching} />} 52 + ListFooterComponent={<View />} 53 + ListFooterComponentStyle={{ height: 64 }} 54 + onRefresh={() => feedQuery.refetch()} 55 + renderItem={({ item }) => { 56 + return ( 57 + <View style={{ flex: 1, flexDirection: "row", gap: 8, padding: 8, minWidth: "100%", flexShrink: 0 }}> 58 + <Image source={item.post.author.avatar} style={{ width: 44, height: 44, borderRadius: 19 }} /> 59 + <View style={{ flex: 1, gap: 4 }}> 60 + <View style={{ flex: 1, flexDirection: "row", gap: 4 }}> 61 + <Text 62 + ellipsizeMode="tail" 63 + numberOfLines={1} 64 + style={{ 65 + fontSize: 16, 66 + fontWeight: 700, 67 + overflow: "hidden", 68 + flexShrink: 1, 69 + color: colorScheme === "light" ? "black" : "white", 70 + }} 71 + > 72 + {item.post.author.displayName} 73 + </Text> 74 + <Text 75 + ellipsizeMode="tail" 76 + numberOfLines={1} 77 + style={{ 78 + fontSize: 16, 79 + opacity: 0.75, 80 + overflow: "hidden", 81 + flexShrink: 1, 82 + color: colorScheme === "light" ? "black" : "white", 83 + }} 84 + > 85 + {"@" + item.post.author.handle} 86 + </Text> 87 + </View> 88 + {(item.post.record as AppBskyFeedPost.Main).text.trim().length > 0 && ( 89 + <Text selectable style={{ fontSize: 16, color: colorScheme === "light" ? "black" : "white" }}> 90 + {segmentize( 91 + (item.post.record as AppBskyFeedPost.Main).text, 92 + (item.post.record as AppBskyFeedPost.Main).facets 93 + ).map((val, i) => { 94 + if (val.features?.at(0)?.$type === "app.bsky.richtext.facet#link") 95 + return ( 96 + <Link key={i} href={(val.features!.at(0)! as any).uri} style={{ color: "#1974D2" }}> 97 + {val.text} 98 + </Link> 99 + ); 100 + 101 + return val.text; 102 + })} 103 + </Text> 104 + )} 105 + {item.post.embed && <EmbedView embed={item.post.embed} />} 106 + </View> 107 + </View> 108 + ); 109 + }} 110 + /> 111 + </View> 112 + )} 113 + </SafeAreaView> 114 + <StatusBar style="inverted" /> 115 + </> 116 + ); 117 + }
+21
src/app/_layout.tsx
··· 1 + import { Stack } from "expo-router"; 2 + import { StatusBar } from "expo-status-bar"; 3 + import "react-native-reanimated"; 4 + import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 5 + 6 + export const unstable_settings = { 7 + anchor: "(tabs)", 8 + }; 9 + 10 + const queryClient = new QueryClient(); 11 + 12 + export default function RootLayout() { 13 + return ( 14 + <QueryClientProvider client={queryClient}> 15 + <Stack> 16 + <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> 17 + </Stack> 18 + <StatusBar style="auto" /> 19 + </QueryClientProvider> 20 + ); 21 + }
+91
src/components/EmbedView.tsx
··· 1 + import { 2 + AppBskyEmbedExternal, 3 + AppBskyEmbedImages, 4 + AppBskyEmbedRecord, 5 + AppBskyEmbedRecordWithMedia, 6 + AppBskyEmbedVideo, 7 + AppBskyFeedPost, 8 + AppBskyRichtextFacet, 9 + } from "@atcute/bluesky"; 10 + //import { segmentize } from "@atcute/bluesky-richtext-segmenter"; 11 + import { $type, is } from "@atcute/lexicons"; 12 + import { FlashList } from "@shopify/flash-list"; 13 + import { Image } from "expo-image"; 14 + import { View, Text, FlatList } from "react-native"; 15 + 16 + export default function EmbedView(props: { 17 + embed?: $type.enforce< 18 + | AppBskyEmbedExternal.View 19 + | AppBskyEmbedImages.View 20 + | AppBskyEmbedRecord.View 21 + | AppBskyEmbedRecordWithMedia.View 22 + | AppBskyEmbedVideo.View 23 + >; 24 + recursed?: true; 25 + }) { 26 + const switchOnEmbed = () => { 27 + switch (props.embed?.$type) { 28 + case "app.bsky.embed.images#view": 29 + if (props.embed.images.length > 1) 30 + return ( 31 + <FlatList 32 + style={{ maxHeight: 256, gap: 8 }} 33 + contentContainerStyle={{ gap: 8 }} 34 + horizontal 35 + data={props.embed.images} 36 + renderItem={({ item }) => { 37 + return ( 38 + <Image 39 + style={{ 40 + aspectRatio: item.aspectRatio ? item.aspectRatio?.width / item.aspectRatio?.height : 1, 41 + flex: 1, 42 + height: "100%", 43 + maxHeight: "100%", 44 + width: "25%", 45 + borderRadius: 16, 46 + }} 47 + source={item.thumb} 48 + alt={item.alt} 49 + contentFit="fill" 50 + allowDownscaling={false} 51 + autoplay 52 + /> 53 + ); 54 + }} 55 + /> 56 + ); 57 + else 58 + return ( 59 + <View 60 + style={{ 61 + height: "100%", 62 + flex: 1, 63 + alignItems: "center", 64 + }} 65 + > 66 + <Image 67 + style={{ 68 + aspectRatio: props.embed.images[0].aspectRatio 69 + ? props.embed.images[0].aspectRatio?.width / props.embed.images[0].aspectRatio?.height 70 + : 1, 71 + maxWidth: "100%", 72 + height: "auto", 73 + maxHeight: "100%", 74 + width: "100%", 75 + borderRadius: 16, 76 + }} 77 + source={props.embed.images[0].fullsize} 78 + contentFit="contain" 79 + allowDownscaling={false} 80 + autoplay 81 + /> 82 + </View> 83 + ); 84 + 85 + default: 86 + return <Text>{props.embed?.$type}</Text>; 87 + } 88 + }; 89 + 90 + return <>{props.embed && switchOnEmbed()}</>; 91 + }
+32
src/components/VideoPlayer.tsx
··· 1 + import { useEvent } from "expo"; 2 + import { useVideoPlayer, VideoView } from "expo-video"; 3 + import { Button, View, StyleSheet } from "react-native"; 4 + 5 + export default function VideoPlayer(props: { videoSource: string }) { 6 + const player = useVideoPlayer(props.videoSource, (player) => {}); 7 + 8 + const { isPlaying } = useEvent(player, "playingChange", { isPlaying: player.playing }); 9 + 10 + return ( 11 + <View style={styles.contentContainer}> 12 + <VideoView style={styles.video} player={player} allowsFullscreen allowsPictureInPicture /> 13 + </View> 14 + ); 15 + } 16 + 17 + const styles = StyleSheet.create({ 18 + contentContainer: { 19 + flex: 1, 20 + padding: 10, 21 + alignItems: "center", 22 + justifyContent: "center", 23 + paddingHorizontal: 50, 24 + }, 25 + video: { 26 + width: 350, 27 + height: 275, 28 + }, 29 + controlsContainer: { 30 + padding: 10, 31 + }, 32 + });
+5 -1
tsconfig.json
··· 1 1 { 2 + "$schema": "https://www.schemastore.org/tsconfig.json", 2 3 "extends": "expo/tsconfig.base", 3 4 "compilerOptions": { 4 5 "strict": true, ··· 6 7 "@/*": [ 7 8 "./*" 8 9 ] 9 - } 10 + }, 11 + "types": [ 12 + "@atcute/bluesky" 13 + ] 10 14 }, 11 15 "include": [ 12 16 "**/*.ts",