react-native-navigation 8.8.6 → 8.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativenavigation/NavigationApplication.java +3 -0
- package/android/src/main/java/com/reactnativenavigation/NavigationPackage.kt +27 -8
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRow.kt +262 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowAttacher.kt +205 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowConfigStore.kt +32 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowLayout.kt +139 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowModule.kt +37 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowOptions.kt +68 -0
- package/android/src/main/java/com/reactnativenavigation/options/BottomTabOptions.java +4 -1
- package/android/src/main/java/com/reactnativenavigation/react/ReactView.java +13 -0
- package/android/src/main/java/com/reactnativenavigation/react/events/ComponentType.java +2 -1
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabPresenter.java +28 -0
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java +59 -0
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java +76 -0
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/CustomBottomTabItemView.kt +73 -0
- package/android/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleBarReactButtonView.java +27 -11
- package/android/src/test/java/com/reactnativenavigation/views/TitleAndButtonsContainerTest.kt +15 -1
- package/android/src/test/java/com/reactnativenavigation/views/TitleBarReactButtonViewTest.java +135 -0
- package/ios/ARCHITECTURE.md +5 -0
- package/ios/BottomTabPresenter.h +7 -0
- package/ios/BottomTabPresenter.mm +27 -0
- package/ios/RNNAppDelegate.h +16 -0
- package/ios/RNNAppDelegate.mm +73 -0
- package/ios/RNNBottomTabOptions.h +2 -0
- package/ios/RNNBottomTabOptions.mm +5 -1
- package/ios/RNNBottomTabsController.h +2 -0
- package/ios/RNNBottomTabsController.mm +209 -1
- package/ios/RNNBottomTabsCustomRow.h +57 -0
- package/ios/RNNBottomTabsCustomRow.mm +252 -0
- package/ios/RNNBottomTabsCustomRowOptions.h +42 -0
- package/ios/RNNBottomTabsCustomRowOptions.mm +37 -0
- package/ios/RNNBottomTabsOptions.h +2 -0
- package/ios/RNNBottomTabsOptions.mm +2 -0
- package/ios/RNNComponentViewCreator.h +2 -1
- package/ios/RNNCustomTabBarItemView.h +26 -0
- package/ios/RNNCustomTabBarItemView.mm +83 -0
- package/ios/RNNReactRootViewCreator.mm +1 -0
- package/ios/RNNViewControllerFactory.mm +1 -0
- package/ios/ReactNativeNavigation.xcodeproj/project.pbxproj +24 -0
- package/lib/module/ARCHITECTURE.md +30 -0
- package/lib/module/Navigation.js +34 -1
- package/lib/module/Navigation.js.map +1 -1
- package/lib/module/NavigationDelegate.js +21 -0
- package/lib/module/NavigationDelegate.js.map +1 -1
- package/lib/module/adapters/AndroidCustomRowForwarder.js +75 -0
- package/lib/module/adapters/AndroidCustomRowForwarder.js.map +1 -0
- package/lib/module/commands/Commands.js +8 -0
- package/lib/module/commands/Commands.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/interfaces/Options.js.map +1 -1
- package/lib/module/linking/DeferredLinkQueue.js +52 -0
- package/lib/module/linking/DeferredLinkQueue.js.map +1 -0
- package/lib/module/linking/DeferredLinkQueue.test.js +54 -0
- package/lib/module/linking/DeferredLinkQueue.test.js.map +1 -0
- package/lib/module/linking/LinkingHandler.js +139 -0
- package/lib/module/linking/LinkingHandler.js.map +1 -0
- package/lib/module/linking/LinkingHandler.test.js +384 -0
- package/lib/module/linking/LinkingHandler.test.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.js +56 -0
- package/lib/module/linking/ModalLayoutBuilder.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js +154 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js.map +1 -0
- package/lib/module/linking/RouteMatcher.js +104 -0
- package/lib/module/linking/RouteMatcher.js.map +1 -0
- package/lib/module/linking/RouteMatcher.test.js +164 -0
- package/lib/module/linking/RouteMatcher.test.js.map +1 -0
- package/lib/module/linking/URLParser.js +56 -0
- package/lib/module/linking/URLParser.js.map +1 -0
- package/lib/module/linking/URLParser.test.js +100 -0
- package/lib/module/linking/URLParser.test.js.map +1 -0
- package/lib/module/linking/types.js +4 -0
- package/lib/module/linking/types.js.map +1 -0
- package/lib/typescript/Navigation.d.ts +22 -0
- package/lib/typescript/Navigation.d.ts.map +1 -1
- package/lib/typescript/NavigationDelegate.d.ts +13 -0
- package/lib/typescript/NavigationDelegate.d.ts.map +1 -1
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts +23 -0
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts.map +1 -0
- package/lib/typescript/commands/Commands.d.ts +1 -0
- package/lib/typescript/commands/Commands.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +85 -0
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/linking/DeferredLinkQueue.d.ts +26 -0
- package/lib/typescript/linking/DeferredLinkQueue.d.ts.map +1 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts +2 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.d.ts +71 -0
- package/lib/typescript/linking/LinkingHandler.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts +2 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts +21 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts +2 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.d.ts +23 -0
- package/lib/typescript/linking/RouteMatcher.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts +2 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.d.ts +16 -0
- package/lib/typescript/linking/URLParser.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.test.d.ts +2 -0
- package/lib/typescript/linking/URLParser.test.d.ts.map +1 -0
- package/lib/typescript/linking/types.d.ts +107 -0
- package/lib/typescript/linking/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +30 -0
- package/src/Navigation.ts +36 -1
- package/src/NavigationDelegate.ts +22 -0
- package/src/adapters/AndroidCustomRowForwarder.ts +83 -0
- package/src/commands/Commands.ts +15 -0
- package/src/index.ts +1 -0
- package/src/interfaces/Options.ts +87 -0
- package/src/linking/DeferredLinkQueue.test.ts +60 -0
- package/src/linking/DeferredLinkQueue.ts +55 -0
- package/src/linking/LinkingHandler.test.ts +332 -0
- package/src/linking/LinkingHandler.ts +169 -0
- package/src/linking/ModalLayoutBuilder.test.ts +105 -0
- package/src/linking/ModalLayoutBuilder.ts +60 -0
- package/src/linking/RouteMatcher.test.ts +128 -0
- package/src/linking/RouteMatcher.ts +126 -0
- package/src/linking/URLParser.test.ts +105 -0
- package/src/linking/URLParser.ts +62 -0
- package/src/linking/types.ts +115 -0
package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowLayout.kt
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
package com.reactnativenavigation.customrow
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.util.TypedValue
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import com.reactnativenavigation.views.bottomtabs.BottomTabs
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves how the floating custom row should anchor and whether the bottom
|
|
12
|
+
* system-bar inset belongs inside the row or was already applied by RNN's
|
|
13
|
+
* [com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController].
|
|
14
|
+
*
|
|
15
|
+
* On most devices (including Pixel with gesture/3-button nav) RNN pads the
|
|
16
|
+
* bottom-tabs controller by `systemBars().bottom`, so the native bar already
|
|
17
|
+
* sits above the nav area — adding the same inset again creates a visible gap.
|
|
18
|
+
*
|
|
19
|
+
* With edge-to-edge (API 35+ theme opt-in) content can extend behind the nav
|
|
20
|
+
* bar; the row must pin to the overlay host bottom and reserve inset inside.
|
|
21
|
+
*/
|
|
22
|
+
internal object BottomTabsCustomRowLayout {
|
|
23
|
+
|
|
24
|
+
enum class AnchorMode {
|
|
25
|
+
/** Native bar bottom is already above system bars (RNN bottom padding). */
|
|
26
|
+
NATIVE_BAR_ABOVE_SYSTEM_BARS,
|
|
27
|
+
/** Row extends to the host bottom; inset is applied inside the row. */
|
|
28
|
+
EDGE_TO_EDGE,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
data class Placement(
|
|
32
|
+
val anchorMode: AnchorMode,
|
|
33
|
+
/** Inset applied inside the row for cell/chrome layout (0 when native bar already cleared it). */
|
|
34
|
+
val rowSafeBottomInsetPx: Int,
|
|
35
|
+
val left: Int,
|
|
36
|
+
val top: Int,
|
|
37
|
+
val width: Int,
|
|
38
|
+
val height: Int,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
fun resolvePlacement(
|
|
42
|
+
activity: Activity,
|
|
43
|
+
row: BottomTabsCustomRow,
|
|
44
|
+
bottomTabs: BottomTabs,
|
|
45
|
+
overlayHost: ViewGroup,
|
|
46
|
+
navBarInsetPx: Int,
|
|
47
|
+
): Placement? {
|
|
48
|
+
val nativeHeight = bottomTabs.height
|
|
49
|
+
if (nativeHeight <= 0) return null
|
|
50
|
+
|
|
51
|
+
val horizontalMargin = row.effectiveHorizontalMarginPx()
|
|
52
|
+
val bottomMargin = row.effectiveBottomMarginPx()
|
|
53
|
+
|
|
54
|
+
val tabLeftInHost = tabLeftRelativeToHost(bottomTabs, overlayHost)
|
|
55
|
+
val tabRightInHost = tabLeftInHost + bottomTabs.width
|
|
56
|
+
|
|
57
|
+
// Row is hosted on `android.R.id.content`; anchor to `BottomTabs` bottom
|
|
58
|
+
// (above RNN's nav-bar padding). Never use decor.height — that draws over
|
|
59
|
+
// the system navigation buttons.
|
|
60
|
+
val anchorMode = resolveAnchorMode(activity, bottomTabs, overlayHost, navBarInsetPx)
|
|
61
|
+
val rowSafeBottom = 0
|
|
62
|
+
|
|
63
|
+
val contentHeight = row.effectiveContentHeightPx(nativeHeight)
|
|
64
|
+
val totalHeight = contentHeight + bottomMargin
|
|
65
|
+
|
|
66
|
+
val left = tabLeftInHost + horizontalMargin
|
|
67
|
+
val width = (tabRightInHost - horizontalMargin) - left
|
|
68
|
+
|
|
69
|
+
// RNN already lays out `BottomTabs` above its bottom padding — match that
|
|
70
|
+
// edge. Do not subtract `navBarInsetPx` again (that was lifting the bar).
|
|
71
|
+
val bottom = tabBottomRelativeToHost(bottomTabs, overlayHost) - bottomMargin
|
|
72
|
+
val top = bottom - totalHeight
|
|
73
|
+
|
|
74
|
+
return Placement(anchorMode, rowSafeBottom, left, top, width, totalHeight)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fun resolveAnchorMode(
|
|
78
|
+
activity: Activity,
|
|
79
|
+
bottomTabs: BottomTabs,
|
|
80
|
+
overlayHost: ViewGroup,
|
|
81
|
+
@Suppress("UNUSED_PARAMETER") navBarInsetPx: Int,
|
|
82
|
+
): AnchorMode {
|
|
83
|
+
// RNN's bottom-tabs host ends at `android.R.id.content` bottom while the
|
|
84
|
+
// system nav bar sits below that — never treat "flush with content" as
|
|
85
|
+
// edge-to-edge or we reserve a phantom inset and leave a white gap.
|
|
86
|
+
if (!isEdgeToEdgeEnabled(activity)) {
|
|
87
|
+
return AnchorMode.NATIVE_BAR_ABOVE_SYSTEM_BARS
|
|
88
|
+
}
|
|
89
|
+
val decor = activity.window?.decorView ?: return AnchorMode.NATIVE_BAR_ABOVE_SYSTEM_BARS
|
|
90
|
+
val tolerancePx = dpToPx(activity, 4f)
|
|
91
|
+
val tabBottomOnScreen = screenBottom(bottomTabs)
|
|
92
|
+
val decorBottomOnScreen = screenBottom(decor)
|
|
93
|
+
return if (kotlin.math.abs(tabBottomOnScreen - decorBottomOnScreen) <= tolerancePx) {
|
|
94
|
+
AnchorMode.EDGE_TO_EDGE
|
|
95
|
+
} else {
|
|
96
|
+
AnchorMode.NATIVE_BAR_ABOVE_SYSTEM_BARS
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private fun screenBottom(view: android.view.View): Int {
|
|
101
|
+
val loc = IntArray(2).also(view::getLocationOnScreen)
|
|
102
|
+
return loc[1] + view.height
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fun isEdgeToEdgeEnabled(activity: Activity): Boolean {
|
|
106
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
val typedValue = TypedValue()
|
|
110
|
+
val resolved = activity.theme.resolveAttribute(
|
|
111
|
+
android.R.attr.windowOptOutEdgeToEdgeEnforcement,
|
|
112
|
+
typedValue,
|
|
113
|
+
true
|
|
114
|
+
)
|
|
115
|
+
return resolved &&
|
|
116
|
+
typedValue.type == TypedValue.TYPE_INT_BOOLEAN &&
|
|
117
|
+
typedValue.data == 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private fun tabLeftRelativeToHost(bottomTabs: BottomTabs, overlayHost: ViewGroup): Int {
|
|
121
|
+
val tabLoc = IntArray(2).also(bottomTabs::getLocationOnScreen)
|
|
122
|
+
val hostLoc = IntArray(2).also(overlayHost::getLocationOnScreen)
|
|
123
|
+
return tabLoc[0] - hostLoc[0]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private fun tabBottomRelativeToHost(bottomTabs: BottomTabs, overlayHost: ViewGroup): Int {
|
|
127
|
+
val tabLoc = IntArray(2).also(bottomTabs::getLocationOnScreen)
|
|
128
|
+
val hostLoc = IntArray(2).also(overlayHost::getLocationOnScreen)
|
|
129
|
+
val tabTopInHost = tabLoc[1] - hostLoc[1]
|
|
130
|
+
return tabTopInHost + bottomTabs.height
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private fun dpToPx(activity: Activity, dp: Float): Int =
|
|
134
|
+
TypedValue.applyDimension(
|
|
135
|
+
TypedValue.COMPLEX_UNIT_DIP,
|
|
136
|
+
dp,
|
|
137
|
+
activity.resources.displayMetrics
|
|
138
|
+
).toInt()
|
|
139
|
+
}
|
package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowModule.kt
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.reactnativenavigation.customrow
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReactMethod
|
|
7
|
+
import com.facebook.react.bridge.ReadableMap
|
|
8
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* RN bridge module that lets JS push the latest `bottomTabs.customRow`
|
|
12
|
+
* configuration to native. The JS-side `AndroidCustomRowForwarder`
|
|
13
|
+
* scans `Navigation.setRoot` / `setDefaultOptions` / `mergeOptions`
|
|
14
|
+
* payloads and calls [configure] whenever it finds a `customRow` block.
|
|
15
|
+
*/
|
|
16
|
+
@ReactModule(name = BottomTabsCustomRowModule.NAME)
|
|
17
|
+
class BottomTabsCustomRowModule(
|
|
18
|
+
reactContext: ReactApplicationContext,
|
|
19
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
20
|
+
|
|
21
|
+
init {
|
|
22
|
+
val app = reactContext.applicationContext as? Application
|
|
23
|
+
if (app != null) BottomTabsCustomRowAttacher.registerOnce(app)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun getName(): String = NAME
|
|
27
|
+
|
|
28
|
+
@ReactMethod
|
|
29
|
+
fun configure(config: ReadableMap?) {
|
|
30
|
+
BottomTabsCustomRowConfigStore.update(BottomTabsCustomRowOptions.fromMap(config))
|
|
31
|
+
BottomTabsCustomRowAttacher.rescan()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
const val NAME = "RNNBottomTabsCustomRowModule"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowOptions.kt
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.reactnativenavigation.customrow
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors the iOS-side `RNNBottomTabsCustomRowOptions` data shape. All
|
|
8
|
+
* fields are optional. Defaults are chosen to give an Android equivalent
|
|
9
|
+
* of the iOS 26 floating glass pill on Android 12+ (RenderEffect blur),
|
|
10
|
+
* and an opaque material chrome on older versions.
|
|
11
|
+
*/
|
|
12
|
+
data class BottomTabsCustomRowOptions(
|
|
13
|
+
val height: Float? = null,
|
|
14
|
+
val backgroundColor: Int? = null,
|
|
15
|
+
val backgroundEffect: BackgroundEffect? = null,
|
|
16
|
+
val cornerRadius: Float? = null,
|
|
17
|
+
val horizontalMargin: Float? = null,
|
|
18
|
+
val bottomMargin: Float? = null,
|
|
19
|
+
) {
|
|
20
|
+
enum class BackgroundEffect { Glass, Blur, None }
|
|
21
|
+
|
|
22
|
+
companion object {
|
|
23
|
+
fun fromMap(map: ReadableMap?): BottomTabsCustomRowOptions {
|
|
24
|
+
if (map == null) return BottomTabsCustomRowOptions()
|
|
25
|
+
return BottomTabsCustomRowOptions(
|
|
26
|
+
height = map.optFloat("height"),
|
|
27
|
+
backgroundColor = map.optColor("backgroundColor"),
|
|
28
|
+
backgroundEffect = map.optEffect("backgroundEffect"),
|
|
29
|
+
cornerRadius = map.optFloat("cornerRadius"),
|
|
30
|
+
horizontalMargin = map.optFloat("horizontalMargin"),
|
|
31
|
+
bottomMargin = map.optFloat("bottomMargin"),
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private fun ReadableMap.optFloat(key: String): Float? {
|
|
36
|
+
if (!hasKey(key) || isNull(key)) return null
|
|
37
|
+
return getDouble(key).toFloat()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun ReadableMap.optColor(key: String): Int? {
|
|
41
|
+
if (!hasKey(key) || isNull(key)) return null
|
|
42
|
+
// Color may arrive as a number (Android-style int) or a wrapped
|
|
43
|
+
// theme-color object. Support both shallowly.
|
|
44
|
+
return when (getType(key)) {
|
|
45
|
+
com.facebook.react.bridge.ReadableType.Number -> getInt(key)
|
|
46
|
+
com.facebook.react.bridge.ReadableType.Map -> {
|
|
47
|
+
val sub = getMap(key)
|
|
48
|
+
val light = sub?.let { if (it.hasKey("light")) it.getInt("light") else null }
|
|
49
|
+
light ?: sub?.let { if (it.hasKey("color")) it.getInt("color") else null }
|
|
50
|
+
}
|
|
51
|
+
com.facebook.react.bridge.ReadableType.String -> {
|
|
52
|
+
runCatching { Color.parseColor(getString(key)) }.getOrNull()
|
|
53
|
+
}
|
|
54
|
+
else -> null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private fun ReadableMap.optEffect(key: String): BackgroundEffect? {
|
|
59
|
+
if (!hasKey(key) || isNull(key)) return null
|
|
60
|
+
return when (getString(key)?.lowercase()) {
|
|
61
|
+
"glass" -> BackgroundEffect.Glass
|
|
62
|
+
"blur" -> BackgroundEffect.Blur
|
|
63
|
+
"none" -> BackgroundEffect.None
|
|
64
|
+
else -> null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -44,6 +44,7 @@ public class BottomTabOptions {
|
|
|
44
44
|
options.dotIndicator = DotIndicatorOptions.parse(context, json.optJSONObject("dotIndicator"));
|
|
45
45
|
options.selectTabOnPress = BoolParser.parse(json, "selectTabOnPress");
|
|
46
46
|
options.popToRoot = BoolParser.parse(json, "popToRoot");
|
|
47
|
+
options.component = ComponentOptions.parse(json.optJSONObject("component"));
|
|
47
48
|
|
|
48
49
|
return options;
|
|
49
50
|
}
|
|
@@ -67,6 +68,7 @@ public class BottomTabOptions {
|
|
|
67
68
|
public Bool selectTabOnPress = new NullBool();
|
|
68
69
|
public Bool popToRoot = new NullBool();
|
|
69
70
|
public FontOptions font = new FontOptions();
|
|
71
|
+
public ComponentOptions component = new ComponentOptions();
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
void mergeWith(final BottomTabOptions other) {
|
|
@@ -90,6 +92,7 @@ public class BottomTabOptions {
|
|
|
90
92
|
if (other.dotIndicator.hasValue()) dotIndicator = other.dotIndicator;
|
|
91
93
|
if (other.selectTabOnPress.hasValue()) selectTabOnPress = other.selectTabOnPress;
|
|
92
94
|
if (other.popToRoot.hasValue()) popToRoot = other.popToRoot;
|
|
95
|
+
if (other.component.hasValue()) component = other.component;
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
void mergeWithDefault(final BottomTabOptions defaultOptions) {
|
|
@@ -113,7 +116,7 @@ public class BottomTabOptions {
|
|
|
113
116
|
if (!dotIndicator.hasValue()) dotIndicator = defaultOptions.dotIndicator;
|
|
114
117
|
if (!selectTabOnPress.hasValue()) selectTabOnPress = defaultOptions.selectTabOnPress;
|
|
115
118
|
if (!popToRoot.hasValue()) popToRoot = defaultOptions.popToRoot;
|
|
116
|
-
|
|
119
|
+
if (!component.hasValue()) component = defaultOptions.component;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
}
|
|
@@ -16,6 +16,7 @@ import com.facebook.react.ReactApplication;
|
|
|
16
16
|
import com.facebook.react.ReactHost;
|
|
17
17
|
import com.facebook.react.bridge.ReactContext;
|
|
18
18
|
import com.facebook.react.interfaces.fabric.ReactSurface;
|
|
19
|
+
import com.facebook.react.runtime.ReactSurfaceImpl;
|
|
19
20
|
import com.facebook.react.uimanager.UIManagerHelper;
|
|
20
21
|
import com.facebook.react.uimanager.common.UIManagerType;
|
|
21
22
|
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
@@ -71,6 +72,18 @@ public class ReactView extends FrameLayout implements IReactView, Renderable {
|
|
|
71
72
|
reactSurface.stop();
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Replace the surface's initial props. Useful for components that need to
|
|
77
|
+
* receive runtime updates from native (e.g. bottom tab item components).
|
|
78
|
+
* No-op when the underlying surface implementation does not support
|
|
79
|
+
* runtime prop updates.
|
|
80
|
+
*/
|
|
81
|
+
public void setProps(Bundle props) {
|
|
82
|
+
if (reactSurface instanceof ReactSurfaceImpl) {
|
|
83
|
+
((ReactSurfaceImpl) reactSurface).updateInitProps(props);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
public void sendComponentWillStart(ComponentType type) {
|
|
75
88
|
this.post(() -> {
|
|
76
89
|
ReactContext currentReactContext = getReactContext();
|
|
@@ -20,7 +20,9 @@ import com.reactnativenavigation.utils.ImageLoadingListenerAdapter;
|
|
|
20
20
|
import com.reactnativenavigation.utils.LateInit;
|
|
21
21
|
import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;
|
|
22
22
|
import com.reactnativenavigation.views.bottomtabs.BottomTabs;
|
|
23
|
+
import com.reactnativenavigation.views.bottomtabs.CustomBottomTabItemView;
|
|
23
24
|
|
|
25
|
+
import java.util.ArrayList;
|
|
24
26
|
import java.util.List;
|
|
25
27
|
|
|
26
28
|
public class BottomTabPresenter {
|
|
@@ -33,6 +35,7 @@ public class BottomTabPresenter {
|
|
|
33
35
|
private final LateInit<BottomTabs> bottomTabs = new LateInit<>();
|
|
34
36
|
private final List<ViewController<?>> tabs;
|
|
35
37
|
private final int defaultDotIndicatorSize;
|
|
38
|
+
private boolean useCustomItemViews;
|
|
36
39
|
|
|
37
40
|
public BottomTabPresenter(Context context, List<ViewController<?>> tabs, ImageLoader imageLoader, TypefaceLoader typefaceLoader, Options defaultOptions) {
|
|
38
41
|
this.tabs = tabs;
|
|
@@ -53,10 +56,27 @@ public class BottomTabPresenter {
|
|
|
53
56
|
this.bottomTabs.set(bottomTabs);
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/**
|
|
60
|
+
* When `true`, tabs whose options declare `bottomTab.component` are
|
|
61
|
+
* skipped during native icon/text application. The accompanying
|
|
62
|
+
* `CustomBottomTabItemView` overlay is responsible for visual rendering.
|
|
63
|
+
*/
|
|
64
|
+
public void setUseCustomItemViews(boolean useCustomItemViews) {
|
|
65
|
+
this.useCustomItemViews = useCustomItemViews;
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
public void applyOptions() {
|
|
57
69
|
bottomTabs.perform(bottomTabs -> {
|
|
58
70
|
for (int i = 0; i < tabs.size(); i++) {
|
|
59
71
|
BottomTabOptions tab = tabs.get(i).resolveCurrentOptions(defaultOptions).bottomTabOptions;
|
|
72
|
+
if (useCustomItemViews && tab.component.hasValue()) {
|
|
73
|
+
if (tab.testId.hasValue()) bottomTabs.setTag(i, tab.testId.get());
|
|
74
|
+
if (tab.badge.hasValue()) {
|
|
75
|
+
CustomBottomTabItemView v = bottomTabs.getCustomItemView(i);
|
|
76
|
+
if (v != null) v.setBadge(tab.badge.get(""));
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
60
80
|
bottomTabs.setIconWidth(i, tab.iconWidth.get(null));
|
|
61
81
|
bottomTabs.setIconHeight(i, tab.iconHeight.get(null));
|
|
62
82
|
bottomTabs.setTitleTypeface(i, tab.font.getTypeface(typefaceLoader, defaultTypeface));
|
|
@@ -86,6 +106,14 @@ public class BottomTabPresenter {
|
|
|
86
106
|
int index = bottomTabFinder.findByControllerId(child.getId());
|
|
87
107
|
if (index >= 0) {
|
|
88
108
|
BottomTabOptions tab = options.bottomTabOptions;
|
|
109
|
+
if (useCustomItemViews && bottomTabs.getCustomItemView(index) != null) {
|
|
110
|
+
if (tab.badge.hasValue()) {
|
|
111
|
+
CustomBottomTabItemView v = bottomTabs.getCustomItemView(index);
|
|
112
|
+
if (v != null) v.setBadge(tab.badge.get(""));
|
|
113
|
+
}
|
|
114
|
+
if (tab.testId.hasValue()) bottomTabs.setTag(index, tab.testId.get());
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
89
117
|
if (tab.iconWidth.hasValue()) bottomTabs.setIconWidth(index, tab.iconWidth.get(null));
|
|
90
118
|
if (tab.iconHeight.hasValue()) bottomTabs.setIconHeight(index, tab.iconHeight.get(null));
|
|
91
119
|
if (tab.font.hasValue()) bottomTabs.setTitleTypeface(index, tab.font.getTypeface(typefaceLoader, defaultTypeface));
|
|
@@ -16,6 +16,8 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
|
16
16
|
import androidx.core.view.ViewCompat;
|
|
17
17
|
import androidx.core.view.WindowInsetsCompat;
|
|
18
18
|
|
|
19
|
+
import android.util.Log;
|
|
20
|
+
|
|
19
21
|
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
|
|
20
22
|
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem;
|
|
21
23
|
import com.reactnativenavigation.options.BottomTabOptions;
|
|
@@ -35,7 +37,9 @@ import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;
|
|
|
35
37
|
import com.reactnativenavigation.views.bottomtabs.BottomTabs;
|
|
36
38
|
import com.reactnativenavigation.views.bottomtabs.BottomTabsContainer;
|
|
37
39
|
import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout;
|
|
40
|
+
import com.reactnativenavigation.views.bottomtabs.CustomBottomTabItemView;
|
|
38
41
|
|
|
42
|
+
import java.util.ArrayList;
|
|
39
43
|
import java.util.Collection;
|
|
40
44
|
import java.util.Deque;
|
|
41
45
|
import java.util.LinkedList;
|
|
@@ -43,6 +47,8 @@ import java.util.List;
|
|
|
43
47
|
|
|
44
48
|
public class BottomTabsController extends ParentController<BottomTabsLayout> implements AHBottomNavigation.OnTabSelectedListener, TabSelector {
|
|
45
49
|
|
|
50
|
+
private static final String LOG_TAG = "BottomTabsController";
|
|
51
|
+
|
|
46
52
|
private BottomTabsContainer bottomTabsContainer;
|
|
47
53
|
private BottomTabs bottomTabs;
|
|
48
54
|
private final Deque<Integer> selectionStack;
|
|
@@ -52,6 +58,7 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
|
|
|
52
58
|
private final BottomTabsAttacher tabsAttacher;
|
|
53
59
|
private final BottomTabsPresenter presenter;
|
|
54
60
|
private final BottomTabPresenter tabPresenter;
|
|
61
|
+
private boolean useCustomItemViews;
|
|
55
62
|
|
|
56
63
|
public BottomTabsAnimator getAnimator() {
|
|
57
64
|
return presenter.getAnimator();
|
|
@@ -106,13 +113,59 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
|
|
|
106
113
|
bottomTabs.setOnTabSelectedListener(this);
|
|
107
114
|
root.addBottomTabsContainer(bottomTabsContainer);
|
|
108
115
|
|
|
116
|
+
useCustomItemViews = resolveUseCustomItemViews();
|
|
117
|
+
tabPresenter.setUseCustomItemViews(useCustomItemViews);
|
|
118
|
+
|
|
109
119
|
bottomTabs.addItems(createTabs());
|
|
120
|
+
|
|
121
|
+
if (useCustomItemViews) {
|
|
122
|
+
attachCustomItemViewsToCells();
|
|
123
|
+
}
|
|
124
|
+
|
|
110
125
|
setInitialTab(resolveCurrentOptions);
|
|
111
126
|
tabsAttacher.attach();
|
|
112
127
|
|
|
113
128
|
return root;
|
|
114
129
|
}
|
|
115
130
|
|
|
131
|
+
private boolean resolveUseCustomItemViews() {
|
|
132
|
+
if (tabs.isEmpty()) return false;
|
|
133
|
+
int withComponent = 0;
|
|
134
|
+
for (ViewController<?> tab : tabs) {
|
|
135
|
+
BottomTabOptions options = tab.resolveCurrentOptions(initialOptions).bottomTabOptions;
|
|
136
|
+
if (options.component.hasValue()) withComponent++;
|
|
137
|
+
}
|
|
138
|
+
if (withComponent == 0) return false;
|
|
139
|
+
if (withComponent != tabs.size()) {
|
|
140
|
+
Log.w(LOG_TAG,
|
|
141
|
+
"Mixed bottomTab.component usage detected (" + withComponent + " of "
|
|
142
|
+
+ tabs.size() + " tabs). All tabs must declare a component or none — "
|
|
143
|
+
+ "falling back to native rendering for all tabs.");
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private void attachCustomItemViewsToCells() {
|
|
150
|
+
List<CustomBottomTabItemView> overlays = new ArrayList<>();
|
|
151
|
+
int initialIndex = bottomTabs.getCurrentItem();
|
|
152
|
+
for (int i = 0; i < tabs.size(); i++) {
|
|
153
|
+
BottomTabOptions options = tabs.get(i).resolveCurrentOptions(initialOptions).bottomTabOptions;
|
|
154
|
+
String componentId = options.component.componentId.get(tabs.get(i).getId() + "_tab_" + i);
|
|
155
|
+
String componentName = options.component.name.get();
|
|
156
|
+
String badge = options.badge.hasValue() ? options.badge.get() : null;
|
|
157
|
+
CustomBottomTabItemView itemView = new CustomBottomTabItemView(
|
|
158
|
+
getActivity(),
|
|
159
|
+
componentId,
|
|
160
|
+
componentName,
|
|
161
|
+
i,
|
|
162
|
+
i == initialIndex,
|
|
163
|
+
badge);
|
|
164
|
+
overlays.add(itemView);
|
|
165
|
+
}
|
|
166
|
+
bottomTabs.setCustomItemViews(overlays);
|
|
167
|
+
}
|
|
168
|
+
|
|
116
169
|
private void setInitialTab(Options resolveCurrentOptions) {
|
|
117
170
|
int initialTabIndex = 0;
|
|
118
171
|
if (resolveCurrentOptions.bottomTabsOptions.currentTabId.hasValue())
|
|
@@ -121,6 +174,9 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
|
|
|
121
174
|
initialTabIndex = resolveCurrentOptions.bottomTabsOptions.currentTabIndex.get();
|
|
122
175
|
}
|
|
123
176
|
bottomTabs.setCurrentItem(initialTabIndex, false);
|
|
177
|
+
if (useCustomItemViews) {
|
|
178
|
+
bottomTabs.onCustomItemViewSelectionChanged(initialTabIndex);
|
|
179
|
+
}
|
|
124
180
|
}
|
|
125
181
|
|
|
126
182
|
@NonNull
|
|
@@ -294,6 +350,9 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
|
|
|
294
350
|
ViewController<?> previouslyVisible = getCurrentChild();
|
|
295
351
|
bottomTabs.setCurrentItem(newIndex, false);
|
|
296
352
|
getCurrentChild().onSelected(previouslyVisible);
|
|
353
|
+
if (useCustomItemViews) {
|
|
354
|
+
bottomTabs.onCustomItemViewSelectionChanged(newIndex);
|
|
355
|
+
}
|
|
297
356
|
}
|
|
298
357
|
|
|
299
358
|
private void saveTabSelection(int newIndex, boolean enableSelectionHistory) {
|
|
@@ -8,6 +8,8 @@ import android.content.Context;
|
|
|
8
8
|
import android.graphics.Color;
|
|
9
9
|
import android.graphics.drawable.Drawable;
|
|
10
10
|
import android.view.View;
|
|
11
|
+
import android.view.ViewGroup;
|
|
12
|
+
import android.widget.FrameLayout;
|
|
11
13
|
import android.widget.LinearLayout;
|
|
12
14
|
|
|
13
15
|
import androidx.annotation.IntRange;
|
|
@@ -25,6 +27,19 @@ public class BottomTabs extends AHBottomNavigation {
|
|
|
25
27
|
private boolean itemsCreationEnabled = true;
|
|
26
28
|
private boolean shouldCreateItems = true;
|
|
27
29
|
private List<Runnable> onItemCreationEnabled = new ArrayList<>();
|
|
30
|
+
private final List<CustomBottomTabItemView> customItemViews = new ArrayList<>();
|
|
31
|
+
private boolean externalCustomItemViewHost = false;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* When enabled, this view stops re-parenting custom React tab item views
|
|
35
|
+
* into its native cells on every layout pass — the caller assumes full
|
|
36
|
+
* ownership of where those item views live in the view tree (used by
|
|
37
|
+
* the customRow floating-row implementation). Existing behavior is
|
|
38
|
+
* unchanged when this remains {@code false} (the default).
|
|
39
|
+
*/
|
|
40
|
+
public void setExternalCustomItemViewHost(boolean enabled) {
|
|
41
|
+
this.externalCustomItemViewHost = enabled;
|
|
42
|
+
}
|
|
28
43
|
|
|
29
44
|
public BottomTabs(Context context) {
|
|
30
45
|
super(context);
|
|
@@ -131,6 +146,67 @@ public class BottomTabs extends AHBottomNavigation {
|
|
|
131
146
|
if (tabsContainer != null) tabsContainer.setLayoutDirection(direction.get());
|
|
132
147
|
}
|
|
133
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Replace the visual content of every tab cell with the provided custom
|
|
151
|
+
* views. The custom view is attached as a child of the AHBottomNavigation
|
|
152
|
+
* cell view so taps continue to be handled by the native cell. Pass an
|
|
153
|
+
* empty list to remove all overlays.
|
|
154
|
+
*/
|
|
155
|
+
public void setCustomItemViews(List<CustomBottomTabItemView> customViews) {
|
|
156
|
+
clearCustomItemViews();
|
|
157
|
+
if (customViews == null || customViews.isEmpty()) return;
|
|
158
|
+
|
|
159
|
+
customItemViews.addAll(customViews);
|
|
160
|
+
attachCustomItemViews();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public void onCustomItemViewSelectionChanged(int selectedIndex) {
|
|
164
|
+
for (int i = 0; i < customItemViews.size(); i++) {
|
|
165
|
+
customItemViews.get(i).setItemSelected(i == selectedIndex);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public CustomBottomTabItemView getCustomItemView(int index) {
|
|
170
|
+
if (index < 0 || index >= customItemViews.size()) return null;
|
|
171
|
+
return customItemViews.get(index);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public boolean hasCustomItemViews() {
|
|
175
|
+
return !customItemViews.isEmpty();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private void clearCustomItemViews() {
|
|
179
|
+
for (CustomBottomTabItemView view : customItemViews) {
|
|
180
|
+
ViewGroup parent = (ViewGroup) view.getParent();
|
|
181
|
+
if (parent != null) parent.removeView(view);
|
|
182
|
+
}
|
|
183
|
+
customItemViews.clear();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private void attachCustomItemViews() {
|
|
187
|
+
if (externalCustomItemViewHost) return;
|
|
188
|
+
for (int i = 0; i < customItemViews.size(); i++) {
|
|
189
|
+
View cell = getViewAtPosition(i);
|
|
190
|
+
if (!(cell instanceof ViewGroup)) continue;
|
|
191
|
+
CustomBottomTabItemView itemView = customItemViews.get(i);
|
|
192
|
+
ViewGroup parent = (ViewGroup) itemView.getParent();
|
|
193
|
+
if (parent != null && parent != cell) parent.removeView(itemView);
|
|
194
|
+
if (itemView.getParent() == null) {
|
|
195
|
+
((ViewGroup) cell).addView(itemView, new FrameLayout.LayoutParams(
|
|
196
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
197
|
+
FrameLayout.LayoutParams.MATCH_PARENT));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@Override
|
|
203
|
+
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
204
|
+
super.onLayout(changed, l, t, r, b);
|
|
205
|
+
if (changed && !customItemViews.isEmpty()) {
|
|
206
|
+
attachCustomItemViews();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
134
210
|
private boolean hasItemsAndIsMeasured(int w, int h, int oldw, int oldh) {
|
|
135
211
|
return w != 0 && h != 0 && (w != oldw || h != oldh) && getItemsCount() > 0;
|
|
136
212
|
}
|
package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/CustomBottomTabItemView.kt
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package com.reactnativenavigation.views.bottomtabs
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import android.view.MotionEvent
|
|
7
|
+
import android.widget.FrameLayout
|
|
8
|
+
import com.reactnativenavigation.react.ReactView
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hosts a [ReactView] that renders a user-supplied React component as a
|
|
12
|
+
* bottom tab item. The view sits on top of the native AHBottomNavigation tab
|
|
13
|
+
* cell and forwards touches through to the underlying cell so native
|
|
14
|
+
* selection, ripple and `selectTabOnPress: false` keep working.
|
|
15
|
+
*
|
|
16
|
+
* The hosted component receives the following props at creation:
|
|
17
|
+
* `componentId`, `tabIndex`, `selected`, `badge`. Selection updates are
|
|
18
|
+
* pushed via [setSelected]; badge updates via [setBadge].
|
|
19
|
+
*/
|
|
20
|
+
@SuppressLint("ViewConstructor")
|
|
21
|
+
class CustomBottomTabItemView(
|
|
22
|
+
context: Context,
|
|
23
|
+
val componentId: String,
|
|
24
|
+
val componentName: String,
|
|
25
|
+
val tabIndex: Int,
|
|
26
|
+
initialSelected: Boolean,
|
|
27
|
+
initialBadge: String?
|
|
28
|
+
) : FrameLayout(context) {
|
|
29
|
+
|
|
30
|
+
val reactView: ReactView = ReactView(context, componentId, componentName)
|
|
31
|
+
private var isCurrentlySelected: Boolean = initialSelected
|
|
32
|
+
private var badge: String? = initialBadge
|
|
33
|
+
|
|
34
|
+
init {
|
|
35
|
+
addView(reactView)
|
|
36
|
+
reactView.isClickable = false
|
|
37
|
+
reactView.isFocusable = false
|
|
38
|
+
isClickable = false
|
|
39
|
+
isFocusable = false
|
|
40
|
+
pushProps()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Touches must always reach the underlying AHBottomNavigation cell so
|
|
45
|
+
* that native selection, ripple, accessibility focus and
|
|
46
|
+
* `selectTabOnPress: false` keep working. Returning false here makes
|
|
47
|
+
* this view completely transparent to touch input and prevents any
|
|
48
|
+
* `Touchable*` rendered inside the React tree from swallowing taps.
|
|
49
|
+
*/
|
|
50
|
+
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean = false
|
|
51
|
+
|
|
52
|
+
fun setItemSelected(selected: Boolean) {
|
|
53
|
+
if (this.isCurrentlySelected == selected) return
|
|
54
|
+
this.isCurrentlySelected = selected
|
|
55
|
+
pushProps()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fun setBadge(badge: String?) {
|
|
59
|
+
if (this.badge == badge) return
|
|
60
|
+
this.badge = badge
|
|
61
|
+
pushProps()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private fun pushProps() {
|
|
65
|
+
val bundle = Bundle().apply {
|
|
66
|
+
putString("componentId", componentId)
|
|
67
|
+
putInt("tabIndex", tabIndex)
|
|
68
|
+
putBoolean("selected", isCurrentlySelected)
|
|
69
|
+
if (badge != null) putString("badge", badge)
|
|
70
|
+
}
|
|
71
|
+
reactView.setProps(bundle)
|
|
72
|
+
}
|
|
73
|
+
}
|