@onekeyfe/react-native-native-list 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 (121) hide show
  1. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +5 -0
  2. package/android/src/main/java/com/onekey/nativelist/NativeListGeneratedIcons.kt +47 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +5 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +120 -338
  5. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +5 -0
  6. package/docs/SPEC.md +122 -7
  7. package/ios/NativeListAccessoryStack.swift +6 -16
  8. package/ios/NativeListDataRowRenderer.swift +15 -6
  9. package/ios/NativeListDesignAssets.swift +13 -84
  10. package/ios/NativeListGeneratedIcons.swift +55 -0
  11. package/ios/NativeListIconBinding.swift +171 -0
  12. package/ios/NativeListIdentityRenderer.swift +12 -10
  13. package/ios/NativeListLeadingVisual.swift +11 -9
  14. package/ios/NativeListMarketRenderer.swift +7 -13
  15. package/ios/NativeListMediaTileRenderer.swift +6 -2
  16. package/ios/NativeListMetricCardRenderer.swift +4 -2
  17. package/ios/NativeListSectionHeaderRenderer.swift +12 -4
  18. package/ios/tests/NativeListAccessoryStackTests.swift +12 -0
  19. package/ios/tests/NativeListSharedIconTests.swift +236 -0
  20. package/lib/module/web/NativeListGeneratedIcons.js +31 -0
  21. package/lib/module/web/NativeListWebEngine.js +21 -323
  22. package/lib/module/web/templates/ActivityRowRenderer.js +4 -24
  23. package/lib/module/web/templates/MarketTableRowRenderer.js +11 -15
  24. package/lib/module/web/templates/MediaTileRowRenderer.js +2 -12
  25. package/lib/typescript/src/web/NativeListGeneratedIcons.d.ts +2 -0
  26. package/lib/typescript/src/web/NativeListWebEngine.d.ts +0 -1
  27. package/package.json +10 -4
  28. package/react-native-native-list.podspec +0 -5
  29. package/scripts/generate-icons.mjs +150 -0
  30. package/scripts/generate-icons.test.mjs +288 -0
  31. package/scripts/native-list-icons.json +260 -0
  32. package/src/web/NativeListGeneratedIcons.ts +28 -0
  33. package/src/web/NativeListWebEngine.ts +25 -405
  34. package/src/web/templates/ActivityRowRenderer.ts +15 -53
  35. package/src/web/templates/MarketTableRowRenderer.ts +17 -29
  36. package/src/web/templates/MediaTileRowRenderer.ts +2 -15
  37. package/ios/Resources/NativeListIcons.xcassets/Contents.json +0 -1
  38. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/Contents.json +0 -1
  39. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/icon.svg +0 -1
  40. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/Contents.json +0 -1
  41. package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/icon.svg +0 -1
  42. package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/Contents.json +0 -1
  43. package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/icon.svg +0 -1
  44. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/Contents.json +0 -1
  45. package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/icon.svg +0 -1
  46. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/Contents.json +0 -1
  47. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/icon.svg +0 -1
  48. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/Contents.json +0 -1
  49. package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/icon.svg +0 -1
  50. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/Contents.json +0 -1
  51. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/icon.svg +0 -1
  52. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/Contents.json +0 -1
  53. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/icon.svg +0 -1
  54. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/Contents.json +0 -1
  55. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/icon.svg +0 -1
  56. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/Contents.json +0 -1
  57. package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/icon.svg +0 -1
  58. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/Contents.json +0 -15
  59. package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/icon.svg +0 -4
  60. package/ios/Resources/NativeListIcons.xcassets/onekey_document2.imageset/Contents.json +0 -1
  61. package/ios/Resources/NativeListIcons.xcassets/onekey_document2.imageset/icon.svg +0 -1
  62. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/Contents.json +0 -1
  63. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/icon.svg +0 -1
  64. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_ver_solid.imageset/Contents.json +0 -1
  65. package/ios/Resources/NativeListIcons.xcassets/onekey_dot_ver_solid.imageset/icon.svg +0 -1
  66. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/Contents.json +0 -1
  67. package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/icon.svg +0 -1
  68. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/Contents.json +0 -1
  69. package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/icon.svg +0 -1
  70. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/Contents.json +0 -15
  71. package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/icon.svg +0 -3
  72. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/Contents.json +0 -1
  73. package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/icon.svg +0 -1
  74. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/Contents.json +0 -1
  75. package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/icon.svg +0 -1
  76. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/Contents.json +0 -1
  77. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/icon.svg +0 -1
  78. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/Contents.json +0 -1
  79. package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/icon.svg +0 -1
  80. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/Contents.json +0 -1
  81. package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/icon.svg +0 -1
  82. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/Contents.json +0 -1
  83. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/icon.svg +0 -1
  84. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_large.imageset/Contents.json +0 -1
  85. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_large.imageset/icon.svg +0 -1
  86. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/Contents.json +0 -1
  87. package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/icon.svg +0 -1
  88. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/Contents.json +0 -1
  89. package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/icon.svg +0 -1
  90. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/Contents.json +0 -15
  91. package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/icon.svg +0 -3
  92. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/Contents.json +0 -15
  93. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/icon.svg +0 -14
  94. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/Contents.json +0 -15
  95. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/icon.svg +0 -12
  96. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/Contents.json +0 -15
  97. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/icon.svg +0 -6
  98. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/Contents.json +0 -15
  99. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/icon.svg +0 -15
  100. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/Contents.json +0 -15
  101. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/icon.svg +0 -3
  102. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/Contents.json +0 -15
  103. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/icon.svg +0 -7
  104. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/Contents.json +0 -15
  105. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/icon.svg +0 -18
  106. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/Contents.json +0 -15
  107. package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/icon.svg +0 -7
  108. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/Contents.json +0 -1
  109. package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/icon.svg +0 -1
  110. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/Contents.json +0 -1
  111. package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/icon.svg +0 -1
  112. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/Contents.json +0 -1
  113. package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/icon.svg +0 -1
  114. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/Contents.json +0 -1
  115. package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/icon.svg +0 -1
  116. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/Contents.json +0 -1
  117. package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/icon.svg +0 -1
  118. package/ios/Resources/NativeListIcons.xcassets/onekey_ticket.imageset/Contents.json +0 -1
  119. package/ios/Resources/NativeListIcons.xcassets/onekey_ticket.imageset/icon.svg +0 -1
  120. package/ios/Resources/NativeListIcons.xcassets/onekey_wallet.imageset/Contents.json +0 -1
  121. package/ios/Resources/NativeListIcons.xcassets/onekey_wallet.imageset/icon.svg +0 -1
