@onekeyfe/react-native-image 3.0.157 → 3.0.158

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.
@@ -115,6 +115,9 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
115
115
  private var requestActive = false
116
116
  private var isResetting = false
117
117
  private var displayState = DisplayState.loading
118
+ /// `.loading` with the previous image kept on screen as the placeholder
119
+ /// (see `showLoading(preservedImage:)`).
120
+ private var isPreservingDisplayedImage = false
118
121
  private var requestStartedAt: CFTimeInterval?
119
122
 
120
123
  var view: UIView { hostView }
@@ -337,8 +340,11 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
337
340
  }
338
341
 
339
342
  let hasCustomIdentity = OneKeyImageRequestContext.headers(from: sourceHeadersJson) != nil
340
- let displaySize = resizeWidth.flatMap { $0.isFinite && $0 > 0 ? CGFloat($0) : nil }
341
- ?? max(hostView.bounds.width, hostView.bounds.height)
343
+ let displaySize = OneKeyImageDecodeSizing.tosDisplaySize(
344
+ bounds: hostView.bounds.size,
345
+ resizeWidth: resizeWidth,
346
+ resizeHeight: resizeHeight
347
+ ) ?? 0
342
348
  let requestURL =
343
349
  (optimizeTos ?? true)
344
350
  ? OneKeyTosURL.optimized(
@@ -439,6 +445,7 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
439
445
  guard self.claimTerminal(generation) else { return }
440
446
  self.requestActive = false
441
447
  self.displayState = .image
448
+ self.isPreservingDisplayedImage = false
442
449
  self.skeletonIndicator.stopAnimatingIndicator()
443
450
  self.fallbackLayer.isHidden = true
444
451
  self.hostView.backgroundColor = .clear
@@ -547,6 +554,7 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
547
554
  optimizeTos = true
548
555
  round = false
549
556
  resizeWidth = nil
557
+ resizeHeight = nil
550
558
  overscan = 1.1
551
559
  loadingStrategy = .static
552
560
  placeholderColor = nil
@@ -577,6 +585,9 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
577
585
  guard Self.isCurrentRequestGeneration(generation, current: requestGeneration) else {
578
586
  return
579
587
  }
588
+ // Also the placeholder write path (SDWebImage hands the preserved image
589
+ // back through setImageBlock when the load starts), so the preservation
590
+ // flag is NOT touched here; it ends with the terminal callbacks.
580
591
  hostView.image = image
581
592
  }
582
593
 
@@ -592,11 +603,19 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
592
603
  if let preservedImage {
593
604
  // Same identity, different thumbnail key: the image already on screen
594
605
  // stands in for the placeholder, so no blank frame and no indicator.
606
+ // The indicator is unmounted, not just stopped: SDWebImage restarts a
607
+ // mounted `sd_imageIndicator` when the load begins, and `applyVariant`
608
+ // must not reinstall it while this state lasts.
609
+ isPreservingDisplayedImage = true
595
610
  hostView.image = preservedImage
596
611
  skeletonIndicator.stopAnimatingIndicator()
612
+ if hostView.sd_imageIndicator != nil {
613
+ hostView.sd_imageIndicator = nil
614
+ }
597
615
  hostView.backgroundColor = .clear
598
616
  return
599
617
  }
618
+ isPreservingDisplayedImage = false
600
619
  hostView.image = nil
601
620
  applyLoadingAppearance(letLibraryStartIndicator: letLibraryStartIndicator)
602
621
  }
@@ -623,8 +642,11 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
623
642
  let rawScreenScale: CGFloat = hostView.window?.screen.scale ?? UIScreen.main.scale
624
643
  let screenScale = min(max(rawScreenScale, 1), 3)
625
644
  let hasCustomIdentity = OneKeyImageRequestContext.headers(from: sourceHeadersJson) != nil
626
- let displaySize = resizeWidth.flatMap { $0.isFinite && $0 > 0 ? CGFloat($0) : nil }
627
- ?? max(hostView.bounds.width, hostView.bounds.height)
645
+ let displaySize = OneKeyImageDecodeSizing.tosDisplaySize(
646
+ bounds: hostView.bounds.size,
647
+ resizeWidth: resizeWidth,
648
+ resizeHeight: resizeHeight
649
+ ) ?? 0
628
650
  let requestURL =
629
651
  (optimizeTos ?? true)
