@onekeyfe/react-native-perp-depth-bar 3.0.48 → 3.0.52

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 (81) hide show
  1. package/android/src/main/java/com/margelo/nitro/perpdepthbar/HybridPerpDepthBars.kt +121 -15
  2. package/android/src/main/java/com/margelo/nitro/perpdepthbar/HybridPerpSideRatio.kt +30 -0
  3. package/ios/HybridPerpDepthBars.swift +84 -5
  4. package/ios/HybridPerpSideRatio.swift +19 -1
  5. package/ios/PerpColorParser.swift +10 -1
  6. package/lib/nitrogen/generated/android/c++/JHybridPerpDepthBarsSpec.cpp +47 -1
  7. package/lib/nitrogen/generated/android/c++/JHybridPerpDepthBarsSpec.hpp +6 -1
  8. package/lib/nitrogen/generated/android/c++/JHybridPerpSideRatioSpec.cpp +4 -1
  9. package/lib/nitrogen/generated/android/c++/JHybridPerpSideRatioSpec.hpp +1 -1
  10. package/lib/nitrogen/generated/android/c++/views/JHybridPerpDepthBarsStateUpdater.cpp +8 -0
  11. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/perpdepthbar/HybridPerpDepthBarsSpec.kt +19 -0
  12. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/perpdepthbar/HybridPerpSideRatioSpec.kt +3 -1
  13. package/lib/nitrogen/generated/ios/PerpDepthBar-Swift-Cxx-Bridge.hpp +11 -0
  14. package/lib/nitrogen/generated/ios/PerpDepthBar-Swift-Cxx-Umbrella.hpp +3 -0
  15. package/lib/nitrogen/generated/ios/c++/HybridPerpDepthBarsSpecSwift.hpp +29 -2
  16. package/lib/nitrogen/generated/ios/c++/HybridPerpSideRatioSpecSwift.hpp +6 -1
  17. package/lib/nitrogen/generated/ios/c++/views/HybridPerpDepthBarsComponent.mm +10 -0
  18. package/lib/nitrogen/generated/ios/swift/HybridPerpDepthBarsSpec.swift +4 -1
  19. package/lib/nitrogen/generated/ios/swift/HybridPerpDepthBarsSpec_cxx.swift +44 -0
  20. package/lib/nitrogen/generated/ios/swift/HybridPerpSideRatioSpec.swift +1 -1
  21. package/lib/nitrogen/generated/ios/swift/HybridPerpSideRatioSpec_cxx.swift +11 -0
  22. package/lib/nitrogen/generated/shared/c++/HybridPerpDepthBarsSpec.cpp +6 -0
  23. package/lib/nitrogen/generated/shared/c++/HybridPerpDepthBarsSpec.hpp +7 -1
  24. package/lib/nitrogen/generated/shared/c++/HybridPerpSideRatioSpec.cpp +1 -0
  25. package/lib/nitrogen/generated/shared/c++/HybridPerpSideRatioSpec.hpp +1 -1
  26. package/lib/nitrogen/generated/shared/c++/views/HybridPerpDepthBarsComponent.cpp +24 -0
  27. package/lib/nitrogen/generated/shared/c++/views/HybridPerpDepthBarsComponent.hpp +2 -0
  28. package/lib/nitrogen/generated/shared/json/PerpDepthBarsConfig.json +2 -0
  29. package/lib/nitrogen/nitrogen/generated/android/c++/JHybridPerpDepthBarsSpec.cpp +47 -1
  30. package/lib/nitrogen/nitrogen/generated/android/c++/JHybridPerpDepthBarsSpec.hpp +6 -1
  31. package/lib/nitrogen/nitrogen/generated/android/c++/JHybridPerpSideRatioSpec.cpp +4 -1
  32. package/lib/nitrogen/nitrogen/generated/android/c++/JHybridPerpSideRatioSpec.hpp +1 -1
  33. package/lib/nitrogen/nitrogen/generated/android/c++/views/JHybridPerpDepthBarsStateUpdater.cpp +8 -0
  34. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/perpdepthbar/HybridPerpDepthBarsSpec.kt +19 -0
  35. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/perpdepthbar/HybridPerpSideRatioSpec.kt +3 -1
  36. package/lib/nitrogen/nitrogen/generated/ios/PerpDepthBar-Swift-Cxx-Bridge.hpp +11 -0
  37. package/lib/nitrogen/nitrogen/generated/ios/PerpDepthBar-Swift-Cxx-Umbrella.hpp +3 -0
  38. package/lib/nitrogen/nitrogen/generated/ios/c++/HybridPerpDepthBarsSpecSwift.hpp +29 -2
  39. package/lib/nitrogen/nitrogen/generated/ios/c++/HybridPerpSideRatioSpecSwift.hpp +6 -1
  40. package/lib/nitrogen/nitrogen/generated/ios/c++/views/HybridPerpDepthBarsComponent.mm +10 -0
  41. package/lib/nitrogen/nitrogen/generated/ios/swift/HybridPerpDepthBarsSpec.swift +4 -1
  42. package/lib/nitrogen/nitrogen/generated/ios/swift/HybridPerpDepthBarsSpec_cxx.swift +44 -0
  43. package/lib/nitrogen/nitrogen/generated/ios/swift/HybridPerpSideRatioSpec.swift +1 -1
  44. package/lib/nitrogen/nitrogen/generated/ios/swift/HybridPerpSideRatioSpec_cxx.swift +11 -0
  45. package/lib/nitrogen/nitrogen/generated/shared/c++/HybridPerpDepthBarsSpec.cpp +6 -0
  46. package/lib/nitrogen/nitrogen/generated/shared/c++/HybridPerpDepthBarsSpec.hpp +7 -1
  47. package/lib/nitrogen/nitrogen/generated/shared/c++/HybridPerpSideRatioSpec.cpp +1 -0
  48. package/lib/nitrogen/nitrogen/generated/shared/c++/HybridPerpSideRatioSpec.hpp +1 -1
  49. package/lib/nitrogen/nitrogen/generated/shared/c++/views/HybridPerpDepthBarsComponent.cpp +24 -0
  50. package/lib/nitrogen/nitrogen/generated/shared/c++/views/HybridPerpDepthBarsComponent.hpp +2 -0
  51. package/lib/nitrogen/nitrogen/generated/shared/json/PerpDepthBarsConfig.json +2 -0
  52. package/lib/typescript/src/PerpDepthBars.nitro.d.ts +81 -3
  53. package/lib/typescript/src/PerpDepthBars.nitro.d.ts.map +1 -1
  54. package/lib/typescript/src/PerpSideRatio.nitro.d.ts +25 -2
  55. package/lib/typescript/src/PerpSideRatio.nitro.d.ts.map +1 -1
  56. package/nitrogen/generated/android/c++/JHybridPerpDepthBarsSpec.cpp +47 -1
  57. package/nitrogen/generated/android/c++/JHybridPerpDepthBarsSpec.hpp +6 -1
  58. package/nitrogen/generated/android/c++/JHybridPerpSideRatioSpec.cpp +4 -1
  59. package/nitrogen/generated/android/c++/JHybridPerpSideRatioSpec.hpp +1 -1
  60. package/nitrogen/generated/android/c++/views/JHybridPerpDepthBarsStateUpdater.cpp +8 -0
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/perpdepthbar/HybridPerpDepthBarsSpec.kt +19 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/perpdepthbar/HybridPerpSideRatioSpec.kt +3 -1
  63. package/nitrogen/generated/ios/PerpDepthBar-Swift-Cxx-Bridge.hpp +11 -0
  64. package/nitrogen/generated/ios/PerpDepthBar-Swift-Cxx-Umbrella.hpp +3 -0
  65. package/nitrogen/generated/ios/c++/HybridPerpDepthBarsSpecSwift.hpp +29 -2
  66. package/nitrogen/generated/ios/c++/HybridPerpSideRatioSpecSwift.hpp +6 -1
  67. package/nitrogen/generated/ios/c++/views/HybridPerpDepthBarsComponent.mm +10 -0
  68. package/nitrogen/generated/ios/swift/HybridPerpDepthBarsSpec.swift +4 -1
  69. package/nitrogen/generated/ios/swift/HybridPerpDepthBarsSpec_cxx.swift +44 -0
  70. package/nitrogen/generated/ios/swift/HybridPerpSideRatioSpec.swift +1 -1
  71. package/nitrogen/generated/ios/swift/HybridPerpSideRatioSpec_cxx.swift +11 -0
  72. package/nitrogen/generated/shared/c++/HybridPerpDepthBarsSpec.cpp +6 -0
  73. package/nitrogen/generated/shared/c++/HybridPerpDepthBarsSpec.hpp +7 -1
  74. package/nitrogen/generated/shared/c++/HybridPerpSideRatioSpec.cpp +1 -0
  75. package/nitrogen/generated/shared/c++/HybridPerpSideRatioSpec.hpp +1 -1
  76. package/nitrogen/generated/shared/c++/views/HybridPerpDepthBarsComponent.cpp +24 -0
  77. package/nitrogen/generated/shared/c++/views/HybridPerpDepthBarsComponent.hpp +2 -0
  78. package/nitrogen/generated/shared/json/PerpDepthBarsConfig.json +2 -0
  79. package/package.json +1 -1
  80. package/src/PerpDepthBars.nitro.ts +85 -9
  81. package/src/PerpSideRatio.nitro.ts +27 -3
