@onekeyfe/react-native-image 3.0.98

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 (186) hide show
  1. package/LICENSE +21 -0
  2. package/OneKeyImage.podspec +32 -0
  3. package/README.md +88 -0
  4. package/android/CMakeLists.txt +12 -0
  5. package/android/build.gradle +85 -0
  6. package/android/consumer-rules.pro +10 -0
  7. package/android/gradle.properties +5 -0
  8. package/android/src/main/AndroidManifest.xml +1 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  10. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImage.kt +616 -0
  11. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageCache.kt +131 -0
  12. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensions.kt +169 -0
  13. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageGlideRegistry.kt +285 -0
  14. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageModel.kt +68 -0
  15. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImagePackage.kt +28 -0
  16. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageSafety.kt +1120 -0
  17. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyTosUrl.kt +93 -0
  18. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyAnimationTimelineInspectorTest.kt +210 -0
  19. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageCacheTypeTest.kt +16 -0
  20. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensionsTest.kt +70 -0
  21. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageModelTest.kt +65 -0
  22. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageRequestSignatureTest.kt +33 -0
  23. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageSafetyTest.kt +346 -0
  24. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyResourceCleanupTest.kt +43 -0
  25. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTerminalEventPairTest.kt +38 -0
  26. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTosUrlTest.kt +82 -0
  27. package/ios/OneKeyImage.swift +636 -0
  28. package/ios/OneKeyImageCache.swift +215 -0
  29. package/ios/OneKeyImageRequestContext.swift +781 -0
  30. package/ios/OneKeyTosURL.swift +69 -0
  31. package/ios/tests/OneKeyImageInfrastructureTests.swift +555 -0
  32. package/ios/tests/OneKeyTosURLTests.swift +142 -0
  33. package/lib/module/OneKeyImage.nitro.js +30 -0
  34. package/lib/module/index.js +223 -0
  35. package/lib/module/package.json +1 -0
  36. package/lib/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  37. package/lib/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
  38. package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  39. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
  40. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
  41. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
  42. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
  43. package/lib/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
  44. package/lib/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
  45. package/lib/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
  46. package/lib/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
  47. package/lib/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
  48. package/lib/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
  49. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
  50. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
  51. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
  52. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
  53. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
  54. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
  55. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
  56. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
  57. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
  58. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
  59. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
  60. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
  61. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
  62. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
  63. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
  64. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
  65. package/lib/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
  66. package/lib/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
  67. package/lib/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
  68. package/lib/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
  69. package/lib/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
  70. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
  71. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
  72. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
  73. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
  74. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
  75. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
  76. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
  77. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
  78. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
  79. package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
  80. package/lib/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  81. package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  82. package/lib/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
  83. package/lib/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  84. package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  85. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
  86. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
  87. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
  88. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
  89. package/lib/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
  90. package/lib/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
  91. package/lib/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
  92. package/lib/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
  93. package/lib/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
  94. package/lib/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
  95. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
  96. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
  97. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
  98. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
  99. package/lib/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
  100. package/lib/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
  101. package/lib/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
  102. package/lib/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
  103. package/lib/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
  104. package/lib/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
  105. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
  106. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
  107. package/lib/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
  108. package/lib/typescript/package.json +1 -0
  109. package/lib/typescript/src/OneKeyImage.nitro.d.ts +67 -0
  110. package/lib/typescript/src/index.d.ts +55 -0
  111. package/nitro.json +31 -0
  112. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  113. package/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
  114. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  115. package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
  116. package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
  117. package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
  118. package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
  119. package/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
  120. package/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
  121. package/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
  122. package/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
  123. package/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
  124. package/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
  125. package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
  126. package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
  127. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
  128. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
  129. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
  130. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
  131. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
  132. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
  133. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
  134. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
  135. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
  141. package/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
  142. package/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
  143. package/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
  144. package/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
  145. package/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
  146. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
  147. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
  148. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
  149. package/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
  150. package/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
  151. package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
  152. package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
  153. package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
  154. package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
  155. package/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
  156. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  157. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  158. package/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
  159. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  160. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  161. package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
  162. package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
  163. package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
  164. package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
  165. package/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
  166. package/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
  167. package/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
  168. package/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
  169. package/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
  170. package/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
  171. package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
  172. package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
  173. package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
  174. package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
  175. package/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
  176. package/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
  177. package/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
  178. package/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
  179. package/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
  180. package/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
  181. package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
  182. package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
  183. package/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
  184. package/package.json +128 -0
  185. package/src/OneKeyImage.nitro.ts +86 -0
  186. package/src/index.tsx +376 -0
