@legendapp/list 2.0.0-next.1 → 2.0.0-next.2

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 (260) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.cursor/rules/changelog.mdc +60 -0
  3. package/.github/FUNDING.yml +15 -0
  4. package/.gitignore +5 -0
  5. package/.prettierrc.json +5 -0
  6. package/.vscode/settings.json +14 -0
  7. package/CLAUDE.md +126 -0
  8. package/biome.json +46 -0
  9. package/bun.lock +1289 -0
  10. package/bunfig.toml +2 -0
  11. package/dist/CHANGELOG.md +119 -0
  12. package/dist/LICENSE +21 -0
  13. package/dist/README.md +139 -0
  14. package/{animated.d.mts → dist/animated.d.mts} +1 -1
  15. package/{animated.d.ts → dist/animated.d.ts} +1 -1
  16. package/{index.d.mts → dist/index.d.mts} +16 -10
  17. package/{index.d.ts → dist/index.d.ts} +16 -10
  18. package/{index.js → dist/index.js} +52 -32
  19. package/{index.mjs → dist/index.mjs} +52 -32
  20. package/{keyboard-controller.d.mts → dist/keyboard-controller.d.mts} +4 -4
  21. package/{keyboard-controller.d.ts → dist/keyboard-controller.d.ts} +4 -4
  22. package/dist/package.json +35 -0
  23. package/example/README.md +40 -0
  24. package/example/api/data/genres.json +23 -0
  25. package/example/api/data/playlist/10402-10749.json +1 -0
  26. package/example/api/data/playlist/10402-10770.json +1 -0
  27. package/example/api/data/playlist/10402-37.json +1 -0
  28. package/example/api/data/playlist/10749-10752.json +1 -0
  29. package/example/api/data/playlist/10749-10770.json +1 -0
  30. package/example/api/data/playlist/10749-37.json +1 -0
  31. package/example/api/data/playlist/10749-878.json +1 -0
  32. package/example/api/data/playlist/10751-10402.json +1 -0
  33. package/example/api/data/playlist/10751-10752.json +1 -0
  34. package/example/api/data/playlist/10751-37.json +1 -0
  35. package/example/api/data/playlist/10751-53.json +1 -0
  36. package/example/api/data/playlist/10751-878.json +1 -0
  37. package/example/api/data/playlist/10751-9648.json +1 -0
  38. package/example/api/data/playlist/10752-37.json +1 -0
  39. package/example/api/data/playlist/12-10402.json +1 -0
  40. package/example/api/data/playlist/12-10749.json +1 -0
  41. package/example/api/data/playlist/12-18.json +1 -0
  42. package/example/api/data/playlist/12-27.json +1 -0
  43. package/example/api/data/playlist/12-35.json +1 -0
  44. package/example/api/data/playlist/14-36.json +1 -0
  45. package/example/api/data/playlist/14-878.json +1 -0
  46. package/example/api/data/playlist/16-10751.json +1 -0
  47. package/example/api/data/playlist/16-10770.json +1 -0
  48. package/example/api/data/playlist/16-35.json +1 -0
  49. package/example/api/data/playlist/16-36.json +1 -0
  50. package/example/api/data/playlist/16-53.json +1 -0
  51. package/example/api/data/playlist/18-10751.json +1 -0
  52. package/example/api/data/playlist/18-10752.json +1 -0
  53. package/example/api/data/playlist/18-37.json +1 -0
  54. package/example/api/data/playlist/18-53.json +1 -0
  55. package/example/api/data/playlist/18-878.json +1 -0
  56. package/example/api/data/playlist/27-10749.json +1 -0
  57. package/example/api/data/playlist/27-10770.json +1 -0
  58. package/example/api/data/playlist/28-10749.json +1 -0
  59. package/example/api/data/playlist/28-10751.json +1 -0
  60. package/example/api/data/playlist/28-10770.json +1 -0
  61. package/example/api/data/playlist/28-16.json +1 -0
  62. package/example/api/data/playlist/28-18.json +1 -0
  63. package/example/api/data/playlist/28-36.json +1 -0
  64. package/example/api/data/playlist/28-37.json +1 -0
  65. package/example/api/data/playlist/28-53.json +1 -0
  66. package/example/api/data/playlist/28-80.json +1 -0
  67. package/example/api/data/playlist/28-99.json +1 -0
  68. package/example/api/data/playlist/35-10749.json +1 -0
  69. package/example/api/data/playlist/35-10751.json +1 -0
  70. package/example/api/data/playlist/35-10752.json +1 -0
  71. package/example/api/data/playlist/35-27.json +1 -0
  72. package/example/api/data/playlist/35-36.json +1 -0
  73. package/example/api/data/playlist/35-53.json +1 -0
  74. package/example/api/data/playlist/35-80.json +1 -0
  75. package/example/api/data/playlist/36-37.json +1 -0
  76. package/example/api/data/playlist/36-878.json +1 -0
  77. package/example/api/data/playlist/36-9648.json +1 -0
  78. package/example/api/data/playlist/53-10752.json +1 -0
  79. package/example/api/data/playlist/80-10770.json +1 -0
  80. package/example/api/data/playlist/80-14.json +1 -0
  81. package/example/api/data/playlist/80-18.json +1 -0
  82. package/example/api/data/playlist/80-37.json +1 -0
  83. package/example/api/data/playlist/878-37.json +1 -0
  84. package/example/api/data/playlist/9648-10770.json +1 -0
  85. package/example/api/data/playlist/9648-37.json +1 -0
  86. package/example/api/data/playlist/9648-53.json +1 -0
  87. package/example/api/data/playlist/9648-878.json +1 -0
  88. package/example/api/data/playlist/99-10749.json +1 -0
  89. package/example/api/data/playlist/99-14.json +1 -0
  90. package/example/api/data/playlist/99-18.json +1 -0
  91. package/example/api/data/playlist/99-27.json +1 -0
  92. package/example/api/data/playlist/99-53.json +1 -0
  93. package/example/api/data/playlist/99-9648.json +1 -0
  94. package/example/api/data/playlist/index.ts +73 -0
  95. package/example/api/data/rows.json +1 -0
  96. package/example/api/index.ts +36 -0
  97. package/example/app/(tabs)/_layout.tsx +60 -0
  98. package/example/app/(tabs)/cards.tsx +81 -0
  99. package/example/app/(tabs)/index.tsx +205 -0
  100. package/example/app/(tabs)/moviesL.tsx +7 -0
  101. package/example/app/(tabs)/moviesLR.tsx +7 -0
  102. package/example/app/+not-found.tsx +32 -0
  103. package/example/app/_layout.tsx +34 -0
  104. package/example/app/accurate-scrollto/index.tsx +125 -0
  105. package/example/app/accurate-scrollto-2/index.tsx +52 -0
  106. package/example/app/accurate-scrollto-huge/index.tsx +128 -0
  107. package/example/app/add-to-end/index.tsx +82 -0
  108. package/example/app/ai-chat/index.tsx +236 -0
  109. package/example/app/bidirectional-infinite-list/index.tsx +133 -0
  110. package/example/app/cards-columns/index.tsx +37 -0
  111. package/example/app/cards-flashlist/index.tsx +122 -0
  112. package/example/app/cards-flatlist/index.tsx +94 -0
  113. package/example/app/cards-no-recycle/index.tsx +110 -0
  114. package/example/app/cards-renderItem.tsx +354 -0
  115. package/example/app/chat-example/index.tsx +167 -0
  116. package/example/app/chat-infinite/index.tsx +239 -0
  117. package/example/app/chat-keyboard/index.tsx +248 -0
  118. package/example/app/chat-resize-outer/index.tsx +247 -0
  119. package/example/app/columns/index.tsx +78 -0
  120. package/example/app/countries/index.tsx +182 -0
  121. package/example/app/countries-flashlist/index.tsx +163 -0
  122. package/example/app/countries-reorder/index.tsx +187 -0
  123. package/example/app/extra-data/index.tsx +86 -0
  124. package/example/app/filter-elements/filter-data-provider.tsx +55 -0
  125. package/example/app/filter-elements/index.tsx +118 -0
  126. package/example/app/initial-scroll-index/index.tsx +106 -0
  127. package/example/app/initial-scroll-index/renderFixedItem.tsx +215 -0
  128. package/example/app/initial-scroll-index-free-height/index.tsx +70 -0
  129. package/example/app/initial-scroll-index-keyed/index.tsx +62 -0
  130. package/example/app/lazy-list/index.tsx +123 -0
  131. package/example/app/movies-flashlist/index.tsx +7 -0
  132. package/example/app/mutable-cells/index.tsx +104 -0
  133. package/example/app/video-feed/index.tsx +119 -0
  134. package/example/app.config.js +22 -0
  135. package/example/app.json +45 -0
  136. package/example/assets/fonts/SpaceMono-Regular.ttf +0 -0
  137. package/example/assets/images/adaptive-icon.png +0 -0
  138. package/example/assets/images/favicon.png +0 -0
  139. package/example/assets/images/icon.png +0 -0
  140. package/example/assets/images/partial-react-logo.png +0 -0
  141. package/example/assets/images/react-logo.png +0 -0
  142. package/example/assets/images/react-logo@2x.png +0 -0
  143. package/example/assets/images/react-logo@3x.png +0 -0
  144. package/example/assets/images/splash-icon.png +0 -0
  145. package/example/autoscroll.sh +101 -0
  146. package/example/bun.lock +2266 -0
  147. package/example/bunfig.toml +2 -0
  148. package/example/components/Breathe.tsx +54 -0
  149. package/example/components/Circle.tsx +69 -0
  150. package/example/components/Collapsible.tsx +44 -0
  151. package/example/components/ExternalLink.tsx +24 -0
  152. package/example/components/HapticTab.tsx +18 -0
  153. package/example/components/HelloWave.tsx +37 -0
  154. package/example/components/Movies.tsx +179 -0
  155. package/example/components/ParallaxScrollView.tsx +81 -0
  156. package/example/components/ThemedText.tsx +60 -0
  157. package/example/components/ThemedView.tsx +14 -0
  158. package/example/components/__tests__/ThemedText-test.tsx +10 -0
  159. package/example/components/__tests__/__snapshots__/ThemedText-test.tsx.snap +24 -0
  160. package/example/components/ui/IconSymbol.ios.tsx +32 -0
  161. package/example/components/ui/IconSymbol.tsx +43 -0
  162. package/example/components/ui/TabBarBackground.ios.tsx +22 -0
  163. package/example/components/ui/TabBarBackground.tsx +6 -0
  164. package/example/constants/Colors.ts +26 -0
  165. package/example/constants/constants.ts +5 -0
  166. package/example/constants/useScrollTest.ts +19 -0
  167. package/example/hooks/useColorScheme.ts +1 -0
  168. package/example/hooks/useColorScheme.web.ts +8 -0
  169. package/example/hooks/useThemeColor.ts +22 -0
  170. package/example/ios/.xcode.env +11 -0
  171. package/example/ios/Podfile +64 -0
  172. package/example/ios/Podfile.lock +2767 -0
  173. package/example/ios/Podfile.properties.json +5 -0
  174. package/example/ios/listtest/AppDelegate.swift +70 -0
  175. package/example/ios/listtest/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
  176. package/example/ios/listtest/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
  177. package/example/ios/listtest/Images.xcassets/Contents.json +6 -0
  178. package/example/ios/listtest/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
  179. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/Contents.json +23 -0
  180. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image.png +0 -0
  181. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image@2x.png +0 -0
  182. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image@3x.png +0 -0
  183. package/example/ios/listtest/Info.plist +85 -0
  184. package/example/ios/listtest/PrivacyInfo.xcprivacy +48 -0
  185. package/example/ios/listtest/SplashScreen.storyboard +42 -0
  186. package/example/ios/listtest/Supporting/Expo.plist +12 -0
  187. package/example/ios/listtest/listtest-Bridging-Header.h +3 -0
  188. package/example/ios/listtest/listtest.entitlements +5 -0
  189. package/example/ios/listtest.xcodeproj/project.pbxproj +547 -0
  190. package/example/ios/listtest.xcodeproj/xcshareddata/xcschemes/listtest.xcscheme +88 -0
  191. package/example/ios/listtest.xcworkspace/contents.xcworkspacedata +10 -0
  192. package/example/metro.config.js +16 -0
  193. package/example/package.json +73 -0
  194. package/example/scripts/reset-project.js +84 -0
  195. package/example/tsconfig.json +26 -0
  196. package/package.json +88 -34
  197. package/posttsup.ts +24 -0
  198. package/src/Container.tsx +176 -0
  199. package/src/Containers.tsx +85 -0
  200. package/src/ContextContainer.ts +145 -0
  201. package/src/DebugView.tsx +83 -0
  202. package/src/LazyLegendList.tsx +41 -0
  203. package/src/LeanView.tsx +18 -0
  204. package/src/LegendList.tsx +558 -0
  205. package/src/ListComponent.tsx +191 -0
  206. package/src/ScrollAdjust.tsx +24 -0
  207. package/src/ScrollAdjustHandler.ts +26 -0
  208. package/src/Separator.tsx +14 -0
  209. package/src/animated.tsx +6 -0
  210. package/src/calculateItemsInView.ts +363 -0
  211. package/src/calculateOffsetForIndex.ts +23 -0
  212. package/src/calculateOffsetWithOffsetPosition.ts +26 -0
  213. package/src/checkAllSizesKnown.ts +17 -0
  214. package/src/checkAtBottom.ts +36 -0
  215. package/src/checkAtTop.ts +27 -0
  216. package/src/checkThreshold.ts +30 -0
  217. package/src/constants.ts +11 -0
  218. package/src/createColumnWrapperStyle.ts +16 -0
  219. package/src/doInitialAllocateContainers.ts +40 -0
  220. package/src/doMaintainScrollAtEnd.ts +34 -0
  221. package/src/findAvailableContainers.ts +98 -0
  222. package/src/finishScrollTo.ts +8 -0
  223. package/src/getId.ts +21 -0
  224. package/src/getItemSize.ts +52 -0
  225. package/src/getRenderedItem.ts +34 -0
  226. package/src/getScrollVelocity.ts +47 -0
  227. package/src/handleLayout.ts +70 -0
  228. package/src/helpers.ts +39 -0
  229. package/src/index.ts +11 -0
  230. package/src/keyboard-controller.tsx +63 -0
  231. package/src/onScroll.ts +66 -0
  232. package/src/prepareMVCP.ts +50 -0
  233. package/src/reanimated.tsx +63 -0
  234. package/src/requestAdjust.ts +41 -0
  235. package/src/scrollTo.ts +40 -0
  236. package/src/scrollToIndex.ts +34 -0
  237. package/src/setDidLayout.ts +25 -0
  238. package/src/setPaddingTop.ts +28 -0
  239. package/src/state.tsx +304 -0
  240. package/src/types.ts +610 -0
  241. package/src/updateAlignItemsPaddingTop.ts +18 -0
  242. package/src/updateAllPositions.ts +130 -0
  243. package/src/updateItemSize.ts +203 -0
  244. package/src/updateTotalSize.ts +44 -0
  245. package/src/useAnimatedValue.ts +6 -0
  246. package/src/useCombinedRef.ts +22 -0
  247. package/src/useInit.ts +17 -0
  248. package/src/useSyncLayout.tsx +68 -0
  249. package/src/useValue$.ts +53 -0
  250. package/src/viewability.ts +279 -0
  251. package/tsconfig.json +59 -0
  252. package/tsup.config.ts +21 -0
  253. /package/{animated.js → dist/animated.js} +0 -0
  254. /package/{animated.mjs → dist/animated.mjs} +0 -0
  255. /package/{keyboard-controller.js → dist/keyboard-controller.js} +0 -0
  256. /package/{keyboard-controller.mjs → dist/keyboard-controller.mjs} +0 -0
  257. /package/{reanimated.d.mts → dist/reanimated.d.mts} +0 -0
  258. /package/{reanimated.d.ts → dist/reanimated.d.ts} +0 -0
  259. /package/{reanimated.js → dist/reanimated.js} +0 -0
  260. /package/{reanimated.mjs → dist/reanimated.mjs} +0 -0