@@ -7,6 +7,8 @@ import android.view.MotionEvent
7
7
  import android.view.View
8
8
  import com.facebook.proguard.annotations.DoNotStrip
9
9
  import com.facebook.react.uimanager.ThemedReactContext
10
+ import com.margelo.nitro.core.ArrayBuffer
11
+ import java.nio.ByteOrder
10
12
  import kotlin.math.abs
11
13
  import kotlin.math.exp
12
14
 
@@ -34,6 +36,12 @@ class HybridPerpDepthBars(val context: ThemedReactContext) : HybridPerpDepthBars
34
36
  private var lastEpoch = Double.NaN
35
37
  private var hasDrawn = false
36
38
  private var isDisposed = false
39
+ // Set once the caller drives depth through the imperative `setDepth` path. In
40
+ // that mode the `percents` prop is intentionally left empty, so `afterUpdate`
41
+ // must NOT re-sync targets from it — doing so would wipe `current` to an empty
42
+ // array on every unrelated prop commit (e.g. high-frequency prices/sizes text
43
+ // updates), blanking the bars between `setDepth` calls (REACT-NATIVE-1JZ).
44
+ private var imperativeDepth = false
37
45
 
38
46
  private val choreographer = Choreographer.getInstance()
39
47
  private var frameScheduled = false
@@ -42,8 +50,12 @@ class HybridPerpDepthBars(val context: ThemedReactContext) : HybridPerpDepthBars
42
50
 
