@onekeyfe/react-native-native-list 3.0.105 → 3.0.106

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 (45) hide show
  1. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +16 -3
  2. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +6 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +671 -52
  4. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +282 -54
  5. package/ios/HybridNativeList.swift +8 -2
  6. package/ios/NativeListCell.swift +630 -31
  7. package/ios/NativeListDesignAssets.swift +24 -1
  8. package/ios/RNCNativeListView.swift +216 -42
  9. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/Contents.json +15 -0
  10. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/icon.svg +14 -0
  11. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/Contents.json +15 -0
  12. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/icon.svg +12 -0
  13. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/Contents.json +15 -0
  14. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/icon.svg +6 -0
  15. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/Contents.json +15 -0
  16. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/icon.svg +15 -0
  17. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/Contents.json +15 -0
  18. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/icon.svg +3 -0
  19. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/Contents.json +15 -0
  20. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/icon.svg +7 -0
  21. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/Contents.json +15 -0
  22. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/icon.svg +18 -0
  23. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/Contents.json +15 -0
  24. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/icon.svg +7 -0
  25. package/lib/module/NativeList.js +102 -8
  26. package/lib/module/NativeList.web.js +20 -2
  27. package/lib/module/avatarPrefetch.js +174 -0
  28. package/lib/module/validation.js +45 -2
  29. package/lib/module/web/NativeListAvatarWorker.js +542 -0
  30. package/lib/module/web/NativeListWebAvatarCache.js +267 -0
  31. package/lib/module/web/NativeListWebEngine.js +958 -47
  32. package/lib/typescript/src/NativeList.web.d.ts +2 -2
  33. package/lib/typescript/src/avatarPrefetch.d.ts +28 -0
  34. package/lib/typescript/src/models.d.ts +72 -4
  35. package/lib/typescript/src/web/NativeListWebAvatarCache.d.ts +6 -0
  36. package/lib/typescript/src/web/NativeListWebEngine.d.ts +10 -1
  37. package/package.json +2 -2
  38. package/src/NativeList.tsx +149 -13
  39. package/src/NativeList.web.tsx +30 -3
  40. package/src/avatarPrefetch.ts +236 -0
  41. package/src/models.ts +98 -7
  42. package/src/validation.ts +131 -2
  43. package/src/web/NativeListAvatarWorker.js +567 -0
  44. package/src/web/NativeListWebAvatarCache.ts +314 -0
  45. package/src/web/NativeListWebEngine.ts +1409 -60
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
+ <path
3
+ fill="#8897A5"
4
+ d="M11 2a1 1 0 1 1 2 0v1.8l1.6 1.6a1 1 0 1 1-1.4 1.4L12 5.6l-1.2 1.2a1 1 0 0 1-1.4-1.4L11 3.8z"
5
+ />
6
+ <rect width="18" height="14" x="3" y="6" fill="#3FA9F5" rx="5" />
7
+ <rect width="3" height="6" x="1.5" y="10" fill="#8897A5" rx="1.5" />
8
+ <rect width="3" height="6" x="19.5" y="10" fill="#8897A5" rx="1.5" />
9
+ <circle cx="9" cy="12" r="1.5" fill="#10243E" />
10
+ <circle cx="15" cy="12" r="1.5" fill="#10243E" />
11
+ <path
12
+ fill="#10243E"
13
+ d="M8.5 15.4c.9.8 2.08 1.2 3.5 1.2s2.6-.4 3.5-1.2c.24-.2.6-.18.8.06.2.23.17.6-.06.8-1.14.98-2.58 1.46-4.24 1.46s-3.1-.48-4.24-1.46a.58.58 0 0 1-.06-.8c.2-.24.56-.26.8-.06"
14
+ />
15
+ </svg>
@@ -0,0 +1,15 @@
1
+ {
2
+ "images": [
3
+ {
4
+ "filename": "icon.svg",
5
+ "idiom": "universal"
6
+ }
7
+ ],
8
+ "info": {
9
+ "author": "xcode",
10
+ "version": 1
11
+ },
12
+ "properties": {
13
+ "preserves-vector-representation": true
14
+ }
15
+ }
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
+ <path d="M17.87 8.25 14.12 12l3.75 3.75-2.12 2.121-3.75-3.75-3.75 3.75-2.121-2.121L9.879 12l-3.75-3.75 2.12-2.121L12 9.879l3.75-3.75 2.122 2.121Z" />
3
+ </svg>
@@ -0,0 +1,15 @@
1
+ {
2
+ "images": [
3
+ {
4
+ "filename": "icon.svg",
5
+ "idiom": "universal"
6
+ }
7
+ ],
8
+ "info": {
9
+ "author": "xcode",
10
+ "version": 1
11
+ },
12
+ "properties": {
13
+ "preserves-vector-representation": true
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
+ <path
3
+ fill-rule="evenodd"
4
+ d="M12 2c5.185 0 9.448 3.947 9.95 9H22v2h-.05c-.502 5.053-4.765 9-9.95 9s-9.448-3.947-9.95-9H2v-2h.05C2.552 5.947 6.815 2 12 2M9.523 13c.09 1.982.438 3.726.934 5.002.29.746.612 1.282.917 1.614.304.331.517.384.626.384s.322-.053.626-.384c.305-.332.627-.868.917-1.614.496-1.276.845-3.02.934-5.002zm-5.459 0a8 8 0 0 0 4.8 6.36 10 10 0 0 1-.271-.633C7.994 17.187 7.61 15.189 7.52 13zm12.416 0c-.09 2.189-.474 4.187-1.073 5.727a10 10 0 0 1-.271.633 8 8 0 0 0 4.8-6.36zM8.863 4.639A8 8 0 0 0 4.064 11h3.457c.09-2.189.473-4.187 1.072-5.727q.127-.327.27-.634M12 4c-.109 0-.322.053-.626.384-.305.332-.627.868-.917 1.614-.496 1.276-.844 3.02-.934 5.002h4.954c-.09-1.982-.438-3.726-.934-5.002-.29-.746-.612-1.282-.917-1.614C12.322 4.053 12.109 4 12 4m3.136.639q.144.307.271.634c.599 1.54.982 3.538 1.073 5.727h3.456a8 8 0 0 0-4.8-6.361"
5
+ clip-rule="evenodd"
6
+ />
7
+ </svg>
@@ -0,0 +1,15 @@
1
+ {
2
+ "images": [
3
+ {
4
+ "filename": "icon.svg",
5
+ "idiom": "universal"
6
+ }
7
+ ],
8
+ "info": {
9
+ "author": "xcode",
10
+ "version": 1
11
+ },
12
+ "properties": {
13
+ "preserves-vector-representation": true
14
+ }
15
+ }
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
+ <path
3
+ fill="#4285F4"
4
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09"
5
+ />
6
+ <path
7
+ fill="#34A853"
8
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23"
9
+ />
10
+ <path
11
+ fill="#FBBC05"
12
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22z"
13
+ />
14
+ <path
15
+ fill="#EA4335"
16
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53"
17
+ />
18
+ </svg>
@@ -0,0 +1,15 @@
1
+ {
2
+ "images": [
3
+ {
4
+ "filename": "icon.svg",
5
+ "idiom": "universal"
6
+ }
7
+ ],
8
+ "info": {
9
+ "author": "xcode",
10
+ "version": 1
11
+ },
12
+ "properties": {
13
+ "preserves-vector-representation": true
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
+ <path
3
+ fill-rule="evenodd"
4
+ d="M12 2a5 5 0 0 1 5 5v2h3v13H4V9h3V7a5 5 0 0 1 5-5m-1 11v5h2v-5zm1-9a3 3 0 0 0-3 3v2h6V7a3 3 0 0 0-3-3"
5
+ clip-rule="evenodd"
6
+ />
7
+ </svg>
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
 
3
- import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
3
+ import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
4
+ import { OneKeyImageCache, OneKeyImageCachePolicy } from '@onekeyfe/react-native-image';
5
+ import { NativeAvatarPrefetchModel, NativeAvatarPrefetchQueue } from "./avatarPrefetch.js";
4
6
  import { callback, getHostComponent } from 'react-native-nitro-modules';
7
+ import { isSelectableRow } from "./selection.js";
5
8
  import { normalizeIndexScroll, normalizeKeyScroll, normalizePositionScroll, resolveLocationIndex, scrollFailure, validateOffset } from "./scrolling.js";
6
- import { serializePatches, serializeSnapshot } from "./validation.js";
9
+ import { applyRowPatches, serializePatches, serializeSnapshot } from "./validation.js";
7
10
  import { jsx as _jsx } from "react/jsx-runtime";
8
11
  const NativeListConfig = require('../nitrogen/generated/shared/json/NativeListConfig.json');
9
12
  const NativeListHost = getHostComponent('NativeList', () => NativeListConfig);
@@ -48,8 +51,52 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
48
51
  onRefresh,
49
52
  onScrollToIndexFailed
50
53
  };
54
+ const snapshotJson = useMemo(() => serializeSnapshot(snapshot), [snapshot]);
51
55
  const snapshotRef = useRef(snapshot);
52
- snapshotRef.current = snapshot;
56
+ const previousSnapshotJsonRef = useRef(snapshotJson);
57
+ const snapshotChanged = previousSnapshotJsonRef.current !== snapshotJson;
58
+ if (snapshotChanged) {
59
+ snapshotRef.current = snapshot;
60
+ previousSnapshotJsonRef.current = snapshotJson;
61
+ }
62
+ // OneKey patch: range events already cross the bridge only when row indices
63
+ // change. Prefetch is optional work; scrolling and visible loads stay native.
64
+ const avatarQueueRef = useRef(null);
65
+ const avatarRangeRef = useRef(undefined);
66
+ // OneKey patch: a changed prop is a complete snapshot; unrelated renders must
67
+ // not erase image patches already dispatched through the imperative handle.
68
+ // const avatarPrefetchPaused = useRef(false);
69
+ const avatarModelRef = useRef(null);
70
+ const avatarLifecycle = useRef('pending');
71
+ if (!avatarModelRef.current) avatarModelRef.current = new NativeAvatarPrefetchModel(snapshot.rows);else if (snapshotChanged) avatarModelRef.current.replaceSnapshot(snapshot.rows);
72
+ const updateAvatarPrefetch = () => {
73
+ const range = avatarRangeRef.current;
74
+ if (!range) return;
75
+ avatarQueueRef.current?.update(avatarModelRef.current?.window(range.first, range.last, range.direction) ?? []);
76
+ };
77
+ const updateAvatarPrefetchRef = useRef(updateAvatarPrefetch);
78
+ updateAvatarPrefetchRef.current = updateAvatarPrefetch;
79
+ useEffect(() => {
80
+ avatarLifecycle.current = 'mounted';
81
+ const queue = new NativeAvatarPrefetchQueue(source => OneKeyImageCache.preload([{
82
+ uri: source.uri,
83
+ headers: source.headers,
84
+ resizeWidth: source.width,
85
+ resizeHeight: source.height,
86
+ optimizeTos: false,
87
+ cachePolicy: source.cachePolicy === 'memory' ? OneKeyImageCachePolicy.MEMORY : source.cachePolicy === 'disk' ? OneKeyImageCachePolicy.DISK : OneKeyImageCachePolicy.MEMORY_DISK
88
+ }]));
89
+ avatarQueueRef.current = queue;
90
+ updateAvatarPrefetchRef.current();
91
+ return () => {
92
+ queue.dispose();
93
+ avatarQueueRef.current = null;
94
+ avatarLifecycle.current = 'unmounted';
95
+ };
96
+ }, []);
97
+ useEffect(() => {
98
+ updateAvatarPrefetchRef.current();
99
+ }, [snapshot]);
53
100
  const initialScrollRef = useRef(initialScrollIndex === undefined && initialScrollKey === undefined ? undefined : initialScrollIndex !== undefined ? normalizeIndexScroll({
54
101
  index: initialScrollIndex,
55
102
  animated: false,
@@ -64,7 +111,6 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
64
111
  }, 'start')
65
112
  });
66
113
  const didApplyInitialScroll = useRef(false);
67
- const snapshotJson = useMemo(() => serializeSnapshot(snapshot), [snapshot]);
68
114
  const emitIndexFailure = (index, reason) => {
69
115
  callbacksRef.current.onScrollToIndexFailed?.(scrollFailure(snapshotRef.current.rows, index, reason));
70
116
  };
@@ -89,14 +135,40 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
89
135
  applyInitialScrollCallbackRef.current = applyInitialScroll;
90
136
  useImperativeHandle(forwardedRef, () => ({
91
137
  applySnapshot(nextSnapshot) {
138
+ const nextSnapshotJson = serializeSnapshot(nextSnapshot);
139
+ nativeRef.current?.applySnapshot(nextSnapshotJson);
92
140
  snapshotRef.current = nextSnapshot;
93
- nativeRef.current?.applySnapshot(serializeSnapshot(nextSnapshot));
141
+ if (nativeRef.current && avatarLifecycle.current !== 'unmounted') {
142
+ avatarModelRef.current?.replaceSnapshot(nextSnapshot.rows);
143
+ updateAvatarPrefetchRef.current();
144
+ }
94
145
  },
95
146
  applyPatches(patches) {
96
- if (patches.length > 0) nativeRef.current?.applyPatches(serializePatches(patches));
147
+ if (patches.length === 0) return;
148
+ const nextSnapshot = applyRowPatches(snapshotRef.current, patches);
149
+ const native = nativeRef.current;
150
+ const dispatch = native ? () => {
151
+ native.applyPatches(serializePatches(patches));
152
+ snapshotRef.current = nextSnapshot;
153
+ } : undefined;
154
+ if (avatarLifecycle.current === 'unmounted') {
155
+ dispatch?.();
156
+ return;
157
+ }
158
+ if (avatarModelRef.current?.applyPatches(patches, dispatch)) updateAvatarPrefetchRef.current();
97
159
  },
98
160
  reconcileSelection(selectedKeys) {
99
161
  nativeRef.current?.reconcileSelection(JSON.stringify(selectedKeys));
162
+ const current = snapshotRef.current;
163
+ if (current.selection && (current.selection.mode !== 'single' || selectedKeys.length <= 1) && selectedKeys.every(key => current.rows.some(row => row.key === key && isSelectableRow(row)))) {
164
+ snapshotRef.current = {
165
+ ...current,
166
+ selection: {
167
+ ...current.selection,
168
+ selectedKeys
169
+ }
170
+ };
171
+ }
100
172
  },
101
173
  scrollToKey(paramsOrKey, animated, alignment) {
102
174
  const {
@@ -162,7 +234,21 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
162
234
  callbacksRef.current.onActionAnchorInvalidated?.(parsePayload(payloadJson));
163
235
  }),
164
236
  onSelectionDelta: callback(payloadJson => {
165
- callbacksRef.current.onSelectionDelta?.(parsePayload(payloadJson));
237
+ const payload = parsePayload(payloadJson);
238
+ const current = snapshotRef.current;
239
+ if (current.selection) {
240
+ const selectedKeys = new Set(current.selection.selectedKeys);
241
+ payload.removedKeys.forEach(key => selectedKeys.delete(key));
242
+ payload.addedKeys.forEach(key => selectedKeys.add(key));
243
+ snapshotRef.current = {
244
+ ...current,
245
+ selection: {
246
+ ...current.selection,
247
+ selectedKeys: [...selectedKeys]
248
+ }
249
+ };
250
+ }
251
+ callbacksRef.current.onSelectionDelta?.(payload);
166
252
  }),
167
253
  onReorder: callback(payloadJson => {
168
254
  callbacksRef.current.onReorder?.(parsePayload(payloadJson));
@@ -171,7 +257,15 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
171
257
  callbacksRef.current.onEndReached?.(parsePayload(payloadJson));
172
258
  }),
173
259
  onVisibleRangeChanged: callback(payloadJson => {
174
- callbacksRef.current.onVisibleRangeChanged?.(parsePayload(payloadJson));
260
+ const payload = parsePayload(payloadJson);
261
+ const previous = avatarRangeRef.current;
262
+ avatarRangeRef.current = {
263
+ first: payload.firstIndex,
264
+ last: payload.lastIndex,
265
+ direction: previous && payload.firstIndex !== previous.first ? Math.sign(payload.firstIndex - previous.first) : previous?.direction ?? 1
266
+ };
267
+ updateAvatarPrefetchRef.current();
268
+ callbacksRef.current.onVisibleRangeChanged?.(payload);
175
269
  })
176
270
  }), []);
177
271
  return /*#__PURE__*/_jsx(NativeListHost, {
@@ -29,7 +29,14 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
29
29
  webVirtualizationEnabledRef.current = webVirtualizationEnabled;
30
30
  const validatedSnapshot = useMemo(() => validateSnapshot(snapshot), [snapshot]);
31
31
  const snapshotRef = useRef(validatedSnapshot);
32
- snapshotRef.current = validatedSnapshot;
32
+ const snapshotPropRef = useRef(validatedSnapshot);
33
+ // An imperative snapshot survives host mounting until the snapshot prop changes.
34
+ if (snapshotPropRef.current !== validatedSnapshot) {
35
+ snapshotPropRef.current = validatedSnapshot;
36
+ snapshotRef.current = validatedSnapshot;
37
+ }
38
+ // OneKey patch: React refs can be ready before the DOM engine is mounted.
39
+ const pendingPatchesRef = useRef(undefined);
33
40
  const appliedSnapshotRef = useRef(undefined);
34
41
  const callbacksRef = useRef({});
35
42
  callbacksRef.current = {
@@ -61,6 +68,9 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
61
68
  const engine = new NativeListWebEngine(host, snapshotRef.current, callbacksRef.current, webVirtualizationEnabledRef.current);
62
69
  engineRef.current = engine;
63
70
  appliedSnapshotRef.current = snapshotRef.current;
71
+ const pending = pendingPatchesRef.current;
72
+ pendingPatchesRef.current = undefined;
73
+ if (pending?.snapshot === snapshotRef.current) pending.batches.forEach(patches => engine.applyPatches(patches));
64
74
  const initial = initialScrollRef.current;
65
75
  if (initial && !didApplyInitialScroll.current) {
66
76
  didApplyInitialScroll.current = true;
@@ -89,6 +99,7 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
89
99
  useImperativeHandle(forwardedRef, () => ({
90
100
  applySnapshot(nextSnapshot) {
91
101
  const next = validateSnapshot(nextSnapshot);
102
+ pendingPatchesRef.current = undefined;
92
103
  snapshotRef.current = next;
93
104
  appliedSnapshotRef.current = next;
94
105
  engineRef.current?.applySnapshot(next);
@@ -96,7 +107,14 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
96
107
  applyPatches(patches) {
97
108
  if (patches.length > 0) {
98
109
  serializePatches(patches);
99
- engineRef.current?.applyPatches(patches);
110
+ // engineRef.current?.applyPatches(patches);
111
+ if (engineRef.current) engineRef.current.applyPatches(patches);else {
112
+ if (pendingPatchesRef.current?.snapshot !== snapshotRef.current) pendingPatchesRef.current = {
113
+ snapshot: snapshotRef.current,
114
+ batches: []
115
+ };
116
+ pendingPatchesRef.current.batches.push(patches);
117
+ }
100
118
  }
101
119
  },
102
120
  reconcileSelection(selectedKeys) {
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+
3
+ // OneKey patch: share a bounded URI-only window across renderers. No bitmap or
4
+ // account-specific data enters list snapshots or the UI runtime.
5
+
6
+ const PREFIX = 'onekey-avatar://blockie/v1/';
7
+ const MAX_CANDIDATES = 64;
8
+ const IMAGE_PATCH_FIELDS = ['leading', 'secondaryLeading', 'image', 'networkImage', 'thumbnail', 'visual'];
9
+ export function avatarPrefetchWindow(rows, first, last, direction, resolveRow) {
10
+ if (first < 0 || last < first || first >= rows.length) return [];
11
+ last = Math.min(last, rows.length - 1);
12
+ const result = [];
13
+ const seen = new Set();
14
+ let limit = MAX_CANDIDATES;
15
+ const add = (source, priority) => {
16
+ if (!source?.uri.startsWith(PREFIX) || source.cachePolicy === 'none' || seen.has(source.uri) || result.length >= limit) return;
17
+ seen.add(source.uri);
18
+ result.push({
19
+ source,
20
+ priority
21
+ });
22
+ };
23
+ const visual = (value, priority) => {
24
+ if (!value) return;
25
+ if ('image' in value) add(value.image, priority);
26
+ if ('networkImage' in value) add(value.networkImage, priority);
27
+ if ('images' in value) value.images.forEach(image => add(image, priority));
28
+ if ('overlays' in value) value.overlays?.forEach(overlay => add(overlay.image, priority));
29
+ };
30
+ const row = (value, priority) => {
31
+ value = resolveRow?.(value) ?? value;
32
+ if ('leading' in value) visual(value.leading, priority);
33
+ if ('secondaryLeading' in value) visual(value.secondaryLeading, priority);
34
+ if ('image' in value) add(value.image, priority);
35
+ if ('networkImage' in value) add(value.networkImage, priority);
36
+ if ('thumbnail' in value) add(value.thumbnail, priority);
37
+ if (value.type === 'walletGroup') {
38
+ visual(value.parent.leading, priority);
39
+ for (const child of value.children.slice(0, MAX_CANDIDATES)) {
40
+ if (result.length >= limit) break;
41
+ visual(child.leading, priority);
42
+ }
43
+ }
44
+ };
45
+ // Bound row examination too: a giant non-avatar group must not scan all data.
46
+ for (let index = first; index <= last && index < first + 64; index += 1) row(rows[index], 0);
47
+ const span = Math.min(24, (last - first + 1) * 2);
48
+ const step = direction < 0 ? -1 : 1;
49
+ const aheadStart = step > 0 ? last + 1 : first - 1;
50
+ const aheadLimit = Math.min(MAX_CANDIDATES, result.length + 24);
51
+ limit = aheadLimit;
52
+ for (let distance = 0; distance < span && result.length < aheadLimit; distance += 1) {
53
+ const index = aheadStart + distance * step;
54
+ if (index < 0 || index >= rows.length) break;
55
+ row(rows[index], 1);
56
+ }
57
+ const behindStart = step > 0 ? first - 1 : last + 1;
58
+ const behindLimit = Math.min(MAX_CANDIDATES, result.length + 8);
59
+ limit = behindLimit;
60
+ for (let distance = 0; distance < Math.min(8, span) && result.length < behindLimit; distance += 1) {
61
+ const index = behindStart - distance * step;
62
+ if (index < 0 || index >= rows.length) break;
63
+ row(rows[index], 2);
64
+ }
65
+ return result;
66
+ }
67
+
68
+ // OneKey patch: imperative image patches update a sparse prefetch overlay, not
69
+ // the readonly prop or a full cloned snapshot. The key index is rebuilt only for
70
+ // a complete snapshot; ordinary balance patches retain no extra row copies.
71
+ export class NativeAvatarPrefetchModel {
72
+ imageRows = new Map();
73
+ constructor(rows) {
74
+ this.rows = rows;
75
+ this.rowByKey = new Map(rows.map(row => [row.key, row]));
76
+ }
77
+ replaceSnapshot(rows) {
78
+ this.rows = rows;
79
+ this.rowByKey = new Map(rows.map(row => [row.key, row]));
80
+ this.imageRows.clear();
81
+ }
82
+ applyPatches(patches, dispatch) {
83
+ // Native methods have no acknowledgement. Mirror only dispatched batches;
84
+ // a missing host or a synchronous serialization/dispatch failure changes nothing.
85
+ if (!dispatch) return false;
86
+ dispatch();
87
+ const seen = new Set();
88
+ for (const patch of patches) {
89
+ const row = this.rowByKey.get(patch.key);
90
+ // Both native renderers reject the whole batch for an unknown key/type.
91
+ if (!row || row.type !== patch.type || seen.has(patch.key)) return false;
92
+ seen.add(patch.key);
93
+ }
94
+ let changed = false;
95
+ for (const patch of patches) {
96
+ const changes = patch.changes;
97
+ let imageChanges;
98
+ for (const key of IMAGE_PATCH_FIELDS) {
99
+ if (changes[key] !== undefined) {
100
+ imageChanges ??= {};
101
+ imageChanges[key] = changes[key];
102
+ }
103
+ }
104
+ // JSON.stringify omits undefined top-level fields. Native therefore keeps
105
+ // them unchanged; an explicit replacement visual without image clears it.
106
+ if (!imageChanges) continue;
107
+ const row = this.imageRows.get(patch.key) ?? this.rowByKey.get(patch.key);
108
+ // Preserve the validated row discriminant, matching applyRowPatches' shallow merge.
109
+ this.imageRows.set(patch.key, {
110
+ ...row,
111
+ ...imageChanges,
112
+ key: row.key,
113
+ type: row.type
114
+ });
115
+ changed = true;
116
+ }
117
+ return changed;
118
+ }
119
+ window(first, last, direction) {
120
+ return avatarPrefetchWindow(this.rows, first, last, direction, row => this.imageRows.get(row.key) ?? row);
121
+ }
122
+ }
123
+
124
+ // Native preload has no cancellation token. One in-flight source is the bound;
125
+ // direction changes replace all not-yet-started work rather than appending it.
126
+ export class NativeAvatarPrefetchQueue {
127
+ pending = [];
128
+ disposed = false;
129
+ recent = new Map();
130
+ constructor(preload) {
131
+ this.preload = preload;
132
+ }
133
+ update(candidates) {
134
+ if (this.disposed) return;
135
+ const now = Date.now();
136
+ this.pending = candidates.filter(({
137
+ source,
138
+ priority
139
+ }) => priority !== 0 && source.uri !== this.activeUri && now - (this.recent.get(source.uri) ?? 0) > 30_000).map(({
140
+ source
141
+ }) => source);
142
+ this.schedule();
143
+ }
144
+ dispose() {
145
+ this.disposed = true;
146
+ this.pending = [];
147
+ this.recent.clear();
148
+ if (this.timer !== undefined) clearTimeout(this.timer);
149
+ this.timer = undefined;
150
+ }
151
+ schedule() {
152
+ if (this.disposed || this.activeUri || this.timer !== undefined || !this.pending.length) return;
153
+ // Yield between short batches without imposing a frame-per-image throughput cap.
154
+ this.timer = setTimeout(() => {
155
+ this.timer = undefined;
156
+ const source = this.pending.shift();
157
+ if (!source || this.disposed) return;
158
+ this.activeUri = source.uri;
159
+ Promise.resolve().then(() => this.preload(source)).then(success => {
160
+ if (success && !this.disposed) {
161
+ this.recent.delete(source.uri);
162
+ this.recent.set(source.uri, Date.now());
163
+ if (this.recent.size > 128) this.recent.delete(this.recent.keys().next().value);
164
+ }
165
+ }).catch(() => {
166
+ /* Visible loading retains its own failure/retry behavior. */
167
+ }).finally(() => {
168
+ this.activeUri = undefined;
169
+ this.schedule();
170
+ });
171
+ }, 0);
172
+ }
173
+ }
174
+ //# sourceMappingURL=avatarPrefetch.js.map
@@ -124,6 +124,26 @@ function assertLeadingVisual(visual, path) {
124
124
  assertImage(visual.image, `${path}.image`);
125
125
  assertVisualShape(visual.shape, `${path}.shape`);
126
126
  assertText(visual.cornerIcon?.name, `${path}.cornerIcon.name`);
127
+ // OneKey patch: validate optional selector overlays at the JSON boundary.
128
+ assertText(visual.fallbackIcon?.name, `${path}.fallbackIcon.name`);
129
+ if ((visual.overlays?.length ?? 0) > 2) fail(`${path}.overlays`, 'supports at most two overlays');
130
+ visual.overlays?.forEach((overlay, index) => {
131
+ if (!['topLeft', 'bottomRight'].includes(overlay.position)) fail(`${path}.overlays[${index}].position`, 'must be topLeft or bottomRight');
132
+ if (overlay.size !== undefined && (overlay.size <= 0 || overlay.size > 40)) fail(`${path}.overlays[${index}].size`, 'must be within 1...40');
133
+ if (overlay.padding !== undefined && (!Number.isFinite(overlay.padding) || overlay.padding < 0 || overlay.padding * 2 >= (overlay.size ?? 20))) fail(`${path}.overlays[${index}].padding`, 'must fit inside the overlay');
134
+ if (overlay.offset !== undefined && (!Number.isFinite(overlay.offset) || overlay.offset < 0 || overlay.offset > 20)) fail(`${path}.overlays[${index}].offset`, 'must be within 0...20');
135
+ for (const field of ['width', 'height']) {
136
+ const value = overlay[field];
137
+ if (value !== undefined && (!Number.isFinite(value) || value <= 0 || value > 40)) fail(`${path}.overlays[${index}].${field}`, 'must be within 1...40');
138
+ }
139
+ for (const field of ['offsetX', 'offsetY']) {
140
+ const value = overlay[field];
141
+ if (value !== undefined && (!Number.isFinite(value) || value < 0 || value > 20)) fail(`${path}.overlays[${index}].${field}`, 'must be within 0...20');
142
+ }
143
+ if (overlay.padding !== undefined && overlay.padding * 2 >= Math.min(overlay.width ?? overlay.size ?? 20, overlay.height ?? overlay.size ?? 20)) fail(`${path}.overlays[${index}].padding`, 'must fit inside the overlay');
144
+ assertImage(overlay.image, `${path}.overlays[${index}].image`);
145
+ assertText(overlay.text, `${path}.overlays[${index}].text`);
146
+ });
127
147
  if (visual.kind === 'token') {
128
148
  assertImage(visual.networkImage, `${path}.networkImage`);
129
149
  }
@@ -169,6 +189,10 @@ function assertWalletGroup(row, path) {
169
189
  }
170
190
  function assertRow(row, index, path = `rows[${index}]`) {
171
191
  assertKey(row.key, `${path}.key`);
192
+ // OneKey patch: explicit dimensions and opacity cannot corrupt list layout.
193
+ if (row.height !== undefined && (row.height < 0 || row.height > 4096)) fail(`${path}.height`, 'must be within 0...4096');
194
+ if (row.heightRounding !== undefined && (row.height === undefined || !['floor', 'nearest'].includes(row.heightRounding))) fail(`${path}.heightRounding`, 'requires an explicit height and must be floor or nearest');
195
+ if (row.opacity !== undefined && (row.opacity < 0 || row.opacity > 1)) fail(`${path}.opacity`, 'must be within 0...1');
172
196
  if (row.groupId && !row.groupPosition) {
173
197
  fail(`${path}.groupPosition`, 'is required when groupId is present');
174
198
  }
@@ -186,6 +210,16 @@ function assertRow(row, index, path = `rows[${index}]`) {
186
210
  }
187
211
  assertText(row.title, `${path}.title`);
188
212
  assertText(row.subtitle, `${path}.subtitle`);
213
+ // OneKey patch: selector text segments truncate independently.
214
+ row.subtitleSegments?.forEach((segment, segmentIndex) => {
215
+ assertText(segment.text, `${path}.subtitleSegments[${segmentIndex}].text`);
216
+ if (segment.tone !== undefined && !['primary', 'secondary', 'disabled', 'caution', 'positive', 'negative'].includes(segment.tone)) fail(`${path}.subtitleSegments[${segmentIndex}].tone`, 'invalid selector text tone');
217
+ });
218
+ let previousMatchEnd = 0;
219
+ row.titleMatch?.forEach(match => {
220
+ if (!Number.isInteger(match.start) || !Number.isInteger(match.end) || match.start < previousMatchEnd || match.end <= match.start || match.end > row.title.length) fail(`${path}.titleMatch`, 'must contain ordered, non-overlapping UTF-16 ranges inside title');
221
+ previousMatchEnd = match.end;
222
+ });
189
223
  assertText(row.tertiary, `${path}.tertiary`);
190
224
  if (row.tertiaryTone !== undefined && !['secondary', 'info'].includes(row.tertiaryTone)) {
191
225
  fail(`${path}.tertiaryTone`, 'must be secondary or info');
@@ -293,9 +327,12 @@ function assertRow(row, index, path = `rows[${index}]`) {
293
327
  assertTrailingAccessories(row.trailing, `${path}.trailing`);
294
328
  break;
295
329
  case 'system':
296
- if (!['loading', 'retry', 'noMatch', 'end', 'spacer'].includes(row.variant)) {
297
- fail(`${path}.variant`, 'must be loading, retry, noMatch, end, or spacer');
330
+ if (
331
+ // OneKey patch: deprecated-wallet warnings retain the original scrolling semantics.
332
+ !['loading', 'retry', 'noMatch', 'end', 'spacer', 'warning'].includes(row.variant)) {
333
+ fail(`${path}.variant`, 'must be loading, retry, noMatch, end, spacer, or warning');
298
334
  }
335
+ if (row.variant === 'warning') assertText(row.title, `${path}.title`);
299
336
  if (row.variant !== 'spacer') {
300
337
  assertText(row.message, `${path}.message`);
301
338
  }
@@ -397,6 +434,12 @@ export function validateSnapshot(snapshot) {
397
434
  }
398
435
  function assertPatchChanges(patch, index) {
399
436
  const path = `patches[${index}].changes`;
437
+ // OneKey patch: partial balance updates retain a valid, current accessibility label.
438
+ if ('accessibilityLabel' in patch.changes) {
439
+ assertText(patch.changes.accessibilityLabel, `${path}.accessibilityLabel`);
440
+ }
441
+ // OneKey patch: partial updates may refer to an existing height but still require a valid policy.
442
+ if ('heightRounding' in patch.changes && patch.changes.heightRounding !== undefined && !['floor', 'nearest'].includes(patch.changes.heightRounding)) fail(`${path}.heightRounding`, 'must be floor or nearest');
400
443
  if (patch.changes.revision !== undefined && (!Number.isSafeInteger(patch.changes.revision) || patch.changes.revision < 0)) {
401
444
  fail(`${path}.revision`, 'must be a non-negative safe integer');
402
445
  }