@onekeyfe/react-native-native-list 3.0.105

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 (181) hide show
  1. package/README.md +342 -0
  2. package/android/CMakeLists.txt +20 -0
  3. package/android/build.gradle +105 -0
  4. package/android/gradle.properties +4 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  7. package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +125 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +165 -0
  9. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +147 -0
  10. package/android/src/main/java/com/onekey/nativelist/NativeListPackage.kt +25 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +2939 -0
  12. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1794 -0
  13. package/common/fonts/Roobert-Bold.ttf +0 -0
  14. package/common/fonts/Roobert-Medium.ttf +0 -0
  15. package/common/fonts/Roobert-Regular.ttf +0 -0
  16. package/common/fonts/Roobert-SemiBold.ttf +0 -0
  17. package/docs/DESIGN.md +129 -0
  18. package/ios/HybridNativeList.swift +131 -0
  19. package/ios/NativeListCell.swift +2858 -0
  20. package/ios/NativeListDesignAssets.swift +110 -0
  21. package/ios/NativeListModels.swift +215 -0
  22. package/ios/RNCNativeListView.swift +1737 -0
  23. package/ios/Resources/NativeListIcons.xcassets/Contents.json +1 -0
  24. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/Contents.json +1 -0
  25. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/icon.svg +1 -0
  26. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/Contents.json +1 -0
  27. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/icon.svg +1 -0
  28. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/Contents.json +1 -0
  29. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/icon.svg +1 -0
  30. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/Contents.json +1 -0
  31. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/icon.svg +1 -0
  32. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/Contents.json +1 -0
  33. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/icon.svg +1 -0
  34. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/Contents.json +1 -0
  35. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/icon.svg +1 -0
  36. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/Contents.json +1 -0
  37. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/icon.svg +1 -0
  38. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/Contents.json +1 -0
  39. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/icon.svg +1 -0
  40. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/Contents.json +1 -0
  41. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/icon.svg +1 -0
  42. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/Contents.json +1 -0
  43. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/icon.svg +1 -0
  44. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/Contents.json +1 -0
  45. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/icon.svg +1 -0
  46. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/Contents.json +1 -0
  47. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/icon.svg +1 -0
  48. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/Contents.json +1 -0
  49. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/icon.svg +1 -0
  50. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/Contents.json +1 -0
  51. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/icon.svg +1 -0
  52. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/Contents.json +1 -0
  53. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/icon.svg +1 -0
  54. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/Contents.json +1 -0
  55. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/icon.svg +1 -0
  56. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/Contents.json +1 -0
  57. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/icon.svg +1 -0
  58. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/Contents.json +1 -0
  59. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/icon.svg +1 -0
  60. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/Contents.json +1 -0
  61. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/icon.svg +1 -0
  62. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/Contents.json +1 -0
  63. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/icon.svg +1 -0
  64. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/Contents.json +1 -0
  65. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/icon.svg +1 -0
  66. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/Contents.json +1 -0
  67. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/icon.svg +1 -0
  68. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/Contents.json +1 -0
  69. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/icon.svg +1 -0
  70. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/Contents.json +1 -0
  71. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/icon.svg +1 -0
  72. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/Contents.json +1 -0
  73. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/icon.svg +1 -0
  74. package/lib/module/NativeList.js +183 -0
  75. package/lib/module/NativeList.nitro.js +4 -0
  76. package/lib/module/NativeList.types.js +4 -0
  77. package/lib/module/NativeList.web.js +167 -0
  78. package/lib/module/index.js +6 -0
  79. package/lib/module/models.js +2 -0
  80. package/lib/module/package.json +1 -0
  81. package/lib/module/scrolling.js +121 -0
  82. package/lib/module/selection.js +85 -0
  83. package/lib/module/validation.js +562 -0
  84. package/lib/module/web/NativeListWebEngine.js +2238 -0
  85. package/lib/nitrogen/generated/.gitattributes +1 -0
  86. package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  87. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
  88. package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
  89. package/lib/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
  90. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
  91. package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
  92. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
  93. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
  94. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
  95. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
  96. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
  97. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
  98. package/lib/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
  99. package/lib/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
  100. package/lib/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
  101. package/lib/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
  102. package/lib/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
  103. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
  104. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
  105. package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
  106. package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
  107. package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
  108. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
  109. package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
  110. package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
  111. package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  112. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
  113. package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
  114. package/lib/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
  115. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
  116. package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
  117. package/lib/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
  118. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
  119. package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
  120. package/lib/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
  121. package/lib/typescript/package.json +1 -0
  122. package/lib/typescript/src/NativeList.d.ts +18 -0
  123. package/lib/typescript/src/NativeList.nitro.d.ts +29 -0
  124. package/lib/typescript/src/NativeList.types.d.ts +87 -0
  125. package/lib/typescript/src/NativeList.web.d.ts +19 -0
  126. package/lib/typescript/src/index.d.ts +7 -0
  127. package/lib/typescript/src/models.d.ts +456 -0
  128. package/lib/typescript/src/scrolling.d.ts +32 -0
  129. package/lib/typescript/src/selection.d.ts +27 -0
  130. package/lib/typescript/src/validation.d.ts +7 -0
  131. package/lib/typescript/src/web/NativeListWebEngine.d.ts +203 -0
  132. package/nitro.json +23 -0
  133. package/nitrogen/generated/.gitattributes +1 -0
  134. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  135. package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
  136. package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
  137. package/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
  138. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
  139. package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
  146. package/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
  147. package/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
  148. package/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
  149. package/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
  150. package/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
  151. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
  152. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
  153. package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
  154. package/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
  155. package/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
  156. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
  157. package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
  158. package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
  159. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  160. package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
  161. package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
  162. package/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
  163. package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
  164. package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
  165. package/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
  166. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
  167. package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
  168. package/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
  169. package/package.json +126 -0
  170. package/react-native-native-list.podspec +38 -0
  171. package/react-native.config.js +19 -0
  172. package/src/NativeList.nitro.ts +48 -0
  173. package/src/NativeList.tsx +298 -0
  174. package/src/NativeList.types.ts +125 -0
  175. package/src/NativeList.web.tsx +258 -0
  176. package/src/index.ts +35 -0
  177. package/src/models.ts +604 -0
  178. package/src/scrolling.ts +189 -0
  179. package/src/selection.ts +159 -0
  180. package/src/validation.ts +877 -0
  181. package/src/web/NativeListWebEngine.ts +3767 -0