@@ -0,0 +1,236 @@
1
+ import { LegendList } from "@legendapp/list";
2
+ import { useHeaderHeight } from "@react-navigation/elements";
3
+ import { useEffect, useState } from "react";
4
+ import { Dimensions, KeyboardAvoidingView, Platform, StyleSheet, Text, View } from "react-native";
5
+ import { SafeAreaView } from "react-native-safe-area-context";
6
+
7
+ type Message = {
8
+ id: string;
9
+ text: string;
10
+ sender: "user" | "system";
11
+ timeStamp: number;
12
+ isPlaceholder?: boolean;
13
+ };
14
+
15
+ let idCounter = 0;
16
+
17
+ const AIChat = () => {
18
+ const [messages, setMessages] = useState<Message[]>([]);
19
+ const headerHeight = Platform.OS === "ios" ? useHeaderHeight() : 80;
20
+ const screenHeight = Dimensions.get("window").height;
21
+ const availableHeight = screenHeight - headerHeight; // Subtract header and some padding
22
+
23
+ useEffect(() => {
24
+ // After 1 second, add user message and system placeholder
25
+ const timer1 = setTimeout(() => {
26
+ setMessages([
27
+ {
28
+ id: String(idCounter++),
29
+ text: "Hey, can you help me understand how React Native virtualization works?",
30
+ sender: "user",
31
+ timeStamp: Date.now(),
32
+ },
33
+ {
34
+ id: String(idCounter++),
35
+ text: "",
36
+ sender: "system",
37
+ timeStamp: Date.now(),
38
+ isPlaceholder: true,
39
+ },
40
+ ]);
41
+ }, 1000);
42
+
43
+ // After 3 seconds total (2 seconds after the first), replace placeholder with long message
44
+ const timer2 = setTimeout(() => {
45
+ setMessages((prevMessages) =>
46
+ prevMessages.map((msg) =>
47
+ msg.isPlaceholder
48
+ ? {
49
+ id: String(idCounter++),
50
+ sender: "system",
51
+ timeStamp: Date.now(),
52
+ text: `React Native virtualization is a performance optimization technique that's crucial for handling large lists efficiently. Here's how it works:
53
+
54
+ 1. **Rendering Only Visible Items**: Instead of rendering all items in a list at once, virtualization only renders the items that are currently visible on screen, plus a small buffer of items just outside the visible area.
55
+
56
+ 2. **Dynamic Item Creation/Destruction**: As you scroll, items that move out of view are removed from the DOM/native view hierarchy, and new items that come into view are created. This keeps memory usage constant regardless of list size.
57
+
58
+ 3. **View Recycling**: Advanced virtualization systems reuse view components rather than creating new ones, which reduces garbage collection and improves performance.
59
+
60
+ 4. **Estimated vs Actual Sizing**: The system uses estimated item sizes to calculate scroll positions and total content size, then adjusts as actual sizes are measured.
61
+
62
+ 5. **Legend List Implementation**: Legend List enhances this by providing better handling of dynamic item sizes, bidirectional scrolling, and maintains scroll position more accurately than FlatList.
63
+
64
+ The key benefits are:
65
+ - Constant memory usage regardless of data size
66
+ - Smooth scrolling performance
67
+ - Better handling of dynamic content
68
+ - Reduced time to interactive
69
+
70
+ This makes it possible to scroll through thousands of items without performance degradation, which is essential for modern mobile apps dealing with large datasets like social media feeds, chat histories, or product catalogs.`,
71
+ isPlaceholder: false,
72
+ }
73
+ : msg,
74
+ ),
75
+ );
76
+ }, 3000);
77
+
78
+ return () => {
79
+ clearTimeout(timer1);
80
+ clearTimeout(timer2);
81
+ };
82
+ }, []);
83
+
84
+ return (
85
+ <SafeAreaView style={styles.container} edges={["bottom"]}>
86
+ <KeyboardAvoidingView
87
+ style={styles.container}
88
+ behavior="padding"
89
+ keyboardVerticalOffset={headerHeight}
90
+ contentContainerStyle={{ flex: 1 }}
91
+ >
92
+ <LegendList
93
+ data={messages}
94
+ contentContainerStyle={styles.contentContainer}
95
+ keyExtractor={(item) => item.id}
96
+ estimatedItemSize={60}
97
+ maintainVisibleContentPosition
98
+ maintainScrollAtEnd
99
+ alignItemsAtEnd
100
+ renderItem={({ item }) => (
101
+ <>
102
+ {item.isPlaceholder ? (
103
+ <View
104
+ style={[
105
+ styles.systemMessageContainer,
106
+ styles.systemStyle,
107
+ { minHeight: availableHeight * 0.9 }, // Take up most of available space
108
+ ]}
109
+ >
110
+ <View style={[styles.placeholderContainer, styles.messageContainer]}>
111
+ <View style={styles.typingIndicator}>
112
+ <View style={[styles.dot, styles.dot1]} />
113
+ <View style={[styles.dot, styles.dot2]} />
114
+ <View style={[styles.dot, styles.dot3]} />
115
+ </View>
116
+ <Text style={styles.placeholderText}>AI is thinking...</Text>
117
+ </View>
118
+ </View>
119
+ ) : (
120
+ <View
121
+ style={[
122
+ styles.messageContainer,
123
+ item.sender === "system"
124
+ ? styles.systemMessageContainer
125
+ : styles.userMessageContainer,
126
+ item.sender === "system" ? styles.systemStyle : styles.userStyle,
127
+ ]}
128
+ >
129
+ <Text
130
+ style={[styles.messageText, item.sender === "user" && styles.userMessageText]}
131
+ >
132
+ {item.text}
133
+ </Text>
134
+ <View
135
+ style={[
136
+ styles.timeStamp,
137
+ item.sender === "system" ? styles.systemStyle : styles.userStyle,
138
+ ]}
139
+ >
140
+ <Text style={styles.timeStampText}>
141
+ {new Date(item.timeStamp).toLocaleTimeString()}
142
+ </Text>
143
+ </View>
144
+ </View>
145
+ )}
146
+ </>
147
+ )}
148
+ />
149
+ </KeyboardAvoidingView>
150
+ </SafeAreaView>
151
+ );
152
+ };
153
+
154
+ const styles = StyleSheet.create({
155
+ container: {
156
+ flex: 1,
157
+ backgroundColor: "#fff",
158
+ },
159
+ contentContainer: {
160
+ paddingHorizontal: 16,
161
+ },
162
+ messageContainer: {
163
+ padding: 16,
164
+ borderRadius: 16,
165
+ marginVertical: 4,
166
+ },
167
+ messageText: {
168
+ fontSize: 16,
169
+ lineHeight: 22,
170
+ },
171
+ userMessageText: {
172
+ color: "white",
173
+ },
174
+ systemMessageContainer: {},
175
+ userMessageContainer: {
176
+ backgroundColor: "#007AFF",
177
+ },
178
+ systemStyle: {
179
+ maxWidth: "85%",
180
+ alignSelf: "flex-start",
181
+ },
182
+ userStyle: {
183
+ maxWidth: "75%",
184
+ alignSelf: "flex-end",
185
+ alignItems: "flex-end",
186
+ },
187
+ timeStamp: {
188
+ marginVertical: 5,
189
+ },
190
+ timeStampText: {
191
+ fontSize: 12,
192
+ color: "#888",
193
+ },
194
+ placeholderContainer: {
195
+ backgroundColor: "#f8f9fa",
196
+ borderWidth: 1,
197
+ borderColor: "#e9ecef",
198
+ },
199
+ typingIndicator: {
200
+ flexDirection: "row",
201
+ alignItems: "center",
202
+ marginBottom: 12,
203
+ },
204
+ dot: {
205
+ width: 8,
206
+ height: 8,
207
+ borderRadius: 4,
208
+ backgroundColor: "#007AFF",
209
+ marginHorizontal: 2,
210
+ },
211
+ dot1: {
212
+ animationName: "typing",
213
+ animationDuration: "1.4s",
214
+ animationIterationCount: "infinite",
215
+ animationDelay: "0s",
216
+ },
217
+ dot2: {
218
+ animationName: "typing",
219
+ animationDuration: "1.4s",
220
+ animationIterationCount: "infinite",
221
+ animationDelay: "0.2s",
222
+ },
223
+ dot3: {
224
+ animationName: "typing",
225
+ animationDuration: "1.4s",
226
+ animationIterationCount: "infinite",
227
+ animationDelay: "0.4s",
228
+ },
229
+ placeholderText: {
230
+ fontSize: 14,
231
+ color: "#666",
232
+ fontStyle: "italic",
233
+ },
234
+ });
235
+
236
+ export default AIChat;
@@ -0,0 +1,133 @@
1
+ import { type Item, renderItem } from "@/app/cards-renderItem";
2
+ import { DRAW_DISTANCE, ESTIMATED_ITEM_LENGTH } from "@/constants/constants";
3
+ import { LegendList, type LegendListRef } from "@legendapp/list";
4
+ import { useRef, useState } from "react";
5
+ import { RefreshControl, StyleSheet, View } from "react-native";
6
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
7
+
8
+ let last = performance.now();
9
+
10
+ export default function BidirectionalInfiniteList() {
11
+ const listRef = useRef<LegendListRef>(null);
12
+
13
+ const [data, setData] = useState<Item[]>(
14
+ () =>
15
+ Array.from({ length: 20 }, (_, i) => ({
16
+ id: i.toString(),
17
+ })) as any[],
18
+ );
19
+
20
+ const [refreshing, setRefreshing] = useState(false);
21
+
22
+ const onRefresh = () => {
23
+ console.log("onRefresh");
24
+ setRefreshing(true);
25
+ setTimeout(() => {
26
+ setData((prevData) => {
27
+ const initialIndex = Number.parseInt(prevData[0].id);
28
+ const newData = [
29
+ ...Array.from({ length: 5 }, (_, i) => ({
30
+ id: (initialIndex - i - 1).toString(),
31
+ })).reverse(),
32
+ ...prevData,
33
+ ];
34
+ return newData;
35
+ });
36
+ setRefreshing(false);
37
+ }, 500);
38
+ };
39
+
40
+ // useEffect(() => {
41
+ // setTimeout(() => {
42
+ // setData((prevData) => {
43
+ // const initialIndex = Number.parseInt(prevData[0].id);
44
+ // const newData = [
45
+ // ...Array.from({ length: 1 }, (_, i) => ({
46
+ // id: (initialIndex - i - 1).toString(),
47
+ // })).reverse(),
48
+ // ...prevData,
49
+ // ];
50
+ // return newData;
51
+ // });
52
+ // }, 2000);
53
+ // }, []);
54
+
55
+ const { bottom } = useSafeAreaInsets();
56
+
57
+ return (
58
+ <View style={[StyleSheet.absoluteFill, styles.outerContainer]} key="legendlist">
59
+ <LegendList
60
+ refreshControl={
61
+ <RefreshControl
62
+ refreshing={refreshing}
63
+ //onRefresh={onRefresh}
64
+ tintColor={"#ffffff"}
65
+ progressViewOffset={40}
66
+ />
67
+ }
68
+ ref={listRef}
69
+ initialScrollIndex={10}
70
+ style={[StyleSheet.absoluteFill, styles.scrollContainer]}
71
+ contentContainerStyle={styles.listContainer}
72
+ data={data}
73
+ renderItem={renderItem}
74
+ keyExtractor={(item) => `id${item.id}`}
75
+ estimatedItemSize={ESTIMATED_ITEM_LENGTH}
76
+ drawDistance={DRAW_DISTANCE}
77
+ maintainVisibleContentPosition
78
+ recycleItems={true}
79
+ ListFooterComponent={<View style={{ height: bottom }} />}
80
+ onStartReached={(props) => {
81
+ const time = performance.now();
82
+ console.log("onStartReached", props, last - time);
83
+ last = time;
84
+ onRefresh();
85
+ }}
86
+ onEndReached={({ distanceFromEnd }) => {
87
+ console.log("onEndReached", distanceFromEnd);
88
+ if (distanceFromEnd > 0) {
89
+ setTimeout(() => {
90
+ setData((prevData) => {
91
+ const newData = [
92
+ ...prevData,
93
+ ...Array.from({ length: 10 }, (_, i) => ({
94
+ id: (Number.parseInt(prevData[prevData.length - 1].id) + i + 1).toString(),
95
+ })),
96
+ ];
97
+ return newData;
98
+ });
99
+ }, 500);
100
+ }
101
+ }}
102
+ />
103
+ </View>
104
+ );
105
+ }
106
+
107
+ const styles = StyleSheet.create({
108
+ listHeader: {
109
+ alignSelf: "center",
110
+ height: 100,
111
+ width: 100,
112
+ backgroundColor: "#456AAA",
113
+ borderRadius: 12,
114
+ marginHorizontal: 8,
115
+ marginVertical: 8,
116
+ },
117
+ listEmpty: {
118
+ flex: 1,
119
+ justifyContent: "center",
120
+ alignItems: "center",
121
+ backgroundColor: "#6789AB",
122
+ paddingVertical: 16,
123
+ },
124
+ outerContainer: {
125
+ backgroundColor: "#456",
126
+ },
127
+ scrollContainer: {},
128
+ listContainer: {
129
+ width: 360,
130
+ maxWidth: "100%",
131
+ marginHorizontal: "auto",
132
+ },
133
+ });
@@ -0,0 +1,37 @@
1
+ import Cards from "@/app/(tabs)/cards";
2
+ import { LogBox, Platform, StyleSheet } from "react-native";
3
+
4
+ LogBox.ignoreLogs(["Open debugger"]);
5
+
6
+ export default function CardsColumns() {
7
+ return <Cards numColumns={2} />;
8
+ }
9
+
10
+ const styles = StyleSheet.create({
11
+ listHeader: {
12
+ alignSelf: "center",
13
+ height: 100,
14
+ width: 100,
15
+ backgroundColor: "#456AAA",
16
+ borderRadius: 12,
17
+ marginHorizontal: 8,
18
+ marginVertical: 8,
19
+ },
20
+ listEmpty: {
21
+ flex: 1,
22
+ justifyContent: "center",
23
+ alignItems: "center",
24
+ backgroundColor: "#6789AB",
25
+ paddingVertical: 16,
26
+ },
27
+ outerContainer: {
28
+ backgroundColor: "#456",
29
+ bottom: Platform.OS === "ios" ? 82 : 0,
30
+ },
31
+ scrollContainer: {},
32
+ listContainer: {
33
+ width: 400,
34
+ maxWidth: "100%",
35
+ marginHorizontal: "auto",
36
+ },
37
+ });
@@ -0,0 +1,122 @@
1
+ import renderItem from "@/app/cards-renderItem";
2
+ import { DO_SCROLL_TEST, DRAW_DISTANCE, ESTIMATED_ITEM_LENGTH, RECYCLE_ITEMS } from "@/constants/constants";
3
+ import { useScrollTest } from "@/constants/useScrollTest";
4
+ import { FlashList, type ListRenderItemInfo } from "@shopify/flash-list";
5
+ import { Fragment, useRef } from "react";
6
+ import { StyleSheet, View } from "react-native";
7
+
8
+ export default function HomeScreen() {
9
+ const data = Array.from({ length: 1000 }, (_, i) => ({ id: i.toString() }));
10
+
11
+ const scrollRef = useRef<FlashList<any>>(null);
12
+
13
+ // useEffect(() => {
14
+ // let amtPerInterval = 4;
15
+ // let index = amtPerInterval;
16
+ // const interval = setInterval(() => {
17
+ // scrollRef.current?.scrollToIndex({
18
+ // index,
19
+ // });
20
+ // index += amtPerInterval;
21
+ // }, 100);
22
+
23
+ // return () => clearInterval(interval);
24
+ // });
25
+
26
+ const renderItemFn = (info: ListRenderItemInfo<any>) => {
27
+ return RECYCLE_ITEMS ? renderItem(info) : <Fragment key={info.item.id}>{renderItem(info)}</Fragment>;
28
+ };
29
+
30
+ if (DO_SCROLL_TEST) {
31
+ useScrollTest((offset) => {
32
+ scrollRef.current?.scrollToOffset({
33
+ offset,
34
+ animated: true,
35
+ });
36
+ });
37
+ }
38
+
39
+ return (
40
+ <View style={[StyleSheet.absoluteFill, styles.outerContainer]} key="flashlist">
41
+ <FlashList
42
+ data={data}
43
+ renderItem={renderItemFn}
44
+ keyExtractor={(item) => item.id}
45
+ contentContainerStyle={styles.listContainer}
46
+ estimatedItemSize={ESTIMATED_ITEM_LENGTH}
47
+ drawDistance={DRAW_DISTANCE}
48
+ ref={scrollRef}
49
+ ListHeaderComponent={<View />}
50
+ ListHeaderComponentStyle={styles.listHeader}
51
+ />
52
+ </View>
53
+ );
54
+ }
55
+
56
+ const styles = StyleSheet.create({
57
+ listHeader: {
58
+ alignSelf: "center",
59
+ height: 100,
60
+ width: 100,
61
+ backgroundColor: "#456AAA",
62
+ borderRadius: 12,
63
+ marginHorizontal: 8,
64
+ marginTop: 8,
65
+ },
66
+ outerContainer: {
67
+ backgroundColor: "#456",
68
+ },
69
+ scrollContainer: {
70
+ // paddingHorizontal: 8,
71
+ },
72
+ titleContainer: {
73
+ flexDirection: "row",
74
+ alignItems: "center",
75
+ gap: 8,
76
+ },
77
+ stepContainer: {
78
+ gap: 8,
79
+ marginBottom: 8,
80
+ },
81
+ reactLogo: {
82
+ height: 178,
83
+ width: 290,
84
+ bottom: 0,
85
+ left: 0,
86
+ position: "absolute",
87
+ },
88
+ itemContainer: {
89
+ // padding: 4,
90
+ // borderBottomWidth: 1,
91
+ // borderBottomColor: "#ccc",
92
+ },
93
+ listContainer: {
94
+ //paddingHorizontal: 16,
95
+ //paddingTop: 48,
96
+ },
97
+ itemTitle: {
98
+ fontSize: 18,
99
+ fontWeight: "bold",
100
+ marginBottom: 8,
101
+ color: "#1a1a1a",
102
+ },
103
+ itemBody: {
104
+ fontSize: 14,
105
+ color: "#666666",
106
+ lineHeight: 20,
107
+ flex: 1,
108
+ },
109
+ itemFooter: {
110
+ flexDirection: "row",
111
+ justifyContent: "flex-start",
112
+ gap: 16,
113
+ marginTop: 12,
114
+ paddingTop: 12,
115
+ borderTopWidth: 1,
116
+ borderTopColor: "#f0f0f0",
117
+ },
118
+ footerText: {
119
+ fontSize: 14,
120
+ color: "#888888",
121
+ },
122
+ });
@@ -0,0 +1,94 @@
1
+ import renderItem from "@/app/cards-renderItem";
2
+ import { FlatList, StyleSheet, View } from "react-native";
3
+
4
+ export default function CardsFlatList() {
5
+ const data = Array.from({ length: 1000 }, (_, i) => ({ id: i.toString() }));
6
+
7
+ return (
8
+ <View style={[StyleSheet.absoluteFill, styles.outerContainer]} key="flatlist">
9
+ <FlatList
10
+ style={[StyleSheet.absoluteFill, styles.scrollContainer]}
11
+ data={data}
12
+ renderItem={renderItem as any}
13
+ keyExtractor={(item) => item.id}
14
+ contentContainerStyle={styles.listContainer}
15
+ ListHeaderComponent={<View />}
16
+ ListHeaderComponentStyle={styles.listHeader}
17
+ // Performance optimizations
18
+ windowSize={3} // Reduced window size for better performance
19
+ maxToRenderPerBatch={5} // Reduced batch size for smoother scrolling
20
+ initialNumToRender={8} // Initial render amount
21
+ removeClippedSubviews={true} // Detach views outside of the viewport
22
+ updateCellsBatchingPeriod={50} // Batching period for updates
23
+ />
24
+ </View>
25
+ );
26
+ }
27
+
28
+ const styles = StyleSheet.create({
29
+ listHeader: {
30
+ alignSelf: "center",
31
+ height: 100,
32
+ width: 100,
33
+ backgroundColor: "#456AAA",
34
+ borderRadius: 12,
35
+ marginHorizontal: 8,
36
+ marginTop: 8,
37
+ },
38
+ outerContainer: {
39
+ backgroundColor: "#456",
40
+ },
41
+ scrollContainer: {
42
+ // paddingHorizontal: 16,
43
+ },
44
+ titleContainer: {
45
+ flexDirection: "row",
46
+ alignItems: "center",
47
+ gap: 8,
48
+ },
49
+ stepContainer: {
50
+ gap: 8,
51
+ marginBottom: 8,
52
+ },
53
+ reactLogo: {
54
+ height: 178,
55
+ width: 290,
56
+ bottom: 0,
57
+ left: 0,
58
+ position: "absolute",
59
+ },
60
+ itemContainer: {
61
+ // padding: 4,
62
+ // borderBottomWidth: 1,
63
+ // borderBottomColor: "#ccc",
64
+ },
65
+ listContainer: {
66
+ paddingHorizontal: 16,
67
+ // paddingTop: 48,
68
+ },
69
+ itemTitle: {
70
+ fontSize: 18,
71
+ fontWeight: "bold",
72
+ marginBottom: 8,
73
+ color: "#1a1a1a",
74
+ },
75
+ itemBody: {
76
+ fontSize: 14,
77
+ color: "#666666",
78
+ lineHeight: 20,
79
+ flex: 1,
80
+ },
81
+ itemFooter: {
82
+ flexDirection: "row",
83
+ justifyContent: "flex-start",
84
+ gap: 16,
85
+ marginTop: 12,
86
+ paddingTop: 12,
87
+ borderTopWidth: 1,
88
+ borderTopColor: "#f0f0f0",
89
+ },
90
+ footerText: {
91
+ fontSize: 14,
92
+ color: "#888888",
93
+ },
94
+ });