@onekeyfe/react-native-image 6.7.0-alpha.415 → 6.7.0-alpha.418

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 (83) hide show
  1. package/README.md +12 -0
  2. package/android/build.gradle +3 -1
  3. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyIcon.kt +93 -0
  4. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageGlideRegistry.kt +1 -1
  5. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImagePackage.kt +2 -1
  6. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyNamedSVG.kt +217 -0
  7. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeySVG.kt +31 -0
  8. package/ios/OneKeyIcon.swift +90 -0
  9. package/ios/OneKeyImageCoderBridge.h +4 -0
  10. package/ios/OneKeyImageCoderBridge.m +9 -0
  11. package/ios/OneKeyNamedSVG.swift +282 -0
  12. package/ios/tests/OneKeyIconTests.swift +484 -0
  13. package/lib/module/OneKeyIcon.nitro.js +4 -0
  14. package/lib/module/icon/assetUrls.js +13 -0
  15. package/lib/module/icon/dom.js +21 -0
  16. package/lib/module/icon/index.js +29 -0
  17. package/lib/module/icon/index.web.js +32 -0
  18. package/lib/module/icon/props.js +22 -0
  19. package/lib/module/package.json +6 -1
  20. package/lib/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.cpp +67 -0
  21. package/lib/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.hpp +66 -0
  22. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.cpp +79 -0
  23. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.hpp +54 -0
  24. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyIconSpec.kt +65 -0
  25. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconManager.kt +96 -0
  26. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconStateUpdater.kt +23 -0
  27. package/lib/nitrogen/generated/android/onekeyimage+autolinking.cmake +4 -0
  28. package/lib/nitrogen/generated/android/onekeyimageOnLoad.cpp +18 -0
  29. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +17 -0
  30. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +32 -15
  31. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +5 -0
  32. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.mm +8 -0
  33. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.swift +12 -0
  34. package/lib/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.cpp +11 -0
  35. package/lib/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.hpp +89 -0
  36. package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyIconComponent.mm +150 -0
  37. package/lib/nitrogen/generated/ios/swift/HybridOneKeyIconSpec.swift +56 -0
  38. package/lib/nitrogen/generated/ios/swift/HybridOneKeyIconSpec_cxx.swift +193 -0
  39. package/lib/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.cpp +24 -0
  40. package/lib/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.hpp +66 -0
  41. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.cpp +36 -0
  42. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.hpp +88 -0
  43. package/lib/nitrogen/generated/shared/json/OneKeyIconConfig.json +11 -0
  44. package/lib/typescript/src/OneKeyIcon.nitro.d.ts +7 -0
  45. package/lib/typescript/src/icon/assetUrls.d.ts +2 -0
  46. package/lib/typescript/src/icon/dom.d.ts +3 -0
  47. package/lib/typescript/src/icon/index.d.ts +140 -0
  48. package/lib/typescript/src/icon/index.web.d.ts +134 -0
  49. package/lib/typescript/src/icon/props.d.ts +8 -0
  50. package/nitro.json +10 -0
  51. package/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.cpp +67 -0
  52. package/nitrogen/generated/android/c++/JHybridOneKeyIconSpec.hpp +66 -0
  53. package/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.cpp +79 -0
  54. package/nitrogen/generated/android/c++/views/JHybridOneKeyIconStateUpdater.hpp +54 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyIconSpec.kt +65 -0
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconManager.kt +96 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyIconStateUpdater.kt +23 -0
  58. package/nitrogen/generated/android/onekeyimage+autolinking.cmake +4 -0
  59. package/nitrogen/generated/android/onekeyimageOnLoad.cpp +18 -0
  60. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +17 -0
  61. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +32 -15
  62. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +5 -0
  63. package/nitrogen/generated/ios/OneKeyImageAutolinking.mm +8 -0
  64. package/nitrogen/generated/ios/OneKeyImageAutolinking.swift +12 -0
  65. package/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.cpp +11 -0
  66. package/nitrogen/generated/ios/c++/HybridOneKeyIconSpecSwift.hpp +89 -0
  67. package/nitrogen/generated/ios/c++/views/HybridOneKeyIconComponent.mm +150 -0
  68. package/nitrogen/generated/ios/swift/HybridOneKeyIconSpec.swift +56 -0
  69. package/nitrogen/generated/ios/swift/HybridOneKeyIconSpec_cxx.swift +193 -0
  70. package/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.cpp +24 -0
  71. package/nitrogen/generated/shared/c++/HybridOneKeyIconSpec.hpp +66 -0
  72. package/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.cpp +36 -0
  73. package/nitrogen/generated/shared/c++/views/HybridOneKeyIconComponent.hpp +88 -0
  74. package/nitrogen/generated/shared/json/OneKeyIconConfig.json +11 -0
  75. package/package.json +40 -7
  76. package/scripts/configure-icon-imports.mjs +7 -0
  77. package/scripts/icon-fixtures.test.mjs +398 -0
  78. package/src/OneKeyIcon.nitro.ts +8 -0
  79. package/src/icon/assetUrls.ts +13 -0
  80. package/src/icon/dom.ts +21 -0
  81. package/src/icon/index.tsx +38 -0
  82. package/src/icon/index.web.tsx +36 -0
  83. package/src/icon/props.ts +38 -0
