@onekeyfe/react-native-native-list 6.7.0-alpha.378 → 6.7.0-alpha.382
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.
- package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +14 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +240 -11
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +7 -4
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +6 -3
- package/android/src/main/java/com/onekey/nativelist/NativeListButtonGroupRenderer.kt +102 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +12 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +87 -7
- package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +177 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +43 -14
- package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +25 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +33 -2
- package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +22 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +2 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +2 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +3 -2
- package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +69 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +55 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1912 -107
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListPressPolicyTest.kt +1 -0
- package/docs/ROW_TEMPLATES.md +60 -9
- package/docs/SPEC.md +450 -5
- package/docs/STYLE_SPEC.md +6 -0
- package/ios/HybridNativeList.swift +21 -2
- package/ios/NativeListAccessoryStack.swift +227 -39
- package/ios/NativeListActivityDetailsView.swift +18 -8
- package/ios/NativeListActivityRenderer.swift +8 -2
- package/ios/NativeListButtonGroupRenderer.swift +89 -0
- package/ios/NativeListDataRowRenderer.swift +155 -27
- package/ios/NativeListDesignAssets.swift +3 -0
- package/ios/NativeListIdentityRenderer.swift +166 -30
- package/ios/NativeListLeadingVisual.swift +49 -14
- package/ios/NativeListMessageRenderer.swift +34 -1
- package/ios/NativeListModels.swift +42 -2
- package/ios/NativeListRendererRegistry.swift +1 -0
- package/ios/NativeListResolvedText.swift +2 -1
- package/ios/NativeListRowHost.swift +14 -0
- package/ios/NativeListSectionHeaderRenderer.swift +88 -0
- package/ios/RNCNativeListView.swift +1013 -55
- package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_crypto_coin_outline.imageset/icon.svg +4 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_gas_solid.imageset/icon.svg +3 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_refresh_ccw_outline.imageset/icon.svg +3 -0
- package/ios/tests/NativeListActivityPresentationTests.swift +56 -0
- package/ios/tests/NativeListLifecycleTests.swift +2720 -3
- package/lib/module/NativeList.js +175 -28
- package/lib/module/NativeList.web.js +129 -30
- package/lib/module/index.js +1 -1
- package/lib/module/models.js +1 -0
- package/lib/module/selection.js +1 -1
- package/lib/module/validation.js +421 -8
- package/lib/module/web/NativeListWebEngine.js +507 -19
- package/lib/module/web/templates/ActivityRowRenderer.js +8 -7
- package/lib/module/web/templates/ButtonGroupRowRenderer.js +47 -0
- package/lib/module/web/templates/DataRowRenderer.js +40 -5
- package/lib/module/web/templates/IdentityRowRenderer.js +90 -4
- package/lib/module/web/templates/MessageRowRenderer.js +18 -2
- package/lib/module/web/templates/RowContainerStyle.js +1 -0
- package/lib/module/web/templates/RowRendererRegistry.js +3 -0
- package/lib/module/web/templates/SectionHeaderRowRenderer.js +36 -2
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
- package/lib/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
- package/lib/typescript/src/NativeList.d.ts +1 -1
- package/lib/typescript/src/NativeList.nitro.d.ts +2 -0
- package/lib/typescript/src/NativeList.types.d.ts +18 -0
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/models.d.ts +105 -9
- package/lib/typescript/src/validation.d.ts +12 -0
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +32 -0
- package/lib/typescript/src/web/templates/ButtonGroupRowRenderer.d.ts +14 -0
- package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +1 -1
- package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +9 -3
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +26 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +4 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +10 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +20 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +14 -0
- package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +12 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +43 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +6 -0
- package/nitrogen/generated/shared/json/NativeListConfig.json +2 -0
- package/package.json +3 -3
- package/react-native-native-list.podspec +1 -0
- package/src/NativeList.nitro.ts +2 -0
- package/src/NativeList.tsx +313 -44
- package/src/NativeList.types.ts +22 -0
- package/src/NativeList.web.tsx +204 -48
- package/src/index.ts +2 -0
- package/src/models.ts +117 -3
- package/src/selection.ts +1 -0
- package/src/validation.ts +852 -6
- package/src/web/NativeListWebEngine.ts +803 -20
- package/src/web/templates/ActivityRowRenderer.ts +14 -6
- package/src/web/templates/ButtonGroupRowRenderer.ts +58 -0
- package/src/web/templates/DataRowRenderer.ts +93 -20
- package/src/web/templates/IdentityRowRenderer.ts +150 -5
- package/src/web/templates/MessageRowRenderer.ts +23 -7
- package/src/web/templates/RowContainerStyle.ts +2 -0
- package/src/web/templates/RowRendererRegistry.ts +11 -1
- package/src/web/templates/SectionHeaderRowRenderer.ts +47 -4
|
@@ -3,12 +3,72 @@ import UIKit
|
|
|
3
3
|
// OneKey patch: explicit summary actions use the source text's physical-pixel line box.
|
|
4
4
|
final class NativeListAccessoryButton: UIButton {
|
|
5
5
|
var pressedBackgroundColor: UIColor?
|
|
6
|
+
var normalBackgroundColor: UIColor = .clear
|
|
7
|
+
var fixedTextLineHeight: CGFloat?
|
|
6
8
|
var rowTextOffsetY: CGFloat = 0
|
|
9
|
+
var helpBindingGeneration = 0
|
|
10
|
+
private var helpTrackingGeneration = 0
|
|
11
|
+
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
|
12
|
+
helpTrackingGeneration = helpBindingGeneration
|
|
13
|
+
return super.beginTracking(touch, with: event)
|
|
14
|
+
}
|
|
15
|
+
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
|
16
|
+
guard helpTrackingGeneration == helpBindingGeneration else {
|
|
17
|
+
cancelTracking(with: event)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
super.endTracking(touch, with: event)
|
|
21
|
+
}
|
|
22
|
+
var helpPrimaryLineOnly = false
|
|
23
|
+
var showsHelpUnderline = false
|
|
24
|
+
let helpAnchor = UIView()
|
|
25
|
+
private let helpUnderline = CAShapeLayer()
|
|
26
|
+
|
|
27
|
+
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
28
|
+
if helpPrimaryLineOnly { return helpAnchor.frame.contains(point) }
|
|
29
|
+
return super.point(inside: point, with: event)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private func layoutHelp() {
|
|
33
|
+
guard helpPrimaryLineOnly || showsHelpUnderline else {
|
|
34
|
+
helpUnderline.isHidden = true
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
guard let titleLabel else { return }
|
|
38
|
+
if helpAnchor.superview == nil {
|
|
39
|
+
helpAnchor.isUserInteractionEnabled = false
|
|
40
|
+
helpAnchor.isAccessibilityElement = false
|
|
41
|
+
addSubview(helpAnchor)
|
|
42
|
+
helpUnderline.fillColor = UIColor.clear.cgColor
|
|
43
|
+
helpUnderline.lineWidth = 1
|
|
44
|
+
helpUnderline.lineDashPattern = [3, 2]
|
|
45
|
+
helpAnchor.layer.addSublayer(helpUnderline)
|
|
46
|
+
}
|
|
47
|
+
var frame = titleLabel.frame
|
|
48
|
+
if helpPrimaryLineOnly, let text = attributedTitle(for: .normal) {
|
|
49
|
+
let range = (text.string as NSString).range(of: "\n")
|
|
50
|
+
let primary = text.attributedSubstring(from: NSRange(location: 0, length: range.location == NSNotFound ? text.length : range.location))
|
|
51
|
+
let width = min(frame.width, ceil(primary.size().width))
|
|
52
|
+
frame.origin.x += frame.width - width
|
|
53
|
+
frame.size.width = width
|
|
54
|
+
let paragraph = primary.length > 0 ? primary.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle : nil
|
|
55
|
+
frame.size.height = paragraph?.minimumLineHeight ?? 20
|
|
56
|
+
}
|
|
57
|
+
helpAnchor.frame = frame
|
|
58
|
+
let text = attributedTitle(for: .normal)
|
|
59
|
+
let color = text.flatMap { $0.length > 0 ? $0.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor : nil }
|
|
60
|
+
helpUnderline.strokeColor = (color ?? titleColor(for: .normal))?.cgColor
|
|
61
|
+
helpUnderline.isHidden = !showsHelpUnderline
|
|
62
|
+
let path = UIBezierPath()
|
|
63
|
+
path.move(to: CGPoint(x: 0, y: frame.height - 1))
|
|
64
|
+
path.addLine(to: CGPoint(x: frame.width, y: frame.height - 1))
|
|
65
|
+
helpUnderline.path = path.cgPath
|
|
66
|
+
}
|
|
7
67
|
|
|
8
68
|
override var isHighlighted: Bool {
|
|
9
69
|
didSet {
|
|
10
70
|
guard let pressedBackgroundColor else { return }
|
|
11
|
-
backgroundColor = isHighlighted ? pressedBackgroundColor :
|
|
71
|
+
backgroundColor = isHighlighted ? pressedBackgroundColor : normalBackgroundColor
|
|
12
72
|
}
|
|
13
73
|
}
|
|
14
74
|
|
|
@@ -32,6 +92,13 @@ final class NativeListAccessoryButton: UIButton {
|
|
|
32
92
|
}
|
|
33
93
|
}
|
|
34
94
|
|
|
95
|
+
func clearTitlesForReuse() {
|
|
96
|
+
UIView.performWithoutAnimation {
|
|
97
|
+
super.setAttributedTitle(nil, for: .normal)
|
|
98
|
+
super.setTitle(nil, for: .normal)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
35
102
|
var selectorSummaryLineHeight: CGFloat? {
|
|
36
103
|
didSet {
|
|
37
104
|
invalidateIntrinsicContentSize()
|
|
@@ -52,6 +119,11 @@ final class NativeListAccessoryButton: UIButton {
|
|
|
52
119
|
|
|
53
120
|
override var intrinsicContentSize: CGSize {
|
|
54
121
|
var size = super.intrinsicContentSize
|
|
122
|
+
if let fixedTextLineHeight {
|
|
123
|
+
size.width += 2
|
|
124
|
+
size.height = fixedTextLineHeight + contentEdgeInsets.top + contentEdgeInsets.bottom + 2
|
|
125
|
+
return size
|
|
126
|
+
}
|
|
55
127
|
guard selectorSummaryLineHeight != nil || marketLineHeight != nil,
|
|
56
128
|
let title = attributedTitle(for: .normal)
|
|
57
129
|
else { return size }
|
|
@@ -71,6 +143,10 @@ final class NativeListAccessoryButton: UIButton {
|
|
|
71
143
|
defer {
|
|
72
144
|
if let titleLabel {
|
|
73
145
|
var frame = titleLabel.frame
|
|
146
|
+
if let fixedTextLineHeight {
|
|
147
|
+
frame.size.height = fixedTextLineHeight
|
|
148
|
+
frame.origin.y = (bounds.height - fixedTextLineHeight) / 2
|
|
149
|
+
}
|
|
74
150
|
if contentVerticalAlignment == .top {
|
|
75
151
|
frame.origin.y = contentEdgeInsets.top
|
|
76
152
|
} else if contentVerticalAlignment == .bottom {
|
|
@@ -79,6 +155,7 @@ final class NativeListAccessoryButton: UIButton {
|
|
|
79
155
|
frame.origin.y += rowTextOffsetY
|
|
80
156
|
titleLabel.frame = frame
|
|
81
157
|
}
|
|
158
|
+
layoutHelp()
|
|
82
159
|
}
|
|
83
160
|
if let lineHeight = marketLineHeight, let titleLabel, let title = attributedTitle(for: .normal)
|
|
84
161
|
{
|
|
@@ -127,6 +204,8 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
127
204
|
private var boundCheckboxData: [String: Any]?
|
|
128
205
|
private var boundCheckboxTarget: NativeSelectionTarget?
|
|
129
206
|
private var semanticValueButtons: [UIButton] = []
|
|
207
|
+
private var valuePairSubscriptRanges: [ObjectIdentifier: [(NSRange, Bool)]] = [:]
|
|
208
|
+
private var tertiaryIconButtons: Set<ObjectIdentifier> = []
|
|
130
209
|
private var accessorySizeConstraints: [NSLayoutConstraint] = []
|
|
131
210
|
private var currentItem: NativeListItem?
|
|
132
211
|
private var currentTheme: [String: Any]?
|
|
@@ -210,14 +289,19 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
210
289
|
}
|
|
211
290
|
}
|
|
212
291
|
func anchorInset(for view: UIView) -> CGFloat {
|
|
213
|
-
|
|
214
|
-
|
|
292
|
+
guard accessoryButtons.contains(where: { $0 === view }), view.bounds.width == 38 else {
|
|
293
|
+
return 0
|
|
294
|
+
}
|
|
295
|
+
if tertiaryIconButtons.contains(ObjectIdentifier(view)) { return 7 }
|
|
296
|
+
return currentItem?.data.string("presentation") == "accountSelector" ? 7 : 0
|
|
215
297
|
}
|
|
216
298
|
func reset() {
|
|
217
299
|
NSLayoutConstraint.deactivate(accessorySizeConstraints)
|
|
218
300
|
accessorySizeConstraints.removeAll()
|
|
219
301
|
accessoryActions.removeAll()
|
|
220
302
|
semanticValueButtons.removeAll()
|
|
303
|
+
valuePairSubscriptRanges.removeAll()
|
|
304
|
+
tertiaryIconButtons.removeAll()
|
|
221
305
|
checkboxAction = nil
|
|
222
306
|
boundCheckboxData = nil
|
|
223
307
|
boundCheckboxTarget = nil
|
|
@@ -225,8 +309,8 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
225
309
|
contentGap = nil
|
|
226
310
|
currentItem = nil
|
|
227
311
|
for (index, button) in accessoryButtons.enumerated() {
|
|
228
|
-
button.
|
|
229
|
-
button.
|
|
312
|
+
button.tag = index
|
|
313
|
+
button.clearTitlesForReuse()
|
|
230
314
|
button.setImage(nil, for: .normal)
|
|
231
315
|
button.setImage(nil, for: .disabled)
|
|
232
316
|
button.titleLabel?.font = nativeListFont(
|
|
@@ -236,11 +320,19 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
236
320
|
button.contentHorizontalAlignment = .center
|
|
237
321
|
button.contentVerticalAlignment = .center
|
|
238
322
|
button.rowTextOffsetY = 0
|
|
323
|
+
button.helpBindingGeneration += 1
|
|
324
|
+
button.helpPrimaryLineOnly = false
|
|
325
|
+
button.showsHelpUnderline = false
|
|
239
326
|
button.transform = .identity
|
|
240
327
|
button.marketLineHeight = nil
|
|
241
328
|
button.selectorSummaryLineHeight = nil
|
|
242
329
|
button.backgroundColor = .clear
|
|
243
330
|
button.pressedBackgroundColor = nil
|
|
331
|
+
button.normalBackgroundColor = .clear
|
|
332
|
+
button.fixedTextLineHeight = nil
|
|
333
|
+
button.contentEdgeInsets = .zero
|
|
334
|
+
button.layer.borderWidth = 0
|
|
335
|
+
button.layer.borderColor = nil
|
|
244
336
|
button.layer.cornerRadius = 0
|
|
245
337
|
button.isHidden = true
|
|
246
338
|
button.isEnabled = true
|
|
@@ -254,9 +346,13 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
254
346
|
spinner.alpha = 1
|
|
255
347
|
}
|
|
256
348
|
@objc private func pressed(_ button: UIButton) {
|
|
257
|
-
guard
|
|
258
|
-
|
|
259
|
-
|
|
349
|
+
guard let controlIndex = accessoryButtons.firstIndex(where: { $0 === button }),
|
|
350
|
+
accessoryActions.indices.contains(controlIndex) else { return }
|
|
351
|
+
let action = accessoryActions[controlIndex]
|
|
352
|
+
if !action.0.isEmpty {
|
|
353
|
+
let anchor = accessoryButtons[controlIndex]
|
|
354
|
+
onAction?(action.0, anchor.helpPrimaryLineOnly ? anchor.helpAnchor : button, button.tag, action.1)
|
|
355
|
+
}
|
|
260
356
|
}
|
|
261
357
|
@objc private func checkboxPressed() {
|
|
262
358
|
if let action = checkboxAction { onAction?(action.0, checkboxButton, nil, action.1) }
|
|
@@ -268,8 +364,42 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
268
364
|
_ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
269
365
|
) {
|
|
270
366
|
var textIndex = 0
|
|
271
|
-
for accessory in accessories.prefix(2) {
|
|
367
|
+
for (slot, accessory) in accessories.prefix(2).enumerated() {
|
|
368
|
+
let controlIndex = textIndex
|
|
272
369
|
switch accessory.string("kind") {
|
|
370
|
+
case "button":
|
|
371
|
+
showAccessory(
|
|
372
|
+
textIndex, accessory.string("label"),
|
|
373
|
+
action: accessory.bool("disabled") || item.data.bool("disabled")
|
|
374
|
+
? nil : (accessory.string("actionKey"), nil))
|
|
375
|
+
let button = accessoryButtons[textIndex]
|
|
376
|
+
button.titleLabel?.font = nativeListFont(ofSize: 14, weight: .medium, fonts: fonts)
|
|
377
|
+
button.fixedTextLineHeight = 20
|
|
378
|
+
button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 10, bottom: 4, right: 10)
|
|
379
|
+
button.layer.borderWidth = 1
|
|
380
|
+
button.layer.borderColor = UIColor.clear.cgColor
|
|
381
|
+
button.layer.cornerRadius = 15
|
|
382
|
+
button.backgroundColor = UIColor(
|
|
383
|
+
nativeListHex: accessory.string("backgroundColor",
|
|
384
|
+
default: theme?["strongBackground"] as? String ?? "#F0F0F0"),
|
|
385
|
+
fallback: .clear)
|
|
386
|
+
button.normalBackgroundColor = button.backgroundColor ?? .clear
|
|
387
|
+
button.pressedBackgroundColor = UIColor(
|
|
388
|
+
nativeListHex: accessory.string("pressedBackgroundColor",
|
|
389
|
+
default: theme?["rowPressedBackground"] as? String ?? "#E4E4E4"),
|
|
390
|
+
fallback: .clear)
|
|
391
|
+
button.setTitleColor(
|
|
392
|
+
UIColor(
|
|
393
|
+
nativeListHex: accessory.string("textColor",
|
|
394
|
+
default: theme?["primaryText"] as? String ?? "#202020"),
|
|
395
|
+
fallback: .darkGray),
|
|
396
|
+
for: .normal)
|
|
397
|
+
button.accessibilityIdentifier = accessory.string("testID")
|
|
398
|
+
button.accessibilityLabel =
|
|
399
|
+
accessory.string("accessibilityLabel", default: accessory.string("label"))
|
|
400
|
+
button.isEnabled = !item.data.bool("disabled") && !accessory.bool("disabled")
|
|
401
|
+
button.alpha = button.isEnabled ? 1 : 0.4
|
|
402
|
+
textIndex += 1
|
|
273
403
|
case "value":
|
|
274
404
|
showAccessory(textIndex, accessory.string("text"))
|
|
275
405
|
semanticValueButtons.append(accessoryButtons[textIndex])
|
|
@@ -326,6 +456,7 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
326
456
|
textIndex += 1
|
|
327
457
|
default: break
|
|
328
458
|
}
|
|
459
|
+
if textIndex > controlIndex { accessoryButtons[controlIndex].tag = slot }
|
|
329
460
|
}
|
|
330
461
|
}
|
|
331
462
|
|
|
@@ -411,8 +542,12 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
411
542
|
theme: [String: Any]?
|
|
412
543
|
) {
|
|
413
544
|
guard accessoryButtons.indices.contains(index) else { return }
|
|
414
|
-
let
|
|
415
|
-
let
|
|
545
|
+
let primarySegments = accessory.dictionaries("primaryTextSegments")
|
|
546
|
+
let secondarySegments = accessory.dictionaries("secondaryTextSegments")
|
|
547
|
+
let primary = primarySegments.isEmpty
|
|
548
|
+
? accessory.string("primary") : primarySegments.map { $0.string("text") }.joined()
|
|
549
|
+
let secondary = secondarySegments.isEmpty
|
|
550
|
+
? accessory.string("secondary") : secondarySegments.map { $0.string("text") }.joined()
|
|
416
551
|
guard !primary.isEmpty || !secondary.isEmpty else { return }
|
|
417
552
|
let button = accessoryButtons[index]
|
|
418
553
|
// Value pairs never carry an action; the row owns their touches.
|
|
@@ -422,45 +557,66 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
422
557
|
primaryParagraph.alignment = .right
|
|
423
558
|
primaryParagraph.minimumLineHeight = 20
|
|
424
559
|
primaryParagraph.maximumLineHeight = 20
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
accessory.string("primaryTone"),
|
|
432
|
-
defaultTone: "primary",
|
|
433
|
-
theme: theme
|
|
434
|
-
),
|
|
435
|
-
.paragraphStyle: primaryParagraph,
|
|
436
|
-
]
|
|
437
|
-
))
|
|
560
|
+
var subscriptRanges: [(NSRange, Bool)] = []
|
|
561
|
+
appendValuePairLine(
|
|
562
|
+
to: text, fallback: primary, segments: primarySegments, fontSize: 16,
|
|
563
|
+
weight: .medium,
|
|
564
|
+
color: accessoryTextColor(accessory.string("primaryTone"), defaultTone: "primary", theme: theme),
|
|
565
|
+
paragraph: primaryParagraph, isPrimary: true, subscriptRanges: &subscriptRanges)
|
|
438
566
|
if !primary.isEmpty, !secondary.isEmpty { text.append(NSAttributedString(string: "\n")) }
|
|
439
567
|
let secondaryParagraph = NSMutableParagraphStyle()
|
|
440
568
|
secondaryParagraph.alignment = .right
|
|
441
569
|
secondaryParagraph.minimumLineHeight = 20
|
|
442
570
|
secondaryParagraph.maximumLineHeight = 20
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
.foregroundColor: accessoryTextColor(
|
|
449
|
-
accessory.string("secondaryTone"),
|
|
450
|
-
defaultTone: "secondary",
|
|
451
|
-
theme: theme
|
|
452
|
-
),
|
|
453
|
-
.paragraphStyle: secondaryParagraph,
|
|
454
|
-
]
|
|
455
|
-
))
|
|
571
|
+
appendValuePairLine(
|
|
572
|
+
to: text, fallback: secondary, segments: secondarySegments, fontSize: 14,
|
|
573
|
+
weight: .regular,
|
|
574
|
+
color: accessoryTextColor(accessory.string("secondaryTone"), defaultTone: "secondary", theme: theme),
|
|
575
|
+
paragraph: secondaryParagraph, isPrimary: false, subscriptRanges: &subscriptRanges)
|
|
456
576
|
button.isHidden = false
|
|
457
577
|
button.titleLabel?.numberOfLines = 2
|
|
458
578
|
button.contentHorizontalAlignment = .trailing
|
|
459
579
|
button.setAttributedTitle(text, for: .normal)
|
|
580
|
+
valuePairSubscriptRanges[ObjectIdentifier(button)] = subscriptRanges
|
|
460
581
|
button.setContentHuggingPriority(.required, for: .horizontal)
|
|
461
582
|
button.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
462
583
|
while accessoryActions.count <= index { accessoryActions.append(("", nil)) }
|
|
463
584
|
accessoryActions[index] = ("", nil)
|
|
585
|
+
if let help = accessory.dictionary("primaryHelp") {
|
|
586
|
+
button.helpPrimaryLineOnly = true
|
|
587
|
+
button.showsHelpUnderline = help.string("underline") == "dashed"
|
|
588
|
+
button.isUserInteractionEnabled = help.string("presentation") == "popover"
|
|
589
|
+
accessoryActions[index] = (help.string("presentation") == "popover" ? help.string("actionKey") : "", nil)
|
|
590
|
+
button.setNeedsLayout()
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private func appendValuePairLine(
|
|
595
|
+
to text: NSMutableAttributedString,
|
|
596
|
+
fallback: String,
|
|
597
|
+
segments: [[String: Any]],
|
|
598
|
+
fontSize: CGFloat,
|
|
599
|
+
weight: NativeListFontWeight,
|
|
600
|
+
color: UIColor,
|
|
601
|
+
paragraph: NSParagraphStyle,
|
|
602
|
+
isPrimary: Bool,
|
|
603
|
+
subscriptRanges: inout [(NSRange, Bool)]
|
|
604
|
+
) {
|
|
605
|
+
let runs = segments.isEmpty ? [["text": fallback]] : segments
|
|
606
|
+
for run in runs {
|
|
607
|
+
let content = run.string("text")
|
|
608
|
+
let isSubscript = run.string("style") == "subscript"
|
|
609
|
+
let start = text.length
|
|
610
|
+
text.append(NSAttributedString(string: content, attributes: [
|
|
611
|
+
.font: nativeListFont(
|
|
612
|
+
ofSize: isSubscript ? ceil(fontSize * 0.6) : fontSize, weight: weight, fonts: fonts),
|
|
613
|
+
.foregroundColor: color,
|
|
614
|
+
.paragraphStyle: paragraph,
|
|
615
|
+
]))
|
|
616
|
+
if isSubscript && text.length > start {
|
|
617
|
+
subscriptRanges.append((NSRange(location: start, length: text.length - start), isPrimary))
|
|
618
|
+
}
|
|
619
|
+
}
|
|
464
620
|
}
|
|
465
621
|
|
|
466
622
|
private func showMenuAccessory(
|
|
@@ -508,7 +664,12 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
508
664
|
? nativeListColor(currentTheme, "iconSubdued", "#8D8D8D")
|
|
509
665
|
: UIColor(nativeListHex: data.string("tintColor", default: "#646464"), fallback: .darkGray)
|
|
510
666
|
button.tintColor = tintColor
|
|
511
|
-
|
|
667
|
+
let isDecorativeRefresh =
|
|
668
|
+
data.string("name") == "RefreshCcwOutline" && data.string("actionKey").isEmpty
|
|
669
|
+
let image = isDecorativeRefresh
|
|
670
|
+
? nativeListIcon(named: data.string("name"), size: CGSize(width: 16, height: 16))
|
|
671
|
+
: nativeListIcon(named: data.string("name"))
|
|
672
|
+
if let image {
|
|
512
673
|
button.setImage(image, for: .normal)
|
|
513
674
|
button.setImage(
|
|
514
675
|
image.withTintColor(tintColor, renderingMode: .alwaysOriginal),
|
|
@@ -516,8 +677,22 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
516
677
|
)
|
|
517
678
|
}
|
|
518
679
|
let isDrillIn = data.string("kind") == "chevron"
|
|
680
|
+
let isTertiary = data.string("buttonStyle") == "tertiary"
|
|
681
|
+
if data.string("kind") == "icon" && isTertiary {
|
|
682
|
+
tertiaryIconButtons.insert(ObjectIdentifier(button))
|
|
683
|
+
}
|
|
519
684
|
let isAccountIcon = currentItem?.data.string("presentation") == "accountSelector" && !isDrillIn
|
|
520
|
-
|
|
685
|
+
button.isUserInteractionEnabled = !isDecorativeRefresh
|
|
686
|
+
let size: CGFloat =
|
|
687
|
+
isDecorativeRefresh ? 16 : isDrillIn ? 24 : isTertiary || isAccountIcon && !isAccountCreate ? 38 : 36
|
|
688
|
+
if isTertiary {
|
|
689
|
+
button.pressedBackgroundColor = nativeListColor(
|
|
690
|
+
currentTheme, "rowPressedBackground", "#E8E8E8")
|
|
691
|
+
button.layer.borderWidth = 1
|
|
692
|
+
button.layer.borderColor = UIColor.clear.cgColor
|
|
693
|
+
button.layer.cornerRadius = 19
|
|
694
|
+
button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 6)
|
|
695
|
+
}
|
|
521
696
|
if isAccountCreate { button.layer.cornerRadius = 8 }
|
|
522
697
|
if isAccountIcon && !isAccountCreate && !data.string("actionKey").isEmpty {
|
|
523
698
|
button.pressedBackgroundColor = nativeListColor(
|
|
@@ -655,6 +830,19 @@ final class NativeListAccessoryStack: UIStackView {
|
|
|
655
830
|
}
|
|
656
831
|
button.titleLabel?.font = font
|
|
657
832
|
attributed.addAttributes(attributes, range: NSRange(location: 0, length: attributed.length))
|
|
833
|
+
if style["fontSize"] != nil,
|
|
834
|
+
let ranges = valuePairSubscriptRanges[ObjectIdentifier(button)]
|
|
835
|
+
{
|
|
836
|
+
for (range, isPrimary) in ranges {
|
|
837
|
+
let weight = style["fontWeight"] == nil
|
|
838
|
+
? (isPrimary ? NativeListFontWeight.medium : NativeListFontWeight.regular)
|
|
839
|
+
: marketFontWeight(style.string("fontWeight"), fallback: .regular)
|
|
840
|
+
attributed.addAttribute(
|
|
841
|
+
.font,
|
|
842
|
+
value: nativeListFont(ofSize: ceil(size * 0.6), weight: weight, fonts: fonts),
|
|
843
|
+
range: range)
|
|
844
|
+
}
|
|
845
|
+
}
|
|
658
846
|
button.setAttributedTitle(attributed, for: .normal)
|
|
659
847
|
}
|
|
660
848
|
|
|
@@ -70,8 +70,10 @@ final class NativeListActivityDetailsView: UIStackView {
|
|
|
70
70
|
let verticalPadding = CGFloat(style.double("verticalPadding", default: 8))
|
|
71
71
|
let lineGap = CGFloat(style.double("lineGap"))
|
|
72
72
|
let gap = CGFloat(style.double("leadingGap", default: 12))
|
|
73
|
+
let hideLeading = data.bool("hideLeading")
|
|
73
74
|
let imageWidth = CGFloat(image.double("width", default: 40))
|
|
74
75
|
let imageHeight = CGFloat(image.double("height", default: 40))
|
|
76
|
+
let leadingWidth = hideLeading ? 0 : imageWidth + gap
|
|
75
77
|
let mainWidth = max(0, width - horizontalPadding * 2)
|
|
76
78
|
|
|
77
79
|
let amounts = data.dictionaries("amounts").map { amount -> (width: CGFloat, height: CGFloat) in
|
|
@@ -112,10 +114,10 @@ final class NativeListActivityDetailsView: UIStackView {
|
|
|
112
114
|
let feeColumn = data.dictionary("fee") == nil ? 0 : gap + feeWidth
|
|
113
115
|
let identityWidth: CGFloat
|
|
114
116
|
if table {
|
|
115
|
-
let shared = (mainWidth -
|
|
117
|
+
let shared = (mainWidth - leadingWidth - gap - feeColumn) / 2
|
|
116
118
|
identityWidth = min(mainWidth * 0.42, shared)
|
|
117
119
|
} else {
|
|
118
|
-
identityWidth = mainWidth -
|
|
120
|
+
identityWidth = mainWidth - leadingWidth - gap - feeColumn - min(amountsWidth, mainWidth * 0.5)
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
let title = NativeListResolvedText(data.string("title"), style: style.dictionary("title"), size: 16,
|
|
@@ -140,7 +142,7 @@ final class NativeListActivityDetailsView: UIStackView {
|
|
|
140
142
|
return max(height, ceil(sizingButton.intrinsicContentSize.height))
|
|
141
143
|
} + 8
|
|
142
144
|
}
|
|
143
|
-
return max(imageHeight, identityHeight, amountsHeight, feeHeight) + actionsHeight + verticalPadding * 2
|
|
145
|
+
return max(hideLeading ? 0 : imageHeight, identityHeight, amountsHeight, feeHeight) + actionsHeight + verticalPadding * 2
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
func bind(_ item: NativeListItem, theme: [String: Any]?, fonts: NativeListFontFamilies) {
|
|
@@ -150,20 +152,28 @@ final class NativeListActivityDetailsView: UIStackView {
|
|
|
150
152
|
let image = style.dictionary("image") ?? [:]
|
|
151
153
|
let table = data.string("presentation") == "table"
|
|
152
154
|
let gap = CGFloat(style.double("leadingGap", default: 12))
|
|
155
|
+
let hideLeading = data.bool("hideLeading")
|
|
153
156
|
main.spacing = gap
|
|
154
157
|
identity.spacing = CGFloat(style.double("lineGap"))
|
|
155
158
|
amounts.spacing = CGFloat(style.double("trailingGap", default: 2))
|
|
156
159
|
titleLine.spacing = CGFloat(style.double("titleBadgeGap", default: 4))
|
|
157
160
|
NSLayoutConstraint.deactivate(dimensions)
|
|
161
|
+
let visualWidth = visual.widthAnchor.constraint(equalToConstant: CGFloat(image.double("width", default: 40)))
|
|
162
|
+
if hideLeading { visualWidth.priority = UILayoutPriority(999) }
|
|
158
163
|
dimensions = [
|
|
159
|
-
|
|
164
|
+
visualWidth,
|
|
160
165
|
visual.heightAnchor.constraint(equalToConstant: CGFloat(image.double("height", default: 40))),
|
|
161
166
|
amounts.widthAnchor.constraint(lessThanOrEqualTo: main.widthAnchor, multiplier: table ? 0.42 : 0.5),
|
|
162
167
|
]
|
|
163
168
|
if table { dimensions.append(amounts.widthAnchor.constraint(equalTo: identity.widthAnchor)) }
|
|
164
169
|
NSLayoutConstraint.activate(dimensions)
|
|
165
|
-
visual.
|
|
166
|
-
|
|
170
|
+
visual.isHidden = hideLeading
|
|
171
|
+
if hideLeading {
|
|
172
|
+
visual.recycle()
|
|
173
|
+
} else {
|
|
174
|
+
visual.bind(data.dictionary("leading") ?? [:], style: image, key: item.key, theme: theme,
|
|
175
|
+
isUnread: false, secondaryVisual: data.dictionary("secondaryLeading"), fonts: fonts)
|
|
176
|
+
}
|
|
167
177
|
NativeListResolvedText(data.string("title"), style: style.dictionary("title"), size: 16,
|
|
168
178
|
weight: .medium, color: nativeListColor(theme, "primaryText", "#202020"), lineHeight: 24,
|
|
169
179
|
lines: 1, fonts: fonts
|
|
@@ -225,7 +235,7 @@ final class NativeListActivityDetailsView: UIStackView {
|
|
|
225
235
|
let actionData = data.dictionaries("footerActions")
|
|
226
236
|
actionKeys = actionData.map { $0.string("key") }
|
|
227
237
|
actions.isHidden = actionData.isEmpty
|
|
228
|
-
actions.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: CGFloat(image.double("width", default: 40)) + gap, bottom: 0, trailing: 0)
|
|
238
|
+
actions.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: hideLeading ? 0 : CGFloat(image.double("width", default: 40)) + gap, bottom: 0, trailing: 0)
|
|
229
239
|
for (index, descriptor) in actionData.enumerated() {
|
|
230
240
|
let button = UIButton(type: .system); button.tag = index
|
|
231
241
|
button.setTitle(descriptor.string("label"), for: .normal)
|
|
@@ -249,7 +259,7 @@ final class NativeListActivityDetailsView: UIStackView {
|
|
|
249
259
|
onAction?(actionKeys[button.tag], button, "footerAction", button.tag)
|
|
250
260
|
}
|
|
251
261
|
func recycle() {
|
|
252
|
-
visual.recycle(); amountViews.forEach { $0.recycle() }; actionKeys = []; descriptionKey = ""
|
|
262
|
+
visual.recycle(); visual.isHidden = false; amountViews.forEach { $0.recycle() }; actionKeys = []; descriptionKey = ""
|
|
253
263
|
}
|
|
254
264
|
}
|
|
255
265
|
|
|
@@ -97,6 +97,7 @@ final class NativeListActivityCell: NativeListRendererCell {
|
|
|
97
97
|
details.recycle()
|
|
98
98
|
let style = data.dictionary("style") ?? [:]
|
|
99
99
|
let image = style.dictionary("image") ?? [:]
|
|
100
|
+
let hideLeading = data.bool("hideLeading")
|
|
100
101
|
root.spacing = 12
|
|
101
102
|
root.setCustomSpacing(CGFloat(style.double("leadingGap", default: 12)), after: visual)
|
|
102
103
|
switch style.dictionary("container")?.string("contentVerticalAlignment") {
|
|
@@ -113,12 +114,17 @@ final class NativeListActivityCell: NativeListRendererCell {
|
|
|
113
114
|
bottom: CGFloat(style.double("verticalPadding", default: 8)),
|
|
114
115
|
right: CGFloat(style.double("horizontalPadding", default: layout == "table" ? 16 : 12)))
|
|
115
116
|
NSLayoutConstraint.deactivate(dimensions)
|
|
117
|
+
let visualWidth = visual.widthAnchor.constraint(equalToConstant: CGFloat(image.double("width", default: 40)))
|
|
118
|
+
if hideLeading { visualWidth.priority = UILayoutPriority(999) }
|
|
116
119
|
dimensions = [
|
|
117
|
-
|
|
120
|
+
visualWidth,
|
|
118
121
|
visual.heightAnchor.constraint(equalToConstant: CGFloat(image.double("height", default: 40))),
|
|
119
122
|
]
|
|
120
123
|
NSLayoutConstraint.activate(dimensions)
|
|
121
|
-
|
|
124
|
+
visual.isHidden = hideLeading
|
|
125
|
+
if hideLeading {
|
|
126
|
+
visual.recycle()
|
|
127
|
+
} else if let leading = data.dictionary("leading") {
|
|
122
128
|
visual.bind(
|
|
123
129
|
leading, style: image, key: item.key, theme: theme, isUnread: false,
|
|
124
130
|
secondaryVisual: data.dictionary("secondaryLeading"), fonts: fonts)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
final class NativeListButtonGroupCell: NativeListRendererCell {
|
|
4
|
+
override class func measure(
|
|
5
|
+
_ item: NativeListItem, width: CGFloat, theme: [String: Any]?, layout: String,
|
|
6
|
+
fonts: NativeListFontFamilies
|
|
7
|
+
) -> CGFloat? { 64 }
|
|
8
|
+
|
|
9
|
+
private var buttons: [UIButton] = []
|
|
10
|
+
private var actionKeys: [String] = []
|
|
11
|
+
private var rowOpacity: CGFloat = 1
|
|
12
|
+
|
|
13
|
+
override func applyAppearance() {
|
|
14
|
+
super.applyAppearance()
|
|
15
|
+
contentView.alpha = rowOpacity
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override init(frame: CGRect) {
|
|
19
|
+
super.init(frame: frame)
|
|
20
|
+
root.axis = .horizontal
|
|
21
|
+
root.alignment = .fill
|
|
22
|
+
root.distribution = .fillEqually
|
|
23
|
+
root.spacing = 10
|
|
24
|
+
}
|
|
25
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
26
|
+
|
|
27
|
+
override func bindContent(
|
|
28
|
+
_ item: NativeListItem, theme: [String: Any]?, layout: String,
|
|
29
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
30
|
+
) {
|
|
31
|
+
recycleContent()
|
|
32
|
+
rowOpacity = CGFloat(item.data.dictionary("style")?.dictionary("container")?.double("opacity", default: item.data.double("opacity", default: 1)) ?? item.data.double("opacity", default: 1))
|
|
33
|
+
contentInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
|
|
34
|
+
let info = nativeListColor(theme, "info", "#0F69F6")
|
|
35
|
+
for (index, descriptor) in item.data.dictionaries("buttons").enumerated() {
|
|
36
|
+
let button = NativeListAccessoryButton(type: .custom)
|
|
37
|
+
button.tag = index
|
|
38
|
+
button.setTitle(descriptor.string("label"), for: .normal)
|
|
39
|
+
button.titleLabel?.font = nativeListFont(ofSize: 14, weight: .medium, fonts: fonts)
|
|
40
|
+
button.titleLabel?.lineBreakMode = .byTruncatingTail
|
|
41
|
+
button.setTitleColor(info, for: .normal)
|
|
42
|
+
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 8, bottom: 12, right: 8)
|
|
43
|
+
button.layer.cornerRadius = 12
|
|
44
|
+
button.layer.borderWidth = 1
|
|
45
|
+
button.layer.borderColor = info.withAlphaComponent(0.35).cgColor
|
|
46
|
+
button.normalBackgroundColor = .clear
|
|
47
|
+
button.pressedBackgroundColor = info.withAlphaComponent(0.17)
|
|
48
|
+
button.isEnabled = !item.data.bool("disabled") && !descriptor.bool("disabled") && !descriptor.bool("loading")
|
|
49
|
+
button.alpha = button.isEnabled ? 1 : 0.4
|
|
50
|
+
button.accessibilityIdentifier = descriptor["testID"] as? String
|
|
51
|
+
button.accessibilityLabel = descriptor.string("accessibilityLabel", default: descriptor.string("label"))
|
|
52
|
+
button.addTarget(self, action: #selector(pressed(_:)), for: .touchUpInside)
|
|
53
|
+
if descriptor.bool("loading"), let titleLabel = button.titleLabel {
|
|
54
|
+
let spinner = UIActivityIndicatorView(style: .medium)
|
|
55
|
+
spinner.color = info
|
|
56
|
+
spinner.isUserInteractionEnabled = false
|
|
57
|
+
spinner.translatesAutoresizingMaskIntoConstraints = false
|
|
58
|
+
button.addSubview(spinner)
|
|
59
|
+
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 36, bottom: 12, right: 8)
|
|
60
|
+
NSLayoutConstraint.activate([
|
|
61
|
+
spinner.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -8),
|
|
62
|
+
spinner.centerYAnchor.constraint(equalTo: button.centerYAnchor),
|
|
63
|
+
spinner.widthAnchor.constraint(equalToConstant: 20),
|
|
64
|
+
spinner.heightAnchor.constraint(equalToConstant: 20),
|
|
65
|
+
])
|
|
66
|
+
spinner.startAnimating()
|
|
67
|
+
}
|
|
68
|
+
buttons.append(button)
|
|
69
|
+
actionKeys.append(descriptor.string("actionKey"))
|
|
70
|
+
root.addArrangedSubview(button)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc private func pressed(_ button: UIButton) {
|
|
75
|
+
guard button.isEnabled, buttons.indices.contains(button.tag), buttons[button.tag] === button else { return }
|
|
76
|
+
emitAction(actionKeys[button.tag], from: button, source: "buttonGroup", slot: button.tag)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override func recycleContent() {
|
|
80
|
+
for button in buttons {
|
|
81
|
+
button.removeTarget(self, action: nil, for: .allEvents)
|
|
82
|
+
button.subviews.compactMap { $0 as? UIActivityIndicatorView }.forEach { $0.stopAnimating() }
|
|
83
|
+
root.removeArrangedSubview(button)
|
|
84
|
+
button.removeFromSuperview()
|
|
85
|
+
}
|
|
86
|
+
buttons.removeAll()
|
|
87
|
+
actionKeys.removeAll()
|
|
88
|
+
}
|
|
89
|
+
}
|