@onekeyfe/react-native-native-list 3.0.157-alpha.254 → 3.0.157-alpha.255
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/build.gradle +7 -0
- package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +20 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +179 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +23 -2
- package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +53 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListEndAlignment.kt +5 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMediaPreviewSlot.kt +215 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +15 -6
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +3 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListScrollPositionTracker.kt +29 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +16 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +163 -26
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +4 -0
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListEndAlignmentTest.kt +34 -0
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListScrollPositionTrackerTest.kt +28 -0
- package/docs/DESIGN.md +4 -2
- package/docs/ROW_TEMPLATES.md +45 -0
- package/docs/SPEC.md +227 -1
- package/docs/STYLE_SPEC.md +8 -0
- package/ios/HybridNativeList.swift +30 -0
- package/ios/NativeListActivityDetailsView.swift +229 -0
- package/ios/NativeListActivityRenderer.swift +24 -2
- package/ios/NativeListMediaPreviewSlot.swift +199 -0
- package/ios/NativeListMediaTileRenderer.swift +10 -4
- package/ios/NativeListModels.swift +4 -1
- package/ios/NativeListScrollPositionTracker.swift +30 -0
- package/ios/NativeListSectionHeaderRenderer.swift +27 -1
- package/ios/RNCNativeListView.swift +159 -4
- package/ios/tests/NativeListActivityPresentationTests.swift +43 -0
- package/ios/tests/NativeListLifecycleTests.swift +123 -0
- package/ios/tests/NativeListMediaVisibilityTests.swift +42 -0
- package/ios/tests/NativeListScrollPositionTrackerTests.swift +25 -0
- package/lib/module/NativeList.js +56 -3
- package/lib/module/NativeList.web.js +5 -0
- package/lib/module/validation.js +64 -4
- package/lib/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +39 -3
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +6 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +15 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_bool.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +26 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +24 -9
- package/lib/nitrogen/generated/android/nativelistOnLoad.cpp +2 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +8 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +37 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +23 -2
- package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +32 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +3 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +54 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +6 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +8 -2
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +6 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +11 -2
- package/lib/nitrogen/generated/shared/json/NativeListConfig.json +3 -0
- package/lib/typescript/src/NativeList.nitro.d.ts +3 -0
- package/lib/typescript/src/NativeList.types.d.ts +14 -1
- package/lib/typescript/src/models.d.ts +33 -5
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +39 -3
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +6 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_bool.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +24 -9
- package/nitrogen/generated/android/nativelistOnLoad.cpp +2 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +37 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +23 -2
- package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +32 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +3 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +54 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +6 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +8 -2
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +6 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +11 -2
- package/nitrogen/generated/shared/json/NativeListConfig.json +3 -0
- package/package.json +6 -5
- package/scripts/enable-container-slots.mjs +58 -0
- package/scripts/enable-container-slots.test.mjs +58 -0
- package/src/NativeList.nitro.ts +3 -0
- package/src/NativeList.tsx +84 -2
- package/src/NativeList.types.ts +14 -1
- package/src/NativeList.web.tsx +5 -0
- package/src/models.ts +38 -1
- package/src/validation.ts +146 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import XCTest
|
|
3
|
+
@testable import NativeListModule
|
|
4
|
+
|
|
5
|
+
@MainActor
|
|
6
|
+
final class NativeListActivityPresentationTests: XCTestCase {
|
|
7
|
+
func testSmallNumberRunsPreserveBaselineAcrossAmountsFiatAndFee() throws {
|
|
8
|
+
func segments(_ prefix: String, _ suffix: String) -> [[String: Any]] {
|
|
9
|
+
[["text": prefix], ["text": "5", "style": "subscript"], ["text": suffix]]
|
|
10
|
+
}
|
|
11
|
+
let item = try NativeListItem(data: [
|
|
12
|
+
"type": "activity", "key": "transfer", "title": "Transfer",
|
|
13
|
+
"leading": ["kind": "icon", "name": "ArrowTopOutline"],
|
|
14
|
+
"amounts": [[
|
|
15
|
+
"key": "receive", "text": "0.051 ETH", "textSegments": segments("0.0", "1 ETH"),
|
|
16
|
+
"secondaryText": "$0.051", "secondaryTextSegments": segments("$0.0", "1"),
|
|
17
|
+
]],
|
|
18
|
+
"fee": [
|
|
19
|
+
"primary": "0.051 GAS", "primaryTextSegments": segments("0.0", "1 GAS"),
|
|
20
|
+
"secondary": "€0.051", "secondaryTextSegments": segments("€0.0", "1"),
|
|
21
|
+
],
|
|
22
|
+
])
|
|
23
|
+
let view = NativeListActivityDetailsView()
|
|
24
|
+
view.bind(item, theme: nil)
|
|
25
|
+
func labels(in view: UIView) -> [UILabel] {
|
|
26
|
+
(view as? UILabel).map { [$0] } ?? view.subviews.flatMap { labels(in: $0) }
|
|
27
|
+
}
|
|
28
|
+
let cases: [(String, Int, CGFloat)] = [
|
|
29
|
+
("0.051 ETH", 3, 16), ("$0.051", 4, 14),
|
|
30
|
+
("0.051 GAS", 3, 14), ("€0.051", 4, 12),
|
|
31
|
+
]
|
|
32
|
+
let rendered = labels(in: view)
|
|
33
|
+
for (text, countIndex, parentSize) in cases {
|
|
34
|
+
let label = try XCTUnwrap(rendered.first { $0.attributedText?.string == text })
|
|
35
|
+
let value = try XCTUnwrap(label.attributedText)
|
|
36
|
+
let font = try XCTUnwrap(value.attribute(.font, at: countIndex, effectiveRange: nil) as? UIFont)
|
|
37
|
+
XCTAssertEqual(font.pointSize, ceil(parentSize * 0.6))
|
|
38
|
+
let baseline = value.attribute(.baselineOffset, at: 0, effectiveRange: nil) as? NSNumber
|
|
39
|
+
let countBaseline = value.attribute(.baselineOffset, at: countIndex, effectiveRange: nil) as? NSNumber
|
|
40
|
+
XCTAssertEqual(countBaseline?.doubleValue ?? 0, baseline?.doubleValue ?? 0)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -4,6 +4,42 @@ import XCTest
|
|
|
4
4
|
|
|
5
5
|
@MainActor
|
|
6
6
|
final class NativeListLifecycleTests: XCTestCase {
|
|
7
|
+
func testSectionLoadingStopsOnRebindReuseAndDetach() throws {
|
|
8
|
+
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
|
|
9
|
+
let controller = UIViewController()
|
|
10
|
+
window.rootViewController = controller
|
|
11
|
+
window.isHidden = false
|
|
12
|
+
defer { window.isHidden = true }
|
|
13
|
+
let cell = NativeListSectionHeaderCell(frame: CGRect(x: 0, y: 0, width: 320, height: 40))
|
|
14
|
+
controller.view.addSubview(cell)
|
|
15
|
+
func bind(loading: Bool) throws {
|
|
16
|
+
let item = try NativeListItem(data: [
|
|
17
|
+
"key": "section", "type": "sectionHeader", "sectionKey": "pending",
|
|
18
|
+
"title": "Confirming", "variant": "history", "titleLoading": loading,
|
|
19
|
+
"style": ["title": ["color": "#FFAA00"]],
|
|
20
|
+
])
|
|
21
|
+
cell.bind(item: item, theme: nil, layout: "sectioned", selected: false,
|
|
22
|
+
checkboxState: { _, _, fallback in fallback })
|
|
23
|
+
}
|
|
24
|
+
try bind(loading: true)
|
|
25
|
+
let spinner = try XCTUnwrap(cell.root.arrangedSubviews.first as? UIActivityIndicatorView)
|
|
26
|
+
XCTAssertTrue(spinner.isAnimating)
|
|
27
|
+
XCTAssertFalse(spinner.isAccessibilityElement)
|
|
28
|
+
XCTAssertEqual(spinner.color, UIColor(nativeListHex: "#FFAA00", fallback: .clear))
|
|
29
|
+
|
|
30
|
+
try bind(loading: false)
|
|
31
|
+
XCTAssertFalse(spinner.isAnimating)
|
|
32
|
+
XCTAssertNil(spinner.superview)
|
|
33
|
+
try bind(loading: true)
|
|
34
|
+
XCTAssertTrue(spinner.isAnimating)
|
|
35
|
+
cell.removeFromSuperview()
|
|
36
|
+
XCTAssertFalse(spinner.isAnimating)
|
|
37
|
+
controller.view.addSubview(cell)
|
|
38
|
+
XCTAssertTrue(spinner.isAnimating)
|
|
39
|
+
cell.prepareForReuse()
|
|
40
|
+
XCTAssertFalse(spinner.isAnimating)
|
|
41
|
+
}
|
|
42
|
+
|
|
7
43
|
func testRebindingAndReuseInvalidateThePreviousActionEpoch() throws {
|
|
8
44
|
let cell = NativeListActionCell(frame: CGRect(x: 0, y: 0, width: 320, height: 60))
|
|
9
45
|
let first = try NativeListItem(data: ["key": "row", "type": "action", "title": "First"])
|
|
@@ -82,6 +118,93 @@ final class NativeListLifecycleTests: XCTestCase {
|
|
|
82
118
|
return String(decoding: data, as: UTF8.self)
|
|
83
119
|
}
|
|
84
120
|
|
|
121
|
+
func testContentShrinkCorrectsRestoredOffsetAfterPagerInsetsAndUpdatesObserver() throws {
|
|
122
|
+
let view = try makeScrollingList()
|
|
123
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
124
|
+
list.contentInset = UIEdgeInsets(top: 108, left: 0, bottom: 0, right: 0)
|
|
125
|
+
list.contentOffset.y = 600
|
|
126
|
+
var savedLogicalOffset = list.contentOffset.y + list.contentInset.top
|
|
127
|
+
let offsetObserver = list.observe(\.contentOffset, options: [.new]) { list, _ in
|
|
128
|
+
savedLogicalOffset = list.contentOffset.y + list.contentInset.top
|
|
129
|
+
}
|
|
130
|
+
// Model the pager's synchronous content-size observer and cached-offset restoration.
|
|
131
|
+
let sizeObserver = list.observe(\.contentSize, options: [.old, .new]) { list, change in
|
|
132
|
+
if change.oldValue != change.newValue, list.contentSize.height < 200 {
|
|
133
|
+
list.contentInset.bottom = max(0, list.bounds.height - 44 - list.contentSize.height)
|
|
134
|
+
list.contentOffset.y = 600
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 0))
|
|
138
|
+
view.layoutIfNeeded()
|
|
139
|
+
list.layoutIfNeeded()
|
|
140
|
+
drainMainQueue()
|
|
141
|
+
XCTAssertEqual(list.contentSize.height, 132, accuracy: 0.5)
|
|
142
|
+
XCTAssertEqual(list.contentOffset.y, -44, accuracy: 0.5)
|
|
143
|
+
XCTAssertEqual(savedLogicalOffset, 64, accuracy: 0.5)
|
|
144
|
+
withExtendedLifetime((offsetObserver, sizeObserver)) {}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func testContentShrinkPreservesInRangeAndNegativePullOffsets() throws {
|
|
148
|
+
for offset in [CGFloat(120), CGFloat(-160)] {
|
|
149
|
+
let view = try makeScrollingList()
|
|
150
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
151
|
+
list.contentInset.top = 108
|
|
152
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 20))
|
|
153
|
+
view.layoutIfNeeded()
|
|
154
|
+
list.layoutIfNeeded()
|
|
155
|
+
// Preserve a valid offset or a refresh pull while shrink correction is queued.
|
|
156
|
+
list.contentOffset.y = offset
|
|
157
|
+
drainMainQueue()
|
|
158
|
+
XCTAssertEqual(list.contentOffset.y, offset, accuracy: 0.5)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func testContentShrinkWaitsForRefreshCompletion() throws {
|
|
163
|
+
let view = try makeScrollingList()
|
|
164
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
165
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 0, refreshing: true))
|
|
166
|
+
view.layoutIfNeeded()
|
|
167
|
+
list.layoutIfNeeded()
|
|
168
|
+
list.contentOffset.y = 600
|
|
169
|
+
drainMainQueue()
|
|
170
|
+
XCTAssertEqual(list.contentOffset.y, 600, accuracy: 0.5)
|
|
171
|
+
view.setRefreshing(false)
|
|
172
|
+
drainMainQueue()
|
|
173
|
+
let inset = list.adjustedContentInset
|
|
174
|
+
let maximum = max(-inset.top, list.contentSize.height - list.bounds.height + inset.bottom)
|
|
175
|
+
XCTAssertEqual(list.contentOffset.y, maximum, accuracy: 0.5)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private func makeScrollingList() throws -> NativeListView {
|
|
179
|
+
let view = NativeListView(frame: CGRect(x: 0, y: 0, width: 320, height: 708))
|
|
180
|
+
for index in 0..<3 { view.mountContainerSlot(UIView(), atIndex: index) }
|
|
181
|
+
view.setContainerSlotHeightsJson("[44,44,44]")
|
|
182
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 40))
|
|
183
|
+
view.layoutIfNeeded()
|
|
184
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
185
|
+
list.layoutIfNeeded()
|
|
186
|
+
drainMainQueue()
|
|
187
|
+
return view
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private func scrollingSnapshot(count: Int, refreshing: Bool = false) throws -> String {
|
|
191
|
+
let rows: [[String: Any]] = (0..<count).map {
|
|
192
|
+
["key": "row-\($0)", "type": "action", "title": "Row", "height": 80]
|
|
193
|
+
}
|
|
194
|
+
let value: [String: Any] = [
|
|
195
|
+
"schemaVersion": 1, "generation": count + 1,
|
|
196
|
+
"layout": ["kind": "linear"], "rows": rows,
|
|
197
|
+
"capabilities": ["pullToRefresh": true, "refreshing": refreshing],
|
|
198
|
+
]
|
|
199
|
+
return String(decoding: try JSONSerialization.data(withJSONObject: value), as: UTF8.self)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private func drainMainQueue() {
|
|
203
|
+
let settled = expectation(description: "Deferred content shrink correction")
|
|
204
|
+
DispatchQueue.main.async { settled.fulfill() }
|
|
205
|
+
wait(for: [settled], timeout: 1)
|
|
206
|
+
}
|
|
207
|
+
|
|
85
208
|
private func footerCell(in view: NativeListView) -> NativeListRowHost? {
|
|
86
209
|
view.subviews.lazy.compactMap { container in
|
|
87
210
|
container.subviews.compactMap { $0 as? NativeListRowHost }.first
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import XCTest
|
|
3
|
+
@testable import NativeListModule
|
|
4
|
+
|
|
5
|
+
@MainActor
|
|
6
|
+
final class NativeListMediaVisibilityTests: XCTestCase {
|
|
7
|
+
func testRetainedPagerAndListCellsRequireViewportIntersection() {
|
|
8
|
+
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
|
|
9
|
+
let controller = UIViewController()
|
|
10
|
+
window.rootViewController = controller
|
|
11
|
+
window.isHidden = false
|
|
12
|
+
defer { window.isHidden = true }
|
|
13
|
+
let pager = UIScrollView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
|
|
14
|
+
pager.contentSize = CGSize(width: 640, height: 480)
|
|
15
|
+
controller.view.addSubview(pager)
|
|
16
|
+
let list = UIScrollView(frame: CGRect(x: 320, y: 0, width: 320, height: 480))
|
|
17
|
+
list.contentSize = CGSize(width: 320, height: 960)
|
|
18
|
+
pager.addSubview(list)
|
|
19
|
+
let tile = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
|
|
20
|
+
list.addSubview(tile)
|
|
21
|
+
|
|
22
|
+
XCTAssertNotNil(tile.window, "Pager retains the offscreen cell in the window")
|
|
23
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
24
|
+
pager.contentOffset.x = 320
|
|
25
|
+
XCTAssertTrue(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
26
|
+
list.contentOffset.y = 110
|
|
27
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
28
|
+
list.contentOffset.y = 100
|
|
29
|
+
XCTAssertTrue(NativeListMediaPreviewSlot.intersectsViewport(tile), "Partially visible previews remain eligible")
|
|
30
|
+
list.transform = CGAffineTransform(translationX: 320, y: 0)
|
|
31
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
32
|
+
list.transform = .identity
|
|
33
|
+
XCTAssertTrue(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
34
|
+
list.isHidden = true
|
|
35
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
36
|
+
list.isHidden = false
|
|
37
|
+
list.alpha = 0
|
|
38
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
39
|
+
tile.removeFromSuperview()
|
|
40
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import NativeListModule
|
|
3
|
+
|
|
4
|
+
final class NativeListScrollPositionTrackerTests: XCTestCase {
|
|
5
|
+
func testHysteresisAndDisabledLifecycle() {
|
|
6
|
+
var tracker = NativeListScrollPositionTracker()
|
|
7
|
+
XCTAssertNil(tracker.update(offset: 200))
|
|
8
|
+
tracker.configure(json: "{\"start\":48,\"end\":160}")
|
|
9
|
+
XCTAssertEqual(tracker.update(offset: 0), false)
|
|
10
|
+
XCTAssertNil(tracker.update(offset: 159))
|
|
11
|
+
XCTAssertEqual(tracker.update(offset: 160), true)
|
|
12
|
+
XCTAssertNil(tracker.update(offset: 100))
|
|
13
|
+
XCTAssertEqual(tracker.update(offset: 48), false)
|
|
14
|
+
XCTAssertNil(tracker.update(offset: 49))
|
|
15
|
+
tracker.configure(json: "")
|
|
16
|
+
XCTAssertNil(tracker.update(offset: 200))
|
|
17
|
+
tracker.configure(json: "{\"start\":48,\"end\":160}")
|
|
18
|
+
XCTAssertEqual(tracker.update(offset: 200), true)
|
|
19
|
+
tracker.resetDelivery()
|
|
20
|
+
XCTAssertEqual(tracker.update(offset: 200), true)
|
|
21
|
+
XCTAssertNil(tracker.update(offset: .nan))
|
|
22
|
+
tracker.configure(json: "{\"start\":48,\"end\":48}")
|
|
23
|
+
XCTAssertNil(tracker.update(offset: 48))
|
|
24
|
+
}
|
|
25
|
+
}
|
package/lib/module/NativeList.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
|
|
3
|
+
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { OneKeyImageCache, OneKeyImageCachePolicy } from '@onekeyfe/react-native-image';
|
|
5
5
|
import { NativeAvatarPrefetchModel, NativeAvatarPrefetchQueue } from "./avatarPrefetch.js";
|
|
6
|
+
import { View } from 'react-native';
|
|
6
7
|
import { callback, getHostComponent } from 'react-native-nitro-modules';
|
|
7
8
|
import { isSelectableRow } from "./selection.js";
|
|
8
9
|
import { normalizeIndexScroll, normalizeKeyScroll, normalizePositionScroll, resolveLocationIndex, scrollFailure, validateOffset } from "./scrolling.js";
|
|
@@ -26,6 +27,10 @@ function parsePayload(payloadJson) {
|
|
|
26
27
|
}
|
|
27
28
|
export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
28
29
|
snapshot,
|
|
30
|
+
listHeader,
|
|
31
|
+
listFooter,
|
|
32
|
+
listEmpty,
|
|
33
|
+
scrollPositionThresholds,
|
|
29
34
|
webVirtualizationEnabled: _webVirtualizationEnabled,
|
|
30
35
|
webSectionIndexContainerRef: _webSectionIndexContainerRef,
|
|
31
36
|
keyboardDismissMode = 'none',
|
|
@@ -36,6 +41,7 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
36
41
|
onReorder,
|
|
37
42
|
onEndReached,
|
|
38
43
|
onVisibleRangeChanged,
|
|
44
|
+
onScrollPositionThresholdChange,
|
|
39
45
|
onRefresh,
|
|
40
46
|
onScrollToIndexFailed,
|
|
41
47
|
initialScrollIndex,
|
|
@@ -44,6 +50,13 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
44
50
|
initialScrollViewOffset,
|
|
45
51
|
...viewProps
|
|
46
52
|
}, forwardedRef) {
|
|
53
|
+
const [slotHeights, setSlotHeights] = useState([0, 0, 0]);
|
|
54
|
+
const containerSlotHeightsJson = JSON.stringify(slotHeights);
|
|
55
|
+
const slotInset = snapshot.layout.contentPaddingHorizontal ?? snapshot.layout.contentPadding ?? 0;
|
|
56
|
+
if (snapshot.layout.orientation === 'horizontal' && (listHeader || listEmpty || listFooter)) {
|
|
57
|
+
throw new Error('NativeList container slots require vertical orientation');
|
|
58
|
+
}
|
|
59
|
+
const slotContents = [listHeader, listEmpty, listFooter];
|
|
47
60
|
const normalizedKeyboardShouldPersistTaps = keyboardShouldPersistTaps === true ? 'always' : keyboardShouldPersistTaps === false ? 'never' : keyboardShouldPersistTaps;
|
|
48
61
|
const nativeRef = useRef(null);
|
|
49
62
|
const callbacksRef = useRef({
|
|
@@ -53,6 +66,7 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
53
66
|
onReorder,
|
|
54
67
|
onEndReached,
|
|
55
68
|
onVisibleRangeChanged,
|
|
69
|
+
onScrollPositionThresholdChange,
|
|
56
70
|
onRefresh,
|
|
57
71
|
onScrollToIndexFailed
|
|
58
72
|
});
|
|
@@ -63,9 +77,24 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
63
77
|
onReorder,
|
|
64
78
|
onEndReached,
|
|
65
79
|
onVisibleRangeChanged,
|
|
80
|
+
onScrollPositionThresholdChange,
|
|
66
81
|
onRefresh,
|
|
67
82
|
onScrollToIndexFailed
|
|
68
83
|
};
|
|
84
|
+
const scrollPositionThresholdsJson = useMemo(() => {
|
|
85
|
+
if (!scrollPositionThresholds || scrollPositionThresholds.enabled === false) return '';
|
|
86
|
+
const {
|
|
87
|
+
start,
|
|
88
|
+
end
|
|
89
|
+
} = scrollPositionThresholds;
|
|
90
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end <= start) {
|
|
91
|
+
throw new Error('scrollPositionThresholds requires 0 <= start < end and finite values');
|
|
92
|
+
}
|
|
93
|
+
return JSON.stringify({
|
|
94
|
+
start,
|
|
95
|
+
end
|
|
96
|
+
});
|
|
97
|
+
}, [scrollPositionThresholds]);
|
|
69
98
|
const snapshotJson = useMemo(() => serializeSnapshot(snapshot), [snapshot]);
|
|
70
99
|
const snapshotRef = useRef(snapshot);
|
|
71
100
|
const previousSnapshotJsonRef = useRef(snapshotJson);
|
|
@@ -235,7 +264,10 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
235
264
|
}),
|
|
236
265
|
onRowAction: callback(payloadJson => {
|
|
237
266
|
const payload = parsePayload(payloadJson);
|
|
238
|
-
if (payload.actionKey === 'nativeList.refresh'
|
|
267
|
+
if (payload.actionKey === 'nativeList.refresh' && payload.rowKey === undefined) {
|
|
268
|
+
callbacksRef.current.onRefresh?.();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
239
271
|
callbacksRef.current.onRowAction?.(payload);
|
|
240
272
|
}),
|
|
241
273
|
onActionAnchorInvalidated: callback(payloadJson => {
|
|
@@ -264,6 +296,11 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
264
296
|
onEndReached: callback(payloadJson => {
|
|
265
297
|
callbacksRef.current.onEndReached?.(parsePayload(payloadJson));
|
|
266
298
|
}),
|
|
299
|
+
onScrollPositionThresholdChange: callback(isBeyondThreshold => {
|
|
300
|
+
callbacksRef.current.onScrollPositionThresholdChange?.({
|
|
301
|
+
isBeyondThreshold
|
|
302
|
+
});
|
|
303
|
+
}),
|
|
267
304
|
onVisibleRangeChanged: callback(payloadJson => {
|
|
268
305
|
const payload = parsePayload(payloadJson);
|
|
269
306
|
const previous = avatarRangeRef.current;
|
|
@@ -280,8 +317,24 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
280
317
|
...viewProps,
|
|
281
318
|
...nativeCallbacks,
|
|
282
319
|
snapshotJson: snapshotJson,
|
|
320
|
+
containerSlotHeightsJson: containerSlotHeightsJson,
|
|
321
|
+
scrollPositionThresholdsJson: scrollPositionThresholdsJson,
|
|
283
322
|
keyboardDismissMode: keyboardDismissMode,
|
|
284
|
-
keyboardShouldPersistTaps: normalizedKeyboardShouldPersistTaps
|
|
323
|
+
keyboardShouldPersistTaps: normalizedKeyboardShouldPersistTaps,
|
|
324
|
+
children: slotContents.map((content, index) => /*#__PURE__*/_jsx(View, {
|
|
325
|
+
collapsable: false,
|
|
326
|
+
style: {
|
|
327
|
+
position: 'absolute',
|
|
328
|
+
left: slotInset,
|
|
329
|
+
right: slotInset,
|
|
330
|
+
top: 0
|
|
331
|
+
},
|
|
332
|
+
onLayout: event => {
|
|
333
|
+
const height = Math.ceil(event.nativeEvent.layout.height);
|
|
334
|
+
setSlotHeights(previous => previous[index] === height ? previous : previous.map((value, slot) => slot === index ? height : value));
|
|
335
|
+
},
|
|
336
|
+
children: content
|
|
337
|
+
}, index))
|
|
285
338
|
});
|
|
286
339
|
});
|
|
287
340
|
//# sourceMappingURL=NativeList.js.map
|
|
@@ -22,6 +22,11 @@ export function preloadNativeListAvatarImages(sources) {
|
|
|
22
22
|
}
|
|
23
23
|
export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
24
24
|
snapshot,
|
|
25
|
+
listHeader: _listHeader,
|
|
26
|
+
listEmpty: _listEmpty,
|
|
27
|
+
listFooter: _listFooter,
|
|
28
|
+
scrollPositionThresholds: _scrollPositionThresholds,
|
|
29
|
+
onScrollPositionThresholdChange: _onScrollPositionThresholdChange,
|
|
25
30
|
webVirtualizationEnabled = true,
|
|
26
31
|
webSectionIndexContainerRef,
|
|
27
32
|
keyboardDismissMode: _keyboardDismissMode,
|
package/lib/module/validation.js
CHANGED
|
@@ -128,13 +128,13 @@ const TEXT_STYLE_KEYS_BY_ROW_TYPE = {
|
|
|
128
128
|
identity: ['title', 'subtitle', 'tertiary', 'badge', 'value', 'valueSecondary'],
|
|
129
129
|
walletGroup: [],
|
|
130
130
|
rail: ['title', 'badge', 'status'],
|
|
131
|
-
activity: ['title', 'description', 'status', 'primaryAmount', 'secondaryAmount'],
|
|
131
|
+
activity: ['title', 'description', 'status', 'amounts', 'badges', 'presentation', 'fee', 'descriptionActionKey', 'primaryAmount', 'secondaryAmount'],
|
|
132
132
|
message: ['title', 'body', 'time'],
|
|
133
133
|
dataRow: ['columns', 'columnSecondary', 'index'],
|
|
134
134
|
market: ['title', 'subtitle', 'price', 'change'],
|
|
135
135
|
mediaTile: ['title', 'subtitle', 'badge'],
|
|
136
136
|
metricCard: ['title', 'value', 'subtitle', 'trend'],
|
|
137
|
-
sectionHeader: ['title', 'subtitle', 'value'],
|
|
137
|
+
sectionHeader: ['titleLoading', 'title', 'subtitle', 'value'],
|
|
138
138
|
action: ['title', 'value'],
|
|
139
139
|
system: ['title', 'message', 'actionText']
|
|
140
140
|
};
|
|
@@ -517,6 +517,46 @@ function assertVisual(row, path) {
|
|
|
517
517
|
});
|
|
518
518
|
}
|
|
519
519
|
function assertActivity(row, path) {
|
|
520
|
+
assertText(row.title, `${path}.title`);
|
|
521
|
+
assertText(row.description, `${path}.description`);
|
|
522
|
+
if (row.presentation !== undefined && !['stacked', 'table'].includes(row.presentation)) fail(`${path}.presentation`, 'must be stacked or table');
|
|
523
|
+
if (row.descriptionActionKey !== undefined) assertKey(row.descriptionActionKey, `${path}.descriptionActionKey`);
|
|
524
|
+
if ((row.amounts?.length ?? 0) > 32) fail(`${path}.amounts`, 'supports at most 32 amount lines');
|
|
525
|
+
const amountKeys = new Set();
|
|
526
|
+
row.amounts?.forEach((amount, index) => {
|
|
527
|
+
const amountPath = `${path}.amounts[${index}]`;
|
|
528
|
+
assertKey(amount.key, `${amountPath}.key`);
|
|
529
|
+
if (amountKeys.has(amount.key)) fail(amountPath, 'duplicate amount key');
|
|
530
|
+
amountKeys.add(amount.key);
|
|
531
|
+
assertText(amount.text, `${amountPath}.text`);
|
|
532
|
+
assertLeadingVisual(amount.leading, `${amountPath}.leading`);
|
|
533
|
+
assertText(amount.secondaryText, `${amountPath}.secondaryText`);
|
|
534
|
+
if (amount.tone !== undefined && !['primary', 'secondary', 'positive', 'negative'].includes(amount.tone)) fail(`${amountPath}.tone`, 'invalid text tone');
|
|
535
|
+
amount.textSegments?.forEach((segment, segmentIndex) => {
|
|
536
|
+
assertText(segment.text, `${amountPath}.textSegments[${segmentIndex}].text`);
|
|
537
|
+
if (segment.style !== undefined && segment.style !== 'subscript') fail(amountPath, 'invalid text segment style');
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
const assertActivitySegments = (segments, segmentPath) => segments?.forEach((segment, index) => {
|
|
541
|
+
assertText(segment.text, `${segmentPath}[${index}].text`);
|
|
542
|
+
if (segment.style !== undefined && segment.style !== 'subscript') fail(segmentPath, 'invalid text segment style');
|
|
543
|
+
});
|
|
544
|
+
row.amounts?.forEach((amount, index) => assertActivitySegments(amount.secondaryTextSegments, `${path}.amounts[${index}].secondaryTextSegments`));
|
|
545
|
+
assertActivitySegments(row.fee?.primaryTextSegments, `${path}.fee.primaryTextSegments`);
|
|
546
|
+
assertActivitySegments(row.fee?.secondaryTextSegments, `${path}.fee.secondaryTextSegments`);
|
|
547
|
+
assertText(row.fee?.label, `${path}.fee.label`);
|
|
548
|
+
assertText(row.fee?.primary, `${path}.fee.primary`);
|
|
549
|
+
assertText(row.fee?.secondary, `${path}.fee.secondary`);
|
|
550
|
+
if ((row.badges?.length ?? 0) > 4) fail(`${path}.badges`, 'supports at most 4 activity badges');
|
|
551
|
+
const badgeKeys = new Set();
|
|
552
|
+
row.badges?.forEach((badge, index) => {
|
|
553
|
+
assertKey(badge.key, `${path}.badges[${index}].key`);
|
|
554
|
+
if (badgeKeys.has(badge.key)) fail(`${path}.badges`, 'duplicate badge key');
|
|
555
|
+
badgeKeys.add(badge.key);
|
|
556
|
+
assertText(badge.text, `${path}.badges[${index}].text`);
|
|
557
|
+
if (badge.tone !== undefined && !['neutral', 'info', 'success', 'warning', 'danger'].includes(badge.tone)) fail(`${path}.badges`, 'invalid badge tone');
|
|
558
|
+
});
|
|
559
|
+
if (row.fee?.hidden !== undefined && typeof row.fee.hidden !== 'boolean') fail(`${path}.fee.hidden`, 'must be boolean');
|
|
520
560
|
if ((row.footerActions?.length ?? 0) > MAX_FOOTER_ACTIONS) {
|
|
521
561
|
fail(`${path}.footerActions`, `supports at most ${MAX_FOOTER_ACTIONS} actions`);
|
|
522
562
|
}
|
|
@@ -634,12 +674,13 @@ function assertRow(row, index, path = `rows[${index}]`) {
|
|
|
634
674
|
assertMarketRow(row, path);
|
|
635
675
|
break;
|
|
636
676
|
case 'mediaTile':
|
|
677
|
+
assertMediaPreview(row.media, `${path}.media`);
|
|
637
678
|
assertImage(row.image, `${path}.image`);
|
|
638
679
|
if (row.imageState !== undefined && !['empty', 'error'].includes(row.imageState)) {
|
|
639
680
|
fail(`${path}.imageState`, 'must be empty or error when provided');
|
|
640
681
|
}
|
|
641
|
-
if (row.imageState === undefined && row.image === undefined) {
|
|
642
|
-
fail(`${path}.image`, 'is required unless imageState is empty or error');
|
|
682
|
+
if (row.imageState === undefined && row.image === undefined && row.media === undefined) {
|
|
683
|
+
fail(`${path}.image`, 'is required unless media is provided or imageState is empty or error');
|
|
643
684
|
}
|
|
644
685
|
assertImage(row.networkImage, `${path}.networkImage`);
|
|
645
686
|
assertText(row.title, `${path}.title`);
|
|
@@ -668,6 +709,7 @@ function assertRow(row, index, path = `rows[${index}]`) {
|
|
|
668
709
|
}
|
|
669
710
|
break;
|
|
670
711
|
case 'sectionHeader':
|
|
712
|
+
if (row.titleLoading !== undefined && typeof row.titleLoading !== 'boolean') fail(`${path}.titleLoading`, 'must be boolean');
|
|
671
713
|
assertKey(row.sectionKey, `${path}.sectionKey`);
|
|
672
714
|
if (row.presentation !== undefined && row.presentation !== 'networkSelector') {
|
|
673
715
|
fail(`${path}.presentation`, 'must be networkSelector when provided');
|
|
@@ -746,12 +788,27 @@ function assertGroups(rows) {
|
|
|
746
788
|
}
|
|
747
789
|
}
|
|
748
790
|
}
|
|
791
|
+
function assertMediaPreview(value, path) {
|
|
792
|
+
if (value === undefined) return;
|
|
793
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) fail(path, 'must be a media descriptor');
|
|
794
|
+
const media = value;
|
|
795
|
+
assertImage(media.source, `${path}.source`);
|
|
796
|
+
if (!media.source) fail(`${path}.source`, 'is required');
|
|
797
|
+
const order = media.probeOrder;
|
|
798
|
+
if (!Array.isArray(order) || order.length < 1 || order.length > 2 || order.some(kind => kind !== 'image' && kind !== 'video') || new Set(order).size !== order.length) {
|
|
799
|
+
fail(`${path}.probeOrder`, 'must contain one or two distinct image/video candidates');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
749
802
|
export function validateSnapshot(snapshot) {
|
|
803
|
+
if (snapshot.capabilities?.refreshTriggerDistance !== undefined && (!Number.isFinite(snapshot.capabilities.refreshTriggerDistance) || snapshot.capabilities.refreshTriggerDistance <= 0)) fail('capabilities.refreshTriggerDistance', 'must be a positive finite number');
|
|
750
804
|
assertPlainSerializable(snapshot, 'snapshot', new Set());
|
|
751
805
|
if (snapshot.schemaVersion !== 1) fail('snapshot.schemaVersion', 'must be 1');
|
|
752
806
|
if (!Number.isSafeInteger(snapshot.generation) || snapshot.generation < 0) {
|
|
753
807
|
fail('snapshot.generation', 'must be a non-negative safe integer');
|
|
754
808
|
}
|
|
809
|
+
if (snapshot.layout.gridColumns !== undefined && (!Number.isInteger(snapshot.layout.gridColumns) || snapshot.layout.gridColumns < 2 || snapshot.layout.gridColumns > 7)) {
|
|
810
|
+
fail('snapshot.layout.gridColumns', 'must be an integer from 2 to 7');
|
|
811
|
+
}
|
|
755
812
|
if (snapshot.layout.kind === 'grid' && !snapshot.layout.gridColumns) {
|
|
756
813
|
fail('snapshot.layout.gridColumns', 'is required for grid layout');
|
|
757
814
|
}
|
|
@@ -880,6 +937,7 @@ function assertPatchChanges(patch, index) {
|
|
|
880
937
|
assertLeadingVisual(patch.changes.visual, `${path}.visual`);
|
|
881
938
|
break;
|
|
882
939
|
case 'activity':
|
|
940
|
+
assertActivity(patch.changes, path);
|
|
883
941
|
assertText(patch.changes.title, `${path}.title`);
|
|
884
942
|
assertText(patch.changes.description, `${path}.description`);
|
|
885
943
|
assertText(patch.changes.status, `${path}.status`);
|
|
@@ -933,6 +991,7 @@ function assertPatchChanges(patch, index) {
|
|
|
933
991
|
}, path);
|
|
934
992
|
break;
|
|
935
993
|
case 'mediaTile':
|
|
994
|
+
assertMediaPreview(patch.changes.media, `${path}.media`);
|
|
936
995
|
assertImage(patch.changes.image, `${path}.image`);
|
|
937
996
|
if (patch.changes.imageState !== undefined && !['empty', 'error'].includes(patch.changes.imageState)) {
|
|
938
997
|
fail(`${path}.imageState`, 'must be empty or error when provided');
|
|
@@ -965,6 +1024,7 @@ function assertPatchChanges(patch, index) {
|
|
|
965
1024
|
}
|
|
966
1025
|
break;
|
|
967
1026
|
case 'sectionHeader':
|
|
1027
|
+
if (patch.changes.titleLoading !== undefined && typeof patch.changes.titleLoading !== 'boolean') fail(`${path}.titleLoading`, 'must be boolean');
|
|
968
1028
|
assertSectionHeaderVariant(patch.changes.variant, `${path}.variant`);
|
|
969
1029
|
assertText(patch.changes.title, `${path}.title`);
|
|
970
1030
|
assertText(patch.changes.subtitle, `${path}.subtitle`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// JFunc_void_bool.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <fbjni/fbjni.h>
|
|
11
|
+
#include <functional>
|
|
12
|
+
|
|
13
|
+
#include <functional>
|
|
14
|
+
#include <NitroModules/JNICallable.hpp>
|
|
15
|
+
|
|
16
|
+
namespace margelo::nitro::nativelist {
|
|
17
|
+
|
|
18
|
+
using namespace facebook;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Represents the Java/Kotlin callback `(isBeyondThreshold: Boolean) -> Unit`.
|
|
22
|
+
* This can be passed around between C++ and Java/Kotlin.
|
|
23
|
+
*/
|
|
24
|
+
struct JFunc_void_bool: public jni::JavaClass<JFunc_void_bool> {
|
|
25
|
+
public:
|
|
26
|
+
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/nativelist/Func_void_bool;";
|
|
27
|
+
|
|
28
|
+
public:
|
|
29
|
+
/**
|
|
30
|
+
* Invokes the function this `JFunc_void_bool` instance holds through JNI.
|
|
31
|
+
*/
|
|
32
|
+
void invoke(bool isBeyondThreshold) const {
|
|
33
|
+
static const auto method = javaClassStatic()->getMethod<void(jboolean /* isBeyondThreshold */)>("invoke");
|
|
34
|
+
method(self(), isBeyondThreshold);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An implementation of Func_void_bool that is backed by a C++ implementation (using `std::function<...>`)
|
|
40
|
+
*/
|
|
41
|
+
class JFunc_void_bool_cxx final: public jni::HybridClass<JFunc_void_bool_cxx, JFunc_void_bool> {
|
|
42
|
+
public:
|
|
43
|
+
static jni::local_ref<JFunc_void_bool::javaobject> fromCpp(const std::function<void(bool /* isBeyondThreshold */)>& func) {
|
|
44
|
+
return JFunc_void_bool_cxx::newObjectCxxArgs(func);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public:
|
|
48
|
+
/**
|
|
49
|
+
* Invokes the C++ `std::function<...>` this `JFunc_void_bool_cxx` instance holds.
|
|
50
|
+
*/
|
|
51
|
+
void invoke_cxx(jboolean isBeyondThreshold) {
|
|
52
|
+
_func(static_cast<bool>(isBeyondThreshold));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public:
|
|
56
|
+
[[nodiscard]]
|
|
57
|
+
inline const std::function<void(bool /* isBeyondThreshold */)>& getFunction() const {
|
|
58
|
+
return _func;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public:
|
|
62
|
+
static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/nativelist/Func_void_bool_cxx;";
|
|
63
|
+
static void registerNatives() {
|
|
64
|
+
registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_bool_cxx::invoke_cxx)});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private:
|
|
68
|
+
explicit JFunc_void_bool_cxx(const std::function<void(bool /* isBeyondThreshold */)>& func): _func(func) { }
|
|
69
|
+
|
|
70
|
+
private:
|
|
71
|
+
friend HybridBase;
|
|
72
|
+
std::function<void(bool /* isBeyondThreshold */)> _func;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
} // namespace margelo::nitro::nativelist
|
|
@@ -15,14 +15,15 @@ namespace margelo::nitro::nativelist { enum class NativeListKeyboardShouldPersis
|
|
|
15
15
|
namespace margelo::nitro::nativelist { enum class NativeListScrollAlignment; }
|
|
16
16
|
|
|
17
17
|
#include <string>
|
|
18
|
+
#include <functional>
|
|
19
|
+
#include <optional>
|
|
20
|
+
#include "JFunc_void_bool.hpp"
|
|
21
|
+
#include <NitroModules/JNICallable.hpp>
|
|
18
22
|
#include "NativeListKeyboardDismissMode.hpp"
|
|
19
23
|
#include "JNativeListKeyboardDismissMode.hpp"
|
|
20
24
|
#include "NativeListKeyboardShouldPersistTaps.hpp"
|
|
21
25
|
#include "JNativeListKeyboardShouldPersistTaps.hpp"
|
|
22
|
-
#include <functional>
|
|
23
|
-
#include <optional>
|
|
24
26
|
#include "JFunc_void_std__string.hpp"
|
|
25
|
-
#include <NitroModules/JNICallable.hpp>
|
|
26
27
|
#include "NativeListScrollAlignment.hpp"
|
|
27
28
|
#include "JNativeListScrollAlignment.hpp"
|
|
28
29
|
|
|
@@ -65,6 +66,41 @@ namespace margelo::nitro::nativelist {
|
|
|
65
66
|
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JString> /* snapshotJson */)>("setSnapshotJson");
|
|
66
67
|
method(_javaPart, jni::make_jstring(snapshotJson));
|
|
67
68
|
}
|
|
69
|
+
std::string JHybridNativeListSpec::getContainerSlotHeightsJson() {
|
|
70
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JString>()>("getContainerSlotHeightsJson");
|
|
71
|
+
auto __result = method(_javaPart);
|
|
72
|
+
return __result->toStdString();
|
|
73
|
+
}
|
|
74
|
+
void JHybridNativeListSpec::setContainerSlotHeightsJson(const std::string& containerSlotHeightsJson) {
|
|
75
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JString> /* containerSlotHeightsJson */)>("setContainerSlotHeightsJson");
|
|
76
|
+
method(_javaPart, jni::make_jstring(containerSlotHeightsJson));
|
|
77
|
+
}
|
|
78
|
+
std::string JHybridNativeListSpec::getScrollPositionThresholdsJson() {
|
|
79
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JString>()>("getScrollPositionThresholdsJson");
|
|
80
|
+
auto __result = method(_javaPart);
|
|
81
|
+
return __result->toStdString();
|
|
82
|
+
}
|
|
83
|
+
void JHybridNativeListSpec::setScrollPositionThresholdsJson(const std::string& scrollPositionThresholdsJson) {
|
|
84
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JString> /* scrollPositionThresholdsJson */)>("setScrollPositionThresholdsJson");
|
|
85
|
+
method(_javaPart, jni::make_jstring(scrollPositionThresholdsJson));
|
|
86
|
+
}
|
|
87
|
+
std::optional<std::function<void(bool /* isBeyondThreshold */)>> JHybridNativeListSpec::getOnScrollPositionThresholdChange() {
|
|
88
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JFunc_void_bool::javaobject>()>("getOnScrollPositionThresholdChange_cxx");
|
|
89
|
+
auto __result = method(_javaPart);
|
|
90
|
+
return __result != nullptr ? std::make_optional([&]() -> std::function<void(bool /* isBeyondThreshold */)> {
|
|
91
|
+
if (__result->isInstanceOf(JFunc_void_bool_cxx::javaClassStatic())) [[likely]] {
|
|
92
|
+
auto downcast = jni::static_ref_cast<JFunc_void_bool_cxx::javaobject>(__result);
|
|
93
|
+
return downcast->cthis()->getFunction();
|
|
94
|
+
} else {
|
|
95
|
+
auto __resultRef = jni::make_global(__result);
|
|
96
|
+
return JNICallable<JFunc_void_bool, void(bool)>(std::move(__resultRef));
|
|
97
|
+
}
|
|
98
|
+
}()) : std::nullopt;
|
|
99
|
+
}
|
|
100
|
+
void JHybridNativeListSpec::setOnScrollPositionThresholdChange(const std::optional<std::function<void(bool /* isBeyondThreshold */)>>& onScrollPositionThresholdChange) {
|
|
101
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JFunc_void_bool::javaobject> /* onScrollPositionThresholdChange */)>("setOnScrollPositionThresholdChange_cxx");
|
|
102
|
+
method(_javaPart, onScrollPositionThresholdChange.has_value() ? JFunc_void_bool_cxx::fromCpp(onScrollPositionThresholdChange.value()) : nullptr);
|
|
103
|
+
}
|
|
68
104
|
NativeListKeyboardDismissMode JHybridNativeListSpec::getKeyboardDismissMode() {
|
|
69
105
|
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JNativeListKeyboardDismissMode>()>("getKeyboardDismissMode");
|
|
70
106
|
auto __result = method(_javaPart);
|