@@ -0,0 +1,282 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ private final class OneKeyIconBundleToken: NSObject {}
5
+
6
+ @objc(OneKeyNamedSVGBridge)
7
+ internal final class OneKeyNamedSVGBridge: NSObject {
8
+ @objc(cancelWithOwner:)
9
+ static func cancel(owner: AnyObject) {
10
+ OneKeyNamedSVG.shared.cancel(owner: owner)
11
+ }
12
+
13
+ @objc(requestWithOwner:name:color:width:height:scale:complete:)
14
+ static func request(
15
+ owner: AnyObject,
16
+ name: String,
17
+ color: String,
18
+ width: Int,
19
+ height: Int,
20
+ scale: Double,
21
+ complete: @escaping (UIImage?) -> Void
22
+ ) {
23
+ OneKeyNamedSVG.shared.request(
24
+ owner: owner,
25
+ name: name,
26
+ color: color,
27
+ width: width,
28
+ height: height,
29
+ scale: CGFloat(scale),
30
+ complete: complete
31
+ )
32
+ }
33
+ }
34
+
35
+ /// Process-owned resource metadata and immutable raster cache, independent of JS runtimes.
36
+ public final class OneKeyNamedSVG {
37
+ public static let shared = OneKeyNamedSVG()
38
+ private init() {}
39
+ public enum RecolorMode: String { case fixed, single, multiple, raster }
40
+ public struct IconMetadata {
41
+ public let width: Double
42
+ public let height: Double
43
+ public let recolorMode: RecolorMode
44
+
45
+ public init(width: Double, height: Double, recolorMode: RecolorMode) {
46
+ self.width = width
47
+ self.height = height
48
+ self.recolorMode = recolorMode
49
+ }
50
+ }
51
+ public struct RenderResult {
52
+ public let image: UIImage
53
+ public let mode: RecolorMode
54
+ public let sourceIdentity: String
55
+ public let maskAlpha: CGFloat
56
+
57
+ public func canTint(_ color: String) -> Bool {
58
+ guard let value = OneKeyNamedSVG.channels(color) else { return false }
59
+ return mode == .fixed || mode == .single || (mode == .multiple && value[3] == maskAlpha)
60
+ }
61
+ public func tintColor(_ color: String) -> UIColor? {
62
+ guard mode == .single || mode == .multiple, let value = OneKeyNamedSVG.channels(color) else { return nil }
63
+ return UIColor(red: value[0], green: value[1], blue: value[2], alpha: mode == .single ? value[3] : 1)
64
+ }
65
+ fileprivate func finalImage(_ color: String) -> UIImage {
66
+ guard let tint = tintColor(color) else { return image }
67
+ if OneKeyNamedSVG.channels(color) == [0, 0, 0, 1] { return image }
68
+ // UIImage tint wrappers can retain the original black cgImage. Legacy callers
69
+ // receive actual final-color pixels, so bake the wrapper on the worker.
70
+ let format = UIGraphicsImageRendererFormat(); format.scale = 1; format.preferredRange = .standard
71
+ return UIGraphicsImageRenderer(size: image.size, format: format).image { _ in
72
+ image.withTintColor(tint, renderingMode: .alwaysOriginal).draw(in: CGRect(origin: .zero, size: image.size))
73
+ }
74
+ }
75
+ }
76
+
77
+ private static func channels(_ color: String) -> [CGFloat]? {
78
+ guard color.range(of: #"^rgba\(\d{1,3},\d{1,3},\d{1,3},(?:0(?:\.\d+)?|1(?:\.0+)?)\)$"#, options: .regularExpression) != nil else { return nil }
79
+ let parts = color.dropFirst(5).dropLast().split(separator: ",").compactMap { Double($0) }
80
+ guard parts.count == 4, parts.prefix(3).allSatisfy({ (0...255).contains($0) }), (0...1).contains(parts[3]) else { return nil }
81
+ return parts.enumerated().map { CGFloat($0.offset == 3 ? $0.element : $0.element / 255) }
82
+ }
83
+ public static func isValidColor(_ color: String) -> Bool { channels(color) != nil }
84
+
85
+ private struct Request {
86
+ let name: String
87
+ let color: String
88
+ let width: Int
89
+ let height: Int
90
+ let scale: CGFloat
91
+ let finalColor: Bool
92
+ let complete: (RenderResult?) -> Void
93
+ }
94
+ private let lock = NSLock()
95
+ private let metadataLock = NSLock()
96
+ private var pending: [ObjectIdentifier: Request] = [:]
97
+ private var order: [ObjectIdentifier] = []
98
+ private var workers = 0
99
+ private var metadata: [String: [String: Any]]?
100
+ private var resourceBundle: Bundle?
101
+ private var cache: [String: RenderResult] = [:]
102
+ private var recency: [String] = []
103
+ private var bytes = 0
104
+
105
+ public func cancel(owner: AnyObject) {
106
+ lock.lock(); defer { lock.unlock() }
107
+ let key = ObjectIdentifier(owner)
108
+ pending.removeValue(forKey: key)
109
+ order.removeAll { $0 == key }
110
+ }
111
+
112
+ private func loadManifestIfNeeded() {
113
+ guard metadata == nil else { return }
114
+ let owner = Bundle(for: OneKeyIconBundleToken.self)
115
+ let url = owner.url(forResource: "OneKeyIcons", withExtension: "bundle")
116
+ ?? Bundle.main.url(forResource: "OneKeyIcons", withExtension: "bundle")
117
+ resourceBundle = url.flatMap(Bundle.init(url:))
118
+ if let file = resourceBundle?.url(forResource: "manifest", withExtension: "json"),
119
+ let data = try? Data(contentsOf: file) {
120
+ metadata = (try? JSONSerialization.jsonObject(with: data)) as? [String: [String: Any]]
121
+ }
122
+ if metadata == nil {
123
+ // Hosts own the resources; a missing bundle means every named icon stays blank.
124
+ NSLog("[OneKeyIcon] OneKeyIcons.bundle/manifest.json is missing or invalid; named icons will not render")
125
+ metadata = [:]
126
+ }
127
+ }
128
+
129
+ private static func renderKey(name: String, digest: String, width: Int, height: Int,
130
+ scale: CGFloat, mode: RecolorMode, color: String,
131
+ channels: [CGFloat]) -> (identity: String, key: String, maskAlpha: CGFloat) {
132
+ let identity = "\(name)|\(digest)|\(width)|\(height)|\(scale)"
133
+ let maskAlpha: CGFloat = mode == .multiple ? channels[3] : 1
134
+ let key = "\(identity)|\(mode.rawValue)|\(mode == .raster ? color : mode == .multiple ? String(Double(maskAlpha)) : "")"
135
+ return (identity, key, maskAlpha)
136
+ }
137
+
138
+ /// Main-thread fast path: returns an already-decoded result without disk I/O or
139
+ /// manifest loading, so remounted and recycled views paint in the same frame.
140
+ public func cachedRender(name: String, color: String, width: Int, height: Int,
141
+ scale: CGFloat) -> RenderResult? {
142
+ guard let channels = Self.channels(color), metadataLock.try() else { return nil }
143
+ let entry = metadata?[name]
144
+ metadataLock.unlock()
145
+ guard let digest = entry?["digest"] as? String else { return nil }
146
+ let mode = (entry?["recolorMode"] as? String).flatMap(RecolorMode.init(rawValue:)) ?? .raster
147
+ let key = Self.renderKey(name: name, digest: digest, width: width, height: height, scale: scale,
148
+ mode: mode, color: color, channels: channels).key
149
+ lock.lock(); defer { lock.unlock() }
150
+ guard let hit = cache[key] else { return nil }
151
+ recency.removeAll { $0 == key }; recency.append(key)
152
+ return hit
153
+ }
154
+
155
+ public func iconMetadata(forName name: String) -> IconMetadata? {
156
+ let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
157
+ guard !trimmed.isEmpty else { return nil }
158
+ metadataLock.lock()
159
+ loadManifestIfNeeded()
160
+ let entry = metadata?[trimmed]
161
+ metadataLock.unlock()
162
+ guard let entry else { return nil }
163
+ let mode = (entry["recolorMode"] as? String).flatMap(RecolorMode.init(rawValue:)) ?? .raster
164
+ let width: Double
165
+ let height: Double
166
+ if let vb = entry["viewBox"] as? [NSNumber], vb.count >= 4 {
167
+ let vbWidth = vb[2].doubleValue
168
+ let vbHeight = vb[3].doubleValue
169
+ width = (vbWidth > 0 && vbHeight > 0) ? vbWidth : 24
170
+ height = (vbWidth > 0 && vbHeight > 0) ? vbHeight : 24
171
+ } else {
172
+ width = 24
173
+ height = 24
174
+ }
175
+ return IconMetadata(width: width, height: height, recolorMode: mode)
176
+ }
177
+
178
+ public func request(owner: AnyObject, name: String, color: String, width: Int,
179
+ height: Int, scale: CGFloat, complete: @escaping (UIImage?) -> Void) {
180
+ enqueue(owner: owner, name: name, color: color, width: width, height: height, scale: scale,
181
+ finalColor: true) { complete($0?.image) }
182
+ }
183
+
184
+ public func requestRender(owner: AnyObject, name: String, color: String, width: Int,
185
+ height: Int, scale: CGFloat, complete: @escaping (RenderResult?) -> Void) {
186
+ enqueue(owner: owner, name: name, color: color, width: width, height: height, scale: scale,
187
+ finalColor: false, complete: complete)
188
+ }
189
+
190
+ private func enqueue(owner: AnyObject, name: String, color: String, width: Int,
191
+ height: Int, scale: CGFloat, finalColor: Bool, complete: @escaping (RenderResult?) -> Void) {
192
+ lock.lock(); defer { lock.unlock() }
193
+ let key = ObjectIdentifier(owner)
194
+ if pending[key] == nil { order.append(key) }
195
+ pending[key] = Request(name: name, color: color, width: width, height: height, scale: scale,
196
+ finalColor: finalColor, complete: complete)
197
+ while workers < 2 && workers < pending.count {
198
+ workers += 1
199
+ DispatchQueue.global(qos: .userInitiated).async { self.drain() }
200
+ }
201
+ }
202
+
203
+ private func drain() {
204
+ while true {
205
+ lock.lock()
206
+ guard !order.isEmpty, let request = pending.removeValue(forKey: order.removeFirst()) else {
207
+ workers -= 1; lock.unlock(); return
208
+ }
209
+ lock.unlock()
210
+ let image = autoreleasepool { () -> RenderResult? in
211
+ guard let result = decode(request) else { return nil }
212
+ if !request.finalColor { return result }
213
+ return RenderResult(image: result.finalImage(request.color), mode: .raster,
214
+ sourceIdentity: result.sourceIdentity, maskAlpha: result.maskAlpha)
215
+ }
216
+ DispatchQueue.main.async { request.complete(image) }
217
+ }
218
+ }
219
+
220
+ private func decode(_ request: Request) -> RenderResult? {
221
+ guard (1...4096).contains(request.width), (1...4096).contains(request.height),
222
+ request.width * request.height <= 4_194_304, request.scale.isFinite,
223
+ request.scale > 0,
224
+ let channels = Self.channels(request.color) else { return nil }
225
+ metadataLock.lock()
226
+ loadManifestIfNeeded()
227
+ let entry = metadata?[request.name]
228
+ let bundle = resourceBundle
229
+ metadataLock.unlock()
230
+ guard let digest = entry?["digest"] as? String,
231
+ let url = bundle?.url(forResource: request.name, withExtension: "svg") else { return nil }
232
+ let mode = (entry?["recolorMode"] as? String).flatMap(RecolorMode.init(rawValue:)) ?? .raster
233
+ let (sourceIdentity, key, maskAlpha) = Self.renderKey(
234
+ name: request.name, digest: digest, width: request.width, height: request.height,
235
+ scale: request.scale, mode: mode, color: request.color, channels: channels)
236
+ let paintColor: String
237
+ switch mode {
238
+ case .fixed, .single: paintColor = "rgba(0,0,0,1)"
239
+ case .multiple: paintColor = "rgba(0,0,0,\(maskAlpha))"
240
+ case .raster: paintColor = request.color
241
+ }
242
+ lock.lock()
243
+ if let image = cache[key] {
244
+ recency.removeAll { $0 == key }; recency.append(key)
245
+ lock.unlock(); return image
246
+ }
247
+ lock.unlock()
248
+ guard let source = try? String(contentsOf: url, encoding: .utf8),
249
+ let pattern = try? NSRegularExpression(pattern: #"((?:fill|stroke)\s*=\s*["'])currentColor(["'])"#) else { return nil }
250
+ // CoreSVG interprets integer rgba alpha as an 8-bit channel; decimals retain CSS endpoints.
251
+ let svgColor = paintColor.replacingOccurrences(of: #",(0|1)\)$"#, with: ",$1.0)", options: .regularExpression)
252
+ let xml = pattern.stringByReplacingMatches(in: source, range: NSRange(source.startIndex..., in: source),
253
+ withTemplate: "$1\(svgColor)$2")
254
+ guard let data = xml.data(using: .utf8),
255
+ let image = OneKeyImageCoderBridge.decodeSVG(data, pixelSize: CGSize(width: request.width, height: request.height)),
256
+ image.size.width > 0, image.size.height > 0 else { return nil }
257
+ let size = CGSize(width: request.width, height: request.height)
258
+ let format = UIGraphicsImageRendererFormat()
259
+ format.scale = 1
260
+ format.preferredRange = .standard
261
+ let output = UIGraphicsImageRenderer(size: size, format: format).image { _ in
262
+ let ratio = min(size.width / image.size.width, size.height / image.size.height)
263
+ let fitted = CGSize(width: image.size.width * ratio, height: image.size.height * ratio)
264
+ image.draw(in: CGRect(x: (size.width - fitted.width) / 2, y: (size.height - fitted.height) / 2,
265
+ width: fitted.width, height: fitted.height))
266
+ }
267
+ let result = RenderResult(image: output, mode: mode, sourceIdentity: sourceIdentity, maskAlpha: maskAlpha)
268
+ let cost = (output.cgImage?.bytesPerRow ?? request.width * 4) * request.height
269
+ if cost <= 8_388_608 {
270
+ lock.lock(); defer { lock.unlock() }
271
+ if let previous = cache[key] { bytes -= (previous.image.cgImage?.bytesPerRow ?? request.width * 4) * request.height }
272
+ cache[key] = result
273
+ recency.removeAll { $0 == key }; recency.append(key)
274
+ bytes += cost
275
+ while cache.count > 128 || bytes > 8_388_608 {
276
+ let oldest = recency.removeFirst()
277
+ if let image = cache.removeValue(forKey: oldest) { bytes -= (image.image.cgImage?.bytesPerRow ?? 0) * (image.image.cgImage?.height ?? 0) }
278
+ }
279
+ }
280
+ return result
281
+ }
282
+ }