A personal media tracker built on the AT Protocol opnshelf.xyz
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: implement navigation and screens for movie tracking app, including search functionality

+672 -16
+47 -14
apps/mobile/App.tsx
··· 1 1 import { StatusBar } from 'expo-status-bar'; 2 - import { StyleSheet, Text, View } from 'react-native'; 2 + import { NavigationContainer } from '@react-navigation/native'; 3 + import { createNativeStackNavigator } from '@react-navigation/native-stack'; 4 + import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 5 + import { configureApiClient } from '@opnshelf/api'; 6 + import type { RootStackParamList } from './src/navigation'; 7 + import { HomeScreen } from './src/screens/HomeScreen'; 8 + import { SearchScreen } from './src/screens/SearchScreen'; 9 + import { env } from './src/env'; 10 + 11 + configureApiClient(env.API_URL); 12 + 13 + const Stack = createNativeStackNavigator<RootStackParamList>(); 14 + const queryClient = new QueryClient(); 15 + 16 + const linking = { 17 + config: { 18 + screens: { 19 + Home: '', 20 + Search: 'search', 21 + }, 22 + }, 23 + }; 3 24 4 25 export default function App() { 5 26 return ( 6 - <View style={styles.container}> 7 - <Text>Open up App.tsx to start working on your app!</Text> 8 - <StatusBar style="auto" /> 9 - </View> 27 + <QueryClientProvider client={queryClient}> 28 + <NavigationContainer linking={linking}> 29 + <Stack.Navigator 30 + initialRouteName="Home" 31 + screenOptions={{ 32 + headerStyle: { backgroundColor: '#030712' }, 33 + headerTintColor: '#f9fafb', 34 + headerShadowVisible: false, 35 + contentStyle: { backgroundColor: '#030712' }, 36 + }} 37 + > 38 + <Stack.Screen 39 + name="Home" 40 + component={HomeScreen} 41 + options={{ title: 'OpnShelf' }} 42 + /> 43 + <Stack.Screen 44 + name="Search" 45 + component={SearchScreen} 46 + options={{ title: 'Search Movies' }} 47 + /> 48 + </Stack.Navigator> 49 + <StatusBar style="light" /> 50 + </NavigationContainer> 51 + </QueryClientProvider> 10 52 ); 11 53 } 12 - 13 - const styles = StyleSheet.create({ 14 - container: { 15 - flex: 1, 16 - backgroundColor: '#fff', 17 - alignItems: 'center', 18 - justifyContent: 'center', 19 - }, 20 - });
+8 -2
apps/mobile/package.json
··· 8 8 "ios": "expo start --ios", 9 9 "web": "expo start --web" 10 10 }, 11 - "dependencies": { 11 + "dependencies": { 12 + "@expo/vector-icons": "^15.0.0", 12 13 "@opnshelf/api": "workspace:*", 13 14 "@opnshelf/types": "workspace:*", 15 + "@react-navigation/native": "^7.0.14", 16 + "@react-navigation/native-stack": "^7.2.16", 17 + "@tanstack/react-query": "^5.66.5", 14 18 "expo": "~54.0.32", 15 19 "expo-status-bar": "~3.0.9", 16 20 "react": "19.1.0", 17 - "react-native": "0.81.5" 21 + "react-native": "0.81.5", 22 + "react-native-safe-area-context": "~5.4.0", 23 + "react-native-screens": "~4.10.0" 18 24 }, 19 25 "devDependencies": { 20 26 "@types/react": "~19.1.0",
+9
apps/mobile/src/env.ts
··· 1 + /** 2 + * Mobile app env. Use EXPO_PUBLIC_API_URL to point at the backend. 3 + * Defaults to localhost:3001 (override for device: use your machine IP). 4 + */ 5 + const API_URL = process.env.EXPO_PUBLIC_API_URL ?? 'http://localhost:3001'; 6 + 7 + export const env = { 8 + API_URL, 9 + } as const;
+1
apps/mobile/src/navigation/index.ts
··· 1 + export type { RootStackParamList } from './types';
+4
apps/mobile/src/navigation/types.ts
··· 1 + export type RootStackParamList = { 2 + Home: undefined; 3 + Search: { q?: string }; 4 + };
+114
apps/mobile/src/screens/HomeScreen.tsx
··· 1 + import type { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 + import { Ionicons } from '@expo/vector-icons'; 3 + import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 4 + import type { RootStackParamList } from '../navigation'; 5 + 6 + type Props = NativeStackScreenProps<RootStackParamList, 'Home'>; 7 + 8 + export function HomeScreen({ navigation }: Props) { 9 + return ( 10 + <View style={styles.container}> 11 + <View style={styles.hero}> 12 + <View style={styles.iconWrap}> 13 + <Ionicons name="film" size={48} color="#a855f7" /> 14 + </View> 15 + <Text style={styles.title}>OpnShelf</Text> 16 + <Text style={styles.subtitle}> 17 + Your personal media tracker powered by AT Protocol 18 + </Text> 19 + <TouchableOpacity 20 + style={styles.cta} 21 + onPress={() => navigation.navigate('Search')} 22 + activeOpacity={0.8} 23 + > 24 + <Ionicons name="search" size={20} color="#fff" /> 25 + <Text style={styles.ctaText}>Search Movies</Text> 26 + </TouchableOpacity> 27 + </View> 28 + 29 + <View style={styles.cards}> 30 + <View style={styles.card}> 31 + <Text style={styles.cardTitle}>Track Your Media</Text> 32 + <Text style={styles.cardText}> 33 + Keep track of movies, shows, and games you've watched and played 34 + </Text> 35 + </View> 36 + <View style={styles.card}> 37 + <Text style={styles.cardTitle}>Own Your Data</Text> 38 + <Text style={styles.cardText}> 39 + Built on AT Protocol - your data belongs to you 40 + </Text> 41 + </View> 42 + <View style={styles.card}> 43 + <Text style={styles.cardTitle}>Discover & Share</Text> 44 + <Text style={styles.cardText}> 45 + See what others are watching and share your favorites 46 + </Text> 47 + </View> 48 + </View> 49 + </View> 50 + ); 51 + } 52 + 53 + const styles = StyleSheet.create({ 54 + container: { 55 + flex: 1, 56 + backgroundColor: '#030712', 57 + paddingHorizontal: 16, 58 + paddingTop: 48, 59 + paddingBottom: 24, 60 + }, 61 + hero: { 62 + alignItems: 'center', 63 + marginBottom: 48, 64 + }, 65 + iconWrap: { 66 + marginBottom: 24, 67 + }, 68 + title: { 69 + fontSize: 40, 70 + fontWeight: '700', 71 + color: '#f9fafb', 72 + marginBottom: 16, 73 + }, 74 + subtitle: { 75 + fontSize: 18, 76 + color: '#9ca3af', 77 + marginBottom: 32, 78 + textAlign: 'center', 79 + }, 80 + cta: { 81 + flexDirection: 'row', 82 + alignItems: 'center', 83 + gap: 8, 84 + backgroundColor: '#9333ea', 85 + paddingVertical: 12, 86 + paddingHorizontal: 24, 87 + borderRadius: 8, 88 + }, 89 + ctaText: { 90 + color: '#fff', 91 + fontSize: 16, 92 + fontWeight: '600', 93 + }, 94 + cards: { 95 + gap: 24, 96 + }, 97 + card: { 98 + backgroundColor: '#111827', 99 + padding: 24, 100 + borderRadius: 8, 101 + borderWidth: 1, 102 + borderColor: '#1f2937', 103 + }, 104 + cardTitle: { 105 + fontSize: 18, 106 + fontWeight: '600', 107 + color: '#f9fafb', 108 + marginBottom: 8, 109 + }, 110 + cardText: { 111 + fontSize: 14, 112 + color: '#9ca3af', 113 + }, 114 + });
+254
apps/mobile/src/screens/SearchScreen.tsx
··· 1 + import type { NativeStackScreenProps } from '@react-navigation/native-stack'; 2 + import { useQuery } from '@tanstack/react-query'; 3 + import { searchMovies } from '@opnshelf/api'; 4 + import { Ionicons } from '@expo/vector-icons'; 5 + import { 6 + ActivityIndicator, 7 + FlatList, 8 + Image, 9 + StyleSheet, 10 + Text, 11 + TextInput, 12 + View, 13 + } from 'react-native'; 14 + import { useCallback, useEffect, useRef, useState } from 'react'; 15 + import type { RootStackParamList } from '../navigation'; 16 + type Props = NativeStackScreenProps<RootStackParamList, 'Search'>; 17 + 18 + const DEBOUNCE_MS = 300; 19 + const POSTER_BASE = 'https://image.tmdb.org/t/p/w342'; 20 + 21 + type MovieItem = { 22 + id: number; 23 + title: string; 24 + poster_path?: string; 25 + release_date?: string; 26 + }; 27 + 28 + function MovieCard({ movie }: { movie: MovieItem }) { 29 + return ( 30 + <View style={styles.movieCard}> 31 + <View style={styles.posterWrap}> 32 + {movie.poster_path ? ( 33 + <Image 34 + source={{ uri: `${POSTER_BASE}${movie.poster_path}` }} 35 + style={styles.poster} 36 + resizeMode="cover" 37 + /> 38 + ) : ( 39 + <View style={styles.posterPlaceholder}> 40 + <Text style={styles.posterPlaceholderText}>No poster</Text> 41 + </View> 42 + )} 43 + </View> 44 + <Text style={styles.movieTitle} numberOfLines={2}> 45 + {movie.title} 46 + </Text> 47 + {movie.release_date ? ( 48 + <Text style={styles.movieYear}>{movie.release_date.split('-')[0]}</Text> 49 + ) : null} 50 + </View> 51 + ); 52 + } 53 + 54 + export function SearchScreen({ route }: Props) { 55 + const initialQ = route.params?.q ?? ''; 56 + const [query, setQuery] = useState(initialQ); 57 + const [searchQuery, setSearchQuery] = useState(initialQ); 58 + const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null); 59 + 60 + useEffect(() => { 61 + if (debounceRef.current) clearTimeout(debounceRef.current); 62 + const trimmed = query.trim(); 63 + if (trimmed !== searchQuery) { 64 + debounceRef.current = setTimeout(() => setSearchQuery(trimmed), DEBOUNCE_MS); 65 + } 66 + return () => { 67 + if (debounceRef.current) clearTimeout(debounceRef.current); 68 + }; 69 + }, [query, searchQuery]); 70 + 71 + const { data, isLoading, error } = useQuery({ 72 + queryKey: ['search', searchQuery], 73 + queryFn: () => searchMovies(searchQuery), 74 + enabled: searchQuery.length > 0, 75 + }); 76 + 77 + const results = data?.results ?? []; 78 + const totalResults = data?.total_results ?? 0; 79 + 80 + const renderItem = useCallback( 81 + ({ item }: { item: MovieItem }) => <MovieCard movie={item} />, 82 + [] 83 + ); 84 + 85 + const keyExtractor = useCallback((item: MovieItem) => String(item.id), []); 86 + 87 + return ( 88 + <View style={styles.container}> 89 + <Text style={styles.title}>Search Movies</Text> 90 + 91 + <View style={styles.inputWrap}> 92 + <Ionicons 93 + name="search" 94 + size={20} 95 + color="#9ca3af" 96 + style={styles.inputIcon} 97 + /> 98 + <TextInput 99 + style={styles.input} 100 + value={query} 101 + onChangeText={setQuery} 102 + placeholder="Search for a movie..." 103 + placeholderTextColor="#6b7280" 104 + autoCapitalize="none" 105 + autoCorrect={false} 106 + /> 107 + </View> 108 + 109 + {isLoading && ( 110 + <View style={styles.loading}> 111 + <ActivityIndicator size="large" color="#a855f7" /> 112 + </View> 113 + )} 114 + 115 + {error && ( 116 + <View style={styles.error}> 117 + <Text style={styles.errorText}>Error: {(error as Error).message}</Text> 118 + </View> 119 + )} 120 + 121 + {data && results.length > 0 && ( 122 + <> 123 + <Text style={styles.resultCount}> 124 + Found {totalResults.toLocaleString()} results 125 + </Text> 126 + <FlatList 127 + data={results} 128 + renderItem={renderItem} 129 + keyExtractor={keyExtractor} 130 + numColumns={2} 131 + columnWrapperStyle={styles.row} 132 + contentContainerStyle={styles.listContent} 133 + key="grid" 134 + /> 135 + </> 136 + )} 137 + 138 + {data && results.length === 0 && searchQuery.length > 0 && ( 139 + <View style={styles.empty}> 140 + <Text style={styles.emptyText}> 141 + No results found for "{searchQuery}" 142 + </Text> 143 + </View> 144 + )} 145 + </View> 146 + ); 147 + } 148 + 149 + const styles = StyleSheet.create({ 150 + container: { 151 + flex: 1, 152 + backgroundColor: '#030712', 153 + paddingHorizontal: 16, 154 + paddingTop: 48, 155 + paddingBottom: 24, 156 + }, 157 + title: { 158 + fontSize: 32, 159 + fontWeight: '700', 160 + color: '#f9fafb', 161 + marginBottom: 24, 162 + }, 163 + inputWrap: { 164 + flexDirection: 'row', 165 + alignItems: 'center', 166 + backgroundColor: '#111827', 167 + borderWidth: 1, 168 + borderColor: '#1f2937', 169 + borderRadius: 8, 170 + marginBottom: 24, 171 + }, 172 + inputIcon: { 173 + marginLeft: 16, 174 + }, 175 + input: { 176 + flex: 1, 177 + paddingVertical: 12, 178 + paddingHorizontal: 12, 179 + paddingLeft: 8, 180 + fontSize: 16, 181 + color: '#f9fafb', 182 + }, 183 + loading: { 184 + flex: 1, 185 + justifyContent: 'center', 186 + paddingVertical: 48, 187 + }, 188 + error: { 189 + backgroundColor: 'rgba(127, 29, 29, 0.2)', 190 + borderWidth: 1, 191 + borderColor: 'rgba(127, 29, 29, 0.5)', 192 + borderRadius: 8, 193 + padding: 12, 194 + marginBottom: 24, 195 + }, 196 + errorText: { 197 + color: '#f87171', 198 + }, 199 + resultCount: { 200 + color: '#9ca3af', 201 + marginBottom: 16, 202 + }, 203 + listContent: { 204 + paddingBottom: 24, 205 + }, 206 + row: { 207 + gap: 16, 208 + marginBottom: 16, 209 + }, 210 + movieCard: { 211 + flex: 1, 212 + maxWidth: '50%', 213 + }, 214 + posterWrap: { 215 + aspectRatio: 2 / 3, 216 + backgroundColor: '#111827', 217 + borderRadius: 8, 218 + overflow: 'hidden', 219 + marginBottom: 8, 220 + }, 221 + poster: { 222 + width: '100%', 223 + height: '100%', 224 + }, 225 + posterPlaceholder: { 226 + flex: 1, 227 + justifyContent: 'center', 228 + alignItems: 'center', 229 + }, 230 + posterPlaceholderText: { 231 + color: '#4b5563', 232 + fontSize: 12, 233 + }, 234 + movieTitle: { 235 + fontSize: 14, 236 + fontWeight: '600', 237 + color: '#f9fafb', 238 + marginBottom: 4, 239 + }, 240 + movieYear: { 241 + fontSize: 12, 242 + color: '#6b7280', 243 + }, 244 + empty: { 245 + flex: 1, 246 + justifyContent: 'center', 247 + paddingVertical: 48, 248 + }, 249 + emptyText: { 250 + color: '#9ca3af', 251 + fontSize: 18, 252 + textAlign: 'center', 253 + }, 254 + });
+235
pnpm-lock.yaml
··· 10 10 11 11 apps/mobile: 12 12 dependencies: 13 + '@expo/vector-icons': 14 + specifier: ^15.0.0 15 + version: 15.0.3(expo-font@14.0.11(expo@54.0.32(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 13 16 '@opnshelf/api': 14 17 specifier: workspace:* 15 18 version: link:../../packages/api 16 19 '@opnshelf/types': 17 20 specifier: workspace:* 18 21 version: link:../../packages/types 22 + '@react-navigation/native': 23 + specifier: ^7.0.14 24 + version: 7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 25 + '@react-navigation/native-stack': 26 + specifier: ^7.2.16 27 + version: 7.11.0(@react-navigation/native@7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.10.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 28 + '@tanstack/react-query': 29 + specifier: ^5.66.5 30 + version: 5.90.20(react@19.1.0) 19 31 expo: 20 32 specifier: ~54.0.32 21 33 version: 54.0.32(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) ··· 28 40 react-native: 29 41 specifier: 0.81.5 30 42 version: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) 43 + react-native-safe-area-context: 44 + specifier: ~5.4.0 45 + version: 5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 46 + react-native-screens: 47 + specifier: ~4.10.0 48 + version: 4.10.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 31 49 devDependencies: 32 50 '@types/react': 33 51 specifier: ~19.1.0 ··· 2201 2219 '@types/react': 2202 2220 optional: true 2203 2221 2222 + '@react-navigation/core@7.14.0': 2223 + resolution: {integrity: sha512-tMpzskBzVp0E7CRNdNtJIdXjk54Kwe/TF9ViXAef+YFM1kSfGv4e/B2ozfXE+YyYgmh4WavTv8fkdJz1CNyu+g==} 2224 + peerDependencies: 2225 + react: '>= 18.2.0' 2226 + 2227 + '@react-navigation/elements@2.9.5': 2228 + resolution: {integrity: sha512-iHZU8rRN1014Upz73AqNVXDvSMZDh5/ktQ1CMe21rdgnOY79RWtHHBp9qOS3VtqlUVYGkuX5GEw5mDt4tKdl0g==} 2229 + peerDependencies: 2230 + '@react-native-masked-view/masked-view': '>= 0.2.0' 2231 + '@react-navigation/native': ^7.1.28 2232 + react: '>= 18.2.0' 2233 + react-native: '*' 2234 + react-native-safe-area-context: '>= 4.0.0' 2235 + peerDependenciesMeta: 2236 + '@react-native-masked-view/masked-view': 2237 + optional: true 2238 + 2239 + '@react-navigation/native-stack@7.11.0': 2240 + resolution: {integrity: sha512-yNx9Wr4dfpOHpqjf2sGog4eH6KCYwTAEPlUPrKbvWlQbCRm5bglwPmaTXw9hTovX9v3HIa42yo7bXpbYfq4jzg==} 2241 + peerDependencies: 2242 + '@react-navigation/native': ^7.1.28 2243 + react: '>= 18.2.0' 2244 + react-native: '*' 2245 + react-native-safe-area-context: '>= 4.0.0' 2246 + react-native-screens: '>= 4.0.0' 2247 + 2248 + '@react-navigation/native@7.1.28': 2249 + resolution: {integrity: sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ==} 2250 + peerDependencies: 2251 + react: '>= 18.2.0' 2252 + react-native: '*' 2253 + 2254 + '@react-navigation/routers@7.5.3': 2255 + resolution: {integrity: sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==} 2256 + 2204 2257 '@redocly/ajv@8.17.2': 2205 2258 resolution: {integrity: sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==} 2206 2259 ··· 3668 3721 color-name@1.1.4: 3669 3722 resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 3670 3723 3724 + color-string@1.9.1: 3725 + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 3726 + 3727 + color@4.2.3: 3728 + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 3729 + engines: {node: '>=12.5.0'} 3730 + 3671 3731 colorette@1.4.0: 3672 3732 resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} 3673 3733 ··· 3865 3925 3866 3926 decimal.js@10.6.0: 3867 3927 resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} 3928 + 3929 + decode-uri-component@0.2.2: 3930 + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} 3931 + engines: {node: '>=0.10'} 3868 3932 3869 3933 dedent@1.7.1: 3870 3934 resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} ··· 4339 4403 resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 4340 4404 engines: {node: '>=8'} 4341 4405 4406 + filter-obj@1.1.0: 4407 + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} 4408 + engines: {node: '>=0.10.0'} 4409 + 4342 4410 finalhandler@1.1.2: 4343 4411 resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} 4344 4412 engines: {node: '>= 0.8'} ··· 4669 4737 4670 4738 is-arrayish@0.2.1: 4671 4739 resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} 4740 + 4741 + is-arrayish@0.3.4: 4742 + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} 4672 4743 4673 4744 is-binary-path@2.1.0: 4674 4745 resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} ··· 5971 6042 resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} 5972 6043 engines: {node: '>=0.6'} 5973 6044 6045 + query-string@7.1.3: 6046 + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} 6047 + engines: {node: '>=6'} 6048 + 5974 6049 queue@6.0.2: 5975 6050 resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} 5976 6051 ··· 6010 6085 peerDependencies: 6011 6086 react: ^19.2.4 6012 6087 6088 + react-freeze@1.0.4: 6089 + resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} 6090 + engines: {node: '>=10'} 6091 + peerDependencies: 6092 + react: '>=17.0.0' 6093 + 6013 6094 react-is@17.0.2: 6014 6095 resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} 6015 6096 6016 6097 react-is@18.3.1: 6017 6098 resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} 6018 6099 6100 + react-is@19.2.4: 6101 + resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} 6102 + 6019 6103 react-native-is-edge-to-edge@1.2.1: 6020 6104 resolution: {integrity: sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==} 6021 6105 peerDependencies: 6022 6106 react: '*' 6023 6107 react-native: '*' 6024 6108 6109 + react-native-safe-area-context@5.4.1: 6110 + resolution: {integrity: sha512-x+g3NblZ9jof8y+XkVvaGlpMrSlixhrJJ33BRzhTAKUKctQVecO1heSXmzxc5UdjvGYBKS6kPZVUw2b8NxHcPg==} 6111 + peerDependencies: 6112 + react: '*' 6113 + react-native: '*' 6114 + 6115 + react-native-screens@4.10.0: 6116 + resolution: {integrity: sha512-Tw21NGuXm3PbiUGtZd0AnXirUixaAbPXDjNR0baBH7/WJDaDTTELLcQ7QRXuqAWbmr/EVCrKj1348ei1KFIr8A==} 6117 + peerDependencies: 6118 + react: '*' 6119 + react-native: '*' 6120 + 6025 6121 react-native@0.81.5: 6026 6122 resolution: {integrity: sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==} 6027 6123 engines: {node: '>= 20.19.4'} ··· 6264 6360 setprototypeof@1.2.0: 6265 6361 resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 6266 6362 6363 + sf-symbols-typescript@2.2.0: 6364 + resolution: {integrity: sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==} 6365 + engines: {node: '>=10'} 6366 + 6267 6367 shebang-command@2.0.0: 6268 6368 resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 6269 6369 engines: {node: '>=8'} ··· 6305 6405 simple-plist@1.3.1: 6306 6406 resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} 6307 6407 6408 + simple-swizzle@0.2.4: 6409 + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} 6410 + 6308 6411 sisteransi@1.0.5: 6309 6412 resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} 6310 6413 ··· 6344 6447 resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} 6345 6448 engines: {node: '>= 8'} 6346 6449 6450 + split-on-first@1.1.0: 6451 + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} 6452 + engines: {node: '>=6'} 6453 + 6347 6454 split2@4.2.0: 6348 6455 resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} 6349 6456 engines: {node: '>= 10.x'} ··· 6392 6499 streamsearch@1.1.0: 6393 6500 resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} 6394 6501 engines: {node: '>=10.0.0'} 6502 + 6503 + strict-uri-encode@2.0.0: 6504 + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} 6505 + engines: {node: '>=4'} 6395 6506 6396 6507 string-length@4.0.2: 6397 6508 resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} ··· 6916 7027 uri-js@4.4.1: 6917 7028 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 6918 7029 7030 + use-latest-callback@0.2.6: 7031 + resolution: {integrity: sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==} 7032 + peerDependencies: 7033 + react: '>=16.8' 7034 + 6919 7035 use-sync-external-store@1.6.0: 6920 7036 resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} 6921 7037 peerDependencies: ··· 7058 7174 walker@1.0.8: 7059 7175 resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} 7060 7176 7177 + warn-once@0.1.1: 7178 + resolution: {integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==} 7179 + 7061 7180 watchpack@2.5.1: 7062 7181 resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} 7063 7182 engines: {node: '>=10.13.0'} ··· 9576 9695 optionalDependencies: 9577 9696 '@types/react': 19.1.17 9578 9697 9698 + '@react-navigation/core@7.14.0(react@19.1.0)': 9699 + dependencies: 9700 + '@react-navigation/routers': 7.5.3 9701 + escape-string-regexp: 4.0.0 9702 + fast-deep-equal: 3.1.3 9703 + nanoid: 3.3.11 9704 + query-string: 7.1.3 9705 + react: 19.1.0 9706 + react-is: 19.2.4 9707 + use-latest-callback: 0.2.6(react@19.1.0) 9708 + use-sync-external-store: 1.6.0(react@19.1.0) 9709 + 9710 + '@react-navigation/elements@2.9.5(@react-navigation/native@7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': 9711 + dependencies: 9712 + '@react-navigation/native': 7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 9713 + color: 4.2.3 9714 + react: 19.1.0 9715 + react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) 9716 + react-native-safe-area-context: 5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 9717 + use-latest-callback: 0.2.6(react@19.1.0) 9718 + use-sync-external-store: 1.6.0(react@19.1.0) 9719 + 9720 + '@react-navigation/native-stack@7.11.0(@react-navigation/native@7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.10.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': 9721 + dependencies: 9722 + '@react-navigation/elements': 2.9.5(@react-navigation/native@7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 9723 + '@react-navigation/native': 7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 9724 + color: 4.2.3 9725 + react: 19.1.0 9726 + react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) 9727 + react-native-safe-area-context: 5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 9728 + react-native-screens: 4.10.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 9729 + sf-symbols-typescript: 2.2.0 9730 + warn-once: 0.1.1 9731 + transitivePeerDependencies: 9732 + - '@react-native-masked-view/masked-view' 9733 + 9734 + '@react-navigation/native@7.1.28(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': 9735 + dependencies: 9736 + '@react-navigation/core': 7.14.0(react@19.1.0) 9737 + escape-string-regexp: 4.0.0 9738 + fast-deep-equal: 3.1.3 9739 + nanoid: 3.3.11 9740 + react: 19.1.0 9741 + react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) 9742 + use-latest-callback: 0.2.6(react@19.1.0) 9743 + 9744 + '@react-navigation/routers@7.5.3': 9745 + dependencies: 9746 + nanoid: 3.3.11 9747 + 9579 9748 '@redocly/ajv@8.17.2': 9580 9749 dependencies: 9581 9750 fast-deep-equal: 3.1.3 ··· 9891 10060 '@tanstack/query-devtools': 5.92.0 9892 10061 '@tanstack/react-query': 5.90.20(react@19.2.4) 9893 10062 react: 19.2.4 10063 + 10064 + '@tanstack/react-query@5.90.20(react@19.1.0)': 10065 + dependencies: 10066 + '@tanstack/query-core': 5.90.20 10067 + react: 19.1.0 9894 10068 9895 10069 '@tanstack/react-query@5.90.20(react@19.2.4)': 9896 10070 dependencies: ··· 11225 11399 11226 11400 color-name@1.1.4: {} 11227 11401 11402 + color-string@1.9.1: 11403 + dependencies: 11404 + color-name: 1.1.4 11405 + simple-swizzle: 0.2.4 11406 + 11407 + color@4.2.3: 11408 + dependencies: 11409 + color-convert: 2.0.1 11410 + color-string: 1.9.1 11411 + 11228 11412 colorette@1.4.0: {} 11229 11413 11230 11414 combined-stream@1.0.8: ··· 11389 11573 11390 11574 decimal.js@10.6.0: {} 11391 11575 11576 + decode-uri-component@0.2.2: {} 11577 + 11392 11578 dedent@1.7.1: {} 11393 11579 11394 11580 deep-eql@5.0.2: {} ··· 11918 12104 dependencies: 11919 12105 to-regex-range: 5.0.1 11920 12106 12107 + filter-obj@1.1.0: {} 12108 + 11921 12109 finalhandler@1.1.2: 11922 12110 dependencies: 11923 12111 debug: 2.6.9 ··· 12277 12465 ipaddr.js@1.9.1: {} 12278 12466 12279 12467 is-arrayish@0.2.1: {} 12468 + 12469 + is-arrayish@0.3.4: {} 12280 12470 12281 12471 is-binary-path@2.1.0: 12282 12472 dependencies: ··· 13873 14063 dependencies: 13874 14064 side-channel: 1.1.0 13875 14065 14066 + query-string@7.1.3: 14067 + dependencies: 14068 + decode-uri-component: 0.2.2 14069 + filter-obj: 1.1.0 14070 + split-on-first: 1.1.0 14071 + strict-uri-encode: 2.0.0 14072 + 13876 14073 queue@6.0.2: 13877 14074 dependencies: 13878 14075 inherits: 2.0.4 ··· 13926 14123 react: 19.2.4 13927 14124 scheduler: 0.27.0 13928 14125 14126 + react-freeze@1.0.4(react@19.1.0): 14127 + dependencies: 14128 + react: 19.1.0 14129 + 13929 14130 react-is@17.0.2: {} 13930 14131 13931 14132 react-is@18.3.1: {} 13932 14133 14134 + react-is@19.2.4: {} 14135 + 13933 14136 react-native-is-edge-to-edge@1.2.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): 13934 14137 dependencies: 13935 14138 react: 19.1.0 13936 14139 react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) 13937 14140 14141 + react-native-safe-area-context@5.4.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): 14142 + dependencies: 14143 + react: 19.1.0 14144 + react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) 14145 + 14146 + react-native-screens@4.10.0(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): 14147 + dependencies: 14148 + react: 19.1.0 14149 + react-freeze: 1.0.4(react@19.1.0) 14150 + react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0) 14151 + warn-once: 0.1.1 14152 + 13938 14153 react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0): 13939 14154 dependencies: 13940 14155 '@jest/create-cache-key-function': 29.7.0 ··· 14255 14470 14256 14471 setprototypeof@1.2.0: {} 14257 14472 14473 + sf-symbols-typescript@2.2.0: {} 14474 + 14258 14475 shebang-command@2.0.0: 14259 14476 dependencies: 14260 14477 shebang-regex: 3.0.0 ··· 14302 14519 bplist-creator: 0.1.0 14303 14520 bplist-parser: 0.3.1 14304 14521 plist: 3.1.0 14522 + 14523 + simple-swizzle@0.2.4: 14524 + dependencies: 14525 + is-arrayish: 0.3.4 14305 14526 14306 14527 sisteransi@1.0.5: {} 14307 14528 ··· 14337 14558 14338 14559 source-map@0.7.4: {} 14339 14560 14561 + split-on-first@1.1.0: {} 14562 + 14340 14563 split2@4.2.0: {} 14341 14564 14342 14565 sprintf-js@1.0.3: {} ··· 14366 14589 stream-buffers@2.2.0: {} 14367 14590 14368 14591 streamsearch@1.1.0: {} 14592 + 14593 + strict-uri-encode@2.0.0: {} 14369 14594 14370 14595 string-length@4.0.2: 14371 14596 dependencies: ··· 14812 15037 dependencies: 14813 15038 punycode: 2.3.1 14814 15039 15040 + use-latest-callback@0.2.6(react@19.1.0): 15041 + dependencies: 15042 + react: 19.1.0 15043 + 15044 + use-sync-external-store@1.6.0(react@19.1.0): 15045 + dependencies: 15046 + react: 19.1.0 15047 + 14815 15048 use-sync-external-store@1.6.0(react@19.2.4): 14816 15049 dependencies: 14817 15050 react: 19.2.4 ··· 14945 15178 walker@1.0.8: 14946 15179 dependencies: 14947 15180 makeerror: 1.0.12 15181 + 15182 + warn-once@0.1.1: {} 14948 15183 14949 15184 watchpack@2.5.1: 14950 15185 dependencies: