@onekeyfe/react-native-pager-view 3.0.118 → 3.0.120

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.
@@ -0,0 +1,503 @@
1
+ package com.reactnativepagerview
2
+
3
+ import android.content.Context
4
+ import android.graphics.Color
5
+ import android.graphics.Typeface
6
+ import android.graphics.drawable.GradientDrawable
7
+ import android.text.TextUtils
8
+ import android.util.TypedValue
9
+ import android.view.Gravity
10
+ import android.view.MotionEvent
11
+ import android.view.View
12
+ import android.view.ViewGroup
13
+ import android.widget.FrameLayout
14
+ import android.widget.HorizontalScrollView
15
+ import android.widget.TextView
16
+ import androidx.core.graphics.ColorUtils
17
+ import org.json.JSONArray
18
+ import org.json.JSONObject
19
+ import kotlin.math.ceil
20
+ import kotlin.math.floor
21
+ import kotlin.math.max
22
+ import kotlin.math.min
23
+ import kotlin.math.roundToInt
24
+
25
+ internal data class CollapsiblePagerNativeHeaderItem(
26
+ val key: String,
27
+ val title: String,
28
+ val accessibilityLabel: String,
29
+ val testID: String?,
30
+ )
31
+
32
+ private fun parseHeaderItems(value: String?): List<CollapsiblePagerNativeHeaderItem> {
33
+ if (value.isNullOrEmpty()) return emptyList()
34
+ return runCatching {
35
+ val array = JSONArray(value)
36
+ List(array.length()) { index ->
37
+ val item = array.optJSONObject(index) ?: JSONObject()
38
+ val title = item.optString("title")
39
+ CollapsiblePagerNativeHeaderItem(
40
+ key = item.optString("key"),
41
+ title = title,
42
+ accessibilityLabel = item.optString("accessibilityLabel", title),
43
+ testID = item.optString("testID").takeIf(String::isNotEmpty),
44
+ )
45
+ }
46
+ }.getOrDefault(emptyList())
47
+ }
48
+
49
+ private class CollapsiblePagerHorizontalItemsView(
50
+ context: Context,
51
+ private val showsProgressIndicator: Boolean,
52
+ ) : HorizontalScrollView(context) {
53
+ private val density = resources.displayMetrics.density
54
+ private val content = FrameLayout(context)
55
+ private val indicator = View(context)
56
+ private val buttons = ArrayList<TextView>()
57
+ private val itemFrames = ArrayList<IntArray>()
58
+ private var items: List<CollapsiblePagerNativeHeaderItem> = emptyList()
59
+ private var selectedKey = ""
60
+ private var rowHeightPx = dp(if (showsProgressIndicator) 44.0 else 42.0)
61
+ private var contentPaddingPx = dp(20.0)
62
+ private var itemSpacingPx = dp(8.0)
63
+ private var fontSize = if (showsProgressIndicator) 16.0 else 14.0
64
+ private var fontFamily: String? = null
65
+ private var activeTextColor = Color.BLACK
66
+ private var inactiveTextColor = Color.GRAY
67
+ private var selectedBackgroundColor = Color.TRANSPARENT
68
+ private var indicatorColor = Color.BLACK
69
+ private var indicatorHeightPx = dp(2.0)
70
+ private var indicatorBottomPx = 0
71
+ private var progress = 0f
72
+ private var centerSelectionWhenIdle = true
73
+ private var isTouchDragging = false
74
+
75
+ var onItemPress: ((Int, String) -> Unit)? = null
76
+
77
+ init {
78
+ isHorizontalScrollBarEnabled = false
79
+ isVerticalScrollBarEnabled = false
80
+ isFillViewport = true
81
+ overScrollMode = OVER_SCROLL_NEVER
82
+ clipToPadding = false
83
+ content.addView(indicator)
84
+ addView(
85
+ content,
86
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT),
87
+ )
88
+ }
89
+
90
+ private fun dp(value: Double): Int = (value * density).roundToInt()
91
+
92
+ fun updateItemsJSON(value: String?) {
93
+ val next = parseHeaderItems(value)
94
+ if (items == next) return
95
+ items = next
96
+ buttons.forEach(content::removeView)
97
+ buttons.clear()
98
+ itemFrames.clear()
99
+ items.forEachIndexed { index, item ->
100
+ val button = TextView(context).apply {
101
+ text = item.title
102
+ contentDescription = item.accessibilityLabel
103
+ setTag(com.facebook.react.R.id.react_test_id, item.testID)
104
+ gravity = Gravity.CENTER
105
+ isSingleLine = true
106
+ ellipsize = TextUtils.TruncateAt.END
107
+ isClickable = true
108
+ isFocusable = true
109
+ setPadding(dp(8.0), 0, dp(8.0), 0)
110
+ setOnClickListener { onItemPress?.invoke(index, item.key) }
111
+ }
112
+ buttons.add(button)
113
+ content.addView(button)
114
+ }
115
+ visibility = if (items.isEmpty()) GONE else VISIBLE
116
+ centerSelectionWhenIdle = true
117
+ requestLayout()
118
+ }
119
+
120
+ fun updateSelectedKey(value: String) {
121
+ if (selectedKey == value) return
122
+ selectedKey = value
123
+ centerSelectionWhenIdle = true
124
+ updatePresentation()
125
+ }
126
+
127
+ fun updateStyle(
128
+ rowHeight: Double,
129
+ contentPadding: Double,
130
+ itemSpacing: Double,
131
+ fontSize: Double,
132
+ fontFamily: String?,
133
+ indicatorHeight: Double = 0.0,
134
+ indicatorBottom: Double = 0.0,
135
+ ) {
136
+ rowHeightPx = max(1, dp(rowHeight))
137
+ contentPaddingPx = max(0, dp(contentPadding))
138
+ itemSpacingPx = max(0, dp(itemSpacing))
139
+ this.fontSize = max(1.0, fontSize)
140
+ this.fontFamily = fontFamily
141
+ if (showsProgressIndicator) {
142
+ indicatorHeightPx = max(0, dp(indicatorHeight))
143
+ indicatorBottomPx = max(0, dp(indicatorBottom))
144
+ }
145
+ applyTypeface()
146
+ requestLayout()
147
+ }
148
+
149
+ fun updateColors(
150
+ backgroundColor: Int,
151
+ activeTextColor: Int,
152
+ inactiveTextColor: Int,
153
+ selectedBackgroundColor: Int,
154
+ indicatorColor: Int,
155
+ ) {
156
+ setBackgroundColor(backgroundColor)
157
+ content.setBackgroundColor(backgroundColor)
158
+ this.activeTextColor = activeTextColor
159
+ this.inactiveTextColor = inactiveTextColor
160
+ this.selectedBackgroundColor = selectedBackgroundColor
161
+ this.indicatorColor = indicatorColor
162
+ indicator.setBackgroundColor(indicatorColor)
163
+ updatePresentation()
164
+ }
165
+
166
+ fun setProgress(value: Float) {
167
+ if (!showsProgressIndicator || items.isEmpty()) return
168
+ val next = value.coerceIn(0f, (items.size - 1).toFloat())
169
+ if (kotlin.math.abs(progress - next) >= 0.001f) centerSelectionWhenIdle = true
170
+ progress = next
171
+ updatePresentation()
172
+ }
173
+
174
+ private fun applyTypeface() {
175
+ val typeface = Typeface.create(fontFamily ?: "sans-serif-medium", Typeface.NORMAL)
176
+ buttons.forEach { button ->
177
+ button.typeface = typeface
178
+ button.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize.toFloat())
179
+ }
180
+ }
181
+
182
+ private fun selectedBackground(selected: Boolean) = GradientDrawable().apply {
183
+ shape = GradientDrawable.RECTANGLE
184
+ setColor(if (selected) selectedBackgroundColor else Color.TRANSPARENT)
185
+ cornerRadius = dp(10.0).toFloat()
186
+ }
187
+
188
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
189
+ val width = MeasureSpec.getSize(widthMeasureSpec)
190
+ val height = when (MeasureSpec.getMode(heightMeasureSpec)) {
191
+ MeasureSpec.EXACTLY -> MeasureSpec.getSize(heightMeasureSpec)
192
+ else -> rowHeightPx
193
+ }
194
+ applyTypeface()
195
+ itemFrames.clear()
196
+ var x = contentPaddingPx
197
+ buttons.forEachIndexed { index, button ->
198
+ val textWidth = ceil(button.paint.measureText(button.text.toString()).toDouble()).toInt()
199
+ val buttonWidth = max(dp(44.0), textWidth + dp(if (showsProgressIndicator) 16.0 else 20.0))
200
+ val indicatorWidth = textWidth
201
+ val indicatorX = x + (buttonWidth - indicatorWidth) / 2
202
+ itemFrames.add(intArrayOf(x, buttonWidth, indicatorX, indicatorWidth))
203
+ val itemHeight = if (showsProgressIndicator) height else min(dp(32.0), height)
204
+ val top = if (showsProgressIndicator) 0 else max(0, (height - itemHeight) / 2)
205
+ button.layoutParams = FrameLayout.LayoutParams(buttonWidth, itemHeight).apply {
206
+ leftMargin = x
207
+ topMargin = top
208
+ }
209
+ x += buttonWidth
210
+ if (index < buttons.lastIndex) x += itemSpacingPx
211
+ }
212
+ val contentWidth = max(width, x + contentPaddingPx)
213
+ content.layoutParams = LayoutParams(contentWidth, height)
214
+ content.measure(
215
+ MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY),
216
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
217
+ )
218
+ setMeasuredDimension(width, height)
219
+ updatePresentation()
220
+ }
221
+
222
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
223
+ super.onLayout(changed, left, top, right, bottom)
224
+ updatePresentation()
225
+ }
226
+
227
+ override fun onTouchEvent(event: MotionEvent): Boolean {
228
+ when (event.actionMasked) {
229
+ MotionEvent.ACTION_DOWN -> isTouchDragging = true
230
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
231
+ isTouchDragging = false
232
+ post(::updatePresentation)
233
+ }
234
+ }
235
+ return super.onTouchEvent(event)
236
+ }
237
+
238
+ private fun updatePresentation() {
239
+ if (buttons.isEmpty() || itemFrames.size != buttons.size) return
240
+ val selectedIndex: Int
241
+ if (showsProgressIndicator) {
242
+ val clamped = progress.coerceIn(0f, buttons.lastIndex.toFloat())
243
+ val lower = floor(clamped).toInt()
244
+ val upper = min(lower + 1, buttons.lastIndex)
245
+ val fraction = clamped - lower
246
+ val from = itemFrames[lower]
247
+ val to = itemFrames[upper]
248
+ val indicatorX = (from[2] + (to[2] - from[2]) * fraction).roundToInt()
249
+ val indicatorWidth = (from[3] + (to[3] - from[3]) * fraction).roundToInt()
250
+ indicator.layoutParams = FrameLayout.LayoutParams(indicatorWidth, indicatorHeightPx).apply {
251
+ leftMargin = indicatorX
252
+ topMargin = max(0, measuredHeight - indicatorBottomPx - indicatorHeightPx)
253
+ }
254
+ indicator.visibility = if (indicatorHeightPx > 0) VISIBLE else GONE
255
+ indicator.background = GradientDrawable().apply {
256
+ shape = GradientDrawable.RECTANGLE
257
+ setColor(indicatorColor)
258
+ cornerRadius = indicatorHeightPx / 2f
259
+ }
260
+ selectedIndex = clamped.roundToInt()
261
+ buttons.forEachIndexed { index, button ->
262
+ val emphasis = 1f - min(1f, kotlin.math.abs(clamped - index))
263
+ button.setTextColor(ColorUtils.blendARGB(inactiveTextColor, activeTextColor, emphasis))
264
+ button.background = null
265
+ button.isSelected = index == selectedIndex
266
+ }
267
+ } else {
268
+ selectedIndex = items.indexOfFirst { item -> item.key == selectedKey }
269
+ indicator.visibility = GONE
270
+ buttons.forEachIndexed { index, button ->
271
+ val selected = index == selectedIndex
272
+ button.setTextColor(if (selected) activeTextColor else inactiveTextColor)
273
+ button.background = selectedBackground(selected)
274
+ button.isSelected = selected
275
+ }
276
+ }
277
+
278
+ if (centerSelectionWhenIdle && !isTouchDragging && width > 0 && selectedIndex in itemFrames.indices) {
279
+ val frame = itemFrames[selectedIndex]
280
+ val desired = (frame[0] + frame[1] / 2 - width / 2)
281
+ .coerceIn(0, max(0, content.measuredWidth - width))
282
+ scrollTo(desired, 0)
283
+ centerSelectionWhenIdle = false
284
+ }
285
+ }
286
+ }
287
+
288
+ internal class CollapsiblePagerNativeTabBarView(context: Context) : FrameLayout(context) {
289
+ private val row = CollapsiblePagerHorizontalItemsView(context, true)
290
+ private var heightPx = 0
291
+
292
+ var onItemPress: ((Int, String) -> Unit)?
293
+ get() = row.onItemPress
294
+ set(value) {
295
+ row.onItemPress = value
296
+ }
297
+
298
+ init {
299
+ addView(row, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
300
+ visibility = GONE
301
+ }
302
+
303
+ fun updateItemsJSON(value: String?) {
304
+ row.updateItemsJSON(value)
305
+ visibility = row.visibility
306
+ }
307
+
308
+ fun updateStyle(
309
+ height: Double,
310
+ contentPadding: Double,
311
+ itemSpacing: Double,
312
+ fontSize: Double,
313
+ fontFamily: String?,
314
+ indicatorHeight: Double,
315
+ indicatorBottom: Double,
316
+ ) {
317
+ heightPx = max(1, (height * resources.displayMetrics.density).roundToInt())
318
+ row.updateStyle(
319
+ height,
320
+ contentPadding,
321
+ itemSpacing,
322
+ fontSize,
323
+ fontFamily,
324
+ indicatorHeight,
325
+ indicatorBottom,
326
+ )
327
+ requestLayout()
328
+ }
329
+
330
+ fun updateColors(
331
+ backgroundColor: Int,
332
+ activeTextColor: Int,
333
+ inactiveTextColor: Int,
334
+ indicatorColor: Int,
335
+ ) = row.updateColors(
336
+ backgroundColor,
337
+ activeTextColor,
338
+ inactiveTextColor,
339
+ Color.TRANSPARENT,
340
+ indicatorColor,
341
+ )
342
+
343
+ fun setProgress(progress: Float) = row.setProgress(progress)
344
+
345
+ fun preferredHeightPx(): Int = heightPx
346
+ }
347
+
348
+ internal class CollapsiblePagerNativeSubHeaderView(context: Context) : ViewGroup(context) {
349
+ private val density = resources.displayMetrics.density
350
+ private val tabs = CollapsiblePagerHorizontalItemsView(context, false)
351
+ private val columns = FrameLayout(context)
352
+ private val leading = TextView(context)
353
+ private val middle = TextView(context)
354
+ private val trailing = TextView(context)
355
+ private var configuredHeightPx = dp(74.0)
356
+ private var tabsHeightPx = dp(42.0)
357
+ private var contentPaddingPx = dp(20.0)
358
+ private var itemSpacing = 8.0
359
+ private var itemFontSize = 14.0
360
+ private var columnFontSize = 12.0
361
+ private var trailingColumnWidthPx = dp(80.0)
362
+ private var columnGapPx = dp(8.0)
363
+ private var backgroundColorValue = Color.TRANSPARENT
364
+ private var activeTextColor = Color.BLACK
365
+ private var inactiveTextColor = Color.GRAY
366
+ private var selectedBackgroundColor = Color.TRANSPARENT
367
+ private var fontFamily: String? = null
368
+
369
+ var onItemPress: ((Int, String) -> Unit)?
370
+ get() = tabs.onItemPress
371
+ set(value) {
372
+ tabs.onItemPress = value
373
+ }
374
+
375
+ init {
376
+ addView(tabs)
377
+ addView(columns)
378
+ columns.addView(leading)
379
+ columns.addView(middle)
380
+ columns.addView(trailing)
381
+ listOf(leading, middle, trailing).forEach { label ->
382
+ label.gravity = Gravity.CENTER_VERTICAL
383
+ label.isSingleLine = true
384
+ label.ellipsize = TextUtils.TruncateAt.END
385
+ }
386
+ visibility = GONE
387
+ }
388
+
389
+ private fun dp(value: Double): Int = (value * density).roundToInt()
390
+
391
+ fun updateConfigJSON(value: String?) {
392
+ val config = runCatching { JSONObject(value ?: "{}") }.getOrDefault(JSONObject())
393
+ val style = config.optJSONObject("style") ?: JSONObject()
394
+ val columnsConfig = config.optJSONObject("columns") ?: JSONObject()
395
+ val itemArray = config.optJSONArray("items") ?: JSONArray()
396
+ tabs.updateItemsJSON(itemArray.toString())
397
+ tabs.updateSelectedKey(config.optString("selectedKey"))
398
+ configuredHeightPx = max(1, dp(style.optDouble("height", 74.0)))
399
+ tabsHeightPx = max(0, dp(style.optDouble("tabsHeight", 42.0)))
400
+ contentPaddingPx = max(0, dp(style.optDouble("contentPaddingHorizontal", 20.0)))
401
+ itemSpacing = max(0.0, style.optDouble("itemSpacing", 8.0))
402
+ itemFontSize = max(1.0, style.optDouble("fontSize", 14.0))
403
+ columnFontSize = max(1.0, style.optDouble("columnFontSize", 12.0))
404
+ trailingColumnWidthPx = max(0, dp(style.optDouble("trailingColumnWidth", 80.0)))
405
+ columnGapPx = max(0, dp(style.optDouble("columnGap", 8.0)))
406
+ leading.text = columnsConfig.optString("leading")
407
+ middle.text = columnsConfig.optString("middle")
408
+ trailing.text = columnsConfig.optString("trailing")
409
+ visibility = if (itemArray.length() == 0) GONE else VISIBLE
410
+ applyStyle()
411
+ requestLayout()
412
+ }
413
+
414
+ fun updateColors(
415
+ backgroundColor: Int,
416
+ activeTextColor: Int,
417
+ inactiveTextColor: Int,
418
+ selectedBackgroundColor: Int,
419
+ fontFamily: String?,
420
+ ) {
421
+ backgroundColorValue = backgroundColor
422
+ this.activeTextColor = activeTextColor
423
+ this.inactiveTextColor = inactiveTextColor
424
+ this.selectedBackgroundColor = selectedBackgroundColor
425
+ this.fontFamily = fontFamily
426
+ applyStyle()
427
+ }
428
+
429
+ private fun applyStyle() {
430
+ setBackgroundColor(backgroundColorValue)
431
+ columns.setBackgroundColor(backgroundColorValue)
432
+ tabs.updateStyle(
433
+ tabsHeightPx / density.toDouble(),
434
+ contentPaddingPx / density.toDouble(),
435
+ itemSpacing,
436
+ itemFontSize,
437
+ fontFamily,
438
+ )
439
+ tabs.updateColors(
440
+ backgroundColorValue,
441
+ activeTextColor,
442
+ inactiveTextColor,
443
+ selectedBackgroundColor,
444
+ activeTextColor,
445
+ )
446
+ val typeface = Typeface.create(fontFamily ?: "sans-serif-medium", Typeface.NORMAL)
447
+ listOf(leading, middle, trailing).forEach { label ->
448
+ label.typeface = typeface
449
+ label.setTextSize(TypedValue.COMPLEX_UNIT_SP, columnFontSize.toFloat())
450
+ label.setTextColor(inactiveTextColor)
451
+ }
452
+ }
453
+
454
+ fun preferredHeightPx(): Int = configuredHeightPx
455
+
456
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
457
+ val width = MeasureSpec.getSize(widthMeasureSpec)
458
+ val height = MeasureSpec.getSize(heightMeasureSpec)
459
+ val actualTabsHeight = min(height, tabsHeightPx)
460
+ tabs.measure(
461
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
462
+ MeasureSpec.makeMeasureSpec(actualTabsHeight, MeasureSpec.EXACTLY),
463
+ )
464
+ val columnsHeight = max(0, height - actualTabsHeight)
465
+ columns.measure(
466
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
467
+ MeasureSpec.makeMeasureSpec(columnsHeight, MeasureSpec.EXACTLY),
468
+ )
469
+ setMeasuredDimension(width, height)
470
+ }
471
+
472
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
473
+ val width = right - left
474
+ val height = bottom - top
475
+ val actualTabsHeight = min(height, tabsHeightPx)
476
+ tabs.layout(0, 0, width, actualTabsHeight)
477
+ columns.layout(0, actualTabsHeight, width, height)
478
+ val columnsHeight = height - actualTabsHeight
479
+ val half = width / 2
480
+ val isRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
481
+ if (!isRtl) {
482
+ leading.gravity = Gravity.START or Gravity.CENTER_VERTICAL
483
+ middle.gravity = Gravity.END or Gravity.CENTER_VERTICAL
484
+ trailing.gravity = Gravity.END or Gravity.CENTER_VERTICAL
485
+ val trailingX = width - contentPaddingPx - trailingColumnWidthPx
486
+ leading.layout(contentPaddingPx, 0, half, columnsHeight)
487
+ middle.layout(half, 0, max(half, trailingX - columnGapPx), columnsHeight)
488
+ trailing.layout(trailingX, 0, width - contentPaddingPx, columnsHeight)
489
+ } else {
490
+ leading.gravity = Gravity.END or Gravity.CENTER_VERTICAL
491
+ middle.gravity = Gravity.START or Gravity.CENTER_VERTICAL
492
+ trailing.gravity = Gravity.START or Gravity.CENTER_VERTICAL
493
+ leading.layout(half, 0, width - contentPaddingPx, columnsHeight)
494
+ middle.layout(
495
+ contentPaddingPx + trailingColumnWidthPx + columnGapPx,
496
+ 0,
497
+ half,
498
+ columnsHeight,
499
+ )
500
+ trailing.layout(contentPaddingPx, 0, contentPaddingPx + trailingColumnWidthPx, columnsHeight)
501
+ }
502
+ }
503
+ }
@@ -2,6 +2,7 @@ package com.reactnativepagerview
2
2
 
3
3
  import android.view.View
4
4
  import android.view.ViewGroup
5
+ import android.graphics.Color
5
6
  import androidx.viewpager2.widget.ViewPager2
6
7
  import com.facebook.react.bridge.ReadableArray
7
8
  import com.facebook.react.common.MapBuilder
@@ -18,6 +19,7 @@ import com.reactnativepagerview.event.CollapsibleStateChangedEvent
18
19
  import com.reactnativepagerview.event.PageScrollEvent
19
20
  import com.reactnativepagerview.event.PageScrollStateChangedEvent
20
21
  import com.reactnativepagerview.event.PageSelectedEvent
22
+ import com.reactnativepagerview.event.NativeHeaderPressEvent
21
23
  import org.json.JSONArray
22
24
  import kotlin.math.roundToInt
23
25
 
@@ -41,13 +43,16 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
41
43
  )
42
44
  host.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
43
45
  override fun onPageScrolled(position: Int, offset: Float, offsetPixels: Int) {
46
+ host.updateNativeTabProgress(position, offset)
44
47
  dispatch(reactContext, host, PageScrollEvent(host.id, position, offset))
45
48
  }
46
49
 
47
50
  override fun onPageSelected(position: Int) {
48
51
  host.selectPage(position)
52
+ host.updateNativeTabProgress(position, 0f)
49
53
  dispatch(reactContext, host, PageSelectedEvent(host.id, position))
50
54
  dispatchDiagnostics(reactContext, host, "page-selected")
55
+ host.logPagerState("page-selected")
51
56
  }
