@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,314 @@
1
+ // OneKey patch: avatar bytes stay in a worker and IndexedDB, outside list snapshots.
2
+ const AVATAR_PREFIX = 'onekey-avatar://blockie/v1/';
3
+ const MAX_RETAINED_AVATARS = 128;
4
+ export type AvatarLease = (() => void) & {
5
+ setPriority: (priority: number) => void;
6
+ };
7
+
8
+ type AvatarEntry = {
9
+ id: number;
10
+ uri: string;
11
+ url?: string;
12
+ references: number;
13
+ listeners: Set<{
14
+ resolve: (url: string) => void;
15
+ reject: () => void;
16
+ priority: number;
17
+ }>;
18
+ };
19
+
20
+ type AvatarResponse =
21
+ | { type: 'resolved'; id: number; url: string }
22
+ | { type: 'error'; id: number };
23
+
24
+ export function canonicalNativeListAvatarUri(uri: string): string | undefined {
25
+ if (!uri.startsWith(AVATAR_PREFIX)) return undefined;
26
+ try {
27
+ const seed = decodeURIComponent(uri.slice(AVATAR_PREFIX.length));
28
+ return seed
29
+ ? AVATAR_PREFIX + encodeURIComponent(seed.toLowerCase())
30
+ : undefined;
31
+ } catch {
32
+ return undefined;
33
+ }
34
+ }
35
+
36
+ class NativeListWebAvatarCache {
37
+ private readonly entries = new Map<string, AvatarEntry>();
38
+ private readonly requests = new Map<number, AvatarEntry>();
39
+ private nextId = 0;
40
+ private worker: Worker | undefined;
41
+ // OneKey patch: one cancellable startup serves every pending avatar lease.
42
+ private workerGeneration = 0;
43
+ private startingWorker:
44
+ | { generation: number; controller?: AbortController }
45
+ | undefined;
46
+
47
+ acquire(
48
+ uri: string,
49
+ resolve: (url: string) => void,
50
+ reject: () => void,
51
+ priority = 2
52
+ ): AvatarLease {
53
+ let entry = this.entries.get(uri);
54
+ const isNew = !entry;
55
+ if (!entry) {
56
+ entry = { id: ++this.nextId, uri, references: 0, listeners: new Set() };
57
+ this.entries.set(uri, entry);
58
+ this.requests.set(entry.id, entry);
59
+ }
60
+ const current = entry;
61
+ this.entries.delete(uri);
62
+ this.entries.set(uri, current);
63
+ current.references += 1;
64
+ const listener = { resolve, reject, priority };
65
+ if (current.url) resolve(current.url);
66
+ // OneKey patch: keep lease priority after resolution as well as while queued.
67
+ // else current.listeners.add(listener);
68
+ current.listeners.add(listener);
69
+ if (isNew) {
70
+ // OneKey patch: file workers need async asset loading; keep pending requests in this cache.
71
+ // try {
72
+ // if (!this.worker) {
73
+ // // Deliberately avoid *.worker.js and its inline Blob-worker loader.
74
+ // this.worker = new Worker(new URL('./NativeListAvatarWorker.js', import.meta.url), {
75
+ // name: 'onekey-native-list-avatar',
76
+ // });
77
+ // this.worker.addEventListener('message', this.handleMessage);
78
+ // this.worker.addEventListener('error', this.handleFailure);
79
+ // this.worker.addEventListener('messageerror', this.handleFailure);
80
+ // }
81
+ // this.worker.postMessage({ type: 'acquire', id: current.id, uri, priority });
82
+ // } catch {
83
+ // queueMicrotask(this.handleFailure);
84
+ // }
85
+ if (this.worker) {
86
+ this.sendAcquire(current);
87
+ } else {
88
+ this.startWorker();
89
+ }
90
+ }
91
+ const updatePriority = () => {
92
+ if (current.url) return;
93
+ const priorities = [...current.listeners].map((value) => value.priority);
94
+ this.worker?.postMessage({
95
+ type: 'priority',
96
+ id: current.id,
97
+ priority: Math.min(2, ...priorities),
98
+ });
99
+ };
100
+ updatePriority();
101
+ this.trim();
102
+ let disposed = false;
103
+ const release = () => {
104
+ if (disposed) return;
105
+ disposed = true;
106
+ current.listeners.delete(listener);
107
+ current.references = Math.max(0, current.references - 1);
108
+ if (
109
+ current.references === 0 &&
110
+ !current.url &&
111
+ this.entries.get(uri) === current
112
+ ) {
113
+ this.entries.delete(uri);
114
+ this.requests.delete(current.id);
115
+ this.worker?.postMessage({ type: 'release', id: current.id });
116
+ if (this.requests.size === 0 && this.startingWorker) {
117
+ const starting = this.startingWorker;
118
+ this.startingWorker = undefined;
119
+ this.workerGeneration += 1;
120
+ starting.controller?.abort();
121
+ }
122
+ }
123
+ updatePriority();
124
+ this.trim();
125
+ };
126
+ return Object.assign(release, {
127
+ setPriority: (next: number) => {
128
+ if (disposed || listener.priority === next) return;
129
+ listener.priority = next;
130
+ updatePriority();
131
+ },
132
+ });
133
+ }
134
+
135
+ // OneKey patch: HTTP/extension keeps the bundler's original Worker entry. Electron's
136
+ // file interceptor can serve the same self-contained source as an asset for a Blob worker.
137
+ private startWorker() {
138
+ if (this.worker || this.startingWorker) return;
139
+ const starting: NonNullable<NativeListWebAvatarCache['startingWorker']> = {
140
+ generation: ++this.workerGeneration,
141
+ };
142
+ this.startingWorker = starting;
143
+ const assetURL = new URL('./NativeListAvatarWorker.js', import.meta.url);
144
+ if (assetURL.protocol === 'file:') {
145
+ starting.controller = new AbortController();
146
+ void (async () => {
147
+ const response = await fetch(assetURL, {
148
+ signal: starting.controller?.signal,
149
+ });
150
+ if (!response.ok)
151
+ throw new Error('NativeList avatar worker asset failed to load');
152
+ const source = await response.blob();
153
+ if (this.startingWorker !== starting) return;
154
+ const sourceURL = URL.createObjectURL(source);
155
+ try {
156
+ this.activateWorker(
157
+ new Worker(sourceURL, { name: 'onekey-native-list-avatar' }),
158
+ starting
159
+ );
160
+ } finally {
161
+ // Worker construction captures the script URL; the document must not retain its bytes.
162
+ URL.revokeObjectURL(sourceURL);
163
+ }
164
+ })().catch(() => {
165
+ if (
166
+ this.startingWorker === starting ||
167
+ this.workerGeneration === starting.generation
168
+ ) {
169
+ this.handleFailure();
170
+ }
171
+ });
172
+ return;
173
+ }
174
+ try {
175
+ this.activateWorker(
176
+ new Worker(new URL('./NativeListAvatarWorker.js', import.meta.url), {
177
+ name: 'onekey-native-list-avatar',
178
+ }),
179
+ starting
180
+ );
181
+ } catch {
182
+ queueMicrotask(() => {
183
+ if (this.workerGeneration === starting.generation) this.handleFailure();
184
+ });
185
+ }
186
+ }
187
+
188
+ private activateWorker(
189
+ worker: Worker,
190
+ starting: NonNullable<NativeListWebAvatarCache['startingWorker']>
191
+ ) {
192
+ if (this.startingWorker !== starting) {
193
+ worker.terminate();
194
+ return;
195
+ }
196
+ this.startingWorker = undefined;
197
+ this.worker = worker;
198
+ // OneKey patch: queued events from a failed instance cannot affect its replacement.
199
+ worker.addEventListener(
200
+ 'message',
201
+ (event: MessageEvent<AvatarResponse>) => {
202
+ if (this.worker === worker) this.handleMessage(event);
203
+ }
204
+ );
205
+ const failed = () => {
206
+ if (this.worker === worker) this.handleFailure();
207
+ };
208
+ worker.addEventListener('error', failed);
209
+ worker.addEventListener('messageerror', failed);
210
+ this.requests.forEach((entry) => {
211
+ if (entry.references > 0 && !entry.url) this.sendAcquire(entry);
212
+ });
213
+ }
214
+
215
+ private sendAcquire(entry: AvatarEntry) {
216
+ const worker = this.worker;
217
+ try {
218
+ worker?.postMessage({
219
+ type: 'acquire',
220
+ id: entry.id,
221
+ uri: entry.uri,
222
+ priority: Math.min(
223
+ 2,
224
+ ...[...entry.listeners].map((listener) => listener.priority)
225
+ ),
226
+ });
227
+ } catch {
228
+ queueMicrotask(() => {
229
+ if (this.worker === worker) this.handleFailure();
230
+ });
231
+ }
232
+ }
233
+
234
+ private readonly handleMessage = (event: MessageEvent<AvatarResponse>) => {
235
+ const response = event.data;
236
+ if (!response || !Number.isSafeInteger(response.id)) return;
237
+ const entry = this.requests.get(response.id);
238
+ if (!entry) {
239
+ this.worker?.postMessage({ type: 'release', id: response.id });
240
+ return;
241
+ }
242
+ if (
243
+ response.type === 'resolved' &&
244
+ typeof response.url === 'string' &&
245
+ response.url.startsWith('blob:')
246
+ ) {
247
+ entry.url = response.url;
248
+ entry.listeners.forEach((listener) => listener.resolve(response.url));
249
+ } else {
250
+ this.entries.delete(entry.uri);
251
+ this.requests.delete(entry.id);
252
+ this.worker?.postMessage({ type: 'release', id: entry.id });
253
+ entry.listeners.forEach((listener) => listener.reject());
254
+ }
255
+ // OneKey patch: retain resolved lease priorities until their explicit release.
256
+ // entry.listeners.clear();
257
+ if (!entry.url) entry.listeners.clear();
258
+ this.trim();
259
+ };
260
+
261
+ private readonly handleFailure = () => {
262
+ // OneKey patch: detach the failed generation before callbacks can acquire a replacement.
263
+ // this.worker?.terminate();
264
+ // this.worker = undefined;
265
+ // this.requests.forEach((entry) => {
266
+ // if (!entry.url) entry.listeners.forEach((listener) => listener.reject());
267
+ // entry.listeners.clear();
268
+ // });
269
+ // this.requests.clear();
270
+ // this.entries.clear();
271
+ const failedEntries = [...this.requests.values()];
272
+ const starting = this.startingWorker;
273
+ this.startingWorker = undefined;
274
+ this.workerGeneration += 1;
275
+ starting?.controller?.abort();
276
+ this.worker?.terminate();
277
+ this.worker = undefined;
278
+ this.requests.clear();
279
+ this.entries.clear();
280
+ failedEntries.forEach((entry) => {
281
+ const listeners = [...entry.listeners];
282
+ entry.listeners.clear();
283
+ if (!entry.url) listeners.forEach((listener) => listener.reject());
284
+ });
285
+ };
286
+
287
+ private trim() {
288
+ for (const [uri, entry] of this.entries) {
289
+ if (this.entries.size <= MAX_RETAINED_AVATARS) break;
290
+ if (entry.references === 0) {
291
+ this.entries.delete(uri);
292
+ this.requests.delete(entry.id);
293
+ this.worker?.postMessage({ type: 'release', id: entry.id });
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ const documentCaches = new WeakMap<Document, NativeListWebAvatarCache>();
300
+
301
+ export function acquireNativeListAvatar(
302
+ document: Document,
303
+ uri: string,
304
+ resolve: (url: string) => void,
305
+ reject: () => void,
306
+ priority = 2
307
+ ): AvatarLease {
308
+ let cache = documentCaches.get(document);
309
+ if (!cache) {
310
+ cache = new NativeListWebAvatarCache();
311
+ documentCaches.set(document, cache);
312
+ }
313
+ return cache.acquire(uri, resolve, reject, priority);
314
+ }