package/README.md ADDED
@@ -0,0 +1,342 @@
1
+ # @onekeyfe/react-native-native-list
2
+
3
+ Nitro View, template-driven native list for React Native 0.86. iOS renders a
4
+ `UICollectionView`; Android renders a `RecyclerView`. JavaScript sends one
5
+ serializable snapshot or batch patch instead of mounting a React subtree for
6
+ every row.
7
+
8
+ ## Install
9
+
10
+ Keep NativeList and OneKeyImage on the same release because recycled image
11
+ slots use the native host exported by OneKeyImage.
12
+
13
+ ```sh
14
+ yarn add @onekeyfe/react-native-native-list@3.0.101 \
15
+ @onekeyfe/react-native-image@3.0.101 \
16
+ react-native-nitro-modules@0.37.0
17
+ cd ios && pod install
18
+ ```
19
+
20
+ The package requires the React Native new architecture. Its deployment floors
21
+ are iOS 15.5 and Android API 24.
22
+
23
+ ## Basic usage
24
+
25
+ ```tsx
26
+ import { useMemo, useRef, useState } from 'react';
27
+ import {
28
+ NativeList,
29
+ type NativeListRef,
30
+ type NativeListSnapshot,
31
+ type SelectionDeltaEvent,
32
+ } from '@onekeyfe/react-native-native-list';
33
+
34
+ export function AssetList() {
35
+ const listRef = useRef<NativeListRef>(null);
36
+ const [selectedKeys, setSelectedKeys] = useState<readonly string[]>([]);
37
+ const snapshot = useMemo<NativeListSnapshot>(
38
+ () => ({
39
+ schemaVersion: 1,
40
+ generation: 1,
41
+ layout: { kind: 'sectioned', stickyHeaders: true, itemSpacing: 1 },
42
+ selection: {
43
+ mode: 'multiple',
44
+ selectedKeys,
45
+ rowPressToggles: true,
46
+ },
47
+ rows: [
48
+ {
49
+ type: 'identity',
50
+ key: 'btc',
51
+ sectionKey: 'assets',
52
+ leading: {
53
+ kind: 'token',
54
+ image: {
55
+ uri: 'https://example.com/btc.png',
56
+ width: 40,
57
+ height: 40,
58
+ contentFit: 'cover',
59
+ cachePolicy: 'memory-disk',
60
+ },
61
+ fallbackText: 'BT',
62
+ },
63
+ title: 'Bitcoin',
64
+ subtitle: 'BTC · Bitcoin',
65
+ trailing: [
66
+ { kind: 'value', text: '$64,230' },
67
+ { kind: 'checkbox', state: 'unchecked', target: { scope: 'row' } },
68
+ ],
69
+ },
70
+ ],
71
+ }),
72
+ [selectedKeys]
73
+ );
74
+
75
+ const handleSelection = (event: SelectionDeltaEvent) => {
76
+ setSelectedKeys((current) => {
77
+ const next = new Set(current);
78
+ event.removedKeys.forEach((key) => next.delete(key));
79
+ event.addedKeys.forEach((key) => next.add(key));
80
+ return [...next];
81
+ });
82
+ };
83
+
84
+ return (
85
+ <NativeList
86
+ ref={listRef}
87
+ style={{ flex: 1 }}
88
+ snapshot={snapshot}
89
+ onSelectionDelta={handleSelection}
90
+ onRowAction={({ rowKey, actionKey }) => {
91
+ console.log(rowKey, actionKey);
92
+ }}
93
+ />
94
+ );
95
+ }
96
+ ```
97
+
98
+ Native selection is authoritative for immediate feedback. Persist the emitted
99
+ delta in application state, then pass that state back in a later snapshot or
100
+ call `reconcileSelection(selectedKeys)` once. Reconciliation does not emit a
101
+ second delta.
102
+
103
+ ## Row and layout model
104
+
105
+ `RowModel` is a strict discriminated union. It intentionally accepts no
106
+ `ReactNode`, render function, arbitrary style object, or native object handle.
107
+
108
+ | Row type | Intended template |
109
+ | --------------- | ---------------------------------------------------------- |
110
+ | `identity` | Asset, account, wallet, network, or settings identity |
111
+ | `rail` | Compact horizontal item with optional native drag support |
112
+ | `activity` | Transaction/activity with amounts and up to three actions |
113
+ | `message` | Notification/message with bounded body lines and thumbnail |
114
+ | `dataRow` | Two to four fixed table columns, optional index/checkbox |
115
+ | `mediaTile` | Gallery or browser-preview tile |
116
+ | `metricCard` | KPI or fixed Activity/Performance portfolio card |
117
+ | `sectionHeader` | Sticky/group, tri-state, or fixed list-summary header |
118
+ | `action` | Fixed action row or fixed footer |
119
+ | `system` | Loading, retry, end marker, or bounded spacer |
120
+
121
+ Snapshots support `linear`, `sectioned`, `grid`, and `table` layout semantics.
122
+ `linear` is a full-width row stream; `sectioned` gives section-header rows a
123
+ native visual break and optional pinning; `grid` uses two to four native spans
124
+ with structural rows spanning every column; and `table` uses compact,
125
+ alternating rows with stable weighted columns and column alignment. Vertical or
126
+ horizontal orientation, stable keys, grouped card geometry, empty state, and a
127
+ fixed footer remain common capabilities.
128
+
129
+ The required theme colors cover list, row, selection, text, separator, accent,
130
+ and semantic positive/negative states. The optional `subduedBackground`,
131
+ `strongBackground`, `disabledText`, `icon`, `iconSubdued`,
132
+ `criticalBackground`, `inverseBackground`, and `inverseText` tokens let native
133
+ templates match the OneKey palette without embedding platform-specific style
134
+ objects. Identity-row trailing accessories also accept semantic native icons,
135
+ for example `{ kind: 'icon', name: 'PlusCircleOutline' }`. A `valuePair` can
136
+ independently set `primaryTone` and `secondaryTone` to `primary`, `secondary`,
137
+ `positive`, or `negative`; omitted tones keep the original primary/secondary
138
+ defaults. Token visuals can provide `networkImage` or `cornerIcon`, while media
139
+ tiles can provide `networkImage`, to reproduce OneKey's bottom-right network
140
+ and token-state indicators. `dataRow` supports a two-line asset/volume and
141
+ price/change layout plus fixed leverage/venue badges. `metricCard` supports the
142
+ fixed `activity` and `performance` composite variants used by Portfolio & PnL;
143
+ their metrics and progress remain bounded serializable data.
144
+
145
+ Set a section header's `variant` to `summary` for the fixed 56-point list
146
+ summary template. Its optional `valueActionKey` renders a 16-point secondary
147
+ action beside the 16-point medium primary title; summary headers do not accept
148
+ a checkbox and do not become sticky.
149
+
150
+ For a native trailing section index, use a vertical `sectioned` layout, set
151
+ `capabilities.sectionIndex` to `{ enabled: true }`, and add an `indexTitle` to
152
+ each section header that should appear in the index. The index follows snapshot
153
+ order, skips headers without `indexTitle`, and scrolls to the header's existing
154
+ row `key`. Titles must be unique after Unicode NFC normalization, contain no
155
+ leading/trailing whitespace, and be at most eight Unicode code points. Set
156
+ `hapticsEnabled: false` to follow an application-level reduced-haptics setting.
157
+ The web implementation renders the same explicit index entries as a DOM
158
+ overlay, with click and pointer-drag navigation.
159
+
160
+ The native templates bundle OneKey's Roobert Regular, Medium, SemiBold, and
161
+ Bold faces and use them before the platform-font fallback. Their semantic
162
+ icons and checkbox marks are the same vector paths as the OneKey component
163
+ library, rendered by Core Graphics/UIKit on iOS and Canvas on Android.
164
+
165
+ For native reorder, set `capabilities.reorderable` and mark eligible rail rows
166
+ with `draggable: true`, or add a `drag` trailing accessory to an identity row.
167
+ Reordering never crosses a `sectionKey` boundary.
168
+
169
+ ## OneKeyImage inside cells
170
+
171
+ Pass an `ImageSource` descriptor in the row model; do not pass a
172
+ `<OneKeyImage />` element:
173
+
174
+ ```ts
175
+ const avatar = {
176
+ uri: 'https://example.com/avatar.webp',
177
+ width: 48,
178
+ height: 48,
179
+ headers: { Authorization: 'Bearer token' },
180
+ contentFit: 'cover',
181
+ cachePolicy: 'memory-disk',
182
+ autoplay: false,
183
+ optimizeTos: true,
184
+ overscan: 1.1,
185
+ loadingStrategy: 'static',
186
+ } as const;
187
+ ```
188
+
189
+ Each cell owns a fixed pool of native `OneKeyImageReusableView` slots. On bind,
190
+ NativeList forwards the URI, headers, content fit, cache policy, autoplay, TOS
191
+ options, overscan, and loading strategy. A stable `rowKey:slot` recycling key
192
+ is assigned automatically. Reuse cancels stale work before accepting the next
193
+ source, while retaining the same SDWebImage cache/pipeline on iOS and Glide
194
+ cache/pipeline on Android as `@onekeyfe/react-native-image`.
195
+
196
+ The fixed slots cover token/network overlays, two activity visuals, a message
197
+ thumbnail, and stacks of two or three images. Image width and height are
198
+ required and bounded to 1–4096 logical pixels.
199
+
200
+ ## Batch updates and events
201
+
202
+ ```ts
203
+ listRef.current?.applyPatches([
204
+ {
205
+ type: 'identity',
206
+ key: 'btc',
207
+ changes: { revision: 2, subtitle: 'Updated in one native command' },
208
+ },
209
+ ]);
210
+
211
+ listRef.current?.scrollToKey('btc', true, 'center');
212
+ listRef.current?.scrollToIndex({
213
+ index: 24,
214
+ animated: true,
215
+ viewPosition: 0.5,
216
+ viewOffset: 12,
217
+ });
218
+ listRef.current?.scrollToItem({ item: snapshot.rows[24] });
219
+ listRef.current?.scrollToOffset({ offset: 320, animated: false });
220
+ listRef.current?.scrollToEnd({ animated: true });
221
+ listRef.current?.scrollToLocation({ sectionIndex: 2, itemIndex: 4 });
222
+ listRef.current?.setRefreshing(false);
223
+ ```
224
+
225
+ The ref exposes `applySnapshot`, `applyPatches`, `reconcileSelection`,
226
+ `scrollToKey`, `scrollToIndex`, `scrollToItem`, `scrollToOffset`, `scrollToEnd`,
227
+ `scrollToLocation`, and `setRefreshing`. The legacy
228
+ `scrollToIndex(index, animated, alignment)` and
229
+ `scrollToKey(key, animated, alignment)` signatures remain supported. Object
230
+ parameters follow React Native list semantics: `viewPosition` is from `0`
231
+ (start) through `1` (end), and a positive `viewOffset` leaves space before the
232
+ target row.
233
+
234
+ Use `initialScrollIndex` or the stable-key extension `initialScrollKey` to set
235
+ the first position. They are mutually exclusive, run once after the first
236
+ snapshot and native layout are ready, and can be combined with
237
+ `initialScrollViewPosition` and `initialScrollViewOffset`. Invalid index and
238
+ location requests call `onScrollToIndexFailed`; native lists do not fail merely
239
+ because a target row has not been mounted yet. `scrollToLocation` counts
240
+ non-summary section headers, then rows carrying that header's `sectionKey`.
241
+
242
+ Patches are validated as a batch, keyed by row key, must preserve the row
243
+ discriminator, and are applied atomically. Prefer a new snapshot for insertions,
244
+ removals, layout changes, and section changes; use patches for frequently
245
+ changing fields.
246
+
247
+ Events are `onRowAction`, `onSelectionDelta`, `onReorder`, `onEndReached`,
248
+ `onVisibleRangeChanged`, and `onRefresh`. Visible-range events are coalesced to
249
+ one per display frame and only fire when the first or last visible item changes.
250
+ End-reached fires once per snapshot `generation`.
251
+
252
+ ## Examples and performance checks
253
+
254
+ The React Native example application contains:
255
+
256
+ - **Native List**: a categorized entry page. Each of the four containers, ten
257
+ row templates, and checkbox selection opens its own source-labeled page; no
258
+ container or row paradigm is hidden behind tabs. The scenarios mirror the
259
+ corresponding list surfaces in the OneKey application.
260
+ - **Native List Benchmark**: reproducible 1,000/5,000-row load, fast scroll,
261
+ one-command 500-row patch, and one-command select-all paths.
262
+ - **Native List Account Selector**: two independently scrolling native lists
263
+ with a fixed Add Wallet footer. The stress fixture exposes 1,000 wallets x
264
+ 1,000 accounts while materializing only the selected wallet's 1,000 account
265
+ rows and retaining at most three wallets / 3,000 account rows in an LRU cache.
266
+
267
+ ### Account Selector performance snapshot (2026-09-05)
268
+
269
+ The current acceptance run exercised five page opens, two 101-switch Wallet 1 /
270
+ Wallet 2 batches, 25 downward plus 20 upward account-list drags, a repeated 15
271
+ downward account-list run, and 15 downward plus 15 upward wallet-list drags. It
272
+ also sampled process CPU and memory and verified, after momentum settled, that
273
+ the two lists scroll independently and Add Wallet remains fixed at the bottom.
274
+ Android additionally covered ART `speed` compilation and ten short fast flings.
275
+
276
+ | Metric | iOS Debug Simulator | Android Release-mode emulator |
277
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
278
+ | Environment | iPhone 16 Pro, iOS 18.6 | API 35 arm64, 1080 x 2400, fixed 60 Hz, non-debuggable Hermes |
279
+ | Page open, 5 runs | External tap-to-visible median 589 ms; the initial native visible-range callback did not emit | 5 / 5 native callbacks; mount-to-first-visible p50 110 ms; external observation p50 202 ms |
280
+ | Wallet switching | 202 / 202; mean 135.3 ms, p50 134 ms, p95 143 ms, max 159 ms | 202 / 202; mean 124.4 ms, p50 118 ms, p95 155 ms, max 213 ms |
281
+ | Account-list frame signal | 113.3 effective FPS; p50/p95 interval 6.67/18.33 ms; 6.6% over 17.5 ms | Repeated drag runs reported 92.09-93.06% deadline-missed frames and p50 450 ms |
282
+ | Wallet-list frame signal | 78.7 effective FPS; p50/p95 interval 13.33/20.00 ms; 17.8% over 17.5 ms | Down/up runs reported 89.39-100% deadline-missed frames and p50 400 ms |
283
+ | Process CPU | Wallet switching mean 48.9%; account scrolling mean 22.9% | Wallet switching mean 66.8%; account scrolling mean 67.1% on Android's per-core scale |
284
+ | Retained memory | Physical footprint 286 MB before, 275 MB after the first batch, 287 MB after the second; lifetime peak 335 MB | PSS 138,459 KiB with one wallet cached, 201,668 KiB after warming three wallets, 192,700 KiB after the second batch, 193,976 KiB after scrolling |
285
+ | Functional checks | Independent lists and fixed footer pass | Independent lists and fixed footer pass, including zero-difference stable screenshot crops |
286
+
287
+ Across the tested environments, Android Release is faster for page opening and
288
+ median wallet switching: external page-open p50 is 202 ms versus 589 ms on iOS,
289
+ and wallet-switch p50 is 118 ms versus 134 ms. iOS has the tighter switch tail
290
+ at p95 143 ms versus 155 ms, clearly leads both account-list and wallet-list
291
+ frame results, and uses less process CPU in the measured switch and scroll runs.
292
+ Both platforms pass correctness, independent scrolling, the fixed footer,
293
+ switch responsiveness, and the short-run retained-memory check. The current
294
+ cross-platform performance priority is Android continuous scrolling, followed
295
+ by the iOS wallet list's 120 Hz tail behavior.
296
+
297
+ ### Test Suite source map
298
+
299
+ | Entry | OneKey application source |
300
+ | ------------------- | -------------------------------------------------------------------------------------------- |
301
+ | Linear container | `AssetList/components/TokenManager/TokenManagerList.tsx` |
302
+ | Sectioned container | `ChainSelector/components/AllNetworksManager/NetworksSectionList.tsx` |
303
+ | Grid container | `Home/components/NFTListView/index.tsx` |
304
+ | Table container | `Perp/components/TokenSelector/PerpTokenSelectorRow.tsx` |
305
+ | Identity rows | `Developer/pages/Gallery/Components/stories/ListItem.tsx` |
306
+ | Rail rows | `Perp/components/FavoritesBar/FavoriteTokenItem.tsx` |
307
+ | Activity rows | `components/TxHistoryListView/TxHistoryListItem.tsx` |
308
+ | Message rows | `Notifications/components/NotificationListView.tsx` |
309
+ | Data rows | `Perp/components/TokenSelector/PerpTokenSelectorRow.tsx` |
310
+ | Media tile rows | `Home/components/NFTListView/NFTListItem.tsx` |
311
+ | Metric card rows | `Perp/components/Portfolio/PerpPortfolioContent.tsx` |
312
+ | Section header rows | `AllNetworksManager/NetworksSectionList.tsx`, `TxHistoryListView/TxHistorySectionHeader.tsx` |
313
+ | Action rows | `TokenManager/TokenManagerList.tsx`, `Discovery/pages/BookmarkListModal/index.tsx` |
314
+ | System rows | `components/Loading/ListLoading.tsx`, `TokenListView/CrossNetworkSearchRows.tsx` |
315
+ | Checkbox selection | `components/forms/Checkbox/index.tsx`, `AllNetworksManager/NetworksSectionList.tsx` |
316
+
317
+ The checkbox page exercises list, section, and row targets plus checked,
318
+ unchecked, indeterminate, loading, and disabled presentation. The rail page
319
+ keeps horizontal selection and native reorder isolated from the other pages.
320
+
321
+ Measure a release build on a physical device. Run each scenario at least three
322
+ times, record cold and warm first-visible latency, JS batch-dispatch latency,
323
+ UI-thread frame time/dropped frames, and peak memory. During repeated fast
324
+ scrolls verify that no stale image from a recycled row becomes visible. The
325
+ benchmark page reports deterministic JS timings and visible-range milestones;
326
+ use Instruments or Android Studio Profiler for native frame and memory data.
327
+
328
+ ## Limits
329
+
330
+ - Nitro View/new architecture only; no Paper component.
331
+ - Fixed templates only. Arbitrary children, per-row render functions, and
332
+ arbitrary style dictionaries are rejected by the public model.
333
+ - Template heights and text line counts are bounded. There is no unbounded
334
+ self-sizing content, nested list, or React component embedded in a cell.
335
+ - A snapshot prop is the declarative baseline. Re-rendering with an older
336
+ snapshot after imperative patches or reorder will intentionally restore the
337
+ older state.
338
+ - The web implementation uses a pure DOM, windowed, recycled row engine behind
339
+ the React component. It mirrors the mobile data and imperative APIs, but its
340
+ performance must still be measured independently from iOS and Android.
341
+
342
+ See [docs/DESIGN.md](docs/DESIGN.md) for the architecture and ownership model.
@@ -0,0 +1,20 @@
1
+ project(nativelist)
2
+ cmake_minimum_required(VERSION 3.9.0)
3
+
4
+ set(PACKAGE_NAME nativelist)
5
+ set(CMAKE_VERBOSE_MAKEFILE ON)
6
+ set(CMAKE_CXX_STANDARD 20)
7
+
8
+ add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
9
+
10
+ include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nativelist+autolinking.cmake)
11
+
12
+ include_directories("src/main/cpp")
13
+
14
+ find_library(LOG_LIB log)
15
+
16
+ target_link_libraries(
17
+ ${PACKAGE_NAME}
18
+ ${LOG_LIB}
19
+ android
20
+ )
@@ -0,0 +1,105 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = { name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['NativeList_' + name]
4
+ }
5
+
6
+ def kotlinVersion = getExtOrDefault('kotlinVersion')
7
+ repositories {
8
+ google()
9
+ mavenCentral()
10
+ }
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
14
+ }
15
+ }
16
+
17
+ def reactNativeArchitectures() {
18
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
19
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
20
+ }
21
+
22
+ apply plugin: "com.android.library"
23
+ apply plugin: "kotlin-android"
24
+ apply from: '../nitrogen/generated/android/nativelist+autolinking.gradle'
25
+ apply plugin: "com.facebook.react"
26
+
27
+ def getExtOrIntegerDefault(name) {
28
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NativeList_" + name].toInteger()
29
+ }
30
+
31
+ android {
32
+ namespace "com.margelo.nitro.nativelist"
33
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
34
+
35
+ defaultConfig {
36
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
37
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
38
+
39
+ externalNativeBuild {
40
+ cmake {
41
+ cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
42
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
43
+ abiFilters (*reactNativeArchitectures())
44
+ }
45
+ }
46
+ }
47
+
48
+ externalNativeBuild {
49
+ cmake {
50
+ path "CMakeLists.txt"
51
+ }
52
+ }
53
+
54
+ packagingOptions {
55
+ excludes = [
56
+ "META-INF", "META-INF/**", "**/libc++_shared.so", "**/libfbjni.so",
57
+ "**/libjsi.so", "**/libfolly_json.so", "**/libfolly_runtime.so",
58
+ "**/libglog.so", "**/libhermes.so", "**/libhermes_executor.so",
59
+ "**/libreactnative.so", "**/libreactnativejni.so"
60
+ ]
61
+ }
62
+
63
+ buildFeatures {
64
+ buildConfig true
65
+ prefab true
66
+ }
67
+
68
+ buildTypes {
69
+ release {
70
+ minifyEnabled false
71
+ }
72
+ }
73
+
74
+ lintOptions {
75
+ disable "GradleCompatible"
76
+ }
77
+
78
+ compileOptions {
79
+ sourceCompatibility JavaVersion.VERSION_1_8
80
+ targetCompatibility JavaVersion.VERSION_1_8
81
+ }
82
+
83
+ sourceSets {
84
+ main {
85
+ java.srcDirs += ["generated/java", "generated/jni"]
86
+ assets.srcDirs += ["../common"]
87
+ }
88
+ }
89
+ }
90
+
91
+ repositories {
92
+ google()
93
+ mavenCentral()
94
+ }
95
+
96
+ def kotlinVersion = getExtOrDefault("kotlinVersion")
97
+
98
+ dependencies {
99
+ implementation "com.facebook.react:react-android"
100
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
101
+ implementation "androidx.recyclerview:recyclerview:1.4.0"
102
+ implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
103
+ implementation project(":react-native-nitro-modules")
104
+ implementation project(":onekeyfe_react-native-image")
105
+ }
@@ -0,0 +1,4 @@
1
+ NativeList_kotlinVersion=1.9.24
2
+ NativeList_minSdkVersion=24
3
+ NativeList_targetSdkVersion=35
4
+ NativeList_compileSdkVersion=35
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,6 @@
1
+ #include <jni.h>
2
+ #include "nativelistOnLoad.hpp"
3
+
4
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
+ return margelo::nitro::nativelist::initialize(vm);
6
+ }
@@ -0,0 +1,125 @@
1
+ package com.margelo.nitro.nativelist
2
+
3
+ import android.os.Looper
4
+ import android.view.View
5
+ import com.facebook.proguard.annotations.DoNotStrip
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+
8
+ @DoNotStrip
9
+ class HybridNativeList(context: ThemedReactContext) : HybridNativeListSpec() {
10
+ private val hostView = NativeListView(context)
11
+
12
+ override val view: View = hostView
13
+
14
+ override var snapshotJson: String = ""
15
+ get() = field
16
+ set(value) {
17
+ if (field == value) return
18
+ field = value
19
+ dispatchToUi { hostView.applySnapshot(value) }
20
+ }
21
+
22
+ override var onRowAction: ((payloadJson: String) -> Unit)? = null
23
+ set(value) { field = value; hostView.onRowAction = value }
24
+ override var onActionAnchorInvalidated: ((payloadJson: String) -> Unit)? = null
25
+ set(value) { field = value; hostView.onActionAnchorInvalidated = value }
26
+ override var onSelectionDelta: ((payloadJson: String) -> Unit)? = null
27
+ set(value) { field = value; hostView.onSelectionDelta = value }
28
+ override var onReorder: ((payloadJson: String) -> Unit)? = null
29
+ set(value) { field = value; hostView.onReorder = value }
30
+ override var onEndReached: ((payloadJson: String) -> Unit)? = null
31
+ set(value) { field = value; hostView.onEndReached = value }
32
+ override var onVisibleRangeChanged: ((payloadJson: String) -> Unit)? = null
33
+ set(value) { field = value; hostView.onVisibleRangeChanged = value }
34
+
35
+ override fun applySnapshot(snapshotJson: String) {
36
+ dispatchToUi { hostView.applySnapshot(snapshotJson) }
37
+ }
38
+
39
+ override fun applyPatches(patchesJson: String) {
40
+ dispatchToUi { hostView.applyPatches(patchesJson) }
41
+ }
42
+
43
+ override fun reconcileSelection(selectedKeysJson: String) {
44
+ dispatchToUi { hostView.reconcileSelection(selectedKeysJson) }
45
+ }
46
+
47
+ override fun scrollToKey(
48
+ key: String,
49
+ animated: Boolean,
50
+ alignment: NativeListScrollAlignment,
51
+ viewPosition: Double,
52
+ viewOffset: Double,
53
+ ) {
54
+ dispatchToUi {
55
+ hostView.scrollToKey(
56
+ key,
57
+ animated,
58
+ alignment.stringValue(),
59
+ viewPosition,
60
+ viewOffset,
61
+ )
62
+ }
63
+ }
64
+
65
+ override fun scrollToIndex(
66
+ index: Double,
67
+ animated: Boolean,
68
+ alignment: NativeListScrollAlignment,
69
+ viewPosition: Double,
70
+ viewOffset: Double,
71
+ ) {
72
+ if (!index.isFinite() || index < 0 || index % 1.0 != 0.0 || index > Int.MAX_VALUE) return
73
+ dispatchToUi {
74
+ hostView.scrollToIndex(
75
+ index.toInt(),
76
+ animated,
77
+ alignment.stringValue(),
78
+ viewPosition,
79
+ viewOffset,
80
+ )
81
+ }
82
+ }
83
+
84
+ override fun scrollToOffset(offset: Double, animated: Boolean) {
85
+ if (!offset.isFinite() || offset < 0) return
86
+ dispatchToUi { hostView.scrollToOffset(offset, animated) }
87
+ }
88
+
89
+ override fun scrollToEnd(animated: Boolean) {
90
+ dispatchToUi { hostView.scrollToEnd(animated) }
91
+ }
92
+
93
+ override fun setActionAnchorState(stateJson: String) {
94
+ dispatchToUi { hostView.setActionAnchorState(stateJson) }
95
+ }
96
+
97
+ override fun setRefreshing(refreshing: Boolean) {
98
+ dispatchToUi { hostView.setRefreshing(refreshing) }
99
+ }
100
+
101
+ override fun onDropView() {
102
+ hostView.dispose()
103
+ super.onDropView()
104
+ }
105
+
106
+ override fun dispose() {
107
+ hostView.dispose()
108
+ super.dispose()
109
+ }
110
+
111
+ /**
112
+ * Nitro prop setters can run off the Android UI thread. Apply synchronously
113
+ * during a UI commit; only off-main calls need to be posted to this host.
114
+ */
115
+ private fun dispatchToUi(action: () -> Unit) {
116
+ if (Looper.myLooper() == Looper.getMainLooper()) action() else hostView.post(action)
117
+ }
118
+
119
+ private fun NativeListScrollAlignment.stringValue(): String = when (this) {
120
+ NativeListScrollAlignment.START -> "start"
121
+ NativeListScrollAlignment.CENTER -> "center"
122
+ NativeListScrollAlignment.END -> "end"
123
+ NativeListScrollAlignment.NEAREST -> "nearest"
124
+ }
125
+ }