@@ -109,7 +109,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
109
109
  private let mainStack = UIStackView()
110
110
  private let titleRowStack = UIStackView()
111
111
  private let titleLabel = NativeListTextLabel()
112
- private let titleIconView = UIImageView()
112
+ private let titleIconView = NativeListIconImageView()
113
113
  private var titleImageSlot: NativeListImageSlot?
114
114
  private var titleImageConstraints: [NSLayoutConstraint] = []
115
115
  // Absorbs the title row's slack so the title keeps its intrinsic width and the
@@ -120,7 +120,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
120
120
  private let tertiaryLabel = NativeListTextLabel()
121
121
  private let badgeLabel = NativeListInsetLabel()
122
122
  private let trailingStack = NativeListAccessoryStack(frame: .zero)
123
- private let leadingActionButton = UIButton(type: .system)
123
+ private let leadingActionButton = NativeListIconButton(type: .system)
124
124
  private var leadingWidth: NSLayoutConstraint!
125
125
  private var leadingHeight: NSLayoutConstraint!
126
126
  private var leftInset: CGFloat = 12
@@ -208,6 +208,9 @@ final class NativeListIdentityCell: NativeListRendererCell {
208
208
  _ item: NativeListItem, theme: [String: Any]?, layout: String,
209
209
  checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
210
210
  ) {
211
+ let iconUpdate = NativeListIconUpdate(self)
212
+ defer { iconUpdate.finish() }
213
+
211
214
  selectorTypographyRestorers.reversed().forEach { $0() }
212
215
  selectorTypographyRestorers = []
213
216
  currentItem = item
@@ -298,7 +301,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
298
301
  bindIdentity(item, theme: theme, selected: item.data.bool("selected"), checkboxState)
299
302
  let style = item.data.dictionary("style") ?? [:]
300
303
  if let titleIcon = item.data.dictionary("titleIcon") {
301
- titleIconView.image = nativeListIcon(named: titleIcon.string("name"))
304
+ titleIconView.bindIcon(titleIcon.string("name"))
302
305
  titleIconView.tintColor = UIColor(
303
306
  nativeListHex: titleIcon.string("tintColor",
304
307
  default: theme?["iconSubdued"] as? String ?? "#8D8D8D"),
@@ -407,6 +410,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
407
410
  }
408
411
  settleTitleRowFillerSpacing()
409
412
  applySelectorTypography(item)
413
+
410
414
  }
411
415
 
412
416
  /// The filler is the title row's last arranged view; the stack's default
@@ -422,6 +426,9 @@ final class NativeListIdentityCell: NativeListRendererCell {
422
426
  _ item: NativeListItem,
423
427
  checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
424
428
  ) {
429
+ let iconUpdate = NativeListIconUpdate(self)
430
+ defer { iconUpdate.finish() }
431
+
425
432
  currentItem = item
426
433
  trailingStack.updateSelection(item, checkboxState: checkboxState)
427
434
  if item.data.string("presentation") == "walletSidebar" {
@@ -439,6 +446,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
439
446
  NativeListTextStyles.applyStyledText(titleLabel, style, fonts: fonts)
440
447
  }
441
448
  }
449
+
442
450
  }