@@ -0,0 +1,636 @@
1
+ import Foundation
2
+ import NitroModules
3
+ import SDWebImage
4
+ import Skeleton
5
+ import UIKit
6
+
7
+ private final class OneKeyImageHostView: SDAnimatedImageView {
8
+ var onLayout: (() -> Void)?
9
+ var onWindowChanged: ((Bool) -> Void)?
10
+
11
+ override func layoutSubviews() {
12
+ super.layoutSubviews()
13
+ onLayout?()
14
+ }
15
+
16
+ override func didMoveToWindow() {
17
+ super.didMoveToWindow()
18
+ onWindowChanged?(window != nil)
19
+ }
20
+ }
21
+
22
+ private final class OneKeyImageSkeletonView: UIView {
23
+ private lazy var renderer = OneKeySkeletonRenderer(hostLayer: layer)
24
+ private var requestedRunning = false
25
+
26
+ override init(frame: CGRect) {
27
+ super.init(frame: frame)
28
+ isUserInteractionEnabled = false
29
+ backgroundColor = .clear
30
+ isHidden = true
31
+ }
32
+
33
+ required init?(coder: NSCoder) {
34
+ fatalError("init(coder:) has not been implemented")
35
+ }
36
+
37
+ override func layoutSubviews() {
38
+ super.layoutSubviews()
39
+ renderer.update(width: bounds.width, height: bounds.height)
40
+ }
41
+
42
+ override func didMoveToWindow() {
43
+ super.didMoveToWindow()
44
+ syncRenderer()
45
+ }
46
+
47
+ func setRunning(_ running: Bool) {
48
+ requestedRunning = running
49
+ isHidden = !running
50
+ syncRenderer()
51
+ }
52
+
53
+ private func syncRenderer() {
54
+ if requestedRunning, window != nil, !bounds.isEmpty {
55
+ renderer.update(width: bounds.width, height: bounds.height)
56
+ renderer.start()
57
+ } else {
58
+ renderer.stop()
59
+ }
60
+ }
61
+ }
62
+
63
+ private final class OneKeyImageSkeletonIndicator: NSObject, SDWebImageIndicator {
64
+ private let skeletonView = OneKeyImageSkeletonView()
65
+
66
+ var indicatorView: UIView { skeletonView }
67
+
68
+ func startAnimatingIndicator() {
69
+ skeletonView.setRunning(true)
70
+ }
71
+
72
+ func stopAnimatingIndicator() {
73
+ skeletonView.setRunning(false)
74
+ }
75
+
76
+ func updateFrame(_ frame: CGRect) {
77
+ skeletonView.frame = frame
78
+ }
79
+ }
80
+
81
+ final class HybridOneKeyImage: HybridOneKeyImageSpec, RecyclableView {
82
+ private enum DisplayState { case loading, image, error, fallback }
83
+
84
+ private let hostView = OneKeyImageHostView()
85
+ private let skeletonIndicator = OneKeyImageSkeletonIndicator()
86
+ private let fallbackLayer = CATextLayer()
87
+ private var loadWorkItem: DispatchWorkItem?
88
+ private var displayWorkItem: DispatchWorkItem?
89
+ private var requestGeneration: UInt64 = 0
90
+ private var terminalGeneration: UInt64?
91
+ private var activeSafetyHandle: OneKeyImageSafetyFlightHandle?
92
+ private var lastRequestSignature: String?
93
+ private var pendingDisplayGeneration: UInt64?
94
+ private var attached = false
95
+ private var requestActive = false
96
+ private var isResetting = false
97
+ private var displayState = DisplayState.loading
98
+
99
+ var view: UIView { hostView }
100
+
101
+ var sourceUri: String? {
102
+ didSet { if sourceUri != oldValue { identityDidChange() } }
103
+ }
104
+ var sourceHeadersJson: String? {
105
+ didSet { if sourceHeadersJson != oldValue { identityDidChange() } }
106
+ }
107
+ var variant: OneKeyImageVariant? = .generic { didSet { applyVariant() } }
108
+ var contentFit: OneKeyImageContentFit? = .cover {
109
+ didSet {
110
+ applyContentFit()
111
+ if contentFit != oldValue { identityDidChange() }
112
+ }
113
+ }
114
+ var cachePolicy: OneKeyImageCachePolicy? = .memoryDisk {
115
+ didSet { if cachePolicy != oldValue { identityDidChange() } }
116
+ }
117
+ var autoplay: Bool? = true { didSet { applyAutoplay() } }
118
+ var recyclingKey: String? {
119
+ didSet { if recyclingKey != oldValue { identityDidChange() } }
120
+ }
121
+ var optimizeTos: Bool? = true {
122
+ didSet { if optimizeTos != oldValue { identityDidChange() } }
123
+ }
124
+ var overscan: Double? = 1.1 {
125
+ didSet {
126
+ if !Self.equalOptionalDouble(overscan, oldValue) { identityDidChange() }
127
+ }
128
+ }
129
+ var loadingStrategy: OneKeyImageLoadingStrategy? = .static { didSet { applyVariant() } }
130
+ var onLoadStart: (() -> Void)?
131
+ var onLoad: ((_ width: Double, _ height: Double, _ cacheType: OneKeyImageCacheType) -> Void)?
132
+ var onDisplay: (() -> Void)?
133
+ var onError: ((_ message: String) -> Void)?
134
+ var onLoadEnd: (() -> Void)?
135
+
136
+ override init() {
137
+ super.init()
138
+ hostView.clipsToBounds = true
139
+ hostView.maxBufferSize = OneKeyImageSafetyPolicy.maximumAnimatedBufferBytes
140
+ hostView.clearBufferWhenStopped = true
141
+ hostView.onLayout = { [weak self] in
142
+ guard let self else { return }
143
+ self.skeletonIndicator.updateFrame(self.hostView.bounds)
144
+ self.layoutFallbackLayer()
145
+ self.scheduleLoad()
146
+ }
147
+ hostView.onWindowChanged = { [weak self] attached in
148
+ guard let self else { return }
149
+ self.attached = attached
150
+ self.applyAutoplay()
151
+ if attached {
152
+ self.scheduleLoad()
153
+ self.schedulePendingDisplayIfNeeded()
154
+ }
155
+ }
156
+ fallbackLayer.alignmentMode = .center
157
+ fallbackLayer.contentsScale = UIScreen.main.scale
158
+ hostView.layer.addSublayer(fallbackLayer)
159
+ applyContentFit()
160
+ applyAutoplay()
161
+ applyVariant()
162
+ }
163
+
164
+ deinit {
165
+ cancelCurrentRequest(invalidateGeneration: true)
166
+ }
167
+
168
+ func afterUpdate() {
169
+ scheduleLoad()
170
+ }
171
+
172
+ func reload() throws {
173
+ runOnMainSync {
174
+ self.lastRequestSignature = nil
175
+ self.scheduleLoad(force: true)
176
+ }
177
+ }
178
+
179
+ func cancel() throws {
180
+ runOnMainSync {
181
+ self.cancelCurrentRequest(invalidateGeneration: true)
182
+ self.lastRequestSignature = nil
183
+ self.showLoading(requestIsActive: false, letLibraryStartIndicator: false)
184
+ }
185
+ }
186
+
187
+ func prepareForRecycle() {
188
+ runOnMainSync { self.resetForReuse() }
189
+ }
190
+
191
+ func onDropView() {
192
+ runOnMainSync {
193
+ self.cancelCurrentRequest(invalidateGeneration: true)
194
+ self.lastRequestSignature = nil
195
+ self.showLoading(requestIsActive: false, letLibraryStartIndicator: false)
196
+ }
197
+ }
198
+
199
+ private func scheduleLoad(force: Bool = false) {
200
+ guard !isResetting else { return }
201
+ loadWorkItem?.cancel()
202
+ let work = DispatchWorkItem { [weak self] in self?.startLoad(force: force) }
203
+ loadWorkItem = work
204
+ DispatchQueue.main.async(execute: work)
205
+ }
206
+
207
+ private func identityDidChange() {
208
+ guard !isResetting else { return }
209
+ runOnMainSync {
210
+ self.cancelCurrentRequest(invalidateGeneration: true)
211
+ self.lastRequestSignature = nil
212
+ if let source = self.sourceUri, !source.isEmpty {
213
+ self.showLoading(requestIsActive: false, letLibraryStartIndicator: false)
214
+ } else {
215
+ self.showFallback()
216
+ }
217
+ self.scheduleLoad()
218
+ }
219
+ }
220
+
221
+ private func startLoad(force: Bool) {
222
+ guard !hostView.bounds.isEmpty else {
223
+ cancelCurrentRequest(invalidateGeneration: true)
224
+ lastRequestSignature = nil
225
+ showLoading(requestIsActive: false, letLibraryStartIndicator: false)
226
+ return
227
+ }
228
+ guard let rawString = sourceUri, !rawString.isEmpty else {
229
+ cancelCurrentRequest(invalidateGeneration: true)
230
+ lastRequestSignature = nil
231
+ showFallback()
232
+ return
233
+ }
234
+
235
+ let rawScreenScale: CGFloat = hostView.window?.screen.scale ?? UIScreen.main.scale
236
+ let screenScale = min(max(rawScreenScale, 1), 3)
237
+ let signatureParts: [String] = [
238
+ rawString,
239
+ sourceHeadersJson ?? "",
240
+ recyclingKey ?? "",
241
+ cachePolicy?.stringValue ?? "memory-disk",
242
+ contentFit?.stringValue ?? "cover",
243
+ String(optimizeTos ?? true),
244
+ String(overscan ?? 1.1),
245
+ String(Int(hostView.bounds.width.rounded())),
246
+ String(Int(hostView.bounds.height.rounded())),
247
+ String(Double(screenScale)),
248
+ ]
249
+ let signature = signatureParts.joined(separator: "|")
250
+ if !force, signature == lastRequestSignature { return }
251
+ lastRequestSignature = signature
252
+
253
+ cancelCurrentRequest(invalidateGeneration: true)
254
+ let generation = requestGeneration
255
+ showLoading(requestIsActive: true, letLibraryStartIndicator: true)
256
+ onLoadStart?()
257
+ guard Self.isCurrentRequestGeneration(generation, current: requestGeneration) else { return }
258
+
259
+ if let violation = OneKeyImageSafetyPolicy.preflight(source: rawString) {
260
+ finishWithError(violation, generation: generation)
261
+ return
262
+ }
263
+ guard let rawURL = URL(string: rawString) else {
264
+ finishWithError(URLError(.badURL), generation: generation)
265
+ return
266
+ }
267
+ if let violation = OneKeyImageSafetyPolicy.preflight(url: rawURL) {
268
+ finishWithError(violation, generation: generation)
269
+ return
270
+ }
271
+
272
+ let hasCustomIdentity = OneKeyImageRequestContext.headers(from: sourceHeadersJson) != nil
273
+ let requestURL =
274
+ (optimizeTos ?? true)
275
+ ? OneKeyTosURL.optimized(
276
+ rawURL: rawURL,
277
+ displaySize: max(hostView.bounds.width, hostView.bounds.height),
278
+ scale: screenScale,
279
+ overscan: overscan ?? 1.1,
280
+ hasCustomIdentity: hasCustomIdentity
281
+ )
282
+ : rawURL
283
+ performRequest(
284
+ url: requestURL,
285
+ rawURL: rawURL,
286
+ generation: generation,
287
+ mayFallbackToRaw: requestURL != rawURL
288
+ )
289
+ }
290
+
291
+ private func performRequest(
292
+ url: URL,
293
+ rawURL: URL,
294
+ generation: UInt64,
295
+ mayFallbackToRaw: Bool
296
+ ) {
297
+ guard Self.isCurrentRequestGeneration(generation, current: requestGeneration) else { return }
298
+ let rawScreenScale: CGFloat = hostView.window?.screen.scale ?? UIScreen.main.scale
299
+ let screenScale = min(max(rawScreenScale, 1), 3)
300
+ let thumbnailPixelSize = OneKeyImageDecodeSizing.thumbnailPixelSize(
301
+ viewSize: hostView.bounds.size,
302
+ scale: screenScale,
303
+ contentFit: contentFit ?? .cover
304
+ )
305
+ let headers = OneKeyImageRequestContext.headers(from: sourceHeadersJson)
306
+ let safetyHandle = OneKeyImageSafetyFlightCoordinator.shared.acquire(
307
+ url: url,
308
+ headers: headers
309
+ )
310
+ activeSafetyHandle = safetyHandle
311
+ let context = OneKeyImageRequestContext.make(
312
+ headersJson: sourceHeadersJson,
313
+ cachePolicy: cachePolicy ?? .memoryDisk,
314
+ thumbnailPixelSize: thumbnailPixelSize,
315
+ safetyTracker: safetyHandle.tracker,
316
+ manager: safetyHandle.manager
317
+ )
318
+ hostView.sd_setImage(
319
+ with: url,
320
+ placeholderImage: nil,
321
+ options: OneKeyImageRequestContext.renderOptions,
322
+ context: context,
323
+ progress: { [weak self] receivedSize, _, _ in
324
+ guard safetyHandle.tracker.inspectReceivedByteCount(Int64(receivedSize)) else { return }
325
+ DispatchQueue.main.async { [weak self] in
326
+ guard let self, self.requestGeneration == generation else { return }
327
+ self.hostView.sd_cancelCurrentImageLoad()
328
+ }
329
+ },
330
+ completed: { [weak self] image, error, cacheType, _ in
331
+ guard let self, self.requestGeneration == generation else {
332
+ safetyHandle.finish()
333
+ return
334
+ }
335
+ if let image,
336
+ let violation = OneKeyImageSafetyPolicy.violation(for: image)
337
+ {
338
+ safetyHandle.tracker.record(violation)
339
+ }
340
+ let safetyViolation = safetyHandle.tracker.violation
341
+ self.finishSafetyHandle(safetyHandle)
342
+ if image == nil || error != nil || safetyViolation != nil {
343
+ if Self.shouldRetryRaw(
344
+ mayFallbackToRaw: mayFallbackToRaw,
345
+ safetyViolation: safetyViolation
346
+ ) {
347
+ self.performRequest(
348
+ url: rawURL,
349
+ rawURL: rawURL,
350
+ generation: generation,
351
+ mayFallbackToRaw: false
352
+ )
353
+ return
354
+ }
355
+ self.finishWithError(safetyViolation ?? error, generation: generation)
356
+ return
357
+ }
358
+ guard self.claimTerminal(generation) else { return }
359
+ self.requestActive = false
360
+ self.displayState = .image
361
+ self.skeletonIndicator.stopAnimatingIndicator()
362
+ self.fallbackLayer.isHidden = true
363
+ self.hostView.backgroundColor = .clear
364
+ self.applyAutoplay()
365
+ let onLoad = self.onLoad
366
+ let onLoadEnd = self.onLoadEnd
367
+ Self.deliverTerminalCallbacks(
368
+ primary: {
369
+ onLoad?(
370
+ Double(image?.size.width ?? 0),
371
+ Double(image?.size.height ?? 0),
372
+ Self.cacheType(cacheType)
373
+ )
374
+ },
375
+ onLoadEnd: { onLoadEnd?() }
376
+ )
377
+ guard self.requestGeneration == generation else { return }
378
+ self.pendingDisplayGeneration = generation
379
+ self.schedulePendingDisplayIfNeeded()
380
+ }
381
+ )
382
+ }
383
+
384
+ private func finishWithError(_ error: Error?, generation: UInt64) {
385
+ guard claimTerminal(generation) else { return }
386
+ showError()
387
+ let onError = onError
388
+ let onLoadEnd = onLoadEnd
389
+ Self.deliverTerminalCallbacks(
390
+ primary: { onError?(error?.localizedDescription ?? "Image request failed") },
391
+ onLoadEnd: { onLoadEnd?() }
392
+ )
393
+ }
394
+
395
+ private func claimTerminal(_ generation: UInt64) -> Bool {
396
+ guard
397
+ Self.canFinishTerminal(
398
+ generation,
399
+ current: requestGeneration,
400
+ terminalGeneration: terminalGeneration
401
+ )
402
+ else {
403
+ return false
404
+ }
405
+ terminalGeneration = generation
406
+ return true
407
+ }
408
+
409
+ private func cancelCurrentRequest(invalidateGeneration: Bool) {
410
+ loadWorkItem?.cancel()
411
+ loadWorkItem = nil
412
+ displayWorkItem?.cancel()
413
+ displayWorkItem = nil
414
+ pendingDisplayGeneration = nil
415
+ hostView.sd_cancelCurrentImageLoad()
416
+ activeSafetyHandle?.finish()
417
+ activeSafetyHandle = nil
418
+ requestActive = false
419
+ skeletonIndicator.stopAnimatingIndicator()
420
+ if invalidateGeneration { requestGeneration &+= 1 }
421
+ }
422
+
423
+ private func finishSafetyHandle(_ handle: OneKeyImageSafetyFlightHandle) {
424
+ handle.finish()
425
+ if activeSafetyHandle === handle {
426
+ activeSafetyHandle = nil
427
+ }
428
+ }
429
+
430
+ private func schedulePendingDisplayIfNeeded() {
431
+ guard let generation = pendingDisplayGeneration else { return }
432
+ displayWorkItem?.cancel()
433
+ let work = DispatchWorkItem { [weak self] in
434
+ guard let self,
435
+ self.pendingDisplayGeneration == generation,
436
+ self.requestGeneration == generation,
437
+ self.displayState == .image,
438
+ self.hostView.image != nil,
439
+ self.attached,
440
+ self.hostView.window != nil
441
+ else {
442
+ return
443
+ }
444
+ self.pendingDisplayGeneration = nil
445
+ self.displayWorkItem = nil
446
+ self.onDisplay?()
447
+ }
448
+ displayWorkItem = work
449
+ DispatchQueue.main.async(execute: work)
450
+ }
451
+
452
+ private func resetForReuse() {
453
+ cancelCurrentRequest(invalidateGeneration: true)
454
+ isResetting = true
455
+ sourceUri = nil
456
+ sourceHeadersJson = nil
457
+ variant = .generic
458
+ contentFit = .cover
459
+ cachePolicy = .memoryDisk
460
+ autoplay = true
461
+ recyclingKey = nil
462
+ optimizeTos = true
463
+ overscan = 1.1
464
+ loadingStrategy = .static
465
+ onLoadStart = nil
466
+ onLoad = nil
467
+ onDisplay = nil
468
+ onError = nil
469
+ onLoadEnd = nil
470
+ isResetting = false
471
+ lastRequestSignature = nil
472
+ showLoading(requestIsActive: false, letLibraryStartIndicator: false)
473
+ }
474
+
475
+ private func runOnMainSync(_ action: () -> Void) {
476
+ if Thread.isMainThread {
477
+ action()
478
+ } else {
479
+ DispatchQueue.main.sync(execute: action)
480
+ }
481
+ }
482
+
483
+ private func showLoading(
484
+ requestIsActive: Bool,
485
+ letLibraryStartIndicator: Bool
486
+ ) {
487
+ displayState = .loading
488
+ requestActive = requestIsActive
489
+ hostView.image = nil
490
+ fallbackLayer.isHidden = true
491
+ applyLoadingAppearance(letLibraryStartIndicator: letLibraryStartIndicator)
492
+ }
493
+
494
+ private func showError() {
495
+ showTerminalState(.error)
496
+ }
497
+
498
+ private func showFallback() {
499
+ showTerminalState(.fallback)
500
+ }
501
+
502
+ private func showTerminalState(_ state: DisplayState) {
503
+ displayState = state
504
+ requestActive = false
505
+ skeletonIndicator.stopAnimatingIndicator()
506
+ hostView.image = nil
507
+ hostView.backgroundColor = placeholderColor
508
+ fallbackLayer.string = stateSymbol
509
+ fallbackLayer.fontSize = min(hostView.bounds.width, hostView.bounds.height) * 0.35
510
+ layoutFallbackLayer()
511
+ fallbackLayer.foregroundColor = UIColor.secondaryLabel.cgColor
512
+ fallbackLayer.isHidden = false
513
+ }
514
+
515
+ private func applyVariant() {
516
+ switch displayState {
517
+ case .image:
518
+ break
519
+ case .loading:
520
+ applyLoadingAppearance(letLibraryStartIndicator: false)
521
+ case .error, .fallback:
522
+ hostView.backgroundColor = placeholderColor
523
+ fallbackLayer.string = stateSymbol
524
+ }
525
+ }
526
+
527
+ private func applyLoadingAppearance(letLibraryStartIndicator: Bool) {
528
+ let strategy = loadingStrategy ?? .static
529
+ hostView.backgroundColor = strategy == .none ? .clear : placeholderColor
530
+
531
+ if strategy == .skeleton {
532
+ if hostView.sd_imageIndicator !== skeletonIndicator {
533
+ hostView.sd_imageIndicator = skeletonIndicator
534
+ }
535
+ skeletonIndicator.updateFrame(hostView.bounds)
536
+ if requestActive, !letLibraryStartIndicator {
537
+ skeletonIndicator.startAnimatingIndicator()
538
+ }
539
+ } else {
540
+ skeletonIndicator.stopAnimatingIndicator()
541
+ if hostView.sd_imageIndicator != nil {
542
+ hostView.sd_imageIndicator = nil
543
+ }
544
+ }
545
+ }
546
+
547
+ private func layoutFallbackLayer() {
548
+ let height = max(fallbackLayer.fontSize * 1.3, 1)
549
+ fallbackLayer.frame = CGRect(
550
+ x: 0,
551
+ y: (hostView.bounds.height - height) / 2,
552
+ width: hostView.bounds.width,
553
+ height: height
554
+ )
555
+ }
556
+
557
+ private var placeholderColor: UIColor {
558
+ switch variant ?? .generic {
559
+ case .generic: return UIColor(white: 0.91, alpha: 1)
560
+ case .token: return UIColor(red: 0.91, green: 0.93, blue: 0.98, alpha: 1)
561
+ case .network: return UIColor(red: 0.90, green: 0.94, blue: 0.96, alpha: 1)
562
+ case .avatar: return UIColor(red: 0.92, green: 0.92, blue: 0.94, alpha: 1)
563
+ }
564
+ }
565
+
566
+ private var stateSymbol: String {
567
+ switch variant ?? .generic {
568
+ case .generic: return "◇"
569
+ case .token: return "◈"
570
+ case .network: return "◎"
571
+ case .avatar: return "●"
572
+ }
573
+ }
574
+
575
+ private func applyContentFit() {
576
+ switch contentFit ?? .cover {
577
+ case .cover: hostView.contentMode = .scaleAspectFill
578
+ case .contain: hostView.contentMode = .scaleAspectFit
579
+ case .fill: hostView.contentMode = .scaleToFill
580
+ case .center: hostView.contentMode = .center
581
+ }
582
+ }
583
+
584
+ private func applyAutoplay() {
585
+ hostView.autoPlayAnimatedImage = autoplay ?? true
586
+ if autoplay == false || !attached {
587
+ hostView.stopAnimating()
588
+ } else {
589
+ hostView.startAnimating()
590
+ }
591
+ }
592
+
593
+ private static func cacheType(_ cacheType: SDImageCacheType) -> OneKeyImageCacheType {
594
+ switch cacheType {
595
+ case .none: return .none
596
+ case .memory: return .memory
597
+ case .disk, .all: return .disk
598
+ @unknown default: return .none
599
+ }
600
+ }
601
+
602
+ private static func equalOptionalDouble(_ lhs: Double?, _ rhs: Double?) -> Bool {
603
+ switch (lhs, rhs) {
604
+ case (nil, nil): return true
605
+ case (let lhs?, let rhs?): return lhs == rhs || (lhs.isNaN && rhs.isNaN)
606
+ default: return false
607
+ }
608
+ }
609
+
610
+ static func isCurrentRequestGeneration(_ generation: UInt64, current: UInt64) -> Bool {
611
+ generation == current
612
+ }
613
+
614
+ static func canFinishTerminal(
615
+ _ generation: UInt64,
616
+ current: UInt64,
617
+ terminalGeneration: UInt64?
618
+ ) -> Bool {
619
+ generation == current && terminalGeneration != generation
620
+ }
621
+
622
+ static func deliverTerminalCallbacks(
623
+ primary: () -> Void,
624
+ onLoadEnd: () -> Void
625
+ ) {
626
+ primary()
627
+ onLoadEnd()
628
+ }
629
+
630
+ static func shouldRetryRaw(
631
+ mayFallbackToRaw: Bool,
632
+ safetyViolation: OneKeyImageSafetyViolation?
633
+ ) -> Bool {
634
+ mayFallbackToRaw && safetyViolation == nil
635
+ }
636
+ }