@onekeyfe/react-native-pager-view 6.7.0-alpha.436 → 6.7.0-alpha.445
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/README.md +16 -0
- package/android/build.gradle +1 -0
- package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +135 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +88 -12
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +442 -15
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +11 -1
- package/android/src/main/java/com/reactnativepagerview/event/NativeHeaderPressEvent.kt +8 -0
- package/docs/SPEC.md +298 -3
- package/ios/RNCCollapsiblePagerViewComponentView.mm +834 -20
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +61 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +22 -0
- package/lib/module/CollapsiblePagerView.js +37 -5
- package/lib/module/CollapsiblePagerView.web.js +519 -6
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/lib/module/NativeScrollerNativeComponent.ts +0 -1
- package/lib/typescript/src/CollapsiblePagerView.d.ts +31 -2
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +44 -1
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +11 -0
- package/lib/typescript/src/NativeScroller.d.ts +2 -2
- package/package.json +3 -2
- package/src/CollapsiblePagerView.tsx +68 -5
- package/src/CollapsiblePagerView.web.tsx +464 -5
- package/src/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/src/NativeScroller.tsx +2 -3
- package/src/NativeScrollerNativeComponent.ts +0 -1
|
@@ -2,8 +2,12 @@ package com.reactnativepagerview
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Color
|
|
5
|
+
import android.graphics.Bitmap
|
|
5
6
|
import android.graphics.Typeface
|
|
6
7
|
import android.graphics.drawable.GradientDrawable
|
|
8
|
+
import android.graphics.drawable.StateListDrawable
|
|
9
|
+
import android.widget.ImageView
|
|
10
|
+
import com.margelo.nitro.onekeyimage.OneKeyNamedSVG
|
|
7
11
|
import android.text.TextUtils
|
|
8
12
|
import android.util.TypedValue
|
|
9
13
|
import android.view.Gravity
|
|
@@ -81,6 +85,7 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
81
85
|
private var contentPaddingPx = dp(20.0)
|
|
82
86
|
private var contentPaddingVerticalPx = 0
|
|
83
87
|
private var segmented = false
|
|
88
|
+
private var textVariant = false
|
|
84
89
|
private var itemSpacingPx = dp(8.0)
|
|
85
90
|
private var fontSize = if (showsProgressIndicator) 16.0 else 14.0
|
|
86
91
|
private var fontFamily: String? = null
|
|
@@ -91,6 +96,12 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
91
96
|
private var indicatorHeightPx = dp(2.0)
|
|
92
97
|
private var indicatorBottomPx = 0
|
|
93
98
|
private var progress = 0f
|
|
99
|
+
private var mappedSource: Int? = null
|
|
100
|
+
private var mappedDestination: Int? = null
|
|
101
|
+
private var mappedFraction = 0f
|
|
102
|
+
private var activeFontFamily: String? = null
|
|
103
|
+
private var controlledSelectedKey: String? = null
|
|
104
|
+
private var validControlledPageMapping = false
|
|
94
105
|
private var centerSelectionWhenIdle = true
|
|
95
106
|
private var isTouchDragging = false
|
|
96
107
|
|
|
@@ -165,6 +176,7 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
165
176
|
contentPaddingVerticalPx = max(0, dp(contentPaddingVertical))
|
|
166
177
|
val wasSegmented = segmented
|
|
167
178
|
segmented = showsProgressIndicator && variant == "segmented"
|
|
179
|
+
textVariant = showsProgressIndicator && variant == "text"
|
|
168
180
|
itemSpacingPx = max(0, dp(itemSpacing))
|
|
169
181
|
this.fontSize = max(1.0, fontSize)
|
|
170
182
|
this.fontFamily = fontFamily
|
|
@@ -197,7 +209,20 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
197
209
|
updatePresentation()
|
|
198
210
|
}
|
|
199
211
|
|
|
200
|
-
fun
|
|
212
|
+
fun setControlledSelectedKey(value: String?, validPageMapping: Boolean) {
|
|
213
|
+
controlledSelectedKey = value
|
|
214
|
+
validControlledPageMapping = validPageMapping
|
|
215
|
+
updatePresentation()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fun usesTextVariant(): Boolean = textVariant
|
|
219
|
+
|
|
220
|
+
fun setActiveFontFamily(value: String?) { activeFontFamily = value; requestLayout() }
|
|
221
|
+
|
|
222
|
+
fun setProgress(value: Float, source: Int? = null, destination: Int? = null, fraction: Float = 0f) {
|
|
223
|
+
mappedSource = source
|
|
224
|
+
mappedDestination = destination
|
|
225
|
+
mappedFraction = fraction
|
|
201
226
|
if (!showsProgressIndicator || items.isEmpty()) return
|
|
202
227
|
val next = value.coerceIn(0f, (items.size - 1).toFloat())
|
|
203
228
|
if (kotlin.math.abs(progress - next) >= 0.001f) centerSelectionWhenIdle = true
|
|
@@ -205,10 +230,25 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
205
230
|
updatePresentation()
|
|
206
231
|
}
|
|
207
232
|
|
|
233
|
+
private fun selectedTypeface(): Typeface = activeFontFamily?.takeIf(String::isNotEmpty)?.let { resolveHeaderTypeface(context, it) }
|
|
234
|
+
?: if (android.os.Build.VERSION.SDK_INT >= 28) Typeface.create(Typeface.create("sans-serif", Typeface.NORMAL), 600, false)
|
|
235
|
+
else Typeface.create("sans-serif", Typeface.BOLD)
|
|
236
|
+
|
|
237
|
+
private fun visualFontOwnerIndex(semanticIndex: Int): Int {
|
|
238
|
+
val source = mappedSource
|
|
239
|
+
val destination = mappedDestination
|
|
240
|
+
if (textVariant && controlledSelectedKey != null && validControlledPageMapping && source != null && destination != null) {
|
|
241
|
+
return if (mappedFraction < 0.5f) source else destination
|
|
242
|
+
}
|
|
243
|
+
return semanticIndex
|
|
244
|
+
}
|
|
245
|
+
|
|
208
246
|
private fun applyTypeface() {
|
|
209
247
|
val typeface = resolveHeaderTypeface(context, fontFamily)
|
|
210
|
-
buttons.
|
|
211
|
-
|
|
248
|
+
val fontOwner = visualFontOwnerIndex(buttons.indexOfFirst { it.isSelected })
|
|
249
|
+
buttons.forEachIndexed { index, button ->
|
|
250
|
+
button.typeface = if (textVariant && index == fontOwner) selectedTypeface() else typeface
|
|
251
|
+
button.setPadding(dp(if (textVariant) 10.0 else 8.0), 0, dp(if (textVariant) 10.0 else 8.0), 0)
|
|
212
252
|
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize.toFloat())
|
|
213
253
|
}
|
|
214
254
|
}
|
|
@@ -271,7 +311,15 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
271
311
|
track.layoutParams = FrameLayout.LayoutParams(0, 0)
|
|
272
312
|
var x = contentPaddingPx
|
|
273
313
|
buttons.forEachIndexed { index, button ->
|
|
274
|
-
|
|
314
|
+
var textWidth = ceil(button.paint.measureText(button.text.toString()).toDouble()).toInt()
|
|
315
|
+
if (textVariant) {
|
|
316
|
+
val original = button.paint.typeface
|
|
317
|
+
button.paint.typeface = resolveHeaderTypeface(context, fontFamily)
|
|
318
|
+
textWidth = ceil(button.paint.measureText(button.text.toString()).toDouble()).toInt()
|
|
319
|
+
button.paint.typeface = selectedTypeface()
|
|
320
|
+
textWidth = max(textWidth, ceil(button.paint.measureText(button.text.toString()).toDouble()).toInt())
|
|
321
|
+
button.paint.typeface = original
|
|
322
|
+
}
|
|
275
323
|
// Reuse the rounded padding: dp(16) can be one pixel less than two dp(8) insets.
|
|
276
324
|
val horizontalPadding = if (showsProgressIndicator) {
|
|
277
325
|
button.compoundPaddingLeft + button.compoundPaddingRight
|
|
@@ -343,14 +391,18 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
343
391
|
indicatorX + indicatorWidth,
|
|
344
392
|
indicatorTop + indicatorHeight,
|
|
345
393
|
)
|
|
346
|
-
indicator.visibility = if (indicatorHeight > 0) VISIBLE else GONE
|
|
394
|
+
indicator.visibility = if (!textVariant && indicatorHeight > 0) VISIBLE else GONE
|
|
347
395
|
scrollTargetCenterX = interpolatedIndicatorX + interpolatedIndicatorWidth / 2f
|
|
348
|
-
selectedIndex = clamped.roundToInt()
|
|
396
|
+
selectedIndex = if (controlledSelectedKey != null) items.indexOfFirst { it.key == controlledSelectedKey } else clamped.roundToInt()
|
|
397
|
+
val fontOwner = visualFontOwnerIndex(selectedIndex)
|
|
349
398
|
buttons.forEachIndexed { index, button ->
|
|
350
|
-
val emphasis =
|
|
399
|
+
val emphasis = if (mappedSource != null && mappedDestination != null) {
|
|
400
|
+
(if (index == mappedSource) 1f - mappedFraction else 0f) + (if (index == mappedDestination) mappedFraction else 0f)
|
|
401
|
+
} else 1f - min(1f, kotlin.math.abs(clamped - index))
|
|
351
402
|
button.setTextColor(ColorUtils.blendARGB(inactiveTextColor, activeTextColor, emphasis))
|
|
352
403
|
button.background = null
|
|
353
404
|
button.isSelected = index == selectedIndex
|
|
405
|
+
if (textVariant) button.typeface = if (index == fontOwner) selectedTypeface() else resolveHeaderTypeface(context, fontFamily)
|
|
354
406
|
}
|
|
355
407
|
} else {
|
|
356
408
|
selectedIndex = items.indexOfFirst { item -> item.key == selectedKey }
|
|
@@ -377,6 +429,273 @@ private class CollapsiblePagerHorizontalItemsView(
|
|
|
377
429
|
internal class CollapsiblePagerNativeTabBarView(context: Context) : FrameLayout(context) {
|
|
378
430
|
private val row = CollapsiblePagerHorizontalItemsView(context, true)
|
|
379
431
|
private var heightPx = 0
|
|
432
|
+
private val toolbar = ImageView(context)
|
|
433
|
+
private var config = JSONObject()
|
|
434
|
+
private var items = emptyList<CollapsiblePagerNativeHeaderItem>()
|
|
435
|
+
private var selectedKey = ""
|
|
436
|
+
private var iconOwner: Any = Any()
|
|
437
|
+
private var iconIdentity = ""
|
|
438
|
+
private var loadedIconIdentity = ""
|
|
439
|
+
private var iconRequestPending = false
|
|
440
|
+
private var readyToolbarImage: Bitmap? = null
|
|
441
|
+
private var readyToolbarIdentity = ""
|
|
442
|
+
private var failedIconIdentity = ""
|
|
443
|
+
private var toolbarTouchKey: String? = null
|
|
444
|
+
private var toolbarTouchStarted = false
|
|
445
|
+
private var toolbarKeyboardKey: String? = null
|
|
446
|
+
private var lastNativeProgress = 0f
|
|
447
|
+
private var hasNativeProgress = false
|
|
448
|
+
private var toolbarReservedWidth = 0.0
|
|
449
|
+
private var toolbarPagerBusy = true
|
|
450
|
+
private var toolbarPageIndex = -1
|
|
451
|
+
private var committedToolbarKey: String? = null
|
|
452
|
+
private var committedToolbarEntry: JSONObject? = null
|
|
453
|
+
private var desiredToolbarKey: String? = null
|
|
454
|
+
private var desiredToolbarEntry: JSONObject? = null
|
|
455
|
+
private var loadedToolbarImage: Bitmap? = null
|
|
456
|
+
private var toolbarPresenceAnimating = false
|
|
457
|
+
private var toolbarPresenceInterrupted = false
|
|
458
|
+
private var toolbarVisible = false
|
|
459
|
+
private var toolbarAnimationGeneration = 0
|
|
460
|
+
private var toolbarSize = 38.0
|
|
461
|
+
private var toolbarIconSize = 24.0
|
|
462
|
+
private var toolbarContentWidth = 24.0
|
|
463
|
+
private var toolbarTrailingInset = 20.0
|
|
464
|
+
var onToolbarPress: ((String, DoubleArray) -> Unit)? = null
|
|
465
|
+
|
|
466
|
+
private fun dp(value: Number) = (value.toDouble() * resources.displayMetrics.density).roundToInt()
|
|
467
|
+
|
|
468
|
+
fun updateConfigJSON(value: String?) {
|
|
469
|
+
config = runCatching { JSONObject(value ?: "{}") }.getOrDefault(JSONObject())
|
|
470
|
+
row.setActiveFontFamily(config.optString("activeFontFamily").takeIf(String::isNotEmpty))
|
|
471
|
+
selectedKey = if (config.optBoolean("controlled")) config.optString("selectedKey") else items.getOrNull(lastNativeProgress.roundToInt())?.key ?: ""
|
|
472
|
+
row.setControlledSelectedKey(if (config.optBoolean("controlled")) selectedKey else null, controlledPageIndices() != null)
|
|
473
|
+
if (hasNativeProgress) setProgress(lastNativeProgress)
|
|
474
|
+
else {
|
|
475
|
+
val index = items.indexOfFirst { it.key == selectedKey }
|
|
476
|
+
if (index >= 0) row.setProgress(index.toFloat())
|
|
477
|
+
}
|
|
478
|
+
updateToolbar()
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private fun ownsSettledToolbar(): Boolean = config.optBoolean("controlled") && config.optJSONObject("toolbar") != null
|
|
482
|
+
|
|
483
|
+
internal fun usesSettledToolbarOwner(): Boolean = ownsSettledToolbar()
|
|
484
|
+
|
|
485
|
+
internal fun needsToolbarPagerSettlement(pageIndex: Int): Boolean =
|
|
486
|
+
ownsSettledToolbar() && (toolbarPagerBusy || toolbarPageIndex != pageIndex)
|
|
487
|
+
|
|
488
|
+
private fun toolbarCanCommit(): Boolean {
|
|
489
|
+
if (!isAttachedToWindow) return false
|
|
490
|
+
if (!ownsSettledToolbar()) return true
|
|
491
|
+
if (toolbarPagerBusy || toolbarPageIndex < 0) return false
|
|
492
|
+
return controlledPageIndices() == null || config.optJSONArray("pageKeys")?.optString(toolbarPageIndex) == selectedKey
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
fun updateToolbarPagerState(pageIndex: Int, busy: Boolean) {
|
|
496
|
+
if (toolbarPagerBusy == busy && (busy || toolbarPageIndex == pageIndex)) return
|
|
497
|
+
if (!busy) toolbarPageIndex = pageIndex
|
|
498
|
+
toolbarPagerBusy = busy
|
|
499
|
+
updateToolbar()
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private fun cancelToolbarPending() {
|
|
503
|
+
if (iconRequestPending || readyToolbarImage != null) {
|
|
504
|
+
OneKeyNamedSVG.cancel(iconOwner)
|
|
505
|
+
iconOwner = Any()
|
|
506
|
+
}
|
|
507
|
+
iconRequestPending = false
|
|
508
|
+
readyToolbarImage = null
|
|
509
|
+
readyToolbarIdentity = ""
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private fun setToolbarInteractive(enabled: Boolean) {
|
|
513
|
+
if (!enabled) {
|
|
514
|
+
toolbarKeyboardKey = null
|
|
515
|
+
if (toolbarTouchStarted) toolbarTouchKey = null
|
|
516
|
+
toolbar.isPressed = false
|
|
517
|
+
toolbar.isHovered = false
|
|
518
|
+
toolbar.clearFocus()
|
|
519
|
+
}
|
|
520
|
+
toolbar.isEnabled = enabled
|
|
521
|
+
toolbar.isClickable = enabled
|
|
522
|
+
toolbar.isFocusable = enabled
|
|
523
|
+
toolbar.importantForAccessibility = if (enabled) View.IMPORTANT_FOR_ACCESSIBILITY_AUTO else View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private fun suspendToolbar() {
|
|
527
|
+
setToolbarInteractive(false)
|
|
528
|
+
cancelToolbarPending()
|
|
529
|
+
desiredToolbarKey = null
|
|
530
|
+
desiredToolbarEntry = null
|
|
531
|
+
if (toolbarPresenceAnimating) {
|
|
532
|
+
toolbarAnimationGeneration++
|
|
533
|
+
toolbar.animate().cancel()
|
|
534
|
+
toolbarPresenceAnimating = false
|
|
535
|
+
toolbarPresenceInterrupted = true
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private fun setToolbarVisible(visible: Boolean) {
|
|
540
|
+
if (toolbarVisible == visible && !toolbarPresenceInterrupted) return
|
|
541
|
+
toolbarVisible = visible
|
|
542
|
+
toolbarPresenceInterrupted = false
|
|
543
|
+
val generation = ++toolbarAnimationGeneration
|
|
544
|
+
toolbar.animate().cancel()
|
|
545
|
+
if (visible) toolbar.visibility = VISIBLE
|
|
546
|
+
if (!isAttachedToWindow) {
|
|
547
|
+
toolbar.alpha = if (visible) 1f else 0f
|
|
548
|
+
toolbar.visibility = if (visible) VISIBLE else INVISIBLE
|
|
549
|
+
toolbarPresenceAnimating = false
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
toolbarPresenceAnimating = true
|
|
553
|
+
toolbar.animate().alpha(if (visible) 1f else 0f).setDuration(100).withEndAction {
|
|
554
|
+
if (toolbarAnimationGeneration == generation) {
|
|
555
|
+
toolbarPresenceAnimating = false
|
|
556
|
+
if (!visible) toolbar.visibility = INVISIBLE
|
|
557
|
+
}
|
|
558
|
+
}.start()
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private fun hideToolbar(failed: Boolean) {
|
|
562
|
+
setToolbarInteractive(false)
|
|
563
|
+
committedToolbarKey = null
|
|
564
|
+
committedToolbarEntry = null
|
|
565
|
+
toolbar.contentDescription = null
|
|
566
|
+
toolbar.setTag(com.facebook.react.R.id.react_test_id, null)
|
|
567
|
+
if (failed) toolbar.setImageDrawable(null)
|
|
568
|
+
setToolbarVisible(false)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private fun commitToolbar(key: String, descriptor: JSONObject, image: Bitmap, identity: String) {
|
|
572
|
+
if (!toolbarCanCommit() || key != selectedKey) return
|
|
573
|
+
if (committedToolbarKey != key && toolbarTouchStarted) toolbarTouchKey = null
|
|
574
|
+
toolbarSize = descriptor.optDouble("size", 38.0).coerceIn(1.0, 128.0)
|
|
575
|
+
toolbarIconSize = descriptor.optDouble("iconSize", min(24.0, toolbarSize)).coerceIn(1.0, toolbarSize)
|
|
576
|
+
toolbarContentWidth = descriptor.optDouble("contentWidth", min(24.0, toolbarSize)).coerceIn(0.0, toolbarSize)
|
|
577
|
+
toolbarTrailingInset = descriptor.optDouble("trailingInset", 20.0).coerceIn(0.0, 128.0)
|
|
578
|
+
val inset = max(0, (dp(toolbarSize) - dp(toolbarIconSize)) / 2)
|
|
579
|
+
toolbar.setPadding(inset, inset, inset, inset)
|
|
580
|
+
fun color(name: String, fallback: Int) = (descriptor.opt(name) as? Number)?.toInt() ?: fallback
|
|
581
|
+
val base = if (descriptor.optBoolean("selected")) color("selectedBackgroundColor", Color.TRANSPARENT) else Color.TRANSPARENT
|
|
582
|
+
fun fill(value: Int) = GradientDrawable().apply { shape = GradientDrawable.OVAL; setColor(value) }
|
|
583
|
+
toolbar.background = StateListDrawable().apply {
|
|
584
|
+
addState(intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed), fill(color("pressedBackgroundColor", base)))
|
|
585
|
+
addState(intArrayOf(android.R.attr.state_enabled, android.R.attr.state_hovered), fill(color("hoverBackgroundColor", base)))
|
|
586
|
+
addState(intArrayOf(), fill(base))
|
|
587
|
+
}
|
|
588
|
+
toolbar.setImageBitmap(image)
|
|
589
|
+
toolbar.contentDescription = descriptor.optString("accessibilityLabel")
|
|
590
|
+
toolbar.setTag(com.facebook.react.R.id.react_test_id, descriptor.optString("testID"))
|
|
591
|
+
committedToolbarKey = key
|
|
592
|
+
committedToolbarEntry = descriptor
|
|
593
|
+
loadedToolbarImage = image
|
|
594
|
+
loadedIconIdentity = identity
|
|
595
|
+
iconIdentity = identity
|
|
596
|
+
iconRequestPending = false
|
|
597
|
+
readyToolbarImage = null
|
|
598
|
+
readyToolbarIdentity = ""
|
|
599
|
+
setToolbarVisible(true)
|
|
600
|
+
setToolbarInteractive(true)
|
|
601
|
+
requestLayout()
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private fun applyReadyToolbarImage() {
|
|
605
|
+
val image = readyToolbarImage ?: return
|
|
606
|
+
val key = desiredToolbarKey ?: return
|
|
607
|
+
val entry = desiredToolbarEntry ?: return
|
|
608
|
+
if (readyToolbarIdentity != iconIdentity || !toolbarCanCommit() || key != selectedKey) return
|
|
609
|
+
commitToolbar(key, entry, image, iconIdentity)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
private fun updateToolbar() {
|
|
613
|
+
val entries = config.optJSONObject("toolbar")
|
|
614
|
+
toolbarReservedWidth = if (entries == null) 0.0 else 44.0
|
|
615
|
+
if (entries != null && entries.length() > 0) {
|
|
616
|
+
toolbarReservedWidth = 0.0
|
|
617
|
+
entries.keys().forEach { key ->
|
|
618
|
+
val entry = entries.optJSONObject(key) ?: JSONObject()
|
|
619
|
+
val size = entry.optDouble("size", 38.0).coerceIn(1.0, 128.0)
|
|
620
|
+
toolbarReservedWidth = max(toolbarReservedWidth, entry.optDouble("contentWidth", min(24.0, size)).coerceIn(0.0, size) + entry.optDouble("trailingInset", 20.0).coerceIn(0.0, 128.0))
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (!toolbarCanCommit()) { suspendToolbar(); requestLayout(); return }
|
|
624
|
+
val entry = entries?.optJSONObject(selectedKey)
|
|
625
|
+
if (entry == null || entry.optString("iconName").isEmpty()) {
|
|
626
|
+
cancelToolbarPending()
|
|
627
|
+
desiredToolbarKey = null
|
|
628
|
+
desiredToolbarEntry = null
|
|
629
|
+
hideToolbar(false)
|
|
630
|
+
requestLayout()
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
val descriptor = JSONObject(entry.toString())
|
|
634
|
+
desiredToolbarKey = selectedKey
|
|
635
|
+
desiredToolbarEntry = descriptor
|
|
636
|
+
val size = descriptor.optDouble("size", 38.0).coerceIn(1.0, 128.0)
|
|
637
|
+
val iconSize = descriptor.optDouble("iconSize", min(24.0, size)).coerceIn(1.0, size)
|
|
638
|
+
val name = descriptor.optString("iconName")
|
|
639
|
+
val tint = descriptor.optString("iconColor", "#000000")
|
|
640
|
+
val identity = "$name|$tint|${resources.displayMetrics.density}|$iconSize"
|
|
641
|
+
val loaded = loadedToolbarImage
|
|
642
|
+
if (identity == loadedIconIdentity && loaded != null) {
|
|
643
|
+
cancelToolbarPending()
|
|
644
|
+
commitToolbar(selectedKey, descriptor, loaded, identity)
|
|
645
|
+
return
|
|
646
|
+
}
|
|
647
|
+
if (identity == iconIdentity && iconRequestPending) { applyReadyToolbarImage(); return }
|
|
648
|
+
cancelToolbarPending()
|
|
649
|
+
setToolbarInteractive(false)
|
|
650
|
+
iconIdentity = identity
|
|
651
|
+
if (identity == failedIconIdentity) { hideToolbar(true); return }
|
|
652
|
+
iconOwner = Any()
|
|
653
|
+
val owner = iconOwner
|
|
654
|
+
iconRequestPending = true
|
|
655
|
+
OneKeyNamedSVG.request(owner, context.applicationContext.assets, name, tint,
|
|
656
|
+
dp(iconSize), dp(iconSize), resources.displayMetrics.density) { bitmap ->
|
|
657
|
+
if (iconOwner === owner && iconIdentity == identity && isAttachedToWindow) {
|
|
658
|
+
if (bitmap == null) {
|
|
659
|
+
iconRequestPending = false
|
|
660
|
+
failedIconIdentity = identity
|
|
661
|
+
hideToolbar(true)
|
|
662
|
+
} else {
|
|
663
|
+
readyToolbarImage = bitmap
|
|
664
|
+
readyToolbarIdentity = identity
|
|
665
|
+
applyReadyToolbarImage()
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
requestLayout()
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
private fun performToolbarPress(fromPointer: Boolean): Boolean {
|
|
673
|
+
val key = if (fromPointer) toolbarTouchKey.takeIf { toolbarTouchStarted } else committedToolbarKey
|
|
674
|
+
if (fromPointer) { toolbarTouchStarted = false; toolbarTouchKey = null }
|
|
675
|
+
if (key == null || key != committedToolbarKey || key != selectedKey || committedToolbarEntry == null ||
|
|
676
|
+
!toolbarCanCommit() || !toolbar.isEnabled || toolbar.visibility != VISIBLE) return false
|
|
677
|
+
val location = IntArray(2)
|
|
678
|
+
toolbar.getLocationInWindow(location)
|
|
679
|
+
val density = resources.displayMetrics.density.toDouble()
|
|
680
|
+
onToolbarPress?.invoke(key, doubleArrayOf(location[0] / density, location[1] / density, toolbar.width / density, toolbar.height / density))
|
|
681
|
+
return true
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
override fun onAttachedToWindow() {
|
|
685
|
+
super.onAttachedToWindow()
|
|
686
|
+
toolbarPagerBusy = true
|
|
687
|
+
updateToolbar()
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
override fun onDetachedFromWindow() {
|
|
691
|
+
suspendToolbar()
|
|
692
|
+
toolbarPagerBusy = true
|
|
693
|
+
toolbarPageIndex = -1
|
|
694
|
+
toolbarTouchStarted = false
|
|
695
|
+
toolbarTouchKey = null
|
|
696
|
+
failedIconIdentity = ""
|
|
697
|
+
super.onDetachedFromWindow()
|
|
698
|
+
}
|
|
380
699
|
|
|
381
700
|
var onItemPress: ((Int, String) -> Unit)?
|
|
382
701
|
get() = row.onItemPress
|
|
@@ -386,12 +705,52 @@ internal class CollapsiblePagerNativeTabBarView(context: Context) : FrameLayout(
|
|
|
386
705
|
|
|
387
706
|
init {
|
|
388
707
|
addView(row, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
708
|
+
addView(toolbar)
|
|
709
|
+
toolbar.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
|
710
|
+
toolbar.setPadding(dp(6), dp(6), dp(6), dp(6))
|
|
711
|
+
toolbar.isClickable = true
|
|
712
|
+
toolbar.isFocusable = true
|
|
713
|
+
toolbar.visibility = INVISIBLE
|
|
714
|
+
toolbar.alpha = 0f
|
|
715
|
+
toolbar.isEnabled = false
|
|
716
|
+
toolbar.isClickable = false
|
|
717
|
+
toolbar.isFocusable = false
|
|
718
|
+
toolbar.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
719
|
+
toolbar.setOnTouchListener { _, event ->
|
|
720
|
+
when (event.actionMasked) {
|
|
721
|
+
MotionEvent.ACTION_DOWN -> { toolbarTouchStarted = true; toolbarTouchKey = committedToolbarKey.takeIf { toolbar.isEnabled && toolbarCanCommit() } }
|
|
722
|
+
MotionEvent.ACTION_CANCEL -> { toolbarTouchKey = null }
|
|
723
|
+
}
|
|
724
|
+
false
|
|
725
|
+
}
|
|
726
|
+
toolbar.setOnClickListener { performToolbarPress(true) }
|
|
727
|
+
toolbar.accessibilityDelegate = object : View.AccessibilityDelegate() {
|
|
728
|
+
override fun performAccessibilityAction(host: View, action: Int, args: android.os.Bundle?): Boolean {
|
|
729
|
+
if (action == android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK) return performToolbarPress(false)
|
|
730
|
+
return super.performAccessibilityAction(host, action, args)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
toolbar.setOnKeyListener { _, keyCode, event ->
|
|
734
|
+
if (keyCode != android.view.KeyEvent.KEYCODE_ENTER && keyCode != android.view.KeyEvent.KEYCODE_DPAD_CENTER && keyCode != android.view.KeyEvent.KEYCODE_SPACE) false
|
|
735
|
+
else if (event.action == android.view.KeyEvent.ACTION_DOWN) {
|
|
736
|
+
if (event.repeatCount == 0) toolbarKeyboardKey = committedToolbarKey.takeIf { toolbar.isEnabled && toolbarCanCommit() }
|
|
737
|
+
toolbar.isPressed = toolbarKeyboardKey != null
|
|
738
|
+
toolbarKeyboardKey != null
|
|
739
|
+
} else if (event.action == android.view.KeyEvent.ACTION_UP) {
|
|
740
|
+
val valid = toolbarKeyboardKey != null && toolbarKeyboardKey == committedToolbarKey
|
|
741
|
+
toolbarKeyboardKey = null
|
|
742
|
+
toolbar.isPressed = false
|
|
743
|
+
valid && performToolbarPress(false)
|
|
744
|
+
} else false
|
|
745
|
+
}
|
|
389
746
|
visibility = GONE
|
|
390
747
|
}
|
|
391
748
|
|
|
392
749
|
fun updateItemsJSON(value: String?) {
|
|
750
|
+
items = parseHeaderItems(value)
|
|
393
751
|
row.updateItemsJSON(value)
|
|
394
752
|
visibility = row.visibility
|
|
753
|
+
updateConfigJSON(config.toString())
|
|
395
754
|
}
|
|
396
755
|
|
|
397
756
|
fun updateStyle(
|
|
@@ -425,15 +784,83 @@ internal class CollapsiblePagerNativeTabBarView(context: Context) : FrameLayout(
|
|
|
425
784
|
activeTextColor: Int,
|
|
426
785
|
inactiveTextColor: Int,
|
|
427
786
|
indicatorColor: Int,
|
|
428
|
-
)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
787
|
+
) {
|
|
788
|
+
val text = row.usesTextVariant()
|
|
789
|
+
setBackgroundColor(if (text) backgroundColor else Color.TRANSPARENT)
|
|
790
|
+
row.updateColors(
|
|
791
|
+
if (text) Color.TRANSPARENT else backgroundColor,
|
|
792
|
+
activeTextColor,
|
|
793
|
+
inactiveTextColor,
|
|
794
|
+
Color.TRANSPARENT,
|
|
795
|
+
indicatorColor,
|
|
796
|
+
)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private fun controlledPageIndices(): List<Int>? {
|
|
800
|
+
val pageKeys = config.optJSONArray("pageKeys") ?: return null
|
|
801
|
+
val names = List(pageKeys.length()) { pageKeys.optString(it) }
|
|
802
|
+
val indices = names.map { name -> items.indexOfFirst { it.key == name } }
|
|
803
|
+
if (names.isEmpty() || selectedKey !in names || names.toSet().size != names.size ||
|
|
804
|
+
indices.any { it < 0 } || items.map { it.key }.toSet().size != items.size) return null
|
|
805
|
+
return indices
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
fun usesControlledTextProgress(): Boolean {
|
|
809
|
+
return row.usesTextVariant() && config.optBoolean("controlled") && controlledPageIndices() != null
|
|
810
|
+
}
|
|
435
811
|
|
|
436
|
-
fun setProgress(progress: Float)
|
|
812
|
+
fun setProgress(progress: Float) {
|
|
813
|
+
lastNativeProgress = progress
|
|
814
|
+
hasNativeProgress = true
|
|
815
|
+
var mapped = progress
|
|
816
|
+
var source: Int? = null
|
|
817
|
+
var destination: Int? = null
|
|
818
|
+
var fraction = 0f
|
|
819
|
+
if (config.optBoolean("controlled")) {
|
|
820
|
+
val pageKeys = config.optJSONArray("pageKeys") ?: return
|
|
821
|
+
val indices = controlledPageIndices()
|
|
822
|
+
if (indices == null) {
|
|
823
|
+
val fallback = items.indexOfFirst { it.key == config.optString("selectedKey") }
|
|
824
|
+
if (fallback >= 0) { row.setProgress(fallback.toFloat()); selectedKey = items[fallback].key; updateToolbar() }
|
|
825
|
+
return
|
|
826
|
+
}
|
|
827
|
+
val bounded = progress.coerceIn(0f, (pageKeys.length() - 1).toFloat())
|
|
828
|
+
val lower = floor(bounded).toInt().coerceAtLeast(0)
|
|
829
|
+
val upper = min(lower + 1, pageKeys.length() - 1)
|
|
830
|
+
val from = indices[lower]
|
|
831
|
+
val to = indices[upper]
|
|
832
|
+
if (from < 0 || to < 0) return
|
|
833
|
+
fraction = bounded - lower
|
|
834
|
+
source = from
|
|
835
|
+
destination = to
|
|
836
|
+
mapped = from + (to - from) * fraction
|
|
837
|
+
}
|
|
838
|
+
row.setProgress(mapped, source, destination, fraction)
|
|
839
|
+
if (config.optBoolean("controlled")) return
|
|
840
|
+
val selectedIndex = mapped.roundToInt()
|
|
841
|
+
val nextKey = items.getOrNull(selectedIndex)?.key ?: return
|
|
842
|
+
if (nextKey != selectedKey) { toolbarTouchKey = null; toolbar.isPressed = false; selectedKey = nextKey; updateToolbar() }
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
846
|
+
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
847
|
+
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
848
|
+
val reserved = dp(toolbarReservedWidth)
|
|
849
|
+
row.measure(MeasureSpec.makeMeasureSpec(max(0, width - reserved), MeasureSpec.EXACTLY), heightMeasureSpec)
|
|
850
|
+
toolbar.measure(MeasureSpec.makeMeasureSpec(dp(toolbarSize), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(toolbarSize), MeasureSpec.EXACTLY))
|
|
851
|
+
setMeasuredDimension(width, height)
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
855
|
+
val width = right - left
|
|
856
|
+
val height = bottom - top
|
|
857
|
+
val reserved = dp(toolbarReservedWidth)
|
|
858
|
+
val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
|
|
859
|
+
row.layout(if (rtl) reserved else 0, 0, if (rtl) width else width - reserved, height)
|
|
860
|
+
val inset = dp(toolbarTrailingInset) - (dp(toolbarSize) - dp(toolbarContentWidth)) / 2
|
|
861
|
+
val x = if (rtl) inset else width - inset - dp(toolbarSize)
|
|
862
|
+
toolbar.layout(x, (height - dp(toolbarSize)) / 2, x + dp(toolbarSize), (height + dp(toolbarSize)) / 2)
|
|
863
|
+
}
|
|
437
864
|
|
|
438
865
|
fun preferredHeightPx(): Int = heightPx
|
|
439
866
|
}
|
|
@@ -70,6 +70,10 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
})
|
|
73
|
+
host.onNativeToolbarPress = { key, rect ->
|
|
74
|
+
dispatch(reactContext, host, NativeHeaderPressEvent(host.id, 0, key,
|
|
75
|
+
NativeHeaderPressEvent.TOOLBAR_EVENT_NAME, rect))
|
|
76
|
+
}
|
|
73
77
|
host.onNativeTabPress = { position, key ->
|
|
74
78
|
dispatch(
|
|
75
79
|
reactContext,
|
|
@@ -227,6 +231,11 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
227
231
|
view?.retainedPages = value ?: "[]"
|
|
228
232
|
}
|
|
229
233
|
|
|
234
|
+
@ReactProp(name = "nativeTabBarConfig")
|
|
235
|
+
override fun setNativeTabBarConfig(view: CollapsiblePagerHost?, value: String?) {
|
|
236
|
+
view?.updateNativeTabBarConfig(value)
|
|
237
|
+
}
|
|
238
|
+
|
|
230
239
|
@ReactProp(name = "nativeTabBarItems")
|
|
231
240
|
override fun setNativeTabBarItems(view: CollapsiblePagerHost?, value: String?) {
|
|
232
241
|
view?.updateNativeTabBarItems(value)
|
|
@@ -242,7 +251,7 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
242
251
|
@ReactProp(name = "nativeTabBarVariant")
|
|
243
252
|
override fun setNativeTabBarVariant(view: CollapsiblePagerHost?, value: String?) {
|
|
244
253
|
view ?: return
|
|
245
|
-
view.nativeTabBarVariant = if (value == "segmented"
|
|
254
|
+
view.nativeTabBarVariant = if (value == "segmented" || value == "text") value else "underline"
|
|
246
255
|
view.applyNativeHeaderStyle()
|
|
247
256
|
}
|
|
248
257
|
|
|
@@ -404,6 +413,7 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
404
413
|
CollapsibleStateChangedEvent.EVENT_NAME,
|
|
405
414
|
MapBuilder.of("registrationName", "onCollapsibleStateChanged"),
|
|
406
415
|
)
|
|
416
|
+
.put(NativeHeaderPressEvent.TOOLBAR_EVENT_NAME, MapBuilder.of("registrationName", "onNativeToolbarPress"))
|
|
407
417
|
.put(
|
|
408
418
|
NativeHeaderPressEvent.TAB_EVENT_NAME,
|
|
409
419
|
MapBuilder.of("registrationName", "onNativeTabPress"),
|
|
@@ -10,6 +10,7 @@ class NativeHeaderPressEvent(
|
|
|
10
10
|
private val position: Int,
|
|
11
11
|
private val key: String,
|
|
12
12
|
private val name: String,
|
|
13
|
+
private val anchorRect: DoubleArray? = null,
|
|
13
14
|
) : Event<NativeHeaderPressEvent>(viewTag) {
|
|
14
15
|
override fun getEventName() = name
|
|
15
16
|
|
|
@@ -22,9 +23,16 @@ class NativeHeaderPressEvent(
|
|
|
22
23
|
private fun serializeEventData(): WritableMap = Arguments.createMap().apply {
|
|
23
24
|
putInt("position", position)
|
|
24
25
|
putString("key", key)
|
|
26
|
+
anchorRect?.let { rect ->
|
|
27
|
+
putMap("anchorRect", Arguments.createMap().apply {
|
|
28
|
+
putDouble("x", rect[0]); putDouble("y", rect[1])
|
|
29
|
+
putDouble("width", rect[2]); putDouble("height", rect[3])
|
|
30
|
+
})
|
|
31
|
+
}
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
companion object {
|
|
35
|
+
const val TOOLBAR_EVENT_NAME = "topNativeToolbarPress"
|
|
28
36
|
const val TAB_EVENT_NAME = "topNativeTabPress"
|
|
29
37
|
const val SUB_HEADER_EVENT_NAME = "topNativeSubHeaderPress"
|
|
30
38
|
}
|