@multiplayer-app/session-recorder-react-native 0.0.1-alpha.1 → 0.0.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/RRWEB_INTEGRATION.md +336 -0
  2. package/VIEWSHOT_INTEGRATION_TEST.md +123 -0
  3. package/copy-react-native-dist.sh +38 -0
  4. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.d.ts +6 -0
  5. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
  6. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
  7. package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
  8. package/dist/components/GestureCaptureWrapper/index.js +1 -0
  9. package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
  10. package/dist/components/GestureCaptureWrapper.d.ts +6 -0
  11. package/dist/components/GestureCaptureWrapper.js +1 -0
  12. package/dist/components/GestureCaptureWrapper.js.map +1 -0
  13. package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
  14. package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
  15. package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
  16. package/dist/components/ScreenRecorderView/index.d.ts +1 -0
  17. package/dist/components/ScreenRecorderView/index.js +1 -0
  18. package/dist/components/ScreenRecorderView/index.js.map +1 -0
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/index.js +1 -0
  21. package/dist/components/index.js.map +1 -0
  22. package/dist/config/constants.d.ts +18 -0
  23. package/dist/config/constants.js +1 -0
  24. package/dist/config/constants.js.map +1 -0
  25. package/dist/config/defaults.d.ts +4 -0
  26. package/dist/config/defaults.js +1 -0
  27. package/dist/config/defaults.js.map +1 -0
  28. package/dist/config/index.d.ts +5 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/index.js.map +1 -0
  31. package/dist/config/masking.d.ts +2 -30
  32. package/dist/config/masking.js +1 -1
  33. package/dist/config/masking.js.map +1 -1
  34. package/dist/config/session-recorder.d.ts +2 -0
  35. package/dist/config/session-recorder.js +1 -0
  36. package/dist/config/session-recorder.js.map +1 -0
  37. package/dist/config/validators.d.ts +10 -0
  38. package/dist/config/validators.js +1 -0
  39. package/dist/config/validators.js.map +1 -0
  40. package/dist/context/SessionRecorderContext.d.ts +12 -0
  41. package/dist/context/SessionRecorderContext.js +1 -0
  42. package/dist/context/SessionRecorderContext.js.map +1 -0
  43. package/dist/expo.d.ts +5 -9
  44. package/dist/expo.js +1 -1
  45. package/dist/expo.js.map +1 -1
  46. package/dist/index.d.ts +6 -10
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/otel/helpers.d.ts +45 -3
  50. package/dist/otel/helpers.js +1 -1
  51. package/dist/otel/helpers.js.map +1 -1
  52. package/dist/otel/index.d.ts +4 -25
  53. package/dist/otel/index.js +1 -1
  54. package/dist/otel/index.js.map +1 -1
  55. package/dist/otel/instrumentations/gestureInstrumentation.js +1 -1
  56. package/dist/otel/instrumentations/gestureInstrumentation.js.map +1 -1
  57. package/dist/otel/instrumentations/index.d.ts +3 -4
  58. package/dist/otel/instrumentations/index.js +1 -1
  59. package/dist/otel/instrumentations/index.js.map +1 -1
  60. package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -1
  61. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -1
  62. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +1 -0
  63. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +1 -1
  64. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +1 -1
  65. package/dist/patch/index.d.ts +1 -0
  66. package/dist/patch/index.js +1 -0
  67. package/dist/patch/index.js.map +1 -0
  68. package/dist/patch/xhr.d.ts +2 -0
  69. package/dist/patch/xhr.js +1 -0
  70. package/dist/patch/xhr.js.map +1 -0
  71. package/dist/recorder/eventExporter.d.ts +21 -0
  72. package/dist/recorder/eventExporter.js +1 -0
  73. package/dist/recorder/eventExporter.js.map +1 -0
  74. package/dist/recorder/gestureHandlerRecorder.d.ts +19 -0
  75. package/dist/recorder/gestureHandlerRecorder.js +1 -0
  76. package/dist/recorder/gestureHandlerRecorder.js.map +1 -0
  77. package/dist/recorder/gestureRecorder.d.ts +68 -11
  78. package/dist/recorder/gestureRecorder.js +1 -1
  79. package/dist/recorder/gestureRecorder.js.map +1 -1
  80. package/dist/recorder/index.d.ts +60 -6
  81. package/dist/recorder/index.js +1 -1
  82. package/dist/recorder/index.js.map +1 -1
  83. package/dist/recorder/navigationTracker.js +1 -1
  84. package/dist/recorder/navigationTracker.js.map +1 -1
  85. package/dist/recorder/screenRecorder.d.ts +79 -10
  86. package/dist/recorder/screenRecorder.js +1 -1
  87. package/dist/recorder/screenRecorder.js.map +1 -1
  88. package/dist/services/api.service.d.ts +62 -10
  89. package/dist/services/api.service.js +1 -1
  90. package/dist/services/api.service.js.map +1 -1
  91. package/dist/services/storage.service.d.ts +23 -16
  92. package/dist/services/storage.service.js +1 -1
  93. package/dist/services/storage.service.js.map +1 -1
  94. package/dist/session-recorder.d.ts +166 -0
  95. package/dist/session-recorder.js +1 -0
  96. package/dist/session-recorder.js.map +1 -0
  97. package/dist/types/index.d.ts +15 -76
  98. package/dist/types/index.js +1 -1
  99. package/dist/types/index.js.map +1 -1
  100. package/dist/types/rrweb.d.ts +118 -0
  101. package/dist/types/rrweb.js +1 -0
  102. package/dist/types/rrweb.js.map +1 -0
  103. package/dist/types/session-recorder.d.ts +366 -0
  104. package/dist/types/session-recorder.js +1 -0
  105. package/dist/types/session-recorder.js.map +1 -0
  106. package/dist/types/session.d.ts +59 -0
  107. package/dist/types/session.js +1 -0
  108. package/dist/types/session.js.map +1 -0
  109. package/dist/utils/app-metadata.d.ts +16 -0
  110. package/dist/utils/app-metadata.js +1 -0
  111. package/dist/utils/app-metadata.js.map +1 -0
  112. package/dist/utils/index.d.ts +7 -0
  113. package/dist/utils/index.js +1 -0
  114. package/dist/utils/index.js.map +1 -0
  115. package/dist/utils/logger.d.ts +112 -0
  116. package/dist/utils/logger.js +1 -0
  117. package/dist/utils/logger.js.map +1 -0
  118. package/dist/utils/platform.d.ts +37 -0
  119. package/dist/utils/platform.js +1 -1
  120. package/dist/utils/platform.js.map +1 -1
  121. package/dist/utils/request-utils.d.ts +21 -0
  122. package/dist/utils/request-utils.js +1 -0
  123. package/dist/utils/request-utils.js.map +1 -0
  124. package/dist/utils/rrweb-events.d.ts +65 -0
  125. package/dist/utils/rrweb-events.js +1 -0
  126. package/dist/utils/rrweb-events.js.map +1 -0
  127. package/dist/utils/session.d.ts +5 -0
  128. package/dist/utils/session.js +1 -0
  129. package/dist/utils/session.js.map +1 -0
  130. package/dist/utils/time.d.ts +4 -0
  131. package/dist/utils/time.js +1 -0
  132. package/dist/utils/time.js.map +1 -0
  133. package/dist/utils/type-utils.d.ts +16 -0
  134. package/dist/utils/type-utils.js +1 -0
  135. package/dist/utils/type-utils.js.map +1 -0
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/version.js.map +1 -1
  139. package/docs/AUTO_METADATA_DETECTION.md +108 -0
  140. package/package.json +10 -9
  141. package/scripts/generate-app-metadata.js +173 -0
  142. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +86 -0
  143. package/src/components/GestureCaptureWrapper/index.ts +1 -0
  144. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +72 -0
  145. package/src/components/ScreenRecorderView/index.ts +1 -0
  146. package/src/components/index.ts +1 -0
  147. package/src/config/constants.ts +60 -0
  148. package/src/config/defaults.ts +82 -0
  149. package/src/config/index.ts +6 -0
  150. package/src/config/masking.ts +10 -61
  151. package/src/config/session-recorder.ts +55 -0
  152. package/src/config/validators.ts +31 -0
  153. package/src/context/SessionRecorderContext.tsx +75 -0
  154. package/src/expo.ts +7 -37
  155. package/src/index.ts +14 -17
  156. package/src/otel/helpers.ts +265 -11
  157. package/src/otel/index.ts +37 -247
  158. package/src/otel/instrumentations/index.ts +82 -53
  159. package/src/patch/index.ts +1 -0
  160. package/src/patch/xhr.ts +142 -0
  161. package/src/recorder/eventExporter.ts +141 -0
  162. package/src/recorder/gestureRecorder.ts +194 -125
  163. package/src/recorder/index.ts +132 -24
  164. package/src/recorder/navigationTracker.ts +12 -10
  165. package/src/recorder/screenRecorder.ts +242 -155
  166. package/src/services/api.service.ts +170 -45
  167. package/src/services/storage.service.ts +102 -74
  168. package/src/session-recorder.ts +600 -0
  169. package/src/types/index.ts +19 -79
  170. package/src/types/session-recorder.ts +423 -0
  171. package/src/types/session.ts +65 -0
  172. package/src/utils/app-metadata.ts +31 -0
  173. package/src/utils/index.ts +8 -0
  174. package/src/utils/logger.ts +225 -0
  175. package/src/utils/platform.ts +321 -6
  176. package/src/utils/request-utils.ts +61 -0
  177. package/src/utils/rrweb-events.ts +309 -0
  178. package/src/utils/session.ts +18 -0
  179. package/src/utils/time.ts +17 -0
  180. package/src/utils/type-utils.ts +75 -0
  181. package/src/version.ts +1 -1
  182. package/dist/sessionRecorder.d.ts +0 -54
  183. package/dist/sessionRecorder.js +0 -1
  184. package/dist/sessionRecorder.js.map +0 -1
  185. package/examples/sample-expo-app/README.md +0 -142
  186. package/examples/sample-expo-app/app/(tabs)/_layout.tsx +0 -60
  187. package/examples/sample-expo-app/app/(tabs)/explore.tsx +0 -110
  188. package/examples/sample-expo-app/app/(tabs)/index.tsx +0 -125
  189. package/examples/sample-expo-app/app/(tabs)/posts.tsx +0 -96
  190. package/examples/sample-expo-app/app/(tabs)/users.tsx +0 -131
  191. package/examples/sample-expo-app/app/+not-found.tsx +0 -32
  192. package/examples/sample-expo-app/app/_layout.tsx +0 -53
  193. package/examples/sample-expo-app/app/post/[id].tsx +0 -199
  194. package/examples/sample-expo-app/app/user/[id].tsx +0 -270
  195. package/examples/sample-expo-app/app.json +0 -42
  196. package/examples/sample-expo-app/assets/fonts/SpaceMono-Regular.ttf +0 -0
  197. package/examples/sample-expo-app/assets/images/adaptive-icon.png +0 -0
  198. package/examples/sample-expo-app/assets/images/favicon.png +0 -0
  199. package/examples/sample-expo-app/assets/images/icon.png +0 -0
  200. package/examples/sample-expo-app/assets/images/partial-react-logo.png +0 -0
  201. package/examples/sample-expo-app/assets/images/react-logo.png +0 -0
  202. package/examples/sample-expo-app/assets/images/react-logo@2x.png +0 -0
  203. package/examples/sample-expo-app/assets/images/react-logo@3x.png +0 -0
  204. package/examples/sample-expo-app/assets/images/splash-icon.png +0 -0
  205. package/examples/sample-expo-app/components/Collapsible.tsx +0 -45
  206. package/examples/sample-expo-app/components/ErrorView.tsx +0 -52
  207. package/examples/sample-expo-app/components/ExternalLink.tsx +0 -24
  208. package/examples/sample-expo-app/components/HapticTab.tsx +0 -18
  209. package/examples/sample-expo-app/components/HelloWave.tsx +0 -40
  210. package/examples/sample-expo-app/components/LoadingSpinner.tsx +0 -34
  211. package/examples/sample-expo-app/components/ParallaxScrollView.tsx +0 -82
  212. package/examples/sample-expo-app/components/ThemedText.tsx +0 -60
  213. package/examples/sample-expo-app/components/ThemedView.tsx +0 -14
  214. package/examples/sample-expo-app/components/ui/IconSymbol.ios.tsx +0 -32
  215. package/examples/sample-expo-app/components/ui/IconSymbol.tsx +0 -41
  216. package/examples/sample-expo-app/components/ui/TabBarBackground.ios.tsx +0 -19
  217. package/examples/sample-expo-app/components/ui/TabBarBackground.tsx +0 -6
  218. package/examples/sample-expo-app/constants/Colors.ts +0 -26
  219. package/examples/sample-expo-app/eslint.config.js +0 -10
  220. package/examples/sample-expo-app/hooks/useApi.ts +0 -41
  221. package/examples/sample-expo-app/hooks/useColorScheme.ts +0 -1
  222. package/examples/sample-expo-app/hooks/useColorScheme.web.ts +0 -21
  223. package/examples/sample-expo-app/hooks/useThemeColor.ts +0 -21
  224. package/examples/sample-expo-app/metro.config.js +0 -26
  225. package/examples/sample-expo-app/package-lock.json +0 -26296
  226. package/examples/sample-expo-app/package.json +0 -59
  227. package/examples/sample-expo-app/scripts/reset-project.js +0 -112
  228. package/examples/sample-expo-app/services/api.ts +0 -98
  229. package/examples/sample-expo-app/tsconfig.json +0 -17
  230. package/examples/sample-expo-app/utils/navigation.ts +0 -19
  231. package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
  232. package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -164
  233. package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -114
  234. package/src/sessionRecorder.ts +0 -367