630
652
  ? OneKeyTosURL.optimized(
@@ -679,6 +701,7 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
679
701
  let generation = requestGeneration
680
702
  if requestIsActive, !claimTerminal(generation) { return true }
681
703
  displayState = .image
704
+ isPreservingDisplayedImage = false
682
705
  requestActive = false
683
706
  skeletonIndicator.stopAnimatingIndicator()
684
707
  fallbackLayer.isHidden = true
@@ -716,6 +739,7 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
716
739
  private func showTerminalState(_ state: DisplayState) {
717
740
  displayState = state
718
741
  requestActive = false
742
+ isPreservingDisplayedImage = false
719
743
  skeletonIndicator.stopAnimatingIndicator()
720
744
  resetImageTransition()
721
745
  hostView.image = nil
@@ -733,6 +757,9 @@ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
733
757
  case .image:
734
758
  break
735
759
  case .loading:
760
+ // A preserved image is the placeholder; re-applying the loading look
761
+ // would cover it with the skeleton / placeholder color again.
762
+ if isPreservingDisplayedImage { break }
736
763
  applyLoadingAppearance(letLibraryStartIndicator: false)
737
764
  case .error, .fallback:
738
765
  let hidesTerminalState = loadingStrategy == .none
@@ -610,6 +610,23 @@ enum OneKeyImageDecodeSizing {
610
610
  }
611
611
  }
612
612
 
613
+ /// The logical display size the TOS rendition is picked for. A preload picks
614
+ /// it from the long edge of its `resizeWidth`/`resizeHeight` hint, so a view
615
+ /// carrying the same hint must do the same (before AND after layout) or its
616
+ /// request URL, and with it the cache key, diverges from what the preload
617
+ /// stored. Without a hint the laid-out bounds decide; nil before layout.
618
+ static func tosDisplaySize(
619
+ bounds: CGSize,
620
+ resizeWidth: Double?,
621
+ resizeHeight: Double?
622
+ ) -> CGFloat? {
623
+ if let hint = logicalViewSize(width: resizeWidth, height: resizeHeight) {
624
+ return max(hint.width, hint.height)
625
+ }
626
+ let edge = max(bounds.width, bounds.height)
627
+ return edge.isFinite && edge > 0 ? edge : nil
628
+ }
629
+
613
630
  /// Decode-thumbnail sizes worth probing the memory cache with, most specific
614
631
  /// first. The thumbnail size is part of the SDWebImage cache key, so a probe
615
632
  /// only hits when it reproduces the key some earlier load stored under.
@@ -629,6 +629,55 @@ final class OneKeyImageInfrastructureTests: XCTestCase {
629
629
  )
630
630
  }
631
631
 
632
+ func testRecycledViewForgetsBothResizeHints() throws {
633
+ let image = HybridOneKeyImage()
634
+ image.sourceUri = "https://example.com/hinted.png"
635
+ image.resizeWidth = 48
636
+ image.resizeHeight = 64
637
+ image.prepareForRecycle()
638
+ XCTAssertNil(image.resizeWidth)
639
+ XCTAssertNil(image.resizeHeight)
640
+ XCTAssertNil(image.sourceUri)
641
+ }
642
+
643
+ func testViewAndPreloadPickTheSameTosRenditionForOneHint() throws {
644
+ // Preload: long edge of logicalViewSize(resizeWidth, resizeHeight).
645
+ // View: the same, before and after layout, so the request URL (and with it
646
+ // the cache key) is identical on both sides.
647
+ let raw = try XCTUnwrap(URL(string: "https://common.onekey-asset.com/token.png"))
648
+ let hint = try XCTUnwrap(OneKeyImageDecodeSizing.logicalViewSize(width: 48, height: 64))
649
+ let preloadDisplaySize = max(hint.width, hint.height)
650
+ for bounds in [CGSize.zero, CGSize(width: 48, height: 64), CGSize(width: 30, height: 30)] {
651
+ let viewDisplaySize = try XCTUnwrap(
652
+ OneKeyImageDecodeSizing.tosDisplaySize(bounds: bounds, resizeWidth: 48, resizeHeight: 64)
653
+ )
654
+ XCTAssertEqual(viewDisplaySize, preloadDisplaySize)
655
+ XCTAssertEqual(
656
+ OneKeyTosURL.optimized(
657
+ rawURL: raw, displaySize: viewDisplaySize, scale: 3, overscan: 1.1, hasCustomIdentity: false
658
+ ),
659
+ OneKeyTosURL.optimized(
660
+ rawURL: raw, displaySize: preloadDisplaySize, scale: 3, overscan: 1.1, hasCustomIdentity: false
661
+ )
662
+ )
663
+ }
664
+ // Width-only hint keeps the historical behaviour (the width itself).
665
+ XCTAssertEqual(
666
+ OneKeyImageDecodeSizing.tosDisplaySize(bounds: .zero, resizeWidth: 40, resizeHeight: nil),
667
+ 40
668
+ )
669
+ // No hint: the laid-out bounds decide; nothing before layout.
670
+ XCTAssertEqual(
671
+ OneKeyImageDecodeSizing.tosDisplaySize(
672
+ bounds: CGSize(width: 40, height: 64), resizeWidth: nil, resizeHeight: nil
673
+ ),
674
+ 64
675
+ )
676
+ XCTAssertNil(
677
+ OneKeyImageDecodeSizing.tosDisplaySize(bounds: .zero, resizeWidth: nil, resizeHeight: nil)
678
+ )
679
+ }
680
+
632
681
  func testSupersededRequestNeverPaintsRecycledView() throws {
633
682
  let image = HybridOneKeyImage()
634
683
  let imageView = try XCTUnwrap(image.view as? SDAnimatedImageView)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-image",
3
- "version": "3.0.157",
3
+ "version": "3.0.158",
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.157",
84
+ "@onekeyfe/react-native-skeleton": "3.0.158",
85
85
  "react": "*",
86
86
  "react-native": "*",
87
87
  "react-native-nitro-modules": "0.37.0"