443
451
  override func layoutSubviews() {
444
452
  super.layoutSubviews()
@@ -610,13 +618,7 @@ final class NativeListIdentityCell: NativeListRendererCell {
610
618
  fallback: .darkGray
611
619
  )
612
620
  leadingActionButton.tintColor = tintColor
613
- if let image = nativeListIcon(named: leadingAction.string("name")) {
614
- leadingActionButton.setImage(image, for: .normal)
615
- leadingActionButton.setImage(
616
- image.withTintColor(tintColor, renderingMode: .alwaysOriginal),
617
- for: .disabled
618
- )
619
- }
621
+ leadingActionButton.bindIcon(leadingAction.string("name"))
620
622
  leadingActionButton.isEnabled = !leadingAction.bool("disabled")
621
623
  leadingActionButton.alpha = leadingActionButton.isEnabled ? 1 : 0.4
622
624
  leadingActionKey = leadingAction.string("actionKey")
@@ -14,16 +14,16 @@ final class NativeListLeadingVisual: UIView {
14
14
  var overlayTextFontSize: CGFloat = 10
15
15
  var overlayTextLineHeight: CGFloat?
16
16
  var overlayTextHorizontalInset: CGFloat?
17
- private let icon = UIImageView()
17
+ private let icon = NativeListIconImageView()
18
18
  private let unread = UIView()
19
19
  private let networkBackdrop = UIView()
20
20
  // Ring under the secondary token of an Activity pair (SPEC "Activity leading pair").
21
21
  private let pairBackdrop = UIView()
22
- private let corner = UIImageView()
22
+ private let corner = NativeListIconImageView()
23
23
  private let cornerBackdrop = UIView()
24
24
  private var slots: [NativeListImageSlot] = []
25
25
  private var overlaySlots: [NativeListImageSlot] = []
26
- private var overlayFallbackIcons: [UIImageView] = []
26
+ private var overlayFallbackIcons: [NativeListIconImageView] = []
27
27
  private var overlays: [(UIView, [String: Any])] = []
28
28
  private let dashedBorder = CAShapeLayer()
29
29
  private var visual: [String: Any] = [:]
@@ -54,6 +54,8 @@ final class NativeListLeadingVisual: UIView {
54
54
  isUnread: Bool, secondaryVisual: [String: Any]? = nil,
55
55
  fonts: NativeListFontFamilies
56
56
  ) {
57
+ let iconUpdate = NativeListIconUpdate(self)
58
+ defer { iconUpdate.finish() }
57
59
  self.fonts = fonts
58
60
  fallback.font = nativeListFont(ofSize: 13, weight: .bold, fonts: fonts)
59
61
  self.visual = visual
@@ -111,7 +113,7 @@ final class NativeListLeadingVisual: UIView {
111
113
  fallback.text = String(visual.string("fallbackText").prefix(2))
112
114
  fallback.textColor = UIColor(
113
115
  nativeListHex: visual.string("fallbackTextColor", default: "#8D8D8D"), fallback: .gray)
114
- icon.image = fallbackData.map { nativeListIcon(named: $0.string("name")) } ?? nil
116
+ icon.bindIcon(fallbackData?.string("name"))
115
117
  icon.tintColor = UIColor(
116
118
  nativeListHex: fallbackData?.string("tintColor", default: "#646464") ?? "#646464",
117
119
  fallback: .darkGray)
@@ -175,7 +177,7 @@ final class NativeListLeadingVisual: UIView {
175
177
  let badge = visual.dictionary("cornerIcon")
176
178
  corner.isHidden = badge == nil
177
179
  cornerBackdrop.isHidden = badge == nil
178
- corner.image = badge.flatMap { nativeListIcon(named: $0.string("name")) }
180
+ corner.bindIcon(badge?.string("name"))
179
181
  corner.tintColor = UIColor(
180
182
  nativeListHex: badge?.string("tintColor", default: "#646464") ?? "#646464",
181
183
  fallback: .darkGray)
@@ -187,12 +189,12 @@ final class NativeListLeadingVisual: UIView {
187
189
  overlays.removeAll(keepingCapacity: true)
188
190
  let descriptors = Array(visual.dictionaries("overlays").prefix(2))
189
191
  while overlaySlots.count < descriptors.count { overlaySlots.append(NativeListImageSlot()) }
190
- while overlayFallbackIcons.count < descriptors.count { overlayFallbackIcons.append(UIImageView()) }
192
+ while overlayFallbackIcons.count < descriptors.count { overlayFallbackIcons.append(NativeListIconImageView()) }
191
193
  for (index, data) in descriptors.enumerated() {
192
194
  let frame = index < previousOverlays.count ? previousOverlays[index].0 : UIView()
193
195
  let fallbackData = data.dictionary("fallbackIcon")
194
196
  let fallbackGlyph = overlayFallbackIcons[index]
195
- fallbackGlyph.image = fallbackData.flatMap { nativeListIcon(named: $0.string("name")) }
197
+ fallbackGlyph.bindIcon(fallbackData?.string("name"))
196
198
  fallbackGlyph.tintColor = UIColor(
197
199
  nativeListHex: fallbackData?.string("tintColor", default: "#646464") ?? "#646464",
198
200
  fallback: .darkGray)
@@ -243,8 +245,8 @@ final class NativeListLeadingVisual: UIView {
243
245
  child = label
244
246
  } else {
245
247
  overlaySlots[index].recycle()
246
- let glyph = frame.subviews.first as? UIImageView ?? UIImageView()
247
- glyph.image = nativeListIcon(named: data.string("name"))
248
+ let glyph = frame.subviews.first as? NativeListIconImageView ?? NativeListIconImageView()
249
+ glyph.bindIcon(data.string("name"))
248
250
  glyph.contentMode = .scaleAspectFit
249
251
  glyph.tintColor = UIColor(
250
252
  nativeListHex: data.string("tintColor", default: "#646464"), fallback: .darkGray)
@@ -35,7 +35,7 @@ final class NativeListMarketCell: NativeListRendererCell {
35
35
  private let marketBadgeButtons = (0..<3).map { _ in NativeListAccessoryButton(type: .system) }
36
36
  private let badgeSlots = (0..<3).map { _ in NativeListImageSlot() }
37
37
  private var marketBadgeImages: [UIView] { badgeSlots.map { $0.view } }
38
- private let leadingActionButton = UIButton(type: .system)
38
+ private let leadingActionButton = NativeListIconButton(type: .system)
39
39
  private var leadingWidth: NSLayoutConstraint!
40
40
  private var leadingHeight: NSLayoutConstraint!
41
41
  private var selectorConstraints: [NSLayoutConstraint] = []
@@ -99,6 +99,9 @@ final class NativeListMarketCell: NativeListRendererCell {
99
99
  _ item: NativeListItem, theme: [String: Any]?, layout: String,
100
100
  checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
101
101
  ) {
102
+ let iconUpdate = NativeListIconUpdate(self)
103
+ defer { iconUpdate.finish() }
104
+
102
105
  NSLayoutConstraint.deactivate(selectorConstraints + accessorySizeConstraints)
103
106
  selectorConstraints = []
104
107
  accessorySizeConstraints = []
@@ -166,6 +169,7 @@ final class NativeListMarketCell: NativeListRendererCell {
166
169
  ? .top
167
170
  : style?.dictionary("container")?.string("contentVerticalAlignment") == "bottom"
168
171
  ? .bottom : .center
172
+
169
173
  }
170
174
  private func resetButton(_ button: NativeListAccessoryButton) {
171
175
  button.setTitle(nil, for: .normal)
@@ -498,13 +502,7 @@ final class NativeListMarketCell: NativeListRendererCell {
498
502
  fallback: .darkGray
499
503
  )
500
504
  leadingActionButton.tintColor = tintColor
501
- if let image = nativeListIcon(named: leadingAction.string("name")) {
502
- leadingActionButton.setImage(image, for: .normal)
503
- leadingActionButton.setImage(
504
- image.withTintColor(tintColor, renderingMode: .alwaysOriginal),
505
- for: .disabled
506
- )
507
- }
505
+ leadingActionButton.bindIcon(leadingAction.string("name"))
508
506
  leadingActionButton.isEnabled = !leadingAction.bool("disabled")
509
507
  leadingActionButton.alpha = leadingActionButton.isEnabled ? 1 : 0.4
510
508
  leadingActionButton.accessibilityIdentifier = leadingAction["testID"] as? String
@@ -627,11 +625,7 @@ final class NativeListMarketCell: NativeListRendererCell {
627
625
  button.titleEdgeInsets = .zero
628
626
  button.imageView?.contentMode = .scaleAspectFit
629
627
  if hasBuiltInIcon {
630
- button.setImage(
631
- nativeListIcon(
632
- named: "BadgeVerifiedSolid", size: CGSize(width: iconSize, height: iconSize)),
633
- for: .normal
634
- )
628
+ button.bindIcon("BadgeVerifiedSolid", size: CGSize(width: iconSize, height: iconSize))
635
629
  }
636
630
  // OneKey patch: match source badge metrics at physical-pixel precision.
637
631
  // let height = button.heightAnchor.constraint(equalToConstant: 18)
@@ -11,7 +11,7 @@ final class NativeListMediaTileCell: NativeListRendererCell {
11
11
  private let picture = UIView()
12
12
  private let image = NativeListMediaPreviewSlot()
13
13
  private let network = NativeListImageSlot()
14
- private let errorIcon = UIImageView()
14
+ private let errorIcon = NativeListIconImageView()
15
15
  private let title = NativeListTextLabel()
16
16
  private let subtitle = NativeListTextLabel()
17
17
  private let badge = NativeListTextLabel()
@@ -87,6 +87,9 @@ final class NativeListMediaTileCell: NativeListRendererCell {
87
87
  _ item: NativeListItem, theme: [String: Any]?, layout: String,
88
88
  checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
89
89
  ) {
90
+ let iconUpdate = NativeListIconUpdate(self)
91
+ defer { iconUpdate.finish() }
92
+
90
93
  let style = item.data.dictionary("style") ?? [:]
91
94
  imageStyle = style.dictionary("image") ?? [:]
92
95
  let hp = CGFloat(style.double("horizontalPadding", default: 10))
@@ -122,7 +125,7 @@ final class NativeListMediaTileCell: NativeListRendererCell {
122
125
  let state = item.data.string("imageState")
123
126
  let placeholder = theme?.string("strongBackground", default: "#0000000F") ?? "#0000000F"
124
127
  errorIcon.isHidden = state != "error"
125
- errorIcon.image = nativeListIcon(named: "ImageSquareWavesOutline")
128
+ errorIcon.bindIcon("ImageSquareWavesOutline")
126
129
  errorIcon.tintColor = UIColor(nativeListHex: "#00000044", fallback: .lightGray)
127
130
  let media = item.data.dictionary("media")
128
131
  if state != "empty", state != "error", let source = media?.dictionary("source") ?? item.data.dictionary("image") {
@@ -164,6 +167,7 @@ final class NativeListMediaTileCell: NativeListRendererCell {
164
167
  close.setTitle("×", for: .normal)
165
168
  close.titleLabel?.font = nativeListFont(ofSize: 16, weight: .medium, fonts: fonts)
166
169
  close.setTitleColor(nativeListColor(theme, "primaryText", "#202020"), for: .normal)
170
+
167
171
  }
168
172
  override func layoutSubviews() {
169
173
  let width = CGFloat(
@@ -374,7 +374,7 @@ final class NativeListMetricCardCell: NativeListRendererCell {
374
374
  }
375
375
 
376
376
  private final class NativeListMetricVisual: UIView {
377
- private let icon = UIImageView()
377
+ private let icon = NativeListIconImageView()
378
378
  private let image = NativeListImageSlot()
379
379
  init() {
380
380
  super.init(frame: .zero)
@@ -389,6 +389,8 @@ private final class NativeListMetricVisual: UIView {
389
389
  }
390
390
  required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
391
391
  func bind(_ visual: [String: Any], key: String, placeholder: String) {
392
+ let iconUpdate = NativeListIconUpdate(self)
393
+ defer { iconUpdate.finish() }
392
394
  backgroundColor = UIColor(
393
395
  nativeListHex: visual.string("backgroundColor", default: "#0000000F"), fallback: .lightGray)
394
396
  layer.cornerRadius = visual.string("shape") == "square" ? 0 : 8
@@ -397,7 +399,7 @@ private final class NativeListMetricVisual: UIView {
397
399
  image.view.isHidden = true
398
400
  if kind == "icon" {
399
401
  image.recycle()
400
- icon.image = nativeListIcon(named: visual.string("name"))
402
+ icon.bindIcon(visual.string("name"))
401
403
  icon.tintColor = UIColor(
402
404
  nativeListHex: visual.string("tintColor", default: "#00000072"), fallback: .darkGray)
403
405
  } else if let source = kind == "stackedImages"
@@ -124,8 +124,8 @@ final class NativeListSectionHeaderCell: NativeListRendererCell {
124
124
  private let trailingStack = UIStackView()
125
125
  private let accessoryButtons = [NativeListAccessoryButton(type: .system)]
126
126
  private let checkboxControl = NativeListAccessoryStack(frame: .zero)
127
- private let headerTitleIconImageView = UIImageView()
128
- private let headerValueIconImageView = UIImageView()
127
+ private let headerTitleIconImageView = NativeListIconImageView()
128
+ private let headerValueIconImageView = NativeListIconImageView()
129
129
  private var accessoryActions: [(String, NativeSelectionTarget?)] = []
130
130
  private var currentItem: NativeListItem?
131
131
  private var currentTheme: [String: Any]?
@@ -187,6 +187,9 @@ final class NativeListSectionHeaderCell: NativeListRendererCell {
187
187
  _ item: NativeListItem, theme: [String: Any]?, layout: String,
188
188
  checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
189
189
  ) {
190
+ let iconUpdate = NativeListIconUpdate(self)
191
+ defer { iconUpdate.finish() }
192
+
190
193
  selectorTypographyRestorers.reversed().forEach { $0() }
191
194
  selectorTypographyRestorers = []
192
195
  currentItem = item
@@ -297,6 +300,7 @@ final class NativeListSectionHeaderCell: NativeListRendererCell {
297
300
  width.isActive = true
298
301
  selectorConstraints.append(width)
299
302
  }
303
+
300
304
  }
301
305
  private func bindPositionHeaderFields(_ item: NativeListItem, theme: [String: Any]?) {
302
306
  let style = item.data.dictionary("style") ?? [:]
@@ -371,6 +375,9 @@ final class NativeListSectionHeaderCell: NativeListRendererCell {
371
375
  _ item: NativeListItem,
372
376
  checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
373
377
  ) {
378
+ let iconUpdate = NativeListIconUpdate(self)
379
+ defer { iconUpdate.finish() }
380
+
374
381
  currentItem = item
375
382
  checkboxControl.updateSelection(item, checkboxState: checkboxState)
376
383
  if item.data.string("variant") == "summary" {
@@ -382,6 +389,7 @@ final class NativeListSectionHeaderCell: NativeListRendererCell {
382
389
  applyTextStyle(item)
383
390
  applySelectorTypography(item)
384
391
  }
392
+
385
393
  }
386
394
  private func bindCheckbox(
387
395
  _ item: NativeListItem, _ descriptor: [String: Any],
@@ -523,7 +531,7 @@ final class NativeListSectionHeaderCell: NativeListRendererCell {
523
531
  topInset = 12
524
532
  bottomInset = -2
525
533
  if let icon = item.data.dictionary("titleIcon") {
526
- headerTitleIconImageView.image = nativeListIcon(named: icon.string("name"))
534
+ headerTitleIconImageView.bindIcon(icon.string("name"))
527
535
  headerTitleIconImageView.tintColor = UIColor(
528
536
  nativeListHex: icon.string("tintColor", default: "#646464"),
529
537
  fallback: .darkGray
@@ -593,7 +601,7 @@ final class NativeListSectionHeaderCell: NativeListRendererCell {
593
601
  for: .normal
594
602
  )
595
603
  if let icon = item.data.dictionary("valueIcon") {
596
- headerValueIconImageView.image = nativeListIcon(named: icon.string("name"))
604
+ headerValueIconImageView.bindIcon(icon.string("name"))
597
605
  headerValueIconImageView.tintColor = UIColor(
598
606
  nativeListHex: icon.string("tintColor", default: "#646464"),
599
607
  fallback: .darkGray
@@ -42,6 +42,18 @@ final class NativeListAccessoryStackTests: XCTestCase {
42
42
  XCTAssertTrue(stack.hitTest(value.center, with: nil) === stack)
43
43
  }
44
44
 
45
+ func testSwapMenuUsesTheDotVerSolidImage() throws {
46
+ let metadata = try XCTUnwrap(nativeListIconMetadata(named: "DotVerSolid"))
47
+ XCTAssertGreaterThan(metadata.size.width, 0)
48
+ XCTAssertGreaterThan(metadata.size.height, 0)
49
+ let stack = try makeStack(trailing: [[
50
+ "kind": "icon", "name": "DotVerSolid", "buttonStyle": "tertiary",
51
+ "actionKey": "menu",
52
+ ]])
53
+ let button = try XCTUnwrap(try visibleControl(stack) as? UIButton)
54
+ XCTAssertNotNil(button.image(for: .normal))
55
+ }
56
+
45
57
  func testValuePairAccessoryLeavesTouchesToTheRow() throws {
46
58
  let stack = try makeStack(trailing: [
47
59
  ["kind": "valuePair", "primary": "$15.60", "secondary": "0.002 BTC"],
@@ -0,0 +1,236 @@
1
+ import UIKit
2
+ import XCTest
3
+ import OneKeyImage
4
+ @testable import NativeListModule
5
+
6
+ @MainActor
7
+ final class NativeListSharedIconTests: XCTestCase {
8
+ private func field<T>(_ object: Any, _ name: String, as type: T.Type = T.self) throws -> T {
9
+ var mirror: Mirror? = Mirror(reflecting: object)
10
+ while let current = mirror {
11
+ if let child = current.children.first(where: { $0.label == name }) { return try XCTUnwrap(child.value as? T) }
12
+ mirror = current.superclassMirror
13
+ }
14
+ throw NSError(domain: "Missing field " + name, code: 1)
15
+ }
16
+ private func binding(_ button: UIButton) throws -> Any {
17
+ let mirror = try XCTUnwrap(Mirror(reflecting: button).superclassMirror)
18
+ let storage = try XCTUnwrap(mirror.children.first { $0.label?.contains("iconBinding") == true }?.value)
19
+ return try XCTUnwrap(Mirror(reflecting: storage).children.first?.value)
20
+ }
21
+ private func eventually(_ message: String, _ condition: () -> Bool) {
22
+ let end = Date().addingTimeInterval(5)
23
+ while !condition(), Date() < end { RunLoop.main.run(until: Date().addingTimeInterval(0.01)) }
24
+ XCTAssertTrue(condition(), message)
25
+ }
26
+ private func window() throws -> UIWindow {
27
+ guard UIApplication.perform(NSSelectorFromString("sharedApplication"))?.takeUnretainedValue() is UIApplication else {
28
+ throw XCTSkip("A UIApplication host is required for attached glyph acceptance")
29
+ }
30
+ let window = UIWindow(frame: UIScreen.main.bounds)
31
+ window.rootViewController = UIViewController()
32
+ window.makeKeyAndVisible()
33
+ return window
34
+ }
35
+ private func save(_ image: UIImage, _ name: String) throws {
36
+ guard let directory = ProcessInfo.processInfo.environment["ICON_EVIDENCE_DIR"] else { return }
37
+ try XCTUnwrap(image.pngData()).write(to: URL(fileURLWithPath: directory).appendingPathComponent(name + ".png"))
38
+ }
39
+ private func bind(_ stack: NativeListAccessoryStack, amount: String = "1", name: String = "DotVerSolid", kind: String = "icon", disabled: Bool = false) throws {
40
+ let descriptors: [[String: Any]] = [
41
+ ["kind": "valuePair", "primary": amount, "secondary": "$1"],
42
+ ["kind": kind, "name": name, "text": "Text", "buttonStyle": "tertiary", "actionKey": "menu", "disabled": disabled],
43
+ ]
44
+ let item = try NativeListItem(data: ["key": "swap", "type": "identity", "title": "USDC", "height": 60, "trailing": descriptors])
45
+ stack.axis = .horizontal
46
+ stack.bind(item, descriptors: descriptors, theme: nil,
47
+ style: ["horizontalPadding": 20, "verticalPadding": 8, "leadingGap": 12, "trailingGap": 8],
48
+ checkboxState: { _, _, fallback in fallback }, fonts: NativeListFontFamilies())
49
+ stack.setNeedsLayout(); stack.layoutIfNeeded()
50
+ }
51
+ func testDetachedMeasurementAndRealSwapRebindPreservePendingAndReadyImage() throws {
52
+ let service = OneKeyNamedSVG.shared
53
+ let metadataLock: NSLock = try field(service, "metadataLock")
54
+ let stateLock: NSLock = try field(service, "lock")
55
+ func state(_ owner: NSObject? = nil) throws -> (Int, Int) {
56
+ stateLock.lock(); defer { stateLock.unlock() }
57
+ let pending: Any = try field(service, "pending")
58
+ let keys = Mirror(reflecting: pending).children.compactMap { Mirror(reflecting: $0.value).children.first?.value as? ObjectIdentifier }
59
+ return (try field(service, "workers"), owner.map { owner in keys.filter { $0 == ObjectIdentifier(owner) }.count } ?? keys.count)
60
+ }
61
+ metadataLock.lock()
62
+ var locked = true
63
+ defer { if locked { metadataLock.unlock() } }
64
+ let baseline = try state()
65
+ let stack = NativeListAccessoryStack(frame: CGRect(x: 10, y: 80, width: 180, height: 44))
66
+ try bind(stack)
67
+ let button = try XCTUnwrap(stack.subviews.compactMap { $0 as? NativeListAccessoryButton }.first { !$0.isHidden && $0.image(for: .normal) != nil })
68
+ let slot = try binding(button)
69
+ let owner: NSObject = try field(slot, "owner")
70
+ XCTAssertEqual(try state(owner).0, baseline.0)
71
+ XCTAssertEqual(try state(owner).1, 0)
72
+ let beforeSize = try XCTUnwrap(button.image(for: .normal)).size
73
+ let detachedSize = stack.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
74
+ XCTAssertEqual(try state(owner).0, baseline.0)
75
+ XCTAssertEqual(try state(owner).1, 0)
76
+ let window = try window(); defer { window.isHidden = true }
77
+ do {
78
+ defer { metadataLock.unlock(); locked = false }
79
+ window.rootViewController!.view.addSubview(stack)
80
+ stack.layoutIfNeeded()
81
+ XCTAssertGreaterThan(try state(owner).0 + state(owner).1, baseline.0)
82
+ let bounds = button.bounds.size
83
+ let revision: UInt64 = try field(slot, "revision")
84
+ for index in 0..<100 { try bind(stack, amount: String(index)) }
85
+ XCTAssertEqual(try field(slot, "revision", as: UInt64.self), revision)
86
+ XCTAssertEqual(button.image(for: .normal)?.size, beforeSize)
87
+ XCTAssertEqual(button.bounds.size, bounds)
88
+ XCTAssertEqual(stack.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height, detachedSize.height)
89
+ }
90
+ eventually("Attached Swap glyph has an actual raster") { button.image(for: .normal)?.cgImage != nil && button.image(for: .normal)?.size == beforeSize && (try? self.field(slot, "currentImage", as: UIImage?.self)) != nil }
91
+ // A transparent measurement placeholder also has a CGImage: require painted pixels.
92
+ eventually("Decoded glyph is visible") { self.alphaCount(button.image(for: .normal)) > 0 }
93
+ let ready = try XCTUnwrap(button.image(for: .normal))
94
+ XCTAssertEqual(maximumAlpha(ready), 255)
95
+ try bind(stack, amount: "101")
96
+ XCTAssertTrue(button.image(for: .normal) === ready)
97
+ button.tintColor = .blue
98
+ XCTAssertTrue(button.image(for: .normal) === ready)
99
+ XCTAssertNotNil(button.image(for: .disabled))
100
+ try bind(stack, amount: "101")
101
+ func captureButton() -> UIImage {
102
+ let rect = button.convert(button.bounds, to: stack)
103
+ return UIGraphicsImageRenderer(size: button.bounds.size).image { context in
104
+ context.cgContext.translateBy(x: -rect.minX, y: -rect.minY)
105
+ stack.drawHierarchy(in: stack.bounds, afterScreenUpdates: true)
106
+ }
107
+ }
108
+ try save(captureButton(), "ios-swap-menu-normal")
109
+ var actions = 0
110
+ stack.onAction = { _, _, _, _ in actions += 1 }
111
+ try bind(stack, amount: "101", disabled: true)
112
+ XCTAssertFalse(button.isEnabled); XCTAssertEqual(button.alpha, 0.4, accuracy: 0.000001)
113
+ XCTAssertTrue(button.image(for: .normal) === ready)
114
+ button.sendActions(for: .touchUpInside); XCTAssertEqual(actions, 0)
115
+ button.layoutIfNeeded()
116
+ try save(captureButton(), "ios-swap-menu-disabled")
117
+ try bind(stack, amount: "101")
118
+ try bind(stack, name: "UnknownIcon")
119
+ XCTAssertNil(button.image(for: .normal))
120
+ try bind(stack, kind: "value")
121
+ XCTAssertNil(button.image(for: .normal))
122
+ stack.removeFromSuperview()
123
+ RunLoop.main.run(until: Date().addingTimeInterval(0.05))
124
+ XCTAssertNil(button.image(for: .normal))
125
+ }
126
+ private func alphaCount(_ image: UIImage?) -> Int {
127
+ guard let cg = image?.cgImage else { return 0 }
128
+ var bytes = [UInt8](repeating: 0, count: cg.width * cg.height * 4)
129
+ guard let context = CGContext(data: &bytes, width: cg.width, height: cg.height, bitsPerComponent: 8,
130
+ bytesPerRow: cg.width * 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return 0 }
131
+ context.draw(cg, in: CGRect(x: 0, y: 0, width: cg.width, height: cg.height))
132
+ return stride(from: 3, to: bytes.count, by: 4).filter { bytes[$0] > 0 }.count
133
+ }
134
+ func testMainLineGlyphsPaintInTheSharedNativeSlot() throws {
135
+ let window = try window(); defer { window.isHidden = true }
136
+ let view = NativeListIconImageView(frame: CGRect(x: 10, y: 80, width: 64, height: 64))
137
+ window.rootViewController!.view.addSubview(view)
138
+ for name in ["Document2Outline", "WalletOutline", "PlusLargeOutline", "TicketOutline"] {
139
+ let metadata = try XCTUnwrap(nativeListIconMetadata(named: name))
140
+ XCTAssertEqual(metadata.name, name)
141
+ XCTAssertEqual(metadata.size, CGSize(width: 24, height: 24))
142
+ view.bindIcon(name)
143
+ eventually("Shared main-line glyph paints: " + name) { self.alphaCount(view.image) > 0 }
144
+ let image = try XCTUnwrap(view.image)
145
+ XCTAssertEqual(image.renderingMode, .alwaysTemplate)
146
+ try save(image.withTintColor(.red, renderingMode: .alwaysOriginal), "ios-main-integration-" + name)
147
+ }
148
+ }
149
+ private func maximumAlpha(_ image: UIImage) -> UInt8 {
150
+ guard let cg = image.cgImage else { return 0 }
151
+ var bytes = [UInt8](repeating: 0, count: cg.width * cg.height * 4)
152
+ guard let context = CGContext(data: &bytes, width: cg.width, height: cg.height, bitsPerComponent: 8,
153
+ bytesPerRow: cg.width * 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return 0 }
154
+ context.draw(cg, in: CGRect(x: 0, y: 0, width: cg.width, height: cg.height))
155
+ return stride(from: 3, to: bytes.count, by: 4).map { bytes[$0] }.max() ?? 0
156
+ }
157
+ func testLifecycleLegacyModesAndSizeOverride() throws {
158
+ let window = try window(); defer { window.isHidden = true }
159
+ let view = NativeListIconImageView(frame: CGRect(x: 10, y: 80, width: 64, height: 64))
160
+ window.rootViewController!.view.addSubview(view)
161
+ for name in ["AppleBrand", "BotIllus", "GoogleIllus", "AccountErrorCustom", "StarOutline", "StarSolid", "BadgeVerifiedSolid", "CheckboxCheckedCustom", "CheckboxIndeterminateCustom"] {
162
+ view.bindIcon(name)
163
+ eventually(name) { self.alphaCount(view.image) > 0 }
164
+ let metadata = try XCTUnwrap(nativeListIconMetadata(named: name))
165
+ XCTAssertEqual(view.image?.size, metadata.size)
166
+ if !metadata.original { XCTAssertEqual(maximumAlpha(try XCTUnwrap(view.image)), 255) }
167
+ XCTAssertEqual(view.image?.renderingMode, metadata.original ? .alwaysOriginal : .alwaysTemplate)
168
+ let image = try XCTUnwrap(view.image)
169
+ try save(metadata.original ? image : image.withTintColor(.red, renderingMode: .alwaysOriginal), "ios-new-" + name)
170
+ }
171
+ view.bindIcon("AppleBrand", size: CGSize(width: 28, height: 20))
172
+ eventually("Non-square source stretches to legacy explicit size") { self.alphaCount(view.image) > 0 }
173
+ XCTAssertEqual(view.image?.size, CGSize(width: 28, height: 20))
174
+ XCTAssertEqual(view.image?.renderingMode, .alwaysTemplate)
175
+ try save(try XCTUnwrap(view.image).withTintColor(.red, renderingMode: .alwaysOriginal), "ios-new-AppleBrand-override")
176
+ view.bindIcon("DotVerSolid")
177
+ view.removeFromSuperview()
178
+ RunLoop.main.run(until: Date().addingTimeInterval(0.1))
179
+ XCTAssertEqual(alphaCount(view.image), 0)
180
+ window.rootViewController!.view.addSubview(view)
181
+ eventually("Same glyph redraws after detached completion") { self.alphaCount(view.image) > 0 }
182
+ view.bindIcon("UnknownIcon"); XCTAssertNil(view.image)
183
+ let ordinary = UIGraphicsImageRenderer(size: CGSize(width: 8, height: 8)).image { c in UIColor.green.setFill(); c.fill(CGRect(x: 0, y: 0, width: 8, height: 8)) }
184
+ view.image = ordinary
185
+ XCTAssertTrue(view.image === ordinary)
186
+ }
187
+ func testLegacyCompiledAssetRasterComparison() throws {
188
+ guard let directory = ProcessInfo.processInfo.environment["NATIVE_LIST_LEGACY_BUNDLE"], let bundle = Bundle(path: directory) else {
189
+ throw XCTSkip("An archived pre-migration resource bundle is required for raster comparison")
190
+ }
191
+ let window = try window(); defer { window.isHidden = true }
192
+ let view = NativeListIconImageView(frame: CGRect(x: 0, y: 80, width: 96, height: 96))
193
+ window.rootViewController!.view.addSubview(view)
194
+ let names = [
195
+ "AppleBrand": "onekey_selector_apple_brand", "BotIllus": "onekey_selector_bot_illus",
196
+ "GoogleIllus": "onekey_selector_google_illus", "AccountErrorCustom": "onekey_selector_account_error_custom",
197
+ "BadgeVerifiedSolid": "onekey_badge_verified_solid", "StarOutline": "onekey_star", "StarSolid": "onekey_star_solid",
198
+ "CheckboxCheckedCustom": "onekey_checkbox_checked", "CheckboxIndeterminateCustom": "onekey_checkbox_indeterminate",
199
+ ]
200
+ func raster(_ image: UIImage) -> UIImage {
201
+ let format = UIGraphicsImageRendererFormat(); format.scale = 1
202
+ return UIGraphicsImageRenderer(size: CGSize(width: 96, height: 96), format: format).image { _ in
203
+ image.draw(in: CGRect(x: 0, y: 0, width: 96, height: 96))
204
+ }
205
+ }
206
+ for name in names.keys.sorted() {
207
+ let metadata = try XCTUnwrap(nativeListIconMetadata(named: name))
208
+ let old = try XCTUnwrap(UIImage(named: names[name]!, in: bundle, compatibleWith: nil))
209
+ view.bindIcon(name)
210
+ eventually("Shared raster for legacy comparison " + name) { self.alphaCount(view.image) > 0 }
211
+ let next = try XCTUnwrap(view.image)
212
+ XCTAssertEqual(next.size, old.size)
213
+ let before = raster(metadata.original ? old : old.withTintColor(.red, renderingMode: .alwaysOriginal))
214
+ let after = raster(metadata.original ? next : next.withTintColor(.red, renderingMode: .alwaysOriginal))
215
+ XCTAssertGreaterThan(alphaCount(before), 0); XCTAssertGreaterThan(alphaCount(after), 0)
216
+ if name == "AccountErrorCustom" {
217
+ // The source eye center is inside its 0.447-opacity fill, away from raster edges.
218
+ for image in [before, after] {
219
+ let cg = try XCTUnwrap(image.cgImage)
220
+ var bytes = [UInt8](repeating: 0, count: 96 * 96 * 4)
221
+ let context = try XCTUnwrap(CGContext(data: &bytes, width: 96, height: 96, bitsPerComponent: 8,
222
+ bytesPerRow: 96 * 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue))
223
+ context.draw(cg, in: CGRect(x: 0, y: 0, width: 96, height: 96))
224
+ for (x,y) in [(6,48),(48,6),(48,91),(91,64),(67,61)] {
225
+ let index = (y * 96 + x) * 4
226
+ XCTAssertEqual(Array(bytes[index..<(index+3)]), [0,0,0])
227
+ XCTAssertEqual(bytes[index+3], 114)
228
+ }
229
+ for (x,y) in [(48,48),(15,48)] { XCTAssertEqual(bytes[(y * 96 + x) * 4 + 3], 0) }
230
+ }
231
+ } else { XCTAssertEqual(maximumAlpha(after), maximumAlpha(before)) }
232
+ try save(before, "ios-before-" + name); try save(after, "ios-after-" + name)
233
+ }
234
+ }
235
+
236
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+
3
+ // Generated by scripts/generate-icons.mjs. Coverage and legacy names only; hosts provide SVG resources.
4
+ export const nativeListGeneratedIcons = {
5
+ AccountErrorCustom: 'AccountErrorCustom',
6
+ AllNetworksSolid: 'AllNetworksSolid',
7
+ AppleBrand: 'AppleBrand',
8
+ BadgeVerifiedSolid: 'BadgeRecognizedSolid',
9
+ BotIllus: 'BotIllus',
10
+ CheckboxCheckedCustom: 'CheckboxCheckedCustom',
11
+ CheckboxIndeterminateCustom: 'CheckboxIndeterminateCustom',
12
+ ChevronRightSmallOutline: 'ChevronRightSmallOutline',
13
+ Circle: 'Circle',
14
+ CrossedSmallSolid: 'CrossedSmallSolid',
15
+ CryptoCoinOutline: 'CryptoCoinOutline',
16
+ Document2Outline: 'Document2Outline',
17
+ DotHorOutline: 'DotHorOutline',
18
+ DotVerSolid: 'DotVerSolid',
19
+ DragOutline: 'DragOutline',
20
+ GasSolid: 'GasSolid',
21
+ GlobusOutline: 'GlobusOutline',
22
+ GoogleIllus: 'GoogleIllus',
23
+ LockSolid: 'LockSolid',
24
+ PencilOutline: 'PencilOutline',
25
+ PlusLargeOutline: 'PlusLargeOutline',
26
+ PlusSmallOutline: 'PlusSmallOutline',
27
+ RefreshCcwOutline: 'RefreshCcwOutline',
28
+ TicketOutline: 'TicketOutline',
29
+ WalletOutline: 'WalletOutline'
30
+ };
31
+ //# sourceMappingURL=NativeListGeneratedIcons.js.map