52
57
 
53
58
  override fun onPageScrollStateChanged(state: Int) {
@@ -60,9 +65,34 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
60
65
  dispatch(reactContext, host, PageScrollStateChangedEvent(host.id, value))
61
66
  if (state == ViewPager2.SCROLL_STATE_IDLE) {
62
67
  dispatchDiagnostics(reactContext, host, "pager-idle")
68
+ host.logPagerState("idle")
63
69
  }
64
70
  }
65
71
  })
72
+ host.onNativeTabPress = { position, key ->
73
+ dispatch(
74
+ reactContext,
75
+ host,
76
+ NativeHeaderPressEvent(
77
+ host.id,
78
+ position,
79
+ key,
80
+ NativeHeaderPressEvent.TAB_EVENT_NAME,
81
+ ),
82
+ )
83
+ }
84
+ host.onNativeSubHeaderPress = { position, key ->
85
+ dispatch(
86
+ reactContext,
87
+ host,
88
+ NativeHeaderPressEvent(
89
+ host.id,
90
+ position,
91
+ key,
92
+ NativeHeaderPressEvent.SUB_HEADER_EVENT_NAME,
93
+ ),
94
+ )
95
+ }
66
96
  host.onHeaderOffsetChanged = {
67
97
  if (!host.pager.isFakeDragging && host.pager.scrollState == ViewPager2.SCROLL_STATE_IDLE) {
68
98
  dispatchDiagnostics(reactContext, host, "vertical-idle")
@@ -123,11 +153,11 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
123
153
 
124
154
  @ReactProp(name = "layoutDirection")
125
155
  override fun setLayoutDirection(view: CollapsiblePagerHost?, value: String?) {
126
- view?.pager?.layoutDirection = if (value == "rtl") {
156
+ view?.setPagerLayoutDirection(if (value == "rtl") {
127
157
  View.LAYOUT_DIRECTION_RTL
128
158
  } else {
129
159
  View.LAYOUT_DIRECTION_LTR
130
- }
160
+ })
131
161
  }
132
162
 
133
163
  @ReactProp(name = "initialPage", defaultInt = 0)
@@ -151,6 +181,13 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
151
181
  // OneKey patch: CollapsiblePagerHost always coordinates nested pagers on Android.
152
182
  }
153
183
 
184
+ override fun setNativeSmoothHeaderScrollEnabled(
185
+ view: CollapsiblePagerHost?,
186
+ value: Boolean,
187
+ ) {
188
+ view?.nativeSmoothHeaderScrollEnabled = value
189
+ }
190
+
154
191
  // OneKey patch: round Yoga header dimensions to the nearest physical pixel.
155
192
  @ReactProp(name = "headerHeight", defaultInt = 0)
156
193
  override fun setHeaderHeight(view: CollapsiblePagerHost?, value: Int) {
@@ -173,6 +210,92 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
173
210
  view?.retainedPages = value ?: "[]"
174
211
  }
175
212
 
213
+ override fun setNativeTabBarItems(view: CollapsiblePagerHost?, value: String?) {
214
+ view?.updateNativeTabBarItems(value)
215
+ }
216
+
217
+ override fun setNativeTabBarHeight(view: CollapsiblePagerHost?, value: Double) {
218
+ view ?: return
219
+ view.nativeTabBarHeight = value
220
+ view.applyNativeHeaderStyle()
221
+ }
222
+
223
+ override fun setNativeTabBarContentPaddingHorizontal(
224
+ view: CollapsiblePagerHost?,
225
+ value: Double,
226
+ ) {
227
+ view ?: return
228
+ view.nativeTabBarContentPaddingHorizontal = value
229
+ view.applyNativeHeaderStyle()
230
+ }
231
+
232
+ override fun setNativeTabBarItemSpacing(view: CollapsiblePagerHost?, value: Double) {
233
+ view ?: return
234
+ view.nativeTabBarItemSpacing = value
235
+ view.applyNativeHeaderStyle()
236
+ }
237
+
238
+ override fun setNativeTabBarFontSize(view: CollapsiblePagerHost?, value: Double) {
239
+ view ?: return
240
+ view.nativeTabBarFontSize = value
241
+ view.applyNativeHeaderStyle()
242
+ }
243
+
244
+ override fun setNativeTabBarFontFamily(view: CollapsiblePagerHost?, value: String?) {
245
+ view ?: return
246
+ view.nativeTabBarFontFamily = value
247
+ view.applyNativeHeaderStyle()
248
+ }
249
+
250
+ override fun setNativeTabBarBackgroundColor(view: CollapsiblePagerHost?, value: Int?) {
251
+ view ?: return
252
+ view.nativeTabBarBackgroundColor = value ?: Color.TRANSPARENT
253
+ view.applyNativeHeaderStyle()
254
+ }
255
+
256
+ override fun setNativeTabBarActiveTextColor(view: CollapsiblePagerHost?, value: Int?) {
257
+ view ?: return
258
+ view.nativeTabBarActiveTextColor = value ?: Color.BLACK
259
+ view.applyNativeHeaderStyle()
260
+ }
261
+
262
+ override fun setNativeTabBarInactiveTextColor(view: CollapsiblePagerHost?, value: Int?) {
263
+ view ?: return
264
+ view.nativeTabBarInactiveTextColor = value ?: Color.GRAY
265
+ view.applyNativeHeaderStyle()
266
+ }
267
+
268
+ override fun setNativeTabBarIndicatorColor(view: CollapsiblePagerHost?, value: Int?) {
269
+ view ?: return
270
+ view.nativeTabBarIndicatorColor = value ?: view.nativeTabBarActiveTextColor
271
+ view.applyNativeHeaderStyle()
272
+ }
273
+
274
+ override fun setNativeTabBarIndicatorHeight(view: CollapsiblePagerHost?, value: Double) {
275
+ view ?: return
276
+ view.nativeTabBarIndicatorHeight = value
277
+ view.applyNativeHeaderStyle()
278
+ }
279
+
280
+ override fun setNativeTabBarIndicatorBottom(view: CollapsiblePagerHost?, value: Double) {
281
+ view ?: return
282
+ view.nativeTabBarIndicatorBottom = value
283
+ view.applyNativeHeaderStyle()
284
+ }
285
+
286
+ override fun setNativeSubHeaderConfig(view: CollapsiblePagerHost?, value: String?) {
287
+ view?.updateNativeSubHeader(value)
288
+ }
289
+
290
+ override fun setNativeSubHeaderSelectedBackgroundColor(
291
+ view: CollapsiblePagerHost?,
292
+ value: Int?,
293
+ ) {
294
+ view ?: return
295
+ view.nativeSubHeaderSelectedBackgroundColor = value ?: Color.TRANSPARENT
296
+ view.applyNativeHeaderStyle()
297
+ }
298
+
176
299
  private fun parseStringArray(value: String?): List<String> {
177
300
  if (value.isNullOrEmpty()) return emptyList()
178
301
  return runCatching {
@@ -213,6 +336,14 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
213
336
  CollapsibleStateChangedEvent.EVENT_NAME,
214
337
  MapBuilder.of("registrationName", "onCollapsibleStateChanged"),
215
338
  )
339
+ .put(
340
+ NativeHeaderPressEvent.TAB_EVENT_NAME,
341
+ MapBuilder.of("registrationName", "onNativeTabPress"),
342
+ )
343
+ .put(
344
+ NativeHeaderPressEvent.SUB_HEADER_EVENT_NAME,
345
+ MapBuilder.of("registrationName", "onNativeSubHeaderPress"),
346
+ )
216
347
  .build()
217
348
  .toMutableMap()
218
349