43
51
  override val view: View = object : View(context) {
44
52
  override fun onDraw(canvas: Canvas) {
45
- drawBars(canvas)
46
- drawTexts(canvas)
53
+ if (hasRealData()) {
54
+ drawBars(canvas)
55
+ drawTexts(canvas)
56
+ } else {
57
+ drawPlaceholder(canvas)
58
+ }
47
59
  }
48
60
 
49
61
  override fun onTouchEvent(event: MotionEvent): Boolean {
@@ -101,13 +113,32 @@ class HybridPerpDepthBars(val context: ThemedReactContext) : HybridPerpDepthBars
101
113
  }
102
114
 
103
115
  // MARK: - Text props
116
+ // The strings actually drawn each frame. Decoupled from the `prices`/`sizes`
117
+ // props so the imperative `setText` path can drive them without the prop
118
+ // setters clobbering the values on unrelated prop commits (REACT-NATIVE-1JZ).
119
+ private var displayPrices: Array<String> = emptyArray()
120
+ private var displaySizes: Array<String> = emptyArray()
121
+ // Set once the caller drives text through `setText`; the `prices`/`sizes` props
122
+ // are then ignored (they are left empty in that mode).
123
+ private var imperativeText = false
124
+
104
125
  override var prices: Array<String> = emptyArray()
105
126
  get() = field
106
- set(value) { if (!isDisposed) { field = value; view.invalidate() } }
127
+ set(value) {
128
+ if (isDisposed || imperativeText) return
129
+ field = value
130
+ displayPrices = value
131
+ view.invalidate()
132
+ }
107
133
 
108
134
  override var sizes: Array<String> = emptyArray()
109
135
  get() = field
110
- set(value) { if (!isDisposed) { field = value; view.invalidate() } }
136
+ set(value) {
137
+ if (isDisposed || imperativeText) return
138
+ field = value
139
+ displaySizes = value
140
+ view.invalidate()
141
+ }
111
142
 
112
143
  override var priceColor: String = ""
113
144
  get() = field
@@ -149,15 +180,30 @@ class HybridPerpDepthBars(val context: ThemedReactContext) : HybridPerpDepthBars
149
180
  get() = field
150
181
  set(value) { if (!isDisposed) { field = value; view.invalidate() } }
151
182
 
183
+ // Per-row placeholder drawn natively while there is no real data (see
184
+ // PerpDepthBars.nitro.ts). Replaces the RN skeleton overlay so there is no
185
+ // blank handoff frame between placeholder and real numbers.
186
+ override var placeholderText: String = ""
187
+ get() = field
188
+ set(value) { if (!isDisposed) { field = value; view.invalidate() } }
189
+
190
+ override var placeholderRows: Double = 0.0
191
+ get() = field
192
+ set(value) { if (!isDisposed) { field = value; view.invalidate() } }
193
+
152
194
  override var onRowPress: ((rowIndex: Double) -> Unit)? = null
153
195
 
196
+ /** True once any real per-row data (bars or text) has been provided. */
197
+ private fun hasRealData(): Boolean =
198
+ current.isNotEmpty() || displayPrices.isNotEmpty() || displaySizes.isNotEmpty()
199
+
154
200
  private fun handleTap(y: Float) {
155
201
  if (isDisposed) return
156
202
  val step = ((rowHeight + rowMarginTop) * density).toFloat()
157
203
  if (step <= 0f) return
158
204
  val mt = (rowMarginTop * density).toFloat()
159
205
  val i = ((y - mt) / step).toInt()
160
- val count = maxOf(percents.size, maxOf(prices.size, sizes.size))
206
+ val count = maxOf(percents.size, maxOf(displayPrices.size, displaySizes.size))
161
207
  if (i in 0 until count) onRowPress?.invoke(i.toDouble())
162
208
  }
163
209
 
@@ -168,14 +214,51 @@ class HybridPerpDepthBars(val context: ThemedReactContext) : HybridPerpDepthBars
168
214
  override fun afterUpdate() {
169
215
  super.afterUpdate()
170
216
  if (isDisposed) return
171
- syncTargets()
217
+ // In imperative mode the depth comes from `setDepth`; the `percents` prop is
218
+ // empty and must be ignored here, otherwise every prop commit would wipe the
219
+ // bars. Prop-path callers (never call `setDepth`) keep the original behaviour.
220
+ if (imperativeDepth) return
221
+ syncTargets(percents)
222
+ }
223
+
224
+ /**
225
+ * Imperative depth update — bypasses the high-frequency `percents` prop write
226
+ * path (props/JSICache lock contention, REACT-NATIVE-1JZ). `buffer` is a
227
+ * packed little-endian `Float32Array`: one float per row, each the row's depth
228
+ * percentage with the SAME 0..100 semantics as the `percents` prop. The parsed
229
+ * values flow through the identical [syncTargets] clamp/target/frame-loop logic
230
+ * the prop path uses — only the data source differs.
231
+ */
232
+ override fun setDepth(buffer: ArrayBuffer) {
233
+ if (isDisposed) return
234
+ imperativeDepth = true
235
+ val byteBuffer = buffer.getBuffer(false)
236
+ val count = buffer.size / 4
237
+ val floats = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer()
238
+ val depths = DoubleArray(count) { floats.get(it).toDouble() }
239
+ syncTargets(depths)
172
240
  }
173
241
 
174
- private fun syncTargets() {
175
- val count = percents.size
242
+ /**
243
+ * Imperative per-row text update — companion to [setDepth] for the price/size
244
+ * layer. Bypasses the high-frequency `prices`/`sizes` prop write path (and the
245
+ * `afterUpdate`/prop-commit machinery) and just retargets the drawn strings in
246
+ * one call. Call in the SAME frame as [setDepth] so bar fill and row text come
247
+ * from the same source frame (REACT-NATIVE-1JZ).
248
+ */
249
+ override fun setText(prices: Array<String>, sizes: Array<String>) {
250
+ if (isDisposed) return
251
+ imperativeText = true
252
+ displayPrices = prices
253
+ displaySizes = sizes
254
+ view.invalidate()
255
+ }
256
+
257
+ private fun syncTargets(depths: DoubleArray) {
258
+ val count = depths.size
176
259
  val epochChanged = epoch != lastEpoch
177
260
  val snap = !hasDrawn || reducedMotion || epochChanged
178
- val newTarget = DoubleArray(count) { clampFrac(percents[it]) }
261
+ val newTarget = DoubleArray(count) { clampFrac(depths[it]) }
179
262
 
180
263
  if (snap) {
181
264
  cancelFrameLoop()
@@ -268,28 +351,51 @@ class HybridPerpDepthBars(val context: ThemedReactContext) : HybridPerpDepthBars
268
351
  }
269
352
 
270
353
  private fun drawTexts(canvas: Canvas) {
271
- if (prices.isEmpty() && sizes.isEmpty()) return
354
+ if (displayPrices.isEmpty() && displaySizes.isEmpty()) return
272
355
  val w = view.width.toFloat()
273
356
  if (w <= 0f) return
274
357
  val rh = (rowHeight * density).toFloat()
275
358
  val mt = (rowMarginTop * density).toFloat()
276
359
  val pad = (textInset * density).toFloat()
277
360
  val rightX = w - pad
278
- val rows = maxOf(prices.size, sizes.size)
361
+ val rows = maxOf(displayPrices.size, displaySizes.size)
279
362
  for (i in 0 until rows) {
280
363
  val rowTop = mt + i * (rh + mt)
281
364
  // Vertically center single-line text within the row.
282
- if (i < prices.size && prices[i].isNotEmpty()) {
365
+ if (i < displayPrices.size && displayPrices[i].isNotEmpty()) {
283
366
  val baseline = rowTop + rh / 2f - (pricePaint.descent() + pricePaint.ascent()) / 2f
284
- canvas.drawText(prices[i], pad, baseline, pricePaint)
367
+ canvas.drawText(displayPrices[i], pad, baseline, pricePaint)
285
368
  }
286
- if (i < sizes.size && sizes[i].isNotEmpty()) {
369
+ if (i < displaySizes.size && displaySizes[i].isNotEmpty()) {
287
370
  val baseline = rowTop + rh / 2f - (sizePaint.descent() + sizePaint.ascent()) / 2f
288
- canvas.drawText(sizes[i], rightX, baseline, sizePaint)
371
+ canvas.drawText(displaySizes[i], rightX, baseline, sizePaint)
289
372
  }
290
373
  }
291
374
  }
292
375
 
376
+ /**
377
+ * Draws `placeholderRows` rows of `placeholderText` (price left / size right,
378
+ * no bar) while there is no real data. Mirrors `drawTexts` geometry so the
379
+ * swap to real numbers is seamless.
380
+ */
381
+ private fun drawPlaceholder(canvas: Canvas) {
382
+ val rows = placeholderRows.toInt()
383
+ if (placeholderText.isEmpty() || rows <= 0) return
384
+ val w = view.width.toFloat()
385
+ if (w <= 0f) return
386
+ val rh = (rowHeight * density).toFloat()
387
+ val mt = (rowMarginTop * density).toFloat()
388
+ val pad = (textInset * density).toFloat()
389
+ val rightX = w - pad
390
+ for (i in 0 until rows) {
391
+ val rowTop = mt + i * (rh + mt)
392
+ val priceBaseline = rowTop + rh / 2f - (pricePaint.descent() + pricePaint.ascent()) / 2f
393
+ canvas.drawText(placeholderText, pad, priceBaseline, pricePaint)
394
+ val sizeBaseline = rowTop + rh / 2f - (sizePaint.descent() + sizePaint.ascent()) / 2f
395
+ canvas.drawText(placeholderText, rightX, sizeBaseline, sizePaint)
396
+ }
397
+ }
398
+
293
399
  private fun clampFrac(percent: Double): Double =
294
400
  (percent.coerceIn(0.0, 100.0)) / 100.0
295
401
 
@@ -29,6 +29,11 @@ class HybridPerpSideRatio(val context: ThemedReactContext) : HybridPerpSideRatio
29
29
  private var targetSplit = 0.5
30
30
  private var hasDrawn = false
31
31
  private var isDisposed = false
32
+ // Set once the caller drives the ratio through `setRatio`. The
33
+ // `bidPercentage`/`askPercentage` props are then ignored in `afterUpdate`,
34
+ // otherwise every prop commit would reset the split back to the (static)
35
+ // initial prop values (REACT-NATIVE-1JZ).
36
+ private var imperativeRatio = false
32
37
 
33
38
  private val choreographer = Choreographer.getInstance()
34
39
  private var frameScheduled = false
@@ -87,9 +92,34 @@ class HybridPerpSideRatio(val context: ThemedReactContext) : HybridPerpSideRatio
87
92
  view.invalidate()
88
93
  }
89
94
 
95
+ /**
96
+ * Imperative ratio update — bypasses the high-frequency
97
+ * `bidPercentage`/`askPercentage` prop write path (props/JSICache lock
98
+ * contention, REACT-NATIVE-1JZ). Companion to [HybridPerpDepthBars.setDepth].
99
+ * The two args carry the SAME 0..100 semantics as the props of the same name;
100
+ * we store them exactly like the prop setters and then route through the
101
+ * identical [afterUpdate] clamp/split/frame-loop logic the prop path uses —
102
+ * only the data source differs.
103
+ */
104
+ override fun setRatio(bidPercentage: Double, askPercentage: Double) {
105
+ if (isDisposed) return
106
+ imperativeRatio = true
107
+ // Apply the args directly — do NOT route through the `bidPercentage`/
108
+ // `askPercentage` prop fields, which a later prop commit could overwrite with
109
+ // the static initial values.
110
+ applyRatio(bidPercentage, askPercentage)
111
+ }
112
+
90
113
  override fun afterUpdate() {
91
114
  super.afterUpdate()
92
115
  if (isDisposed) return
116
+ // In imperative mode the ratio comes from `setRatio`; ignore the (static)
117
+ // props so a prop commit can't reset the bar to the initial values.
118
+ if (imperativeRatio) return
119
+ applyRatio(bidPercentage, askPercentage)
120
+ }
121
+
122
+ private fun applyRatio(bidPercentage: Double, askPercentage: Double) {
93
123
  val bid = maxOf(bidPercentage, 1.0)
94
124
  val ask = maxOf(askPercentage, 1.0)
95
125
  val split = bid / (bid + ask)
@@ -1,4 +1,5 @@
1
1
  import Foundation
2
+ import NitroModules
2
3
  import UIKit
3
4
 
4
5
  /// Renders an entire column of order-book depth bars for one side (asks/bids).
@@ -61,6 +62,12 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
61
62
  var sizeFontSize: Double = 11 { didSet { scheduleLayout() } }
62
63
  var textInset: Double = 0 { didSet { scheduleLayout() } }
63
64
 
65
+ // Per-row placeholder drawn natively while there is no real data (see
66
+ // PerpDepthBars.nitro.ts). Replaces the RN skeleton overlay so there is no
67
+ // blank handoff frame between placeholder and real numbers.
68
+ var placeholderText: String = "" { didSet { scheduleLayout() } }
69
+ var placeholderRows: Double = 0 { didSet { scheduleLayout() } }
70
+
64
71
  var priceColor: String = "" {
65
72
  didSet {
66
73
  cachedPriceColor = PerpColorParser.parse(priceColor).cgColor
@@ -94,6 +101,44 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
94
101
  stopDisplayLink()
95
102
  }
96
103
 
104
+ // MARK: - Imperative depth update
105
+ /// Imperatively update the per-row depth percentages, bypassing the
106
+ /// high-frequency `percents` prop write path (props/JSICache lock contention,
107
+ /// REACT-NATIVE-1JZ). `buffer` is a packed little-endian `Float32Array`: one
108
+ /// float per row, each the row's depth percentage with the SAME 0..100
109
+ /// semantics as the `percents` prop. The parsed values are routed through the
110
+ /// exact same `performLayout` clamp (`clampScale`) / target / display-link
111
+ /// path the prop uses — only the data source differs.
112
+ func setDepth(buffer: ArrayBuffer) throws {
113
+ let count = buffer.size / 4
114
+ guard count > 0 else {
115
+ percents = []
116
+ return
117
+ }
118
+ // `buffer.data` is a `UInt8` pointer; reinterpret it as little-endian
119
+ // Float32. iOS (arm/x86) is little-endian and JS `Float32Array` is too, so
120
+ // an unaligned little-endian load matches the prop semantics exactly.
121
+ var depths = [Double](repeating: 0, count: count)
122
+ let raw = UnsafeRawPointer(buffer.data)
123
+ for i in 0..<count {
124
+ let bits = raw.loadUnaligned(fromByteOffset: i * 4, as: UInt32.self)
125
+ depths[i] = Double(Float(bitPattern: UInt32(littleEndian: bits)))
126
+ }
127
+ // Feed through the single source of truth so the clamp/target/easing logic
128
+ // is byte-for-byte identical to the `percents` prop path.
129
+ percents = depths
130
+ }
131
+
132
+ /// Imperatively update the per-row price/size text — companion to `setDepth`
133
+ /// for the text layer. Bypasses the high-frequency `prices`/`sizes` prop write
134
+ /// path; assigning them feeds through the identical `didSet` → `scheduleLayout`
135
+ /// → `performLayout` path the prop setters use (REACT-NATIVE-1JZ). Call in the
136
+ /// SAME frame as `setDepth` so bar fill and row text share one source frame.
137
+ func setText(prices: [String], sizes: [String]) throws {
138
+ self.prices = prices
139
+ self.sizes = sizes
140
+ }
141
+
97
142
  private func handleTap(atY y: CGFloat) {
98
143
  let step = CGFloat(rowHeight + rowMarginTop)
99
144
  guard step > 0 else { return }
@@ -105,7 +150,13 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
105
150
  }
106
151
 
107
152
  private func scheduleLayout() {
108
- view.setNeedsLayout()
153
+ // Imperative setDepth/setText run on the JS thread (off-main); `setNeedsLayout`
154
+ // must be issued on the main thread.
155
+ if Thread.isMainThread {
156
+ view.setNeedsLayout()
157
+ } else {
158
+ DispatchQueue.main.async { [weak self] in self?.view.setNeedsLayout() }
159
+ }
109
160
  }
110
161
 
111
162
  // MARK: - Layout + retarget
@@ -113,7 +164,13 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
113
164
  let bounds = view.bounds
114
165
  guard bounds.width > 0 else { return }
115
166
 
116
- let count = percents.count
167
+ // When there is no real data, draw `placeholderRows` rows of placeholder
168
+ // text (no bar fill) instead of nothing — the native view owns the empty
169
+ // state, so there is no RN overlay to remove and no blank handoff frame.
170
+ let dataCount = percents.count
171
+ let placeholderMode =
172
+ dataCount == 0 && !placeholderText.isEmpty && Int(placeholderRows) > 0
173
+ let count = placeholderMode ? Int(placeholderRows) : dataCount
117
174
  syncLayerCount(count)
118
175
  syncTextLayerCount(count)
119
176
 
@@ -141,8 +198,11 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
141
198
  }
142
199
  CATransaction.commit()
143
200
 
201
+ // Placeholder rows have no bar fill (all-zero targets); real rows use data.
144
202
  var newTargets = [CGFloat](repeating: 0, count: count)
145
- for i in 0..<count { newTargets[i] = clampScale(percents[i]) }
203
+ if !placeholderMode {
204
+ for i in 0..<count { newTargets[i] = clampScale(percents[i]) }
205
+ }
146
206
 
147
207
  if snap {
148
208
  stopDisplayLink()
@@ -177,7 +237,8 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
177
237
  width: max(half - pad, 0), height: priceLine)
178
238
  priceLayer.fontSize = CGFloat(priceFontSize)
179
239
  priceLayer.foregroundColor = cachedPriceColor
180
- priceLayer.string = i < prices.count ? prices[i] : ""
240
+ priceLayer.string =
241
+ placeholderMode ? placeholderText : (i < prices.count ? prices[i] : "")
181
242
 
182
243
  let sizeLayer = sizeLayers[i]
183
244
  sizeLayer.frame = CGRect(
@@ -185,7 +246,8 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
185
246
  width: max(half - pad, 0), height: sizeLine)
186
247
  sizeLayer.fontSize = CGFloat(sizeFontSize)
187
248
  sizeLayer.foregroundColor = cachedSizeColor
188
- sizeLayer.string = i < sizes.count ? sizes[i] : ""
249
+ sizeLayer.string =
250
+ placeholderMode ? placeholderText : (i < sizes.count ? sizes[i] : "")
189
251
  }
190
252
  CATransaction.commit()
191
253
 
@@ -267,6 +329,23 @@ final class HybridPerpDepthBars: HybridPerpDepthBarsSpec {
267
329
  l.alignmentMode = alignment
268
330
  l.truncationMode = .none
269
331
  l.isWrapped = false
332
+ // Disable ALL implicit Core Animation actions on this text layer. CATextLayer
333
+ // crossfades its `contents` whenever `string`/`foregroundColor` changes; even
334
+ // inside `CATransaction.setDisableActions(true)` the fade can leak through, so
335
+ // pin the relevant keys to NSNull() to guarantee instant per-row text switches
336
+ // (PM: no fade/gradient when the order-book text updates). The bar fill keeps
337
+ // its own explicit display-link easing — only text is made instant here.
338
+ let noAction = NSNull()
339
+ l.actions = [
340
+ "contents": noAction,
341
+ "string": noAction,
342
+ "foregroundColor": noAction,
343
+ "fontSize": noAction,
344
+ "bounds": noAction,
345
+ "position": noAction,
346
+ "hidden": noAction,
347
+ "opacity": noAction,
348
+ ]
270
349
  return l
271
350
  }
272
351
 
@@ -58,8 +58,26 @@ final class HybridPerpSideRatio: HybridPerpSideRatioSpec {
58
58
  stopDisplayLink()
59
59
  }
60
60
 
61
+ /// Imperative ratio update — bypasses the high-frequency
62
+ /// `bidPercentage`/`askPercentage` prop write path (props/JSICache lock
63
+ /// contention, REACT-NATIVE-1JZ). Companion to `HybridPerpDepthBars.setDepth`.
64
+ /// The two args carry the SAME 0..100 semantics as the props of the same
65
+ /// name; assigning them feeds through the identical `didSet` →
66
+ /// `scheduleLayout` → `performLayout` clamp/split/display-link path the prop
67
+ /// setters use — only the data source differs.
68
+ func setRatio(bidPercentage: Double, askPercentage: Double) throws {
69
+ self.bidPercentage = bidPercentage
70
+ self.askPercentage = askPercentage
71
+ }
72
+
61
73
  private func scheduleLayout() {
62
- view.setNeedsLayout()
74
+ // Imperative setRatio runs on the JS thread (off-main); `setNeedsLayout` must
75
+ // be issued on the main thread.
76
+ if Thread.isMainThread {
77
+ view.setNeedsLayout()
78
+ } else {
79
+ DispatchQueue.main.async { [weak self] in self?.view.setNeedsLayout() }
80
+ }
63
81
  }
64
82
 
65
83
  private func performLayout() {
@@ -100,7 +100,16 @@ final class PerpLayoutView: UIView {
100
100
 
101
101
  override func layoutSubviews() {
102
102
  super.layoutSubviews()
103
- onLayout?()
103
+ guard let onLayout else { return }
104
+ // Fabric (New Architecture) can drive layout off the main thread. The
105
+ // `onLayout` callback mutates CALayer/CATextLayer state, which UIKit requires
106
+ // on the main thread — doing it off-main asserts ("Unsupported layout off the
107
+ // main thread") and can leave the view blank. Hop to main when needed.
108
+ if Thread.isMainThread {
109
+ onLayout()
110
+ } else {
111
+ DispatchQueue.main.async { onLayout() }
112
+ }
104
113
  }
105
114
 
106
115
  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
@@ -15,6 +15,9 @@
15
15
  #include <optional>
16
16
  #include "JFunc_void_double.hpp"
17
17
  #include <NitroModules/JNICallable.hpp>
18
+ #include <NitroModules/ArrayBuffer.hpp>
19
+ #include <NitroModules/JArrayBuffer.hpp>
20
+ #include <NitroModules/JUnit.hpp>
18
21
 
19
22
  namespace margelo::nitro::perpdepthbar {
20
23
 
@@ -226,6 +229,24 @@ namespace margelo::nitro::perpdepthbar {
226
229
  static const auto method = javaClassStatic()->getMethod<void(double /* textInset */)>("setTextInset");
227
230
  method(_javaPart, textInset);
228
231
  }
232
+ std::string JHybridPerpDepthBarsSpec::getPlaceholderText() {
233
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JString>()>("getPlaceholderText");
234
+ auto __result = method(_javaPart);
235
+ return __result->toStdString();
236
+ }
237
+ void JHybridPerpDepthBarsSpec::setPlaceholderText(const std::string& placeholderText) {
238
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JString> /* placeholderText */)>("setPlaceholderText");
239
+ method(_javaPart, jni::make_jstring(placeholderText));
240
+ }
241
+ double JHybridPerpDepthBarsSpec::getPlaceholderRows() {
242
+ static const auto method = javaClassStatic()->getMethod<double()>("getPlaceholderRows");
243
+ auto __result = method(_javaPart);
244
+ return __result;
245
+ }
246
+ void JHybridPerpDepthBarsSpec::setPlaceholderRows(double placeholderRows) {
247
+ static const auto method = javaClassStatic()->getMethod<void(double /* placeholderRows */)>("setPlaceholderRows");
248
+ method(_javaPart, placeholderRows);
249
+ }
229
250
  std::optional<std::function<void(double /* rowIndex */)>> JHybridPerpDepthBarsSpec::getOnRowPress() {
230
251
  static const auto method = javaClassStatic()->getMethod<jni::local_ref<JFunc_void_double::javaobject>()>("getOnRowPress_cxx");
231
252
  auto __result = method(_javaPart);
@@ -245,6 +266,31 @@ namespace margelo::nitro::perpdepthbar {
245
266
  }
246
267
 
247
268
  // Methods
248
-
269
+ void JHybridPerpDepthBarsSpec::setDepth(const std::shared_ptr<ArrayBuffer>& buffer) {
270
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<JArrayBuffer::javaobject> /* buffer */)>("setDepth");
271
+ method(_javaPart, JArrayBuffer::wrap(buffer));
272
+ }
273
+ void JHybridPerpDepthBarsSpec::setText(const std::vector<std::string>& prices, const std::vector<std::string>& sizes) {
274
+ static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<jni::JArrayClass<jni::JString>> /* prices */, jni::alias_ref<jni::JArrayClass<jni::JString>> /* sizes */)>("setText");
275
+ method(_javaPart, [&]() {
276
+ size_t __size = prices.size();
277
+ jni::local_ref<jni::JArrayClass<jni::JString>> __array = jni::JArrayClass<jni::JString>::newArray(__size);
278
+ for (size_t __i = 0; __i < __size; __i++) {
279
+ const auto& __element = prices[__i];
280
+ auto __elementJni = jni::make_jstring(__element);
281
+ __array->setElement(__i, *__elementJni);
282
+ }
283
+ return __array;
284
+ }(), [&]() {
285
+ size_t __size = sizes.size();
286
+ jni::local_ref<jni::JArrayClass<jni::JString>> __array = jni::JArrayClass<jni::JString>::newArray(__size);
287
+ for (size_t __i = 0; __i < __size; __i++) {
288
+ const auto& __element = sizes[__i];
289
+ auto __elementJni = jni::make_jstring(__element);
290
+ __array->setElement(__i, *__elementJni);
291
+ }
292
+ return __array;
293
+ }());
294
+ }
249
295
 
250
296
  } // namespace margelo::nitro::perpdepthbar
@@ -80,12 +80,17 @@ namespace margelo::nitro::perpdepthbar {
80
80
  void setSizeFontSize(double sizeFontSize) override;
81
81
  double getTextInset() override;
82
82
  void setTextInset(double textInset) override;
83
+ std::string getPlaceholderText() override;
84
+ void setPlaceholderText(const std::string& placeholderText) override;
85
+ double getPlaceholderRows() override;
86
+ void setPlaceholderRows(double placeholderRows) override;
83
87
  std::optional<std::function<void(double /* rowIndex */)>> getOnRowPress() override;
84
88
  void setOnRowPress(const std::optional<std::function<void(double /* rowIndex */)>>& onRowPress) override;
85
89
 
86
90
  public:
87
91
  // Methods
88
-
92
+ void setDepth(const std::shared_ptr<ArrayBuffer>& buffer) override;
93
+ void setText(const std::vector<std::string>& prices, const std::vector<std::string>& sizes) override;
89
94
 
90
95
  private:
91
96
  friend HybridBase;
@@ -114,6 +114,9 @@ namespace margelo::nitro::perpdepthbar {
114
114
  }
115
115
 
116
116
  // Methods
117
-
117
+ void JHybridPerpSideRatioSpec::setRatio(double bidPercentage, double askPercentage) {
118
+ static const auto method = javaClassStatic()->getMethod<void(double /* bidPercentage */, double /* askPercentage */)>("setRatio");
119
+ method(_javaPart, bidPercentage, askPercentage);
120
+ }
118
121
 
119
122
  } // namespace margelo::nitro::perpdepthbar
@@ -69,7 +69,7 @@ namespace margelo::nitro::perpdepthbar {
69
69
 
70
70
  public:
71
71
  // Methods
72
-
72
+ void setRatio(double bidPercentage, double askPercentage) override;
73
73
 
74
74
  private:
75
75
  friend HybridBase;
@@ -96,6 +96,14 @@ void JHybridPerpDepthBarsStateUpdater::updateViewProps(jni::alias_ref<jni::JClas
96
96
  view->setTextInset(props.textInset.value);
97
97
  // TODO: Set isDirty = false
98
98
  }
99
+ if (props.placeholderText.isDirty) {
100
+ view->setPlaceholderText(props.placeholderText.value);
101
+ // TODO: Set isDirty = false
102
+ }
103
+ if (props.placeholderRows.isDirty) {
104
+ view->setPlaceholderRows(props.placeholderRows.value);
105
+ // TODO: Set isDirty = false
106
+ }
99
107
  if (props.onRowPress.isDirty) {
100
108
  view->setOnRowPress(props.onRowPress.value);
101
109
  // TODO: Set isDirty = false
@@ -10,6 +10,7 @@ package com.margelo.nitro.perpdepthbar
10
10
  import androidx.annotation.Keep
11
11
  import com.facebook.jni.HybridData
12
12
  import com.facebook.proguard.annotations.DoNotStrip
13
+ import com.margelo.nitro.core.ArrayBuffer
13
14
  import com.margelo.nitro.views.HybridView
14
15
 
15
16
  /**
@@ -132,6 +133,18 @@ abstract class HybridPerpDepthBarsSpec: HybridView() {
132
133
  @set:Keep
133
134
  abstract var textInset: Double
134
135
 
136
+ @get:DoNotStrip
137
+ @get:Keep
138
+ @set:DoNotStrip
139
+ @set:Keep
140
+ abstract var placeholderText: String
141
+
142
+ @get:DoNotStrip
143
+ @get:Keep
144
+ @set:DoNotStrip
145
+ @set:Keep
146
+ abstract var placeholderRows: Double
147
+
135
148
  abstract var onRowPress: ((rowIndex: Double) -> Unit)?
136
149
 
137
150
  private var onRowPress_cxx: Func_void_double?
@@ -147,7 +160,13 @@ abstract class HybridPerpDepthBarsSpec: HybridView() {
147
160
  }
148
161
 
149
162
  // Methods
163
+ @DoNotStrip
164
+ @Keep
165
+ abstract fun setDepth(buffer: ArrayBuffer): Unit
150
166
 
167
+ @DoNotStrip
168
+ @Keep
169
+ abstract fun setText(prices: Array<String>, sizes: Array<String>): Unit
151
170
 
152
171
  private external fun initHybrid(): HybridData
153
172
 
@@ -91,7 +91,9 @@ abstract class HybridPerpSideRatioSpec: HybridView() {
91
91
  abstract var reducedMotion: Boolean
92
92
 
93
93
  // Methods
94
-
94
+ @DoNotStrip
95
+ @Keep
96
+ abstract fun setRatio(bidPercentage: Double, askPercentage: Double): Unit
95
97
 
96
98
  private external fun initHybrid(): HybridData
97
99
 
@@ -22,6 +22,8 @@ namespace PerpDepthBar { class HybridPerpSideRatioSpec_cxx; }
22
22
  // Include C++ defined types
23
23
  #include "HybridPerpDepthBarsSpec.hpp"
24
24
  #include "HybridPerpSideRatioSpec.hpp"
25
+ #include <NitroModules/Result.hpp>
26
+ #include <exception>
25
27
  #include <functional>
26
28
  #include <memory>
27
29
  #include <optional>
@@ -105,6 +107,15 @@ namespace margelo::nitro::perpdepthbar::bridge::swift {
105
107
  using std__weak_ptr_HybridPerpDepthBarsSpec_ = std::weak_ptr<HybridPerpDepthBarsSpec>;
106
108
  inline std__weak_ptr_HybridPerpDepthBarsSpec_ weakify_std__shared_ptr_HybridPerpDepthBarsSpec_(const std::shared_ptr<HybridPerpDepthBarsSpec>& strong) noexcept { return strong; }
107
109
 
110
+ // pragma MARK: Result<void>
111
+ using Result_void_ = Result<void>;
112
+ inline Result_void_ create_Result_void_() noexcept {
113
+ return Result<void>::withValue();
114
+ }
115
+ inline Result_void_ create_Result_void_(const std::exception_ptr& error) noexcept {
116
+ return Result<void>::withError(error);
117
+ }
118
+
108
119
  // pragma MARK: std::shared_ptr<HybridPerpSideRatioSpec>
109
120
  /**
110
121
  * Specialized version of `std::shared_ptr<HybridPerpSideRatioSpec>`.
@@ -16,6 +16,9 @@ namespace margelo::nitro::perpdepthbar { class HybridPerpSideRatioSpec; }
16
16
  // Include C++ defined types
17
17
  #include "HybridPerpDepthBarsSpec.hpp"
18
18
  #include "HybridPerpSideRatioSpec.hpp"
19
+ #include <NitroModules/ArrayBuffer.hpp>
20
+ #include <NitroModules/Result.hpp>
21
+ #include <exception>
19
22
  #include <functional>
20
23
  #include <memory>
21
24
  #include <optional>