react-native-screens 3.9.0 → 3.11.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/LICENSE +1 -1
- package/README.md +47 -7
- package/android/build.gradle +1 -2
- package/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt +71 -0
- package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +7 -0
- package/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt +29 -0
- package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -1
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +35 -52
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +1 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +83 -34
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +38 -33
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +77 -42
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +25 -9
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +8 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +7 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt +1 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +10 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +72 -11
- package/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt +107 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +155 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt +67 -0
- package/android/src/main/res/anim/rns_default_enter_in.xml +18 -0
- package/android/src/main/res/anim/rns_default_enter_out.xml +19 -0
- package/android/src/main/res/anim/rns_default_exit_in.xml +17 -0
- package/android/src/main/res/anim/rns_default_exit_out.xml +18 -0
- package/android/src/main/res/anim/rns_fade_in.xml +7 -0
- package/android/src/main/res/anim/rns_fade_out.xml +7 -0
- package/android/src/main/res/anim/rns_no_animation_20.xml +6 -0
- package/createNativeStackNavigator/README.md +12 -0
- package/ios/RNSScreen.h +10 -0
- package/ios/RNSScreen.m +38 -0
- package/ios/RNSScreenContainer.m +5 -0
- package/ios/RNSScreenStack.m +29 -13
- package/ios/RNSScreenStackAnimator.m +45 -14
- package/ios/RNSScreenStackHeaderConfig.m +4 -1
- package/ios/RNSScreenWindowTraits.h +1 -0
- package/ios/RNSScreenWindowTraits.m +20 -0
- package/ios/UIViewController+RNScreens.m +10 -0
- package/lib/commonjs/index.js +17 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.native.js +66 -18
- package/lib/commonjs/index.native.js.map +1 -1
- package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +67 -0
- package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -0
- package/lib/commonjs/native-stack/views/HeaderConfig.js +46 -4
- package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
- package/lib/commonjs/native-stack/views/NativeStackView.js +33 -4
- package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
- package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +60 -0
- package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
- package/lib/commonjs/reanimated/ReanimatedScreen.js +7 -79
- package/lib/commonjs/reanimated/ReanimatedScreen.js.map +1 -1
- package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +61 -0
- package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -0
- package/lib/commonjs/reanimated/index.js +2 -2
- package/lib/commonjs/reanimated/index.js.map +1 -1
- package/lib/commonjs/utils.js +20 -0
- package/lib/commonjs/utils.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.native.js +65 -19
- package/lib/module/index.native.js.map +1 -1
- package/lib/module/native-stack/utils/useBackPressSubscription.js +50 -0
- package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -0
- package/lib/module/native-stack/views/HeaderConfig.js +46 -5
- package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
- package/lib/module/native-stack/views/NativeStackView.js +33 -4
- package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
- package/lib/module/reanimated/ReanimatedNativeStackScreen.js +40 -0
- package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
- package/lib/module/reanimated/ReanimatedScreen.js +6 -73
- package/lib/module/reanimated/ReanimatedScreen.js.map +1 -1
- package/lib/module/reanimated/ReanimatedScreenProvider.js +49 -0
- package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -0
- package/lib/module/reanimated/index.js +1 -1
- package/lib/module/reanimated/index.js.map +1 -1
- package/lib/module/utils.js +8 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/native-stack/types.d.ts +34 -2
- package/lib/typescript/native-stack/utils/useBackPressSubscription.d.ts +16 -0
- package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +5 -0
- package/lib/typescript/reanimated/ReanimatedScreen.d.ts +5 -2
- package/lib/typescript/reanimated/ReanimatedScreenProvider.d.ts +2 -0
- package/lib/typescript/reanimated/index.d.ts +1 -1
- package/lib/typescript/types.d.ts +101 -1
- package/lib/typescript/utils.d.ts +2 -0
- package/native-stack/README.md +70 -8
- package/package.json +2 -1
- package/reanimated/package.json +6 -0
- package/src/index.native.tsx +94 -36
- package/src/index.tsx +4 -0
- package/src/native-stack/types.tsx +34 -2
- package/src/native-stack/utils/useBackPressSubscription.tsx +66 -0
- package/src/native-stack/views/HeaderConfig.tsx +46 -3
- package/src/native-stack/views/NativeStackView.tsx +33 -4
- package/src/reanimated/ReanimatedNativeStackScreen.tsx +61 -0
- package/src/reanimated/ReanimatedScreen.tsx +6 -84
- package/src/reanimated/ReanimatedScreenProvider.tsx +42 -0
- package/src/reanimated/index.tsx +1 -1
- package/src/types.tsx +101 -1
- package/src/utils.ts +12 -0
|
@@ -3,7 +3,6 @@ package com.swmansion.rnscreens
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Canvas
|
|
5
5
|
import android.view.View
|
|
6
|
-
import androidx.fragment.app.FragmentTransaction
|
|
7
6
|
import com.facebook.react.bridge.ReactContext
|
|
8
7
|
import com.facebook.react.uimanager.UIManagerModule
|
|
9
8
|
import com.swmansion.rnscreens.Screen.StackAnimation
|
|
@@ -110,7 +109,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
var shouldUseOpenAnimation = true
|
|
113
|
-
var transition = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
|
|
114
112
|
var stackAnimation: StackAnimation? = null
|
|
115
113
|
if (!mStack.contains(newTop)) {
|
|
116
114
|
// if new top screen wasn't on stack we do "open animation" so long it is not the very first
|
|
@@ -121,21 +119,15 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
121
119
|
// before, probably replace or reset was called, so we play the "close animation".
|
|
122
120
|
// Otherwise it's open animation
|
|
123
121
|
val containsTopScreen = mTopScreen?.let { mScreenFragments.contains(it) } == true
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
val isPushReplace = newTop.screen.replaceAnimation === Screen.ReplaceAnimation.PUSH
|
|
123
|
+
shouldUseOpenAnimation = containsTopScreen || isPushReplace
|
|
124
|
+
// if the replace animation is `push`, the new top screen provides the animation, otherwise the previous one
|
|
125
|
+
stackAnimation = if (shouldUseOpenAnimation) newTop.screen.stackAnimation else mTopScreen?.screen?.stackAnimation
|
|
126
126
|
} else if (mTopScreen == null && newTop != null) {
|
|
127
127
|
// mTopScreen was not present before so newTop is the first screen added to a stack
|
|
128
|
-
// and we don't want the animation when it is entering
|
|
129
|
-
// willAppear and Appear events to the user, which won't be sent by default if Screen's
|
|
130
|
-
// stack animation is not NONE (see check for stackAnimation in onCreateAnimation in
|
|
131
|
-
// ScreenStackFragment).
|
|
132
|
-
// We don't do it if the stack is nested since the parent will trigger these events in child
|
|
128
|
+
// and we don't want the animation when it is entering
|
|
133
129
|
stackAnimation = StackAnimation.NONE
|
|
134
|
-
|
|
135
|
-
goingForward = true
|
|
136
|
-
newTop.dispatchOnWillAppear()
|
|
137
|
-
newTop.dispatchOnAppear()
|
|
138
|
-
}
|
|
130
|
+
goingForward = true
|
|
139
131
|
}
|
|
140
132
|
} else if (mTopScreen != null && mTopScreen != newTop) {
|
|
141
133
|
// otherwise if we are performing top screen change we do "close animation"
|
|
@@ -147,40 +139,32 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
147
139
|
// animation logic start
|
|
148
140
|
if (stackAnimation != null) {
|
|
149
141
|
if (shouldUseOpenAnimation) {
|
|
150
|
-
transition = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
|
|
151
142
|
when (stackAnimation) {
|
|
143
|
+
StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_enter_in, R.anim.rns_default_enter_out)
|
|
144
|
+
StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20)
|
|
145
|
+
StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out)
|
|
152
146
|
StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left)
|
|
153
147
|
StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right)
|
|
154
148
|
StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations(
|
|
155
149
|
R.anim.rns_slide_in_from_bottom, R.anim.rns_no_animation_medium
|
|
156
150
|
)
|
|
157
151
|
StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_fade_from_bottom, R.anim.rns_no_animation_350)
|
|
158
|
-
else -> {
|
|
159
|
-
}
|
|
160
152
|
}
|
|
161
153
|
} else {
|
|
162
|
-
transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE
|
|
163
154
|
when (stackAnimation) {
|
|
155
|
+
StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_exit_in, R.anim.rns_default_exit_out)
|
|
156
|
+
StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20)
|
|
157
|
+
StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out)
|
|
164
158
|
StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right)
|
|
165
159
|
StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left)
|
|
166
160
|
StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations(
|
|
167
161
|
R.anim.rns_no_animation_medium, R.anim.rns_slide_out_to_bottom
|
|
168
162
|
)
|
|
169
163
|
StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_no_animation_250, R.anim.rns_fade_to_bottom)
|
|
170
|
-
else -> {
|
|
171
|
-
}
|
|
172
164
|
}
|
|
173
165
|
}
|
|
174
166
|
}
|
|
175
|
-
|
|
176
|
-
transition = FragmentTransaction.TRANSIT_NONE
|
|
177
|
-
}
|
|
178
|
-
if (stackAnimation === StackAnimation.FADE) {
|
|
179
|
-
transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE
|
|
180
|
-
}
|
|
181
|
-
if (stackAnimation != null && isSystemAnimation(stackAnimation)) {
|
|
182
|
-
it.setTransition(transition)
|
|
183
|
-
}
|
|
167
|
+
|
|
184
168
|
// animation logic end
|
|
185
169
|
goingForward = shouldUseOpenAnimation
|
|
186
170
|
|
|
@@ -236,10 +220,35 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
236
220
|
mTopScreen = newTop
|
|
237
221
|
mStack.clear()
|
|
238
222
|
mStack.addAll(mScreenFragments)
|
|
223
|
+
|
|
224
|
+
turnOffA11yUnderTransparentScreen(visibleBottom)
|
|
225
|
+
|
|
239
226
|
it.commitNowAllowingStateLoss()
|
|
240
227
|
}
|
|
241
228
|
}
|
|
242
229
|
|
|
230
|
+
// only top visible screen should be accessible
|
|
231
|
+
private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenStackFragment?) {
|
|
232
|
+
if (mScreenFragments.size > 1 && visibleBottom != null) {
|
|
233
|
+
mTopScreen?.let {
|
|
234
|
+
if (isTransparent(it)) {
|
|
235
|
+
val screenFragmentsBeneathTop = mScreenFragments.slice(0 until mScreenFragments.size - 1).asReversed()
|
|
236
|
+
// go from the top of the stack excluding the top screen
|
|
237
|
+
for (screenFragment in screenFragmentsBeneathTop) {
|
|
238
|
+
screenFragment.screen.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
|
|
239
|
+
|
|
240
|
+
// don't change a11y below non-transparent screens
|
|
241
|
+
if (screenFragment == visibleBottom) {
|
|
242
|
+
break
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
topScreen?.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_AUTO)
|
|
250
|
+
}
|
|
251
|
+
|
|
243
252
|
override fun notifyContainerUpdate() {
|
|
244
253
|
for (screen in mStack) {
|
|
245
254
|
screen.onContainerUpdate()
|
|
@@ -321,10 +330,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
321
330
|
}
|
|
322
331
|
|
|
323
332
|
companion object {
|
|
324
|
-
private fun isSystemAnimation(stackAnimation: StackAnimation): Boolean {
|
|
325
|
-
return stackAnimation === StackAnimation.DEFAULT || stackAnimation === StackAnimation.FADE || stackAnimation === StackAnimation.NONE
|
|
326
|
-
}
|
|
327
|
-
|
|
328
333
|
private fun isTransparent(fragment: ScreenStackFragment): Boolean {
|
|
329
334
|
return (
|
|
330
335
|
fragment.screen.stackPresentation
|
|
@@ -5,6 +5,9 @@ import android.content.Context
|
|
|
5
5
|
import android.graphics.Color
|
|
6
6
|
import android.os.Bundle
|
|
7
7
|
import android.view.LayoutInflater
|
|
8
|
+
import android.view.Menu
|
|
9
|
+
import android.view.MenuInflater
|
|
10
|
+
import android.view.MenuItem
|
|
8
11
|
import android.view.View
|
|
9
12
|
import android.view.ViewGroup
|
|
10
13
|
import android.view.animation.Animation
|
|
@@ -13,7 +16,6 @@ import android.view.animation.Transformation
|
|
|
13
16
|
import android.widget.LinearLayout
|
|
14
17
|
import androidx.appcompat.widget.Toolbar
|
|
15
18
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
16
|
-
import com.facebook.react.bridge.UiThreadUtil
|
|
17
19
|
import com.facebook.react.uimanager.PixelUtil
|
|
18
20
|
import com.google.android.material.appbar.AppBarLayout
|
|
19
21
|
import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
|
|
@@ -24,6 +26,9 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
24
26
|
private var mShadowHidden = false
|
|
25
27
|
private var mIsTranslucent = false
|
|
26
28
|
|
|
29
|
+
var searchView: CustomSearchView? = null
|
|
30
|
+
var onSearchViewCreate: ((searchView: CustomSearchView) -> Unit)? = null
|
|
31
|
+
|
|
27
32
|
@SuppressLint("ValidFragment")
|
|
28
33
|
constructor(screenView: Screen) : super(screenView)
|
|
29
34
|
|
|
@@ -64,7 +69,8 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
64
69
|
fun setToolbarTranslucent(translucent: Boolean) {
|
|
65
70
|
if (mIsTranslucent != translucent) {
|
|
66
71
|
val params = screen.layoutParams
|
|
67
|
-
(params as CoordinatorLayout.LayoutParams).behavior =
|
|
72
|
+
(params as CoordinatorLayout.LayoutParams).behavior =
|
|
73
|
+
if (translucent) null else ScrollingViewBehavior()
|
|
68
74
|
mIsTranslucent = translucent
|
|
69
75
|
}
|
|
70
76
|
}
|
|
@@ -79,35 +85,6 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
79
85
|
notifyViewAppearTransitionEnd()
|
|
80
86
|
}
|
|
81
87
|
|
|
82
|
-
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
|
|
83
|
-
// this means that the fragment will appear with a custom transition, in the case
|
|
84
|
-
// of animation: 'none', onViewAnimationStart and onViewAnimationEnd
|
|
85
|
-
// won't be called and we need to notify stack directly from here.
|
|
86
|
-
// When using the Toolbar back button this is called an extra time with transit = 0 but in
|
|
87
|
-
// this case we don't want to notify. The way I found to detect is case is check isHidden.
|
|
88
|
-
if (transit == 0 && !isHidden &&
|
|
89
|
-
screen.stackAnimation === Screen.StackAnimation.NONE
|
|
90
|
-
) {
|
|
91
|
-
if (enter) {
|
|
92
|
-
// Android dispatches the animation start event for the fragment that is being added first
|
|
93
|
-
// however we want the one being dismissed first to match iOS. It also makes more sense
|
|
94
|
-
// from a navigation point of view to have the disappear event first.
|
|
95
|
-
// Since there are no explicit relationships between the fragment being added / removed
|
|
96
|
-
// the practical way to fix this is delaying dispatching the appear events at the end of
|
|
97
|
-
// the frame.
|
|
98
|
-
UiThreadUtil.runOnUiThread {
|
|
99
|
-
dispatchOnWillAppear()
|
|
100
|
-
dispatchOnAppear()
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
dispatchOnWillDisappear()
|
|
104
|
-
dispatchOnDisappear()
|
|
105
|
-
notifyViewAppearTransitionEnd()
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return null
|
|
109
|
-
}
|
|
110
|
-
|
|
111
88
|
private fun notifyViewAppearTransitionEnd() {
|
|
112
89
|
val screenStack = view?.parent
|
|
113
90
|
if (screenStack is ScreenStack) {
|
|
@@ -120,7 +97,8 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
120
97
|
container: ViewGroup?,
|
|
121
98
|
savedInstanceState: Bundle?
|
|
122
99
|
): View? {
|
|
123
|
-
val view:
|
|
100
|
+
val view: ScreensCoordinatorLayout? =
|
|
101
|
+
context?.let { ScreensCoordinatorLayout(it, this) }
|
|
124
102
|
val params = CoordinatorLayout.LayoutParams(
|
|
125
103
|
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT
|
|
126
104
|
)
|
|
@@ -142,9 +120,49 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
142
120
|
mAppBarLayout?.targetElevation = 0f
|
|
143
121
|
}
|
|
144
122
|
mToolbar?.let { mAppBarLayout?.addView(recycleView(it)) }
|
|
123
|
+
setHasOptionsMenu(true)
|
|
145
124
|
return view
|
|
146
125
|
}
|
|
147
126
|
|
|
127
|
+
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
128
|
+
updateToolbarMenu(menu)
|
|
129
|
+
return super.onPrepareOptionsMenu(menu)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
133
|
+
updateToolbarMenu(menu)
|
|
134
|
+
return super.onCreateOptionsMenu(menu, inflater)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private fun shouldShowSearchBar(): Boolean {
|
|
138
|
+
val config = screen.headerConfig
|
|
139
|
+
val numberOfSubViews = config?.configSubviewsCount ?: 0
|
|
140
|
+
if (config != null && numberOfSubViews > 0) {
|
|
141
|
+
for (i in 0 until numberOfSubViews) {
|
|
142
|
+
val subView = config.getConfigSubview(i)
|
|
143
|
+
if (subView.type == ScreenStackHeaderSubview.Type.SEARCH_BAR) {
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun updateToolbarMenu(menu: Menu) {
|
|
152
|
+
menu.clear()
|
|
153
|
+
if (shouldShowSearchBar()) {
|
|
154
|
+
val currentContext = context
|
|
155
|
+
if (searchView == null && currentContext != null) {
|
|
156
|
+
val newSearchView = CustomSearchView(currentContext, this)
|
|
157
|
+
searchView = newSearchView
|
|
158
|
+
onSearchViewCreate?.invoke(newSearchView)
|
|
159
|
+
}
|
|
160
|
+
val item = menu.add("")
|
|
161
|
+
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
|
162
|
+
item.actionView = searchView
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
148
166
|
fun canNavigateBack(): Boolean {
|
|
149
167
|
val container: ScreenContainer<*>? = screen.container
|
|
150
168
|
check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
|
|
@@ -168,18 +186,22 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
168
186
|
container.dismiss(this)
|
|
169
187
|
}
|
|
170
188
|
|
|
171
|
-
private class
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
189
|
+
private class ScreensCoordinatorLayout(
|
|
190
|
+
context: Context,
|
|
191
|
+
private val mFragment: ScreenFragment
|
|
192
|
+
) : CoordinatorLayout(context) {
|
|
193
|
+
private val mAnimationListener: Animation.AnimationListener =
|
|
194
|
+
object : Animation.AnimationListener {
|
|
195
|
+
override fun onAnimationStart(animation: Animation) {
|
|
196
|
+
mFragment.onViewAnimationStart()
|
|
197
|
+
}
|
|
176
198
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
199
|
+
override fun onAnimationEnd(animation: Animation) {
|
|
200
|
+
mFragment.onViewAnimationEnd()
|
|
201
|
+
}
|
|
180
202
|
|
|
181
|
-
|
|
182
|
-
|
|
203
|
+
override fun onAnimationRepeat(animation: Animation) {}
|
|
204
|
+
}
|
|
183
205
|
|
|
184
206
|
override fun startAnimation(animation: Animation) {
|
|
185
207
|
// For some reason View##onAnimationEnd doesn't get called for
|
|
@@ -205,6 +227,19 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
205
227
|
super.startAnimation(set)
|
|
206
228
|
}
|
|
207
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* This method implements a workaround for RN's autoFocus functionality. Because of the way
|
|
233
|
+
* autoFocus is implemented it dismisses soft keyboard in fragment transition
|
|
234
|
+
* due to change of visibility of the view at the start of the transition. Here we override the
|
|
235
|
+
* call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the
|
|
236
|
+
* hiding of the keyboard in `ReactEditText.java`.
|
|
237
|
+
*/
|
|
238
|
+
override fun clearFocus() {
|
|
239
|
+
if (visibility != INVISIBLE) {
|
|
240
|
+
super.clearFocus()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
208
243
|
}
|
|
209
244
|
|
|
210
245
|
private class ScreensAnimation(private val mFragment: ScreenFragment) : Animation() {
|
|
@@ -17,11 +17,14 @@ import androidx.fragment.app.Fragment
|
|
|
17
17
|
import com.facebook.react.ReactApplication
|
|
18
18
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
|
|
19
19
|
import com.facebook.react.bridge.ReactContext
|
|
20
|
+
import com.facebook.react.bridge.WritableMap
|
|
21
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
20
22
|
import com.facebook.react.views.text.ReactTypefaceUtils
|
|
21
23
|
|
|
22
24
|
class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
23
25
|
private val mConfigSubviews = ArrayList<ScreenStackHeaderSubview>(3)
|
|
24
|
-
val toolbar:
|
|
26
|
+
val toolbar: CustomToolbar
|
|
27
|
+
private var headerTopInset: Int? = null
|
|
25
28
|
private var mTitle: String? = null
|
|
26
29
|
private var mTitleColor = 0
|
|
27
30
|
private var mTitleFontFamily: String? = null
|
|
@@ -62,6 +65,11 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
private fun sendEvent(eventName: String, eventContent: WritableMap?) {
|
|
69
|
+
(context as ReactContext).getJSModule(RCTEventEmitter::class.java)
|
|
70
|
+
?.receiveEvent(id, eventName, eventContent)
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
66
74
|
// no-op
|
|
67
75
|
}
|
|
@@ -73,12 +81,23 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
73
81
|
override fun onAttachedToWindow() {
|
|
74
82
|
super.onAttachedToWindow()
|
|
75
83
|
mIsAttachedToWindow = true
|
|
84
|
+
sendEvent("onAttached", null)
|
|
85
|
+
// we want to save the top inset before the status bar can be hidden, which would resolve in
|
|
86
|
+
// inset being 0
|
|
87
|
+
if (headerTopInset == null) {
|
|
88
|
+
headerTopInset = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
|
89
|
+
rootWindowInsets.systemWindowInsetTop
|
|
90
|
+
else
|
|
91
|
+
// Hacky fallback for old android. Before Marshmallow, the status bar height was always 25
|
|
92
|
+
(25 * resources.displayMetrics.density).toInt()
|
|
93
|
+
}
|
|
76
94
|
onUpdate()
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
override fun onDetachedFromWindow() {
|
|
80
98
|
super.onDetachedFromWindow()
|
|
81
99
|
mIsAttachedToWindow = false
|
|
100
|
+
sendEvent("onDetached", null)
|
|
82
101
|
}
|
|
83
102
|
|
|
84
103
|
private val screen: Screen?
|
|
@@ -99,7 +118,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
99
118
|
}
|
|
100
119
|
return null
|
|
101
120
|
}
|
|
102
|
-
|
|
121
|
+
val screenFragment: ScreenStackFragment?
|
|
103
122
|
get() {
|
|
104
123
|
val screen = parent
|
|
105
124
|
if (screen is Screen) {
|
|
@@ -150,11 +169,8 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
150
169
|
screenFragment?.setToolbar(toolbar)
|
|
151
170
|
}
|
|
152
171
|
if (mIsTopInsetEnabled) {
|
|
153
|
-
|
|
154
|
-
toolbar.setPadding(0,
|
|
155
|
-
} else {
|
|
156
|
-
// Hacky fallback for old android. Before Marshmallow, the status bar height was always 25
|
|
157
|
-
toolbar.setPadding(0, (25 * resources.displayMetrics.density).toInt(), 0, 0)
|
|
172
|
+
headerTopInset.let {
|
|
173
|
+
toolbar.setPadding(0, it ?: 0, 0, 0)
|
|
158
174
|
}
|
|
159
175
|
} else {
|
|
160
176
|
if (toolbar.paddingTop > 0) {
|
|
@@ -369,7 +385,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
369
385
|
mDirection = direction
|
|
370
386
|
}
|
|
371
387
|
|
|
372
|
-
private class DebugMenuToolbar(context: Context) :
|
|
388
|
+
private class DebugMenuToolbar(context: Context, config: ScreenStackHeaderConfig) : CustomToolbar(context, config) {
|
|
373
389
|
override fun showOverflowMenu(): Boolean {
|
|
374
390
|
(context.applicationContext as ReactApplication)
|
|
375
391
|
.reactNativeHost
|
|
@@ -381,7 +397,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
381
397
|
|
|
382
398
|
init {
|
|
383
399
|
visibility = GONE
|
|
384
|
-
toolbar = if (BuildConfig.DEBUG) DebugMenuToolbar(context) else
|
|
400
|
+
toolbar = if (BuildConfig.DEBUG) DebugMenuToolbar(context, this) else CustomToolbar(context, this)
|
|
385
401
|
mDefaultStartInset = toolbar.contentInsetStart
|
|
386
402
|
mDefaultStartInsetWithNavigation = toolbar.contentInsetStartWithNavigation
|
|
387
403
|
|
|
@@ -2,6 +2,7 @@ package com.swmansion.rnscreens
|
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import com.facebook.react.bridge.JSApplicationCausedNativeException
|
|
5
|
+
import com.facebook.react.common.MapBuilder
|
|
5
6
|
import com.facebook.react.module.annotations.ReactModule
|
|
6
7
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
8
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
@@ -129,6 +130,13 @@ class ScreenStackHeaderConfigViewManager : ViewGroupManager<ScreenStackHeaderCon
|
|
|
129
130
|
config.setDirection(direction)
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
|
|
134
|
+
return MapBuilder.builder<String, Any>()
|
|
135
|
+
.put("onAttached", MapBuilder.of("registrationName", "onAttached"))
|
|
136
|
+
.put("onDetached", MapBuilder.of("registrationName", "onDetached"))
|
|
137
|
+
.build()
|
|
138
|
+
}
|
|
139
|
+
|
|
132
140
|
companion object {
|
|
133
141
|
const val REACT_CLASS = "RNSScreenStackHeaderConfig"
|
|
134
142
|
}
|
|
@@ -10,6 +10,12 @@ class ScreenStackHeaderSubview(context: ReactContext?) : ReactViewGroup(context)
|
|
|
10
10
|
private var mReactWidth = 0
|
|
11
11
|
private var mReactHeight = 0
|
|
12
12
|
var type = Type.RIGHT
|
|
13
|
+
|
|
14
|
+
val config: ScreenStackHeaderConfig?
|
|
15
|
+
get() {
|
|
16
|
+
return (parent as? CustomToolbar)?.config
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
14
20
|
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
|
|
15
21
|
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
|
|
@@ -31,6 +37,6 @@ class ScreenStackHeaderSubview(context: ReactContext?) : ReactViewGroup(context)
|
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
enum class Type {
|
|
34
|
-
LEFT, CENTER, RIGHT, BACK
|
|
40
|
+
LEFT, CENTER, RIGHT, BACK, SEARCH_BAR
|
|
35
41
|
}
|
|
36
42
|
}
|
|
@@ -24,6 +24,7 @@ class ScreenStackHeaderSubviewManager : ReactViewManager() {
|
|
|
24
24
|
"center" -> ScreenStackHeaderSubview.Type.CENTER
|
|
25
25
|
"right" -> ScreenStackHeaderSubview.Type.RIGHT
|
|
26
26
|
"back" -> ScreenStackHeaderSubview.Type.BACK
|
|
27
|
+
"searchBar" -> ScreenStackHeaderSubview.Type.SEARCH_BAR
|
|
27
28
|
else -> throw JSApplicationIllegalArgumentException("Unknown type $type")
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -112,6 +112,16 @@ class ScreenViewManager : ViewGroupManager<Screen>() {
|
|
|
112
112
|
view.isStatusBarHidden = statusBarHidden
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
@ReactProp(name = "navigationBarColor", customType = "Color")
|
|
116
|
+
fun setNavigationBarColor(view: Screen, navigationBarColor: Int) {
|
|
117
|
+
view.navigationBarColor = navigationBarColor
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@ReactProp(name = "navigationBarHidden")
|
|
121
|
+
fun setNavigationBarHidden(view: Screen, navigationBarHidden: Boolean?) {
|
|
122
|
+
view.isNavigationBarHidden = navigationBarHidden
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
@ReactProp(name = "nativeBackButtonDismissalEnabled")
|
|
116
126
|
fun setNativeBackButtonDismissalEnabled(
|
|
117
127
|
view: Screen,
|
|
@@ -6,11 +6,15 @@ import android.annotation.SuppressLint
|
|
|
6
6
|
import android.annotation.TargetApi
|
|
7
7
|
import android.app.Activity
|
|
8
8
|
import android.content.pm.ActivityInfo
|
|
9
|
+
import android.graphics.Color
|
|
9
10
|
import android.os.Build
|
|
10
11
|
import android.view.View
|
|
11
12
|
import android.view.ViewParent
|
|
12
13
|
import android.view.WindowManager
|
|
13
14
|
import androidx.core.view.ViewCompat
|
|
15
|
+
import androidx.core.view.WindowCompat
|
|
16
|
+
import androidx.core.view.WindowInsetsCompat
|
|
17
|
+
import androidx.core.view.WindowInsetsControllerCompat
|
|
14
18
|
import com.facebook.react.bridge.GuardedRunnable
|
|
15
19
|
import com.facebook.react.bridge.ReactContext
|
|
16
20
|
import com.facebook.react.bridge.UiThreadUtil
|
|
@@ -21,6 +25,7 @@ object ScreenWindowTraits {
|
|
|
21
25
|
// https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java
|
|
22
26
|
private var mDidSetOrientation = false
|
|
23
27
|
private var mDidSetStatusBarAppearance = false
|
|
28
|
+
private var mDidSetNavigationBarAppearance = false
|
|
24
29
|
private var mDefaultStatusBarColor: Int? = null
|
|
25
30
|
internal fun applyDidSetOrientation() {
|
|
26
31
|
mDidSetOrientation = true
|
|
@@ -30,6 +35,10 @@ object ScreenWindowTraits {
|
|
|
30
35
|
mDidSetStatusBarAppearance = true
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
internal fun applyDidSetNavigationBarAppearance() {
|
|
39
|
+
mDidSetNavigationBarAppearance = true
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
internal fun setOrientation(screen: Screen, activity: Activity?) {
|
|
34
43
|
if (activity == null) {
|
|
35
44
|
return
|
|
@@ -72,23 +81,21 @@ object ScreenWindowTraits {
|
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
internal fun setStyle(screen: Screen, activity: Activity?, context: ReactContext?) {
|
|
75
|
-
if (activity == null || context == null) {
|
|
84
|
+
if (activity == null || context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
76
85
|
return
|
|
77
86
|
}
|
|
78
87
|
val screenForStyle = findScreenForTrait(screen, WindowTraits.STYLE)
|
|
79
88
|
val style = screenForStyle?.statusBarStyle ?: "light"
|
|
80
89
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
systemUiVisibilityFlags
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
systemUiVisibilityFlags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
|
89
|
-
}
|
|
90
|
-
decorView.systemUiVisibility = systemUiVisibilityFlags
|
|
90
|
+
UiThreadUtil.runOnUiThread {
|
|
91
|
+
val decorView = activity.window.decorView
|
|
92
|
+
var systemUiVisibilityFlags = decorView.systemUiVisibility
|
|
93
|
+
systemUiVisibilityFlags = if ("dark" == style) {
|
|
94
|
+
systemUiVisibilityFlags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
|
95
|
+
} else {
|
|
96
|
+
systemUiVisibilityFlags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
|
91
97
|
}
|
|
98
|
+
decorView.systemUiVisibility = systemUiVisibilityFlags
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -144,6 +151,48 @@ object ScreenWindowTraits {
|
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
153
|
|
|
154
|
+
// Methods concerning navigationBar management were taken from `react-native-navigation`'s repo:
|
|
155
|
+
// https://github.com/wix/react-native-navigation/blob/9bb70d81700692141a2c505c081c2d86c7f9c66e/lib/android/app/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt
|
|
156
|
+
internal fun setNavigationBarColor(screen: Screen, activity: Activity?) {
|
|
157
|
+
if (activity == null) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
val window = activity.window
|
|
162
|
+
|
|
163
|
+
val screenForNavBarColor = findScreenForTrait(screen, WindowTraits.NAVIGATION_BAR_COLOR)
|
|
164
|
+
val color = screenForNavBarColor?.navigationBarColor ?: window.navigationBarColor
|
|
165
|
+
|
|
166
|
+
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightNavigationBars =
|
|
167
|
+
isColorLight(color)
|
|
168
|
+
window.navigationBarColor = color
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
internal fun setNavigationBarHidden(screen: Screen, activity: Activity?) {
|
|
172
|
+
if (activity == null) {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
val window = activity.window
|
|
177
|
+
|
|
178
|
+
val screenForNavBarHidden = findScreenForTrait(screen, WindowTraits.NAVIGATION_BAR_HIDDEN)
|
|
179
|
+
val hidden = screenForNavBarHidden?.isNavigationBarHidden ?: false
|
|
180
|
+
|
|
181
|
+
WindowCompat.setDecorFitsSystemWindows(window, hidden)
|
|
182
|
+
if (hidden) {
|
|
183
|
+
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
|
184
|
+
controller.hide(WindowInsetsCompat.Type.navigationBars())
|
|
185
|
+
controller.systemBarsBehavior =
|
|
186
|
+
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
WindowInsetsControllerCompat(
|
|
190
|
+
window,
|
|
191
|
+
window.decorView
|
|
192
|
+
).show(WindowInsetsCompat.Type.navigationBars())
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
147
196
|
internal fun trySetWindowTraits(screen: Screen, activity: Activity?, context: ReactContext?) {
|
|
148
197
|
if (mDidSetOrientation) {
|
|
149
198
|
setOrientation(screen, activity)
|
|
@@ -154,6 +203,10 @@ object ScreenWindowTraits {
|
|
|
154
203
|
setTranslucent(screen, activity, context)
|
|
155
204
|
setHidden(screen, activity)
|
|
156
205
|
}
|
|
206
|
+
if (mDidSetNavigationBarAppearance) {
|
|
207
|
+
setNavigationBarColor(screen, activity)
|
|
208
|
+
setNavigationBarHidden(screen, activity)
|
|
209
|
+
}
|
|
157
210
|
}
|
|
158
211
|
|
|
159
212
|
private fun findScreenForTrait(screen: Screen, trait: WindowTraits): Screen? {
|
|
@@ -211,6 +264,14 @@ object ScreenWindowTraits {
|
|
|
211
264
|
WindowTraits.TRANSLUCENT -> screen.isStatusBarTranslucent != null
|
|
212
265
|
WindowTraits.HIDDEN -> screen.isStatusBarHidden != null
|
|
213
266
|
WindowTraits.ANIMATED -> screen.isStatusBarAnimated != null
|
|
267
|
+
WindowTraits.NAVIGATION_BAR_COLOR -> screen.navigationBarColor != null
|
|
268
|
+
WindowTraits.NAVIGATION_BAR_HIDDEN -> screen.isNavigationBarHidden != null
|
|
214
269
|
}
|
|
215
270
|
}
|
|
271
|
+
|
|
272
|
+
private fun isColorLight(color: Int): Boolean {
|
|
273
|
+
val darkness: Double =
|
|
274
|
+
1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255
|
|
275
|
+
return darkness < 0.5
|
|
276
|
+
}
|
|
216
277
|
}
|