@onekeyfe/react-native-image 3.0.122 → 3.0.124

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.
@@ -344,11 +344,14 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
344
344
  manager: safetyHandle.manager,
345
345
  url: url
346
346
  )
347
- hostView.sd_setImage(
347
+ hostView.sd_internalSetImage(
348
348
  with: url,
349
349
  placeholderImage: nil,
350
350
  options: OneKeyImageRequestContext.renderOptions,
351
351
  context: context,
352
+ setImageBlock: { [weak self] image, _, _, _ in
353
+ self?.applyDisplayedImage(image, generation: generation)
354
+ },
352
355
  progress: { [weak self] receivedSize, _, _ in
353
356
  guard safetyHandle.tracker.inspectReceivedByteCount(Int64(receivedSize)) else { return }
354
357
  DispatchQueue.main.async { [weak self] in
@@ -356,7 +359,9 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
356
359
  self.hostView.sd_cancelCurrentImageLoad()
357
360
  }
358
361
  },
359
- completed: { [weak self] image, error, cacheType, _ in
362
+ completed: { [weak self] image, _, error, cacheType, finished, _ in
363
+ // Progressive loads deliver partial images before the terminal callback.
364
+ guard finished else { return }
360
365
  guard let self, self.requestGeneration == generation else {
361
366
  safetyHandle.finish()
362
367
  return
@@ -515,6 +520,18 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
515
520
  }
516
521
  }
517
522
 
523
+ // OneKey patch: SDWebImage assigns `image` from inside its own completion, which
524
+ // runs before this object's generation guard and never re-checks whether the view
525
+ // still belongs to the request that started the load. Fabric recycles a single
526
+ // OneKeyImage UIView across unrelated images, so a superseded completion could
527
+ // repaint a slot that now shows a different image. Every write goes through here.
528
+ func applyDisplayedImage(_ image: UIImage?, generation: UInt64) {
529
+ guard Self.isCurrentRequestGeneration(generation, current: requestGeneration) else {
530
+ return
531
+ }
532
+ hostView.image = image
533
+ }
534
+
518
535
  private func showLoading(
519
536
  requestIsActive: Bool,
520
537
  letLibraryStartIndicator: Bool
@@ -579,7 +596,7 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
579
596
  skeletonIndicator.stopAnimatingIndicator()
580
597
  fallbackLayer.isHidden = true
581
598
  resetImageTransition()
582
- hostView.image = image
599
+ applyDisplayedImage(image, generation: generation)
583
600
  hostView.backgroundColor = .clear
584
601
  if requestIsActive {
585
602
  applyLoadedImageTransition(cacheType: .memory)
@@ -1,6 +1,7 @@
1
1
  import SDWebImage
2
2
  import SDWebImageSVGCoder
3
3
  import SDWebImageWebPCoder
4
+ import UIKit
4
5
  import XCTest
5
6
 
6
7
  @testable import OneKeyImage
@@ -543,6 +544,18 @@ final class OneKeyImageInfrastructureTests: XCTestCase {
543
544
  )
544
545
  }
545
546
 
547
+ func testSupersededRequestNeverPaintsRecycledView() throws {
548
+ let image = HybridOneKeyImage()
549
+ let imageView = try XCTUnwrap(image.view as? SDAnimatedImageView)
550
+ let painted = UIImage()
551
+
552
+ image.applyDisplayedImage(painted, generation: .max)
553
+ XCTAssertNil(imageView.image)
554
+
555
+ image.applyDisplayedImage(painted, generation: 0)
556
+ XCTAssertIdentical(imageView.image, painted)
557
+ }
558
+
546
559
  func testAnimatedViewUsesBoundedFrameBuffer() throws {
547
560
  let image = HybridOneKeyImage()
548
561
  let imageView = try XCTUnwrap(image.view as? SDAnimatedImageView)
@@ -173,6 +173,30 @@ export function OneKeyImage({
173
173
  const flattenedStyle = StyleSheet.flatten(style);
174
174
  const hasRoundedCorners = Boolean(flattenedStyle && [flattenedStyle.borderRadius, flattenedStyle.borderTopLeftRadius, flattenedStyle.borderTopRightRadius, flattenedStyle.borderBottomLeftRadius, flattenedStyle.borderBottomRightRadius, flattenedStyle.borderStartStartRadius, flattenedStyle.borderStartEndRadius, flattenedStyle.borderEndStartRadius, flattenedStyle.borderEndEndRadius].some(radius => typeof radius === 'number' && radius > 0));
175
175
  const shouldWrapNative = hasOverlay || hasRoundedCorners;
176
+ const hasBorderWidth = Boolean(flattenedStyle && [flattenedStyle.borderWidth, flattenedStyle.borderTopWidth, flattenedStyle.borderRightWidth, flattenedStyle.borderBottomWidth, flattenedStyle.borderLeftWidth].some(width => typeof width === 'number' && width > 0));
177
+ const borderOverlayStyle = flattenedStyle && hasRoundedCorners && hasBorderWidth ? {
178
+ borderWidth: flattenedStyle.borderWidth,
179
+ borderTopWidth: flattenedStyle.borderTopWidth,
180
+ borderRightWidth: flattenedStyle.borderRightWidth,
181
+ borderBottomWidth: flattenedStyle.borderBottomWidth,
182
+ borderLeftWidth: flattenedStyle.borderLeftWidth,
183
+ borderColor: flattenedStyle.borderColor,
184
+ borderTopColor: flattenedStyle.borderTopColor,
185
+ borderRightColor: flattenedStyle.borderRightColor,
186
+ borderBottomColor: flattenedStyle.borderBottomColor,
187
+ borderLeftColor: flattenedStyle.borderLeftColor,
188
+ borderStyle: flattenedStyle.borderStyle,
189
+ borderRadius: flattenedStyle.borderRadius,
190
+ borderTopLeftRadius: flattenedStyle.borderTopLeftRadius,
191
+ borderTopRightRadius: flattenedStyle.borderTopRightRadius,
192
+ borderBottomLeftRadius: flattenedStyle.borderBottomLeftRadius,
193
+ borderBottomRightRadius: flattenedStyle.borderBottomRightRadius,
194
+ borderStartStartRadius: flattenedStyle.borderStartStartRadius,
195
+ borderStartEndRadius: flattenedStyle.borderStartEndRadius,
196
+ borderEndStartRadius: flattenedStyle.borderEndStartRadius,
197
+ borderEndEndRadius: flattenedStyle.borderEndEndRadius,
198
+ zIndex: 1
199
+ } : undefined;
176
200
  const native = /*#__PURE__*/createElement(NativeOneKeyImage, {
177
201
  ...viewProps,
178
202
  ...nativeCallbacks,
@@ -192,7 +216,7 @@ export function OneKeyImage({
192
216
  });
193
217
  if (!shouldWrapNative) return native;
194
218
  return /*#__PURE__*/_jsxs(View, {
195
- style: [style, styles.overlayContainer],
219
+ style: [style, styles.overlayContainer, borderOverlayStyle ? styles.borderlessContainer : undefined],
196
220
  collapsable: false,
197
221
  children: [native, effectiveState === 'loading' && placeholder != null ? /*#__PURE__*/_jsx(View, {
198
222
  pointerEvents: "none",
@@ -202,6 +226,9 @@ export function OneKeyImage({
202
226
  pointerEvents: "none",
203
227
  style: StyleSheet.absoluteFill,
204
228
  children: fallback
229
+ }) : null, borderOverlayStyle ? /*#__PURE__*/_jsx(View, {
230
+ pointerEvents: "none",
231
+ style: [StyleSheet.absoluteFill, borderOverlayStyle]
205
232
  }) : null]
206
233
  });
207
234
  }
@@ -228,6 +255,13 @@ export const OneKeyImageCache = {
228
255
  const styles = StyleSheet.create({
229
256
  overlayContainer: {
230
257
  overflow: 'hidden'
258
+ },
259
+ borderlessContainer: {
260
+ borderWidth: 0,
261
+ borderTopWidth: 0,
262
+ borderRightWidth: 0,
263
+ borderBottomWidth: 0,
264
+ borderLeftWidth: 0
231
265
  }
232
266
  });
233
267
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-image",
3
- "version": "3.0.122",
3
+ "version": "3.0.124",
4
4
  "description": "High-performance native image view for OneKey",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -81,7 +81,7 @@
81
81
  "typescript": "^5.9.2"
82
82
  },
83
83
  "peerDependencies": {
84
- "@onekeyfe/react-native-skeleton": "3.0.122",
84
+ "@onekeyfe/react-native-skeleton": "3.0.124",
85
85
  "react": "*",
86
86
  "react-native": "*",
87
87
  "react-native-nitro-modules": "0.37.0"
package/src/index.tsx CHANGED
@@ -325,6 +325,42 @@ export function OneKeyImage({
325
325
  ].some((radius) => typeof radius === 'number' && radius > 0)
326
326
  );
327
327
  const shouldWrapNative = hasOverlay || hasRoundedCorners;
328
+ const hasBorderWidth = Boolean(
329
+ flattenedStyle &&
330
+ [
331
+ flattenedStyle.borderWidth,
332
+ flattenedStyle.borderTopWidth,
333
+ flattenedStyle.borderRightWidth,
334
+ flattenedStyle.borderBottomWidth,
335
+ flattenedStyle.borderLeftWidth,
336
+ ].some((width) => typeof width === 'number' && width > 0)
337
+ );
338
+ const borderOverlayStyle =
339
+ flattenedStyle && hasRoundedCorners && hasBorderWidth
340
+ ? {
341
+ borderWidth: flattenedStyle.borderWidth,
342
+ borderTopWidth: flattenedStyle.borderTopWidth,
343
+ borderRightWidth: flattenedStyle.borderRightWidth,
344
+ borderBottomWidth: flattenedStyle.borderBottomWidth,
345
+ borderLeftWidth: flattenedStyle.borderLeftWidth,
346
+ borderColor: flattenedStyle.borderColor,
347
+ borderTopColor: flattenedStyle.borderTopColor,
348
+ borderRightColor: flattenedStyle.borderRightColor,
349
+ borderBottomColor: flattenedStyle.borderBottomColor,
350
+ borderLeftColor: flattenedStyle.borderLeftColor,
351
+ borderStyle: flattenedStyle.borderStyle,
352
+ borderRadius: flattenedStyle.borderRadius,
353
+ borderTopLeftRadius: flattenedStyle.borderTopLeftRadius,
354
+ borderTopRightRadius: flattenedStyle.borderTopRightRadius,
355
+ borderBottomLeftRadius: flattenedStyle.borderBottomLeftRadius,
356
+ borderBottomRightRadius: flattenedStyle.borderBottomRightRadius,
357
+ borderStartStartRadius: flattenedStyle.borderStartStartRadius,
358
+ borderStartEndRadius: flattenedStyle.borderStartEndRadius,
359
+ borderEndStartRadius: flattenedStyle.borderEndStartRadius,
360
+ borderEndEndRadius: flattenedStyle.borderEndEndRadius,
361
+ zIndex: 1,
362
+ }
363
+ : undefined;
328
364
 
329
365
  const native = createElement(NativeOneKeyImage, {
330
366
  ...viewProps,
@@ -350,7 +386,14 @@ export function OneKeyImage({
350
386
  if (!shouldWrapNative) return native;
351
387
 
352
388
  return (
353
- <View style={[style, styles.overlayContainer]} collapsable={false}>
389
+ <View
390
+ style={[
391
+ style,
392
+ styles.overlayContainer,
393
+ borderOverlayStyle ? styles.borderlessContainer : undefined,
394
+ ]}
395
+ collapsable={false}
396
+ >
354
397
  {native}
355
398
  {effectiveState === 'loading' && placeholder != null ? (
356
399
  <View pointerEvents="none" style={StyleSheet.absoluteFill}>
@@ -362,6 +405,12 @@ export function OneKeyImage({
362
405
  {fallback}
363
406
  </View>
364
407
  ) : null}
408
+ {borderOverlayStyle ? (
409
+ <View
410
+ pointerEvents="none"
411
+ style={[StyleSheet.absoluteFill, borderOverlayStyle]}
412
+ />
413
+ ) : null}
365
414
  </View>
366
415
  );
367
416
  }
@@ -401,4 +450,11 @@ const styles = StyleSheet.create({
401
450
  overlayContainer: {
402
451
  overflow: 'hidden',
403
452
  },
453
+ borderlessContainer: {
454
+ borderWidth: 0,
455
+ borderTopWidth: 0,
456
+ borderRightWidth: 0,
457
+ borderBottomWidth: 0,
458
+ borderLeftWidth: 0,
459
+ },
404
460
  });