@@ -1,199 +0,0 @@
1
- import React from 'react'
2
- import { ScrollView, StyleSheet, TouchableOpacity, FlatList } from 'react-native'
3
- import { useLocalSearchParams, router } from 'expo-router'
4
- import { ThemedView } from '@/components/ThemedView'
5
- import { ThemedText } from '@/components/ThemedText'
6
- import { LoadingSpinner } from '@/components/LoadingSpinner'
7
- import { ErrorView } from '@/components/ErrorView'
8
- import { IconSymbol } from '@/components/ui/IconSymbol'
9
- import { useApi } from '@/hooks/useApi'
10
- import { apiService, Comment } from '@/services/api'
11
- import { Colors } from '@/constants/Colors'
12
- import { useColorScheme } from '@/hooks/useColorScheme'
13
-
14
- export default function PostDetailScreen() {
15
- const { id } = useLocalSearchParams<{ id: string }>()
16
- const postId = parseInt(id!)
17
- const colorScheme = useColorScheme()
18
-
19
- const {
20
- data: post,
21
- loading: postLoading,
22
- error: postError,
23
- refetch: refetchPost
24
- } = useApi(() => apiService.getPost(postId), [postId])
25
-
26
- const {
27
- data: comments,
28
- loading: commentsLoading,
29
- error: commentsError,
30
- refetch: refetchComments
31
- } = useApi(() => apiService.getPostComments(postId), [postId])
32
-
33
- const handleUserPress = (userId: number) => {
34
- router.push(`/user/${userId}`)
35
- }
36
-
37
- const renderComment = ({ item }: { item: Comment }) => (
38
- <ThemedView style={styles.commentCard}>
39
- <ThemedView style={styles.commentHeader}>
40
- <ThemedText type='defaultSemiBold' style={styles.commentName}>
41
- {item.name}
42
- </ThemedText>
43
- <ThemedText style={styles.commentEmail}>{item.email}</ThemedText>
44
- </ThemedView>
45
- <ThemedText style={styles.commentBody}>{item.body}</ThemedText>
46
- </ThemedView>
47
- )
48
-
49
- if (postLoading) {
50
- return <LoadingSpinner message='Loading post...' />
51
- }
52
-
53
- if (postError) {
54
- return <ErrorView message={postError} onRetry={refetchPost} />
55
- }
56
-
57
- if (!post) {
58
- return <ErrorView message='Post not found' />
59
- }
60
-
61
- return (
62
- <ThemedView style={styles.container}>
63
- {/* Header */}
64
- <ThemedView style={styles.header}>
65
- <TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
66
- <IconSymbol name='chevron.left' size={24} color={Colors[colorScheme ?? 'light'].tint} />
67
- </TouchableOpacity>
68
- <ThemedText type='title' style={styles.headerTitle}>
69
- Post Details
70
- </ThemedText>
71
- </ThemedView>
72
-
73
- <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
74
- {/* Post Content */}
75
- <ThemedView style={styles.postCard}>
76
- <ThemedText type='title' style={styles.postTitle}>
77
- {post.title}
78
- </ThemedText>
79
- <ThemedText style={styles.postBody}>{post.body}</ThemedText>
80
-
81
- <TouchableOpacity style={styles.userButton} onPress={() => handleUserPress(post.userId)}>
82
- <IconSymbol name='person.fill' size={16} color={Colors[colorScheme ?? 'light'].tint} />
83
- <ThemedText style={styles.userButtonText}>View User {post.userId}</ThemedText>
84
- <IconSymbol name='chevron.right' size={16} color={Colors[colorScheme ?? 'light'].tint} />
85
- </TouchableOpacity>
86
- </ThemedView>
87
-
88
- {/* Comments Section */}
89
- <ThemedView style={styles.commentsSection}>
90
- <ThemedText type='subtitle' style={styles.commentsTitle}>
91
- Comments ({comments?.length || 0})
92
- </ThemedText>
93
-
94
- {commentsLoading ? (
95
- <LoadingSpinner message='Loading comments...' />
96
- ) : commentsError ? (
97
- <ErrorView message={commentsError} onRetry={refetchComments} />
98
- ) : comments && comments.length > 0 ? (
99
- <FlatList
100
- data={comments}
101
- renderItem={renderComment}
102
- keyExtractor={(item) => item.id.toString()}
103
- scrollEnabled={false}
104
- showsVerticalScrollIndicator={false}
105
- />
106
- ) : (
107
- <ThemedText style={styles.noComments}>No comments yet</ThemedText>
108
- )}
109
- </ThemedView>
110
- </ScrollView>
111
- </ThemedView>
112
- )
113
- }
114
-
115
- const styles = StyleSheet.create({
116
- container: {
117
- flex: 1
118
- },
119
- header: {
120
- flexDirection: 'row',
121
- alignItems: 'center',
122
- padding: 20,
123
- paddingTop: 60,
124
- paddingBottom: 10
125
- },
126
- backButton: {
127
- marginRight: 16
128
- },
129
- headerTitle: {
130
- flex: 1
131
- },
132
- content: {
133
- flex: 1,
134
- padding: 20
135
- },
136
- postCard: {
137
- padding: 20,
138
- marginBottom: 20,
139
- borderRadius: 12,
140
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
141
- borderWidth: 1,
142
- borderColor: 'rgba(255, 255, 255, 0.2)'
143
- },
144
- postTitle: {
145
- marginBottom: 16,
146
- lineHeight: 28
147
- },
148
- postBody: {
149
- marginBottom: 20,
150
- lineHeight: 22
151
- },
152
- userButton: {
153
- flexDirection: 'row',
154
- alignItems: 'center',
155
- padding: 12,
156
- borderRadius: 8,
157
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
158
- borderWidth: 1,
159
- borderColor: 'rgba(255, 255, 255, 0.2)'
160
- },
161
- userButtonText: {
162
- flex: 1,
163
- marginLeft: 8,
164
- marginRight: 8
165
- },
166
- commentsSection: {
167
- marginBottom: 20
168
- },
169
- commentsTitle: {
170
- marginBottom: 16
171
- },
172
- commentCard: {
173
- padding: 16,
174
- marginBottom: 12,
175
- borderRadius: 8,
176
- backgroundColor: 'rgba(255, 255, 255, 0.05)',
177
- borderWidth: 1,
178
- borderColor: 'rgba(255, 255, 255, 0.1)'
179
- },
180
- commentHeader: {
181
- marginBottom: 8
182
- },
183
- commentName: {
184
- marginBottom: 2
185
- },
186
- commentEmail: {
187
- fontSize: 12,
188
- opacity: 0.6
189
- },
190
- commentBody: {
191
- lineHeight: 18
192
- },
193
- noComments: {
194
- textAlign: 'center',
195
- opacity: 0.6,
196
- fontStyle: 'italic',
197
- padding: 20
198
- }
199
- })
@@ -1,270 +0,0 @@
1
- import React from 'react'
2
- import { ScrollView, StyleSheet, TouchableOpacity, FlatList } from 'react-native'
3
- import { useLocalSearchParams, router } from 'expo-router'
4
- import { ThemedView } from '@/components/ThemedView'
5
- import { ThemedText } from '@/components/ThemedText'
6
- import { LoadingSpinner } from '@/components/LoadingSpinner'
7
- import { ErrorView } from '@/components/ErrorView'
8
- import { IconSymbol } from '@/components/ui/IconSymbol'
9
- import { useApi } from '@/hooks/useApi'
10
- import { apiService, Post } from '@/services/api'
11
- import { Colors } from '@/constants/Colors'
12
- import { useColorScheme } from '@/hooks/useColorScheme'
13
-
14
- export default function UserDetailScreen() {
15
- const { id } = useLocalSearchParams<{ id: string }>()
16
- const userId = parseInt(id!)
17
- const colorScheme = useColorScheme()
18
-
19
- const {
20
- data: user,
21
- loading: userLoading,
22
- error: userError,
23
- refetch: refetchUser
24
- } = useApi(() => apiService.getUser(userId), [userId])
25
-
26
- const {
27
- data: posts,
28
- loading: postsLoading,
29
- error: postsError,
30
- refetch: refetchPosts
31
- } = useApi(() => apiService.getUserPosts(userId), [userId])
32
-
33
- const handlePostPress = (post: Post) => {
34
- router.push(`/post/${post.id}`)
35
- }
36
-
37
- const renderPost = ({ item }: { item: Post }) => (
38
- <TouchableOpacity style={styles.postCard} onPress={() => handlePostPress(item)} activeOpacity={0.7}>
39
- <ThemedText type='subtitle' style={styles.postTitle}>
40
- {item.title}
41
- </ThemedText>
42
- <ThemedText style={styles.postBody} numberOfLines={2}>
43
- {item.body}
44
- </ThemedText>
45
- <ThemedText style={styles.postMeta}>Post ID: {item.id}</ThemedText>
46
- </TouchableOpacity>
47
- )
48
-
49
- if (userLoading) {
50
- return <LoadingSpinner message='Loading user...' />
51
- }
52
-
53
- if (userError) {
54
- return <ErrorView message={userError} onRetry={refetchUser} />
55
- }
56
-
57
- if (!user) {
58
- return <ErrorView message='User not found' />
59
- }
60
-
61
- return (
62
- <ThemedView style={styles.container}>
63
- {/* Header */}
64
- <ThemedView style={styles.header}>
65
- <TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
66
- <IconSymbol name='chevron.left' size={24} color={Colors[colorScheme ?? 'light'].tint} />
67
- </TouchableOpacity>
68
- <ThemedText type='title' style={styles.headerTitle}>
69
- User Profile
70
- </ThemedText>
71
- </ThemedView>
72
-
73
- <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
74
- {/* User Info Card */}
75
- <ThemedView style={styles.userCard}>
76
- <ThemedView style={styles.userAvatar}>
77
- <IconSymbol name='person.fill' size={32} color={Colors[colorScheme ?? 'light'].tint} />
78
- </ThemedView>
79
-
80
- <ThemedText type='title' style={styles.userName}>
81
- {user.name}
82
- </ThemedText>
83
- <ThemedText style={styles.userUsername}>@{user.username}</ThemedText>
84
-
85
- <ThemedView style={styles.userDetails}>
86
- <ThemedView style={styles.detailRow}>
87
- <IconSymbol name='envelope.fill' size={16} color={Colors[colorScheme ?? 'light'].tint} />
88
- <ThemedText style={styles.detailText}>{user.email}</ThemedText>
89
- </ThemedView>
90
-
91
- <ThemedView style={styles.detailRow}>
92
- <IconSymbol name='phone.fill' size={16} color={Colors[colorScheme ?? 'light'].tint} />
93
- <ThemedText style={styles.detailText}>{user.phone}</ThemedText>
94
- </ThemedView>
95
-
96
- <ThemedView style={styles.detailRow}>
97
- <IconSymbol name='globe' size={16} color={Colors[colorScheme ?? 'light'].tint} />
98
- <ThemedText style={styles.detailText}>{user.website}</ThemedText>
99
- </ThemedView>
100
- </ThemedView>
101
-
102
- <ThemedView style={styles.companySection}>
103
- <ThemedText type='subtitle' style={styles.sectionTitle}>
104
- Company
105
- </ThemedText>
106
- <ThemedText style={styles.companyName}>{user.company.name}</ThemedText>
107
- <ThemedText style={styles.companyCatchPhrase}>&ldquo;{user.company.catchPhrase}&rdquo;</ThemedText>
108
- </ThemedView>
109
-
110
- <ThemedView style={styles.addressSection}>
111
- <ThemedText type='subtitle' style={styles.sectionTitle}>
112
- Address
113
- </ThemedText>
114
- <ThemedText style={styles.addressText}>
115
- {user.address.street}, {user.address.suite}
116
- </ThemedText>
117
- <ThemedText style={styles.addressText}>
118
- {user.address.city}, {user.address.zipcode}
119
- </ThemedText>
120
- </ThemedView>
121
- </ThemedView>
122
-
123
- {/* Posts Section */}
124
- <ThemedView style={styles.postsSection}>
125
- <ThemedText type='subtitle' style={styles.postsTitle}>
126
- Posts ({posts?.length || 0})
127
- </ThemedText>
128
-
129
- {postsLoading ? (
130
- <LoadingSpinner message='Loading posts...' />
131
- ) : postsError ? (
132
- <ErrorView message={postsError} onRetry={refetchPosts} />
133
- ) : posts && posts.length > 0 ? (
134
- <FlatList
135
- data={posts}
136
- renderItem={renderPost}
137
- keyExtractor={(item) => item.id.toString()}
138
- scrollEnabled={false}
139
- showsVerticalScrollIndicator={false}
140
- />
141
- ) : (
142
- <ThemedText style={styles.noPosts}>No posts yet</ThemedText>
143
- )}
144
- </ThemedView>
145
- </ScrollView>
146
- </ThemedView>
147
- )
148
- }
149
-
150
- const styles = StyleSheet.create({
151
- container: {
152
- flex: 1
153
- },
154
- header: {
155
- flexDirection: 'row',
156
- alignItems: 'center',
157
- padding: 20,
158
- paddingTop: 60,
159
- paddingBottom: 10
160
- },
161
- backButton: {
162
- marginRight: 16
163
- },
164
- headerTitle: {
165
- flex: 1
166
- },
167
- content: {
168
- flex: 1,
169
- padding: 20
170
- },
171
- userCard: {
172
- padding: 20,
173
- marginBottom: 20,
174
- borderRadius: 12,
175
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
176
- borderWidth: 1,
177
- borderColor: 'rgba(255, 255, 255, 0.2)',
178
- alignItems: 'center'
179
- },
180
- userAvatar: {
181
- width: 80,
182
- height: 80,
183
- borderRadius: 40,
184
- backgroundColor: 'rgba(255, 255, 255, 0.2)',
185
- justifyContent: 'center',
186
- alignItems: 'center',
187
- marginBottom: 16
188
- },
189
- userName: {
190
- marginBottom: 4,
191
- textAlign: 'center'
192
- },
193
- userUsername: {
194
- fontSize: 16,
195
- opacity: 0.7,
196
- marginBottom: 20
197
- },
198
- userDetails: {
199
- width: '100%',
200
- marginBottom: 20
201
- },
202
- detailRow: {
203
- flexDirection: 'row',
204
- alignItems: 'center',
205
- marginBottom: 8
206
- },
207
- detailText: {
208
- marginLeft: 8,
209
- flex: 1
210
- },
211
- companySection: {
212
- width: '100%',
213
- marginBottom: 16,
214
- padding: 16,
215
- borderRadius: 8,
216
- backgroundColor: 'rgba(255, 255, 255, 0.05)'
217
- },
218
- sectionTitle: {
219
- marginBottom: 8
220
- },
221
- companyName: {
222
- marginBottom: 4,
223
- fontWeight: '600'
224
- },
225
- companyCatchPhrase: {
226
- fontSize: 14,
227
- opacity: 0.8,
228
- fontStyle: 'italic'
229
- },
230
- addressSection: {
231
- width: '100%',
232
- padding: 16,
233
- borderRadius: 8,
234
- backgroundColor: 'rgba(255, 255, 255, 0.05)'
235
- },
236
- addressText: {
237
- marginBottom: 2
238
- },
239
- postsSection: {
240
- marginBottom: 20
241
- },
242
- postsTitle: {
243
- marginBottom: 16
244
- },
245
- postCard: {
246
- padding: 16,
247
- marginBottom: 12,
248
- borderRadius: 8,
249
- backgroundColor: 'rgba(255, 255, 255, 0.05)',
250
- borderWidth: 1,
251
- borderColor: 'rgba(255, 255, 255, 0.1)'
252
- },
253
- postTitle: {
254
- marginBottom: 8
255
- },
256
- postBody: {
257
- marginBottom: 8,
258
- lineHeight: 18
259
- },
260
- postMeta: {
261
- fontSize: 12,
262
- opacity: 0.6
263
- },
264
- noPosts: {
265
- textAlign: 'center',
266
- opacity: 0.6,
267
- fontStyle: 'italic',
268
- padding: 20
269
- }
270
- })
@@ -1,42 +0,0 @@
1
- {
2
- "expo": {
3
- "name": "sample-expo-app",
4
- "slug": "sample-expo-app",
5
- "version": "1.0.0",
6
- "orientation": "portrait",
7
- "icon": "./assets/images/icon.png",
8
- "scheme": "sampleexpoapp",
9
- "userInterfaceStyle": "automatic",
10
- "newArchEnabled": true,
11
- "ios": {
12
- "supportsTablet": true
13
- },
14
- "android": {
15
- "adaptiveIcon": {
16
- "foregroundImage": "./assets/images/adaptive-icon.png",
17
- "backgroundColor": "#ffffff"
18
- },
19
- "edgeToEdgeEnabled": true
20
- },
21
- "web": {
22
- "bundler": "metro",
23
- "output": "static",
24
- "favicon": "./assets/images/favicon.png"
25
- },
26
- "plugins": [
27
- "expo-router",
28
- [
29
- "expo-splash-screen",
30
- {
31
- "image": "./assets/images/splash-icon.png",
32
- "imageWidth": 200,
33
- "resizeMode": "contain",
34
- "backgroundColor": "#ffffff"
35
- }
36
- ]
37
- ],
38
- "experiments": {
39
- "typedRoutes": true
40
- }
41
- }
42
- }
@@ -1,45 +0,0 @@
1
- import { PropsWithChildren, useState } from 'react';
2
- import { StyleSheet, TouchableOpacity } from 'react-native';
3
-
4
- import { ThemedText } from '@/components/ThemedText';
5
- import { ThemedView } from '@/components/ThemedView';
6
- import { IconSymbol } from '@/components/ui/IconSymbol';
7
- import { Colors } from '@/constants/Colors';
8
- import { useColorScheme } from '@/hooks/useColorScheme';
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
- });
@@ -1,52 +0,0 @@
1
- import React from 'react'
2
- import { StyleSheet, TouchableOpacity } from 'react-native'
3
- import { ThemedView } from './ThemedView'
4
- import { ThemedText } from './ThemedText'
5
- import { IconSymbol } from './ui/IconSymbol'
6
- import { Colors } from '@/constants/Colors'
7
- import { useColorScheme } from '@/hooks/useColorScheme'
8
-
9
- interface ErrorViewProps {
10
- message: string
11
- onRetry?: () => void
12
- }
13
-
14
- export function ErrorView({ message, onRetry }: ErrorViewProps) {
15
- const colorScheme = useColorScheme()
16
-
17
- return (
18
- <ThemedView style={styles.container}>
19
- <IconSymbol name='exclamationmark.triangle.fill' size={48} color={Colors[colorScheme ?? 'light'].tint} />
20
- <ThemedText style={styles.message}>{message}</ThemedText>
21
- {onRetry && (
22
- <TouchableOpacity style={styles.retryButton} onPress={onRetry}>
23
- <ThemedText style={styles.retryText}>Try Again</ThemedText>
24
- </TouchableOpacity>
25
- )}
26
- </ThemedView>
27
- )
28
- }
29
-
30
- const styles = StyleSheet.create({
31
- container: {
32
- flex: 1,
33
- justifyContent: 'center',
34
- alignItems: 'center',
35
- padding: 20
36
- },
37
- message: {
38
- marginTop: 16,
39
- textAlign: 'center',
40
- marginBottom: 20
41
- },
42
- retryButton: {
43
- backgroundColor: Colors.light.tint,
44
- paddingHorizontal: 20,
45
- paddingVertical: 10,
46
- borderRadius: 8
47
- },
48
- retryText: {
49
- color: 'white',
50
- fontWeight: '600'
51
- }
52
- })
@@ -1,24 +0,0 @@
1
- import { Href, Link } from 'expo-router';
2
- import { openBrowserAsync } from 'expo-web-browser';
3
- import { type ComponentProps } from 'react';
4
- import { Platform } from 'react-native';
5
-
6
- type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & string };
7
-
8
- export function ExternalLink({ href, ...rest }: Props) {
9
- return (
10
- <Link
11
- target="_blank"
12
- {...rest}
13
- href={href}
14
- onPress={async (event) => {
15
- if (Platform.OS !== 'web') {
16
- // Prevent the default behavior of linking to the default browser on native.
17
- event.preventDefault();
18
- // Open the link in an in-app browser.
19
- await openBrowserAsync(href);
20
- }
21
- }}
22
- />
23
- );
24
- }
@@ -1,18 +0,0 @@
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
- }