@soundtrackio/react-native-bottom-tabs 1.4.0
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 +32 -0
- package/android/build.gradle +132 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/rcttabview/ImageSource.kt +86 -0
- package/android/src/main/java/com/rcttabview/LatestRequestTracker.kt +19 -0
- package/android/src/main/java/com/rcttabview/RCTTabView.kt +689 -0
- package/android/src/main/java/com/rcttabview/RCTTabViewManager.kt +222 -0
- package/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt +19 -0
- package/android/src/main/java/com/rcttabview/RCTTabViewPrewarm.kt +40 -0
- package/android/src/main/java/com/rcttabview/RebuildBatch.kt +31 -0
- package/android/src/main/java/com/rcttabview/TabBarVisibility.kt +14 -0
- package/android/src/main/java/com/rcttabview/Utils.kt +31 -0
- package/android/src/main/java/com/rcttabview/events/OnNativeLayoutEvent.kt +25 -0
- package/android/src/main/java/com/rcttabview/events/OnTabBarMeasuredEvent.kt +24 -0
- package/android/src/main/java/com/rcttabview/events/PageSelectedEvent.kt +32 -0
- package/android/src/main/java/com/rcttabview/events/TabLongPressedEvent.kt +24 -0
- package/android/src/test/java/com/rcttabview/LatestRequestTrackerTest.kt +37 -0
- package/android/src/test/java/com/rcttabview/RebuildBatchTest.kt +44 -0
- package/android/src/test/java/com/rcttabview/TabBarVisibilityTest.kt +58 -0
- package/app.plugin.js +1 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h +33 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp +19 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h +35 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.cpp +15 -0
- package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h +25 -0
- package/ios/BottomAccessoryProvider.swift +27 -0
- package/ios/Extensions.swift +101 -0
- package/ios/PlatformAliases.swift +43 -0
- package/ios/RCTBottomAccessoryComponentView.h +17 -0
- package/ios/RCTBottomAccessoryComponentView.mm +76 -0
- package/ios/RCTTabViewComponentView.h +16 -0
- package/ios/RCTTabViewComponentView.mm +285 -0
- package/ios/RepresentableView.swift +55 -0
- package/ios/SVG/CoreSVG.h +19 -0
- package/ios/SVG/CoreSVG.mm +177 -0
- package/ios/SVG/SvgDecoder.h +10 -0
- package/ios/SVG/SvgDecoder.mm +38 -0
- package/ios/TabAppearModifier.swift +38 -0
- package/ios/TabBarFontSize.swift +88 -0
- package/ios/TabItem.swift +35 -0
- package/ios/TabItemEventModifier.swift +163 -0
- package/ios/TabView/AnyTabView.swift +7 -0
- package/ios/TabView/LegacyTabView.swift +70 -0
- package/ios/TabView/NewTabView.swift +138 -0
- package/ios/TabViewImpl.swift +566 -0
- package/ios/TabViewProps.swift +109 -0
- package/ios/TabViewProvider.swift +368 -0
- package/ios/react-native-bottom-tabs-Bridging-Header.h +4 -0
- package/lib/module/BottomAccessoryView.js +47 -0
- package/lib/module/BottomAccessoryView.js.map +1 -0
- package/lib/module/BottomAccessoryViewNativeComponent.ts +27 -0
- package/lib/module/DelayedFreeze.js +31 -0
- package/lib/module/DelayedFreeze.js.map +1 -0
- package/lib/module/NativeSVGDecoder.js +5 -0
- package/lib/module/NativeSVGDecoder.js.map +1 -0
- package/lib/module/SceneMap.js +28 -0
- package/lib/module/SceneMap.js.map +1 -0
- package/lib/module/TabView.js +294 -0
- package/lib/module/TabView.js.map +1 -0
- package/lib/module/TabViewNativeComponent.ts +73 -0
- package/lib/module/expo.js +35 -0
- package/lib/module/expo.js.map +1 -0
- package/lib/module/index.js +20 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/BottomTabBarHeightContext.js +5 -0
- package/lib/module/utils/BottomTabBarHeightContext.js.map +1 -0
- package/lib/module/utils/useBottomTabBarHeight.js +12 -0
- package/lib/module/utils/useBottomTabBarHeight.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/BottomAccessoryView.d.ts +7 -0
- package/lib/typescript/src/BottomAccessoryView.d.ts.map +1 -0
- package/lib/typescript/src/BottomAccessoryView.js +24 -0
- package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts +15 -0
- package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/BottomAccessoryViewNativeComponent.js +4 -0
- package/lib/typescript/src/DelayedFreeze.d.ts +7 -0
- package/lib/typescript/src/DelayedFreeze.d.ts.map +1 -0
- package/lib/typescript/src/DelayedFreeze.js +21 -0
- package/lib/typescript/src/NativeSVGDecoder.d.ts +5 -0
- package/lib/typescript/src/NativeSVGDecoder.d.ts.map +1 -0
- package/lib/typescript/src/NativeSVGDecoder.js +2 -0
- package/lib/typescript/src/SceneMap.d.ts +9 -0
- package/lib/typescript/src/SceneMap.d.ts.map +1 -0
- package/lib/typescript/src/SceneMap.js +15 -0
- package/lib/typescript/src/TabView.d.ts +219 -0
- package/lib/typescript/src/TabView.d.ts.map +1 -0
- package/lib/typescript/src/TabView.js +157 -0
- package/lib/typescript/src/TabViewNativeComponent.d.ts +59 -0
- package/lib/typescript/src/TabViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/TabViewNativeComponent.js +4 -0
- package/lib/typescript/src/expo.d.ts +6 -0
- package/lib/typescript/src/expo.d.ts.map +1 -0
- package/lib/typescript/src/expo.js +34 -0
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/index.js +11 -0
- package/lib/typescript/src/types.d.ts +31 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/types.js +1 -0
- package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts +2 -0
- package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts.map +1 -0
- package/lib/typescript/src/utils/BottomTabBarHeightContext.js +2 -0
- package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts +1 -0
- package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts.map +1 -0
- package/lib/typescript/src/utils/useBottomTabBarHeight.js +9 -0
- package/lib/typescript/tsconfig.tsbuildinfo +1 -0
- package/package.json +135 -0
- package/react-native-bottom-tabs.podspec +43 -0
- package/src/BottomAccessoryView.tsx +59 -0
- package/src/BottomAccessoryViewNativeComponent.ts +27 -0
- package/src/DelayedFreeze.tsx +31 -0
- package/src/NativeSVGDecoder.ts +5 -0
- package/src/SceneMap.tsx +34 -0
- package/src/TabView.tsx +558 -0
- package/src/TabViewNativeComponent.ts +73 -0
- package/src/expo.ts +58 -0
- package/src/index.tsx +23 -0
- package/src/types.ts +36 -0
- package/src/utils/BottomTabBarHeightContext.ts +5 -0
- package/src/utils/useBottomTabBarHeight.ts +15 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
import android.content.res.ColorStateList
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.bridge.ReadableArray
|
|
8
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
11
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
12
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
13
|
+
import com.facebook.react.viewmanagers.RNCTabViewManagerDelegate
|
|
14
|
+
import com.facebook.react.viewmanagers.RNCTabViewManagerInterface
|
|
15
|
+
import com.rcttabview.events.OnNativeLayoutEvent
|
|
16
|
+
import com.rcttabview.events.OnTabBarMeasuredEvent
|
|
17
|
+
import com.rcttabview.events.PageSelectedEvent
|
|
18
|
+
import com.rcttabview.events.TabLongPressEvent
|
|
19
|
+
|
|
20
|
+
data class TabInfo(
|
|
21
|
+
val key: String,
|
|
22
|
+
val title: String,
|
|
23
|
+
val badge: String?,
|
|
24
|
+
val badgeBackgroundColor: Int?,
|
|
25
|
+
val badgeTextColor: Int?,
|
|
26
|
+
val activeTintColor: Int?,
|
|
27
|
+
val iconRenderingMode: String?,
|
|
28
|
+
val hidden: Boolean,
|
|
29
|
+
val testID: String?
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@ReactModule(name = RCTTabViewManager.NAME)
|
|
34
|
+
class RCTTabViewManager(context: ReactApplicationContext) :
|
|
35
|
+
ViewGroupManager<ReactBottomNavigationView>(),
|
|
36
|
+
RNCTabViewManagerInterface<ReactBottomNavigationView> {
|
|
37
|
+
|
|
38
|
+
private val delegate: RNCTabViewManagerDelegate<ReactBottomNavigationView, RCTTabViewManager> =
|
|
39
|
+
RNCTabViewManagerDelegate(this)
|
|
40
|
+
|
|
41
|
+
override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView {
|
|
42
|
+
val view = RCTTabViewPrewarm.take(context) ?: ReactBottomNavigationView(context)
|
|
43
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
44
|
+
view.onTabSelectedListener = { key ->
|
|
45
|
+
eventDispatcher?.dispatchEvent(PageSelectedEvent(viewTag = view.id, key))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
view.onTabLongPressedListener = { key ->
|
|
49
|
+
eventDispatcher?.dispatchEvent(TabLongPressEvent(viewTag = view.id, key))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
view.onNativeLayoutListener = { width, height ->
|
|
53
|
+
eventDispatcher?.dispatchEvent(OnNativeLayoutEvent(viewTag = view.id, width, height))
|
|
54
|
+
}
|
|
55
|
+
view.onTabBarMeasuredListener = { height ->
|
|
56
|
+
eventDispatcher?.dispatchEvent(OnTabBarMeasuredEvent(viewTag = view.id, height))
|
|
57
|
+
}
|
|
58
|
+
return view
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun getName(): String {
|
|
63
|
+
return NAME
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun getChildCount(parent: ReactBottomNavigationView): Int {
|
|
67
|
+
return parent.layoutHolder.childCount ?: 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun getChildAt(parent: ReactBottomNavigationView, index: Int): View? {
|
|
71
|
+
return parent.layoutHolder.getChildAt(index)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override fun removeView(parent: ReactBottomNavigationView, view: View) {
|
|
75
|
+
parent.layoutHolder.removeView(view)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
override fun removeAllViews(parent: ReactBottomNavigationView) {
|
|
79
|
+
parent.layoutHolder.removeAllViews()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
override fun removeViewAt(parent: ReactBottomNavigationView, index: Int) {
|
|
83
|
+
parent.layoutHolder.removeViewAt(index)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override fun needsCustomLayoutForChildren(): Boolean {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override fun setItems(view: ReactBottomNavigationView?, value: ReadableArray?) {
|
|
91
|
+
if (view != null && value != null) {
|
|
92
|
+
val itemsArray = mutableListOf<TabInfo>()
|
|
93
|
+
for (i in 0 until value.size()) {
|
|
94
|
+
value.getMap(i)?.let { item ->
|
|
95
|
+
itemsArray.add(
|
|
96
|
+
TabInfo(
|
|
97
|
+
key = item.getString("key") ?: "",
|
|
98
|
+
title = item.getString("title") ?: "",
|
|
99
|
+
badge = if (item.hasKey("badge")) item.getString("badge") else null,
|
|
100
|
+
badgeBackgroundColor = if (item.hasKey("badgeBackgroundColor")) item.getInt("badgeBackgroundColor") else null,
|
|
101
|
+
badgeTextColor = if (item.hasKey("badgeTextColor")) item.getInt("badgeTextColor") else null,
|
|
102
|
+
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null,
|
|
103
|
+
iconRenderingMode = if (item.hasKey("iconRenderingMode")) item.getString("iconRenderingMode") else null,
|
|
104
|
+
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false,
|
|
105
|
+
testID = item.getString("testID")
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
view.updateItems(itemsArray)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override fun setSelectedPage(view: ReactBottomNavigationView?, value: String?) {
|
|
115
|
+
if (view != null && value != null)
|
|
116
|
+
view.setSelectedItem(value)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
override fun setIcons(view: ReactBottomNavigationView?, value: ReadableArray?) {
|
|
120
|
+
if (view != null)
|
|
121
|
+
view.setIcons(value)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override fun setFocusedIcons(view: ReactBottomNavigationView?, value: ReadableArray?) {
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
override fun setLabeled(view: ReactBottomNavigationView?, value: Boolean) {
|
|
128
|
+
if (view != null)
|
|
129
|
+
view.setLabeled(value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun setRippleColor(view: ReactBottomNavigationView?, value: Int?) {
|
|
133
|
+
if (view != null && value != null) {
|
|
134
|
+
val color = ColorStateList.valueOf(value)
|
|
135
|
+
view.setRippleColor(color)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
override fun setBarTintColor(view: ReactBottomNavigationView?, value: Int?) {
|
|
140
|
+
if (view != null && value != null)
|
|
141
|
+
view.setBarTintColor(value)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
override fun setActiveTintColor(view: ReactBottomNavigationView?, value: Int?) {
|
|
145
|
+
if (view != null && value != null)
|
|
146
|
+
view.setActiveTintColor(value)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
override fun setInactiveTintColor(view: ReactBottomNavigationView?, value: Int?) {
|
|
150
|
+
if (view != null && value != null)
|
|
151
|
+
view.setInactiveTintColor(value)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
override fun setExperimentalBakedTintColors(view: ReactBottomNavigationView?, value: Boolean) {
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
override fun setActiveIndicatorColor(view: ReactBottomNavigationView?, value: Int?) {
|
|
158
|
+
if (view != null && value != null) {
|
|
159
|
+
val color = ColorStateList.valueOf(value)
|
|
160
|
+
view.setActiveIndicatorColor(color)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override fun getDelegate(): ViewManagerDelegate<ReactBottomNavigationView> {
|
|
165
|
+
return delegate
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override fun setHapticFeedbackEnabled(view: ReactBottomNavigationView?, value: Boolean) {
|
|
169
|
+
if (view != null)
|
|
170
|
+
view.isHapticFeedbackEnabled = value
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
override fun setLayoutDirection(view: ReactBottomNavigationView, value: String?) {
|
|
174
|
+
val direction = when (value) {
|
|
175
|
+
"rtl" -> View.LAYOUT_DIRECTION_RTL
|
|
176
|
+
"ltr" -> View.LAYOUT_DIRECTION_LTR
|
|
177
|
+
else -> View.LAYOUT_DIRECTION_LOCALE
|
|
178
|
+
}
|
|
179
|
+
view.applyDirection(direction)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
override fun setFontFamily(view: ReactBottomNavigationView?, value: String?) {
|
|
183
|
+
view?.setFontFamily(value)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
override fun setFontWeight(view: ReactBottomNavigationView?, value: String?) {
|
|
187
|
+
view?.setFontWeight(value)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
override fun setFontSize(view: ReactBottomNavigationView?, value: Int) {
|
|
191
|
+
view?.setFontSize(value)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
override fun setDisablePageAnimations(view: ReactBottomNavigationView?, value: Boolean) {
|
|
195
|
+
view?.disablePageAnimations = value
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
override fun setTabBarHidden(view: ReactBottomNavigationView?, value: Boolean) {
|
|
199
|
+
view?.setTabBarHidden(value)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
override fun setTabBarHideOnKeyboard(view: ReactBottomNavigationView?, value: Boolean) {
|
|
203
|
+
view?.setTabBarHideOnKeyboard(value)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// iOS Methods
|
|
207
|
+
override fun setTranslucent(view: ReactBottomNavigationView?, value: Boolean) {
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
override fun setSidebarAdaptable(view: ReactBottomNavigationView?, value: Boolean) {
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
override fun setScrollEdgeAppearance(view: ReactBottomNavigationView?, value: String?) {
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
override fun setMinimizeBehavior(view: ReactBottomNavigationView?, value: String?) {
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
companion object {
|
|
220
|
+
const val NAME = "RNCTabView"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
import java.util.ArrayList
|
|
8
|
+
|
|
9
|
+
class RCTTabViewPackage : ReactPackage {
|
|
10
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
11
|
+
val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
|
|
12
|
+
viewManagers.add(RCTTabViewManager(reactContext))
|
|
13
|
+
return viewManagers
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
17
|
+
return emptyList()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import java.lang.ref.WeakReference
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Builds a tab bar for an Activity ahead of time, while the main thread is idle during startup,
|
|
10
|
+
* so mounting the real one does not pay Material's inflation inside the frame that shows the
|
|
11
|
+
* first screen. Call [prewarm] from the Activity, e.g. posted right after `onCreate`:
|
|
12
|
+
*
|
|
13
|
+
* ```kotlin
|
|
14
|
+
* Handler(Looper.getMainLooper()).post { RCTTabViewPrewarm.prewarm(this) }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
object RCTTabViewPrewarm {
|
|
18
|
+
private var activity: WeakReference<Activity>? = null
|
|
19
|
+
private var view: ReactBottomNavigationView? = null
|
|
20
|
+
|
|
21
|
+
/** Call on the main thread; [itemCount] placeholder tabs are inflated up front. */
|
|
22
|
+
@JvmStatic
|
|
23
|
+
@JvmOverloads
|
|
24
|
+
fun prewarm(activity: Activity, itemCount: Int = 5) {
|
|
25
|
+
check(Looper.myLooper() == Looper.getMainLooper()) { "prewarm must run on the main thread" }
|
|
26
|
+
val prebuilt = ReactBottomNavigationView(activity)
|
|
27
|
+
prebuilt.prewarmItems(itemCount)
|
|
28
|
+
this.activity = WeakReference(activity)
|
|
29
|
+
view = prebuilt
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The prebuilt view for [context]'s Activity, handed out at most once. */
|
|
33
|
+
internal fun take(context: ThemedReactContext): ReactBottomNavigationView? {
|
|
34
|
+
val prebuilt = view ?: return null
|
|
35
|
+
view = null
|
|
36
|
+
val owner = activity?.get()
|
|
37
|
+
activity = null
|
|
38
|
+
return if (owner != null && owner === context.currentActivity) prebuilt else null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Holds back rebuilds requested while a batch of changes is applied, so a rebuild that
|
|
5
|
+
* would otherwise run after every change runs once when the batch ends.
|
|
6
|
+
*/
|
|
7
|
+
class RebuildBatch {
|
|
8
|
+
private var batching = false
|
|
9
|
+
private var pending = false
|
|
10
|
+
|
|
11
|
+
/** Whether a rebuild requested now should run. Otherwise it runs when the batch ends. */
|
|
12
|
+
fun shouldRebuildNow(): Boolean {
|
|
13
|
+
if (batching) {
|
|
14
|
+
pending = true
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fun begin() {
|
|
21
|
+
batching = true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Ends the batch. True when a rebuild was held back and has to run now. */
|
|
25
|
+
fun end(): Boolean {
|
|
26
|
+
batching = false
|
|
27
|
+
val rebuild = pending
|
|
28
|
+
pending = false
|
|
29
|
+
return rebuild
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Combines the reasons the tab bar can be hidden: the `tabBarHidden` prop and, when
|
|
5
|
+
* `tabBarHideOnKeyboard` is set, a visible keyboard.
|
|
6
|
+
*/
|
|
7
|
+
class TabBarVisibility {
|
|
8
|
+
var hidden = false
|
|
9
|
+
var hideOnKeyboard = false
|
|
10
|
+
var keyboardVisible = false
|
|
11
|
+
|
|
12
|
+
val isVisible: Boolean
|
|
13
|
+
get() = !hidden && !(hideOnKeyboard && keyboardVisible)
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Typeface
|
|
5
|
+
import android.util.TypedValue
|
|
6
|
+
import androidx.appcompat.content.res.AppCompatResources
|
|
7
|
+
|
|
8
|
+
class Utils {
|
|
9
|
+
companion object {
|
|
10
|
+
fun convertPixelsToDp(context: Context, value: Int): Double {
|
|
11
|
+
val displayDensity = context.resources.displayMetrics.density
|
|
12
|
+
return (value / displayDensity).toDouble()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fun getTypefaceStyle(weight: Int?) = when (weight) {
|
|
16
|
+
700 -> Typeface.BOLD
|
|
17
|
+
else -> Typeface.NORMAL
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fun getDefaultColorFor(context: Context, baseColorThemeAttr: Int): Int? {
|
|
21
|
+
val value = TypedValue()
|
|
22
|
+
if (!context.theme.resolveAttribute(baseColorThemeAttr, value, true)) {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
val baseColor = AppCompatResources.getColorStateList(
|
|
26
|
+
context, value.resourceId
|
|
27
|
+
)
|
|
28
|
+
return baseColor.defaultColor
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.rcttabview.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.uimanager.events.Event
|
|
5
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
6
|
+
|
|
7
|
+
class OnNativeLayoutEvent(viewTag: Int, private val width: Double, private val height: Double) :
|
|
8
|
+
Event<TabLongPressEvent>(viewTag) {
|
|
9
|
+
|
|
10
|
+
companion object {
|
|
11
|
+
const val EVENT_NAME = "onNativeLayout"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun getEventName(): String {
|
|
15
|
+
return EVENT_NAME
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
|
19
|
+
val event = Arguments.createMap().apply {
|
|
20
|
+
putDouble("width", width)
|
|
21
|
+
putDouble("height", height)
|
|
22
|
+
}
|
|
23
|
+
rctEventEmitter.receiveEvent(viewTag, eventName, event)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.rcttabview.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.uimanager.events.Event
|
|
5
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
6
|
+
|
|
7
|
+
class OnTabBarMeasuredEvent(viewTag: Int, private val height: Int) :
|
|
8
|
+
Event<TabLongPressEvent>(viewTag) {
|
|
9
|
+
|
|
10
|
+
companion object {
|
|
11
|
+
const val EVENT_NAME = "onTabBarMeasured"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun getEventName(): String {
|
|
15
|
+
return EVENT_NAME
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
|
19
|
+
val event = Arguments.createMap().apply {
|
|
20
|
+
putInt("height", height)
|
|
21
|
+
}
|
|
22
|
+
rctEventEmitter.receiveEvent(viewTag, eventName, event)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package com.rcttabview.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageSelectedEvent(viewTag: Int, private val key: String) : Event<PageSelectedEvent>(viewTag) {
|
|
10
|
+
override fun getEventName(): String {
|
|
11
|
+
return EVENT_NAME
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun canCoalesce(): Boolean {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
|
19
|
+
rctEventEmitter.receiveEvent(viewTag, eventName, serializeEventData())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private fun serializeEventData(): WritableMap {
|
|
23
|
+
val eventData = Arguments.createMap()
|
|
24
|
+
eventData.putString("key", key)
|
|
25
|
+
return eventData
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
companion object {
|
|
29
|
+
const val EVENT_NAME = "topPageSelected"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.rcttabview.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.uimanager.events.Event
|
|
5
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
6
|
+
|
|
7
|
+
class TabLongPressEvent(viewTag: Int, private val key: String) :
|
|
8
|
+
Event<TabLongPressEvent>(viewTag) {
|
|
9
|
+
|
|
10
|
+
companion object {
|
|
11
|
+
const val EVENT_NAME = "onTabLongPress"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun getEventName(): String {
|
|
15
|
+
return EVENT_NAME
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
|
|
19
|
+
val event = Arguments.createMap().apply {
|
|
20
|
+
putString("key", key)
|
|
21
|
+
}
|
|
22
|
+
rctEventEmitter.receiveEvent(viewTag, eventName, event)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertFalse
|
|
4
|
+
import org.junit.Assert.assertTrue
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class LatestRequestTrackerTest {
|
|
8
|
+
private val tracker = LatestRequestTracker<Int>()
|
|
9
|
+
|
|
10
|
+
@Test
|
|
11
|
+
fun `the only request for a key is the latest`() {
|
|
12
|
+
val id = tracker.begin(0)
|
|
13
|
+
assertTrue(tracker.isLatest(0, id))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun `a newer request supersedes an older one for the same key`() {
|
|
18
|
+
val first = tracker.begin(0)
|
|
19
|
+
val second = tracker.begin(0)
|
|
20
|
+
assertFalse(tracker.isLatest(0, first))
|
|
21
|
+
assertTrue(tracker.isLatest(0, second))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Test
|
|
25
|
+
fun `requests for other keys do not supersede each other`() {
|
|
26
|
+
val home = tracker.begin(0)
|
|
27
|
+
val search = tracker.begin(1)
|
|
28
|
+
assertTrue(tracker.isLatest(0, home))
|
|
29
|
+
assertTrue(tracker.isLatest(1, search))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
fun `an id is never the latest for a key it was not issued for`() {
|
|
34
|
+
val home = tracker.begin(0)
|
|
35
|
+
assertFalse(tracker.isLatest(1, home))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertFalse
|
|
4
|
+
import org.junit.Assert.assertTrue
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class RebuildBatchTest {
|
|
8
|
+
private val batch = RebuildBatch()
|
|
9
|
+
|
|
10
|
+
@Test
|
|
11
|
+
fun `rebuilds run at once outside a batch`() {
|
|
12
|
+
assertTrue(batch.shouldRebuildNow())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Test
|
|
16
|
+
fun `rebuilds requested inside a batch are held back`() {
|
|
17
|
+
batch.begin()
|
|
18
|
+
assertFalse(batch.shouldRebuildNow())
|
|
19
|
+
assertFalse(batch.shouldRebuildNow())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Test
|
|
23
|
+
fun `ending a batch releases one rebuild for any number held back`() {
|
|
24
|
+
batch.begin()
|
|
25
|
+
batch.shouldRebuildNow()
|
|
26
|
+
batch.shouldRebuildNow()
|
|
27
|
+
assertTrue(batch.end())
|
|
28
|
+
assertFalse(batch.end())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test
|
|
32
|
+
fun `ending a batch without requests releases nothing`() {
|
|
33
|
+
batch.begin()
|
|
34
|
+
assertFalse(batch.end())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Test
|
|
38
|
+
fun `rebuilds run at once again after a batch`() {
|
|
39
|
+
batch.begin()
|
|
40
|
+
batch.shouldRebuildNow()
|
|
41
|
+
batch.end()
|
|
42
|
+
assertTrue(batch.shouldRebuildNow())
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package com.rcttabview
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertFalse
|
|
4
|
+
import org.junit.Assert.assertTrue
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class TabBarVisibilityTest {
|
|
8
|
+
private val visibility = TabBarVisibility()
|
|
9
|
+
|
|
10
|
+
@Test
|
|
11
|
+
fun `is visible by default`() {
|
|
12
|
+
assertTrue(visibility.isVisible)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Test
|
|
16
|
+
fun `tabBarHidden hides the bar`() {
|
|
17
|
+
visibility.hidden = true
|
|
18
|
+
assertFalse(visibility.isVisible)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Test
|
|
22
|
+
fun `keyboard alone does not hide the bar`() {
|
|
23
|
+
visibility.keyboardVisible = true
|
|
24
|
+
assertTrue(visibility.isVisible)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Test
|
|
28
|
+
fun `keyboard hides the bar when hideOnKeyboard is set`() {
|
|
29
|
+
visibility.hideOnKeyboard = true
|
|
30
|
+
visibility.keyboardVisible = true
|
|
31
|
+
assertFalse(visibility.isVisible)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Test
|
|
35
|
+
fun `bar returns when the keyboard is dismissed`() {
|
|
36
|
+
visibility.hideOnKeyboard = true
|
|
37
|
+
visibility.keyboardVisible = true
|
|
38
|
+
visibility.keyboardVisible = false
|
|
39
|
+
assertTrue(visibility.isVisible)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Test
|
|
43
|
+
fun `disabling hideOnKeyboard restores the bar while the keyboard is shown`() {
|
|
44
|
+
visibility.hideOnKeyboard = true
|
|
45
|
+
visibility.keyboardVisible = true
|
|
46
|
+
visibility.hideOnKeyboard = false
|
|
47
|
+
assertTrue(visibility.isVisible)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
fun `tabBarHidden keeps the bar hidden after the keyboard is dismissed`() {
|
|
52
|
+
visibility.hidden = true
|
|
53
|
+
visibility.hideOnKeyboard = true
|
|
54
|
+
visibility.keyboardVisible = true
|
|
55
|
+
visibility.keyboardVisible = false
|
|
56
|
+
assertFalse(visibility.isVisible)
|
|
57
|
+
}
|
|
58
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./lib/module/expo');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#ifdef __cplusplus
|
|
2
|
+
|
|
3
|
+
#pragma once
|
|
4
|
+
|
|
5
|
+
#include <react/renderer/components/RNCTabView/RNCTabViewShadowNode.h>
|
|
6
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
7
|
+
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
|
8
|
+
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
|
|
11
|
+
class RNCTabViewComponentDescriptor final
|
|
12
|
+
: public ConcreteComponentDescriptor<RNCTabViewShadowNode> {
|
|
13
|
+
public:
|
|
14
|
+
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
|
|
15
|
+
|
|
16
|
+
void adopt(ShadowNode& shadowNode) const override {
|
|
17
|
+
ConcreteComponentDescriptor::adopt(shadowNode);
|
|
18
|
+
|
|
19
|
+
#if !defined(ANDROID)
|
|
20
|
+
auto &tabViewShadowNode =
|
|
21
|
+
static_cast<RNCTabViewShadowNode&>(shadowNode);
|
|
22
|
+
|
|
23
|
+
std::weak_ptr<void> imageLoader =
|
|
24
|
+
contextContainer_->at<std::shared_ptr<void>>("RCTImageLoader");
|
|
25
|
+
tabViewShadowNode.setImageLoader(imageLoader);
|
|
26
|
+
#endif
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
} // namespace facebook::react
|
|
32
|
+
|
|
33
|
+
#endif
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#include <react/renderer/components/RNCTabView/RNCTabViewShadowNode.h>
|
|
2
|
+
|
|
3
|
+
namespace facebook::react {
|
|
4
|
+
|
|
5
|
+
extern const char RNCTabViewComponentName[] = "RNCTabView";
|
|
6
|
+
|
|
7
|
+
void RNCTabViewShadowNode::setImageLoader(
|
|
8
|
+
std::weak_ptr<void> imageLoader) {
|
|
9
|
+
getStateDataMutable().setImageLoader(imageLoader);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
RNCTabViewShadowNode::StateData &
|
|
13
|
+
RNCTabViewShadowNode::getStateDataMutable() {
|
|
14
|
+
ensureUnsealed();
|
|
15
|
+
return const_cast<RNCTabViewShadowNode::StateData &>(getStateData());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
} // namespace facebook::react
|