react-native-screens 3.10.2 → 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.
Files changed (45) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +9 -7
  3. package/android/build.gradle +1 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +28 -11
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +1 -1
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +64 -33
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +9 -31
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +0 -30
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +12 -5
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +10 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +72 -11
  12. package/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt +18 -1
  13. package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +7 -2
  14. package/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt +29 -2
  15. package/android/src/main/res/anim/rns_default_enter_in.xml +18 -0
  16. package/android/src/main/res/anim/rns_default_enter_out.xml +19 -0
  17. package/android/src/main/res/anim/rns_default_exit_in.xml +17 -0
  18. package/android/src/main/res/anim/rns_default_exit_out.xml +18 -0
  19. package/android/src/main/res/anim/rns_fade_in.xml +7 -0
  20. package/android/src/main/res/anim/rns_fade_out.xml +7 -0
  21. package/android/src/main/res/anim/rns_no_animation_20.xml +6 -0
  22. package/createNativeStackNavigator/README.md +12 -0
  23. package/ios/RNSScreen.h +10 -0
  24. package/ios/RNSScreen.m +34 -0
  25. package/ios/RNSScreenContainer.m +5 -0
  26. package/ios/RNSScreenStack.m +22 -7
  27. package/ios/RNSScreenStackAnimator.m +45 -14
  28. package/ios/RNSScreenStackHeaderConfig.m +4 -1
  29. package/ios/RNSScreenWindowTraits.h +1 -0
  30. package/ios/RNSScreenWindowTraits.m +20 -0
  31. package/ios/UIViewController+RNScreens.m +10 -0
  32. package/lib/commonjs/native-stack/views/NativeStackView.js +33 -4
  33. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  34. package/lib/module/native-stack/views/NativeStackView.js +33 -4
  35. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  36. package/lib/typescript/native-stack/types.d.ts +34 -0
  37. package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +1 -1
  38. package/lib/typescript/reanimated/ReanimatedScreen.d.ts +1 -1
  39. package/lib/typescript/types.d.ts +60 -5
  40. package/native-stack/README.md +39 -3
  41. package/package.json +1 -1
  42. package/reanimated/package.json +6 -0
  43. package/src/native-stack/types.tsx +34 -0
  44. package/src/native-stack/views/NativeStackView.tsx +33 -4
  45. package/src/types.tsx +60 -5
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Krzysztof Magiera
3
+ Copyright (c) 2018 Software Mansion <swmansion.com>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -123,8 +123,8 @@ You can also disable the usage of native screens per navigator with [`detachInac
123
123
  To take advantage of the native stack navigator primitive for React Navigation that leverages `UINavigationController` on iOS and `Fragment` on Android, please refer:
124
124
 
125
125
  - for React Navigation >= v6 to the [Native Stack Navigator part of React Navigation documentation](https://reactnavigation.org/docs/native-stack-navigator)
126
- - for React Navigation v5 to the [README in react-native-screens/native-stack](https://github.com/software-mansion/react-native-screens/tree/master/native-stack)
127
- - for older versions to the [README in react-native-screens/createNativeStackNavigator](https://github.com/software-mansion/react-native-screens/tree/master/createNativeStackNavigator)
126
+ - for React Navigation v5 to the [README in react-native-screens/native-stack](https://github.com/software-mansion/react-native-screens/tree/main/native-stack)
127
+ - for older versions to the [README in react-native-screens/createNativeStackNavigator](https://github.com/software-mansion/react-native-screens/tree/main/createNativeStackNavigator)
128
128
 
129
129
  ## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)
130
130
 
@@ -132,12 +132,12 @@ React-native-navigation library already uses native containers for rendering nav
132
132
 
133
133
  ## Interop with other libraries
134
134
 
135
- This library should work out of the box with all existing react-native libraries. If you experience problems with interoperability please [report an issue](https://github.com/kmagiera/react-native-screens/issues).
135
+ This library should work out of the box with all existing react-native libraries. If you experience problems with interoperability please [report an issue](https://github.com/software-mansion/react-native-screens/issues).
136
136
 
137
137
  ## Guide for navigation library authors
138
138
 
139
139
  If you are building a navigation library you may want to use `react-native-screens` to have control over which parts of the React component tree are attached to the native view hierarchy.
140
- To do that, `react-native-screens` provides you with the components documented [here](https://github.com/kmagiera/react-native-screens/tree/master/guides/GUIDE_FOR_LIBRARY_AUTHORS.md).
140
+ To do that, `react-native-screens` provides you with the components documented [here](https://github.com/software-mansion/react-native-screens/tree/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md).
141
141
 
142
142
  ## Common problems
143
143
 
@@ -158,10 +158,11 @@ Use `ScrollView` with prop `contentInsetAdjustmentBehavior=“automatic”` as a
158
158
  | [SVG component becomes transparent when goBack](https://github.com/software-mansion/react-native-screens/issues/773) | [related PRs](https://github.com/software-mansion/react-native-screens/issues/773#issuecomment-783469792) |
159
159
  | [Memory leak while moving from one screen to another in the same stack](https://github.com/software-mansion/react-native-screens/issues/843) | [explanation](https://github.com/software-mansion/react-native-screens/issues/843#issuecomment-832034119) |
160
160
  | [LargeHeader stays small after pop/goBack/swipe gesture on iOS 14+](https://github.com/software-mansion/react-native-screens/issues/649) | [potential fix](https://github.com/software-mansion/react-native-screens/issues/649#issuecomment-712199895) |
161
+ | [`onScroll` and `onMomentumScrollEnd` of previous screen triggered in bottom tabs](https://github.com/software-mansion/react-native-screens/issues/1183) | [explanation](https://github.com/software-mansion/react-native-screens/issues/1183#issuecomment-949313111) |
161
162
 
162
163
  ## Contributing
163
164
 
164
- There are many ways to contribute to this project. See [CONTRIBUTING](https://github.com/kmagiera/react-native-screens/tree/master/guides/CONTRIBUTING.md) guide for more information. Thank you for your interest in contributing!
165
+ There are many ways to contribute to this project. See [CONTRIBUTING](https://github.com/software-mansion/react-native-screens/tree/main/guides/CONTRIBUTING.md) guide for more information. Thank you for your interest in contributing!
165
166
 
166
167
  ## License
167
168
 
@@ -169,7 +170,8 @@ React native screens library is licensed under [The MIT License](LICENSE).
169
170
 
170
171
  ## Credits
171
172
 
172
- This project is supported by amazing people from [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
173
+ This project has been build and is maintained thanks to the support from [Shopify](https://shopify.com), [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
173
174
 
175
+ [![shopify](https://avatars1.githubusercontent.com/u/8085?v=3&s=100 'Shopify.com')](https://shopify.com)
174
176
  [![expo](https://avatars2.githubusercontent.com/u/12504344?v=3&s=100 'Expo.io')](https://expo.io)
175
- [![swm](https://logo.swmansion.com/logo?color=white&variant=desktop&width=150&tag=react-native-screens-github 'Software Mansion')](https://swmansion.com)
177
+ [![swm](https://logo.swmansion.com/logo?color=white&variant=desktop&width=150&tag=react-native-reanimated-github 'Software Mansion')](https://swmansion.com)
@@ -58,4 +58,5 @@ dependencies {
58
58
  implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
59
59
  implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
60
60
  implementation 'com.google.android.material:material:1.1.0'
61
+ implementation "androidx.core:core-ktx:1.5.0"
61
62
  }
@@ -29,6 +29,8 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
29
29
  private var mStatusBarHidden: Boolean? = null
30
30
  private var mStatusBarTranslucent: Boolean? = null
31
31
  private var mStatusBarColor: Int? = null
32
+ private var mNavigationBarColor: Int? = null
33
+ private var mNavigationBarHidden: Boolean? = null
32
34
  var isStatusBarAnimated: Boolean? = null
33
35
  private var mNativeBackButtonDismissalEnabled = true
34
36
 
@@ -46,16 +48,6 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
46
48
  layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION)
47
49
  }
48
50
 
49
- override fun onAnimationStart() {
50
- super.onAnimationStart()
51
- fragment?.onViewAnimationStart()
52
- }
53
-
54
- override fun onAnimationEnd() {
55
- super.onAnimationEnd()
56
- fragment?.onViewAnimationEnd()
57
- }
58
-
59
51
  override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
60
52
  // do nothing, react native will keep the view hierarchy so no need to serialize/deserialize
61
53
  // view's states. The side effect of restoring is that TextInput components would trigger
@@ -209,6 +201,31 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
209
201
  fragment?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) }
210
202
  }
211
203
 
204
+ var navigationBarColor: Int?
205
+ get() = mNavigationBarColor
206
+ set(navigationBarColor) {
207
+ if (navigationBarColor != null) {
208
+ ScreenWindowTraits.applyDidSetNavigationBarAppearance()
209
+ }
210
+ mNavigationBarColor = navigationBarColor
211
+ fragment?.let { ScreenWindowTraits.setNavigationBarColor(this, it.tryGetActivity()) }
212
+ }
213
+
214
+ var isNavigationBarHidden: Boolean?
215
+ get() = mNavigationBarHidden
216
+ set(navigationBarHidden) {
217
+ if (navigationBarHidden != null) {
218
+ ScreenWindowTraits.applyDidSetNavigationBarAppearance()
219
+ }
220
+ mNavigationBarHidden = navigationBarHidden
221
+ fragment?.let {
222
+ ScreenWindowTraits.setNavigationBarHidden(
223
+ this,
224
+ it.tryGetActivity(),
225
+ )
226
+ }
227
+ }
228
+
212
229
  var nativeBackButtonDismissalEnabled: Boolean
213
230
  get() = mNativeBackButtonDismissalEnabled
214
231
  set(enableNativeBackButtonDismissal) {
@@ -232,6 +249,6 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
232
249
  }
233
250
 
234
251
  enum class WindowTraits {
235
- ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED
252
+ ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED, NAVIGATION_BAR_COLOR, NAVIGATION_BAR_HIDDEN
236
253
  }
237
254
  }
@@ -166,7 +166,7 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
166
166
  while (context !is FragmentActivity && context is ContextWrapper) {
167
167
  context = context.baseContext
168
168
  }
169
- check(context is FragmentActivity) { "In order to use RNScreens components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity" }
169
+ check(context is FragmentActivity) { "In order to use RNScreens components your app's activity need to extend ReactActivity" }
170
170
  setFragmentManager(context.supportFragmentManager)
171
171
  }
172
172
 
@@ -38,6 +38,17 @@ open class ScreenFragment : Fragment {
38
38
  // due to progress value being already 0.0f
39
39
  private var mProgress = -1f
40
40
 
41
+ // those 2 vars are needed since sometimes the events would be dispatched twice in child containers
42
+ // (should only happen if parent has `NONE` animation) and we don't need too complicated logic.
43
+ // We just check if, after the event was dispatched, its "counter-event" has been also dispatched before sending the same event again.
44
+ // We do it for 'willAppear' -> 'willDisappear' and 'appear' -> 'disappear'
45
+ private var canDispatchWillAppear = true
46
+ private var canDispatchAppear = true
47
+
48
+ // we want to know if we are currently transitioning in order not to fire lifecycle events
49
+ // in nested fragments. See more explanation in dispatchViewAnimationEvent
50
+ private var isTransitioning = false
51
+
41
52
  constructor() {
42
53
  throw IllegalStateException(
43
54
  "Screen fragments should never be restored. Follow instructions from https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 to properly configure your main activity."
@@ -141,33 +152,52 @@ open class ScreenFragment : Fragment {
141
152
  val childScreenContainers: List<ScreenContainer<*>>
142
153
  get() = mChildScreenContainers
143
154
 
144
- fun dispatchOnWillAppear() {
155
+ private fun canDispatchEvent(event: ScreenLifecycleEvent): Boolean {
156
+ return when (event) {
157
+ ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear
158
+ ScreenLifecycleEvent.Appear -> canDispatchAppear
159
+ ScreenLifecycleEvent.WillDisappear -> !canDispatchWillAppear
160
+ ScreenLifecycleEvent.Disappear -> !canDispatchAppear
161
+ }
162
+ }
163
+
164
+ private fun setLastEventDispatched(event: ScreenLifecycleEvent) {
165
+ when (event) {
166
+ ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear = false
167
+ ScreenLifecycleEvent.Appear -> canDispatchAppear = false
168
+ ScreenLifecycleEvent.WillDisappear -> canDispatchWillAppear = true
169
+ ScreenLifecycleEvent.Disappear -> canDispatchAppear = true
170
+ }
171
+ }
172
+
173
+ private fun dispatchOnWillAppear() {
145
174
  dispatchEvent(ScreenLifecycleEvent.WillAppear, this)
146
175
 
147
176
  dispatchTransitionProgress(0.0f, false)
148
177
  }
149
178
 
150
- fun dispatchOnAppear() {
179
+ private fun dispatchOnAppear() {
151
180
  dispatchEvent(ScreenLifecycleEvent.Appear, this)
152
181
 
153
182
  dispatchTransitionProgress(1.0f, false)
154
183
  }
155
184
 
156
- protected fun dispatchOnWillDisappear() {
185
+ private fun dispatchOnWillDisappear() {
157
186
  dispatchEvent(ScreenLifecycleEvent.WillDisappear, this)
158
187
 
159
188
  dispatchTransitionProgress(0.0f, true)
160
189
  }
161
190
 
162
- protected fun dispatchOnDisappear() {
191
+ private fun dispatchOnDisappear() {
163
192
  dispatchEvent(ScreenLifecycleEvent.Disappear, this)
164
193
 
165
194
  dispatchTransitionProgress(1.0f, true)
166
195
  }
167
196
 
168
197
  private fun dispatchEvent(event: ScreenLifecycleEvent, fragment: ScreenFragment) {
169
- if (fragment is ScreenStackFragment) {
198
+ if (fragment is ScreenStackFragment && fragment.canDispatchEvent(event)) {
170
199
  fragment.screen.let {
200
+ fragment.setLastEventDispatched(event)
171
201
  val lifecycleEvent: Event<*> = when (event) {
172
202
  ScreenLifecycleEvent.WillAppear -> ScreenWillAppearEvent(it.id)
173
203
  ScreenLifecycleEvent.Appear -> ScreenAppearEvent(it.id)
@@ -187,12 +217,7 @@ open class ScreenFragment : Fragment {
187
217
  for (sc in mChildScreenContainers) {
188
218
  if (sc.screenCount > 0) {
189
219
  sc.topScreen?.let {
190
- if (it.stackAnimation !== Screen.StackAnimation.NONE || isRemoving) {
191
- // we do not dispatch events in child when it has `none` animation
192
- // and we are going forward since then they will be dispatched in child via
193
- // `onCreateAnimation` of ScreenStackFragment
194
- sc.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) }
195
- }
220
+ sc.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) }
196
221
  }
197
222
  }
198
223
  }
@@ -238,31 +263,37 @@ open class ScreenFragment : Fragment {
238
263
  }
239
264
 
240
265
  fun onViewAnimationStart() {
241
- // onViewAnimationStart is triggered from View#onAnimationStart method of the fragment's root
242
- // view. We override Screen#onAnimationStart and an appropriate method of the StackFragment's
243
- // root view in order to achieve this.
244
- if (isResumed) {
245
- // Android dispatches the animation start event for the fragment that is being added first
246
- // however we want the one being dismissed first to match iOS. It also makes more sense from
247
- // a navigation point of view to have the disappear event first.
248
- // Since there are no explicit relationships between the fragment being added / removed the
249
- // practical way to fix this is delaying dispatching the appear events at the end of the
250
- // frame.
251
- UiThreadUtil.runOnUiThread { dispatchOnWillAppear() }
252
- } else {
253
- dispatchOnWillDisappear()
254
- }
266
+ dispatchViewAnimationEvent(false)
255
267
  }
256
268
 
257
269
  open fun onViewAnimationEnd() {
258
- // onViewAnimationEnd is triggered from View#onAnimationEnd method of the fragment's root view.
259
- // We override Screen#onAnimationEnd and an appropriate method of the StackFragment's root view
260
- // in order to achieve this.
261
- if (isResumed) {
262
- // See the comment in onViewAnimationStart for why this event is delayed.
263
- UiThreadUtil.runOnUiThread { dispatchOnAppear() }
264
- } else {
265
- dispatchOnDisappear()
270
+ dispatchViewAnimationEvent(true)
271
+ }
272
+
273
+ private fun dispatchViewAnimationEvent(animationEnd: Boolean) {
274
+ isTransitioning = !animationEnd
275
+ // if parent fragment is transitioning, we do not want the events dispatched from the child,
276
+ // since we subscribe to parent's animation start/end and dispatch events in child from there
277
+ // check for `isTransitioning` should be enough since the child's animation should take only
278
+ // 20ms due to always being `StackAnimation.NONE` when nested stack being pushed
279
+ val parent = parentFragment
280
+ if (parent == null || (parent is ScreenFragment && !parent.isTransitioning)) {
281
+ // onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root
282
+ // view. We override an appropriate method of the StackFragment's
283
+ // root view in order to achieve this.
284
+ if (isResumed) {
285
+ // Android dispatches the animation start event for the fragment that is being added first
286
+ // however we want the one being dismissed first to match iOS. It also makes more sense from
287
+ // a navigation point of view to have the disappear event first.
288
+ // Since there are no explicit relationships between the fragment being added / removed the
289
+ // practical way to fix this is delaying dispatching the appear events at the end of the
290
+ // frame.
291
+ UiThreadUtil.runOnUiThread {
292
+ if (animationEnd) dispatchOnAppear() else dispatchOnWillAppear()
293
+ }
294
+ } else {
295
+ if (animationEnd) dispatchOnDisappear() else dispatchOnWillDisappear()
296
+ }
266
297
  }
267
298
  }
268
299
 
@@ -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
@@ -127,17 +125,9 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
127
125
  stackAnimation = if (shouldUseOpenAnimation) newTop.screen.stackAnimation else mTopScreen?.screen?.stackAnimation
128
126
  } else if (mTopScreen == null && newTop != null) {
129
127
  // mTopScreen was not present before so newTop is the first screen added to a stack
130
- // and we don't want the animation when it is entering, but we want to send the
131
- // willAppear and Appear events to the user, which won't be sent by default if Screen's
132
- // stack animation is not NONE (see check for stackAnimation in onCreateAnimation in
133
- // ScreenStackFragment).
134
- // 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
135
129
  stackAnimation = StackAnimation.NONE
136
- if (newTop.screen.stackAnimation !== StackAnimation.NONE && !isNested) {
137
- goingForward = true
138
- newTop.dispatchOnWillAppear()
139
- newTop.dispatchOnAppear()
140
- }
130
+ goingForward = true
141
131
  }
142
132
  } else if (mTopScreen != null && mTopScreen != newTop) {
143
133
  // otherwise if we are performing top screen change we do "close animation"
@@ -149,40 +139,32 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
149
139
  // animation logic start
150
140
  if (stackAnimation != null) {
151
141
  if (shouldUseOpenAnimation) {
152
- transition = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
153
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)
154
146
  StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left)
155
147
  StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right)
156
148
  StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations(
157
149
  R.anim.rns_slide_in_from_bottom, R.anim.rns_no_animation_medium
158
150
  )
159
151
  StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_fade_from_bottom, R.anim.rns_no_animation_350)
160
- else -> {
161
- }
162
152
  }
163
153
  } else {
164
- transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE
165
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)
166
158
  StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right)
167
159
  StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left)
168
160
  StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations(
169
161
  R.anim.rns_no_animation_medium, R.anim.rns_slide_out_to_bottom
170
162
  )
171
163
  StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_no_animation_250, R.anim.rns_fade_to_bottom)
172
- else -> {
173
- }
174
164
  }
175
165
  }
176
166
  }
177
- if (stackAnimation === StackAnimation.NONE) {
178
- transition = FragmentTransaction.TRANSIT_NONE
179
- }
180
- if (stackAnimation === StackAnimation.FADE) {
181
- transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE
182
- }
183
- if (stackAnimation != null && isSystemAnimation(stackAnimation)) {
184
- it.setTransition(transition)
185
- }
167
+
186
168
  // animation logic end
187
169
  goingForward = shouldUseOpenAnimation
188
170
 
@@ -348,10 +330,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
348
330
  }
349
331
 
350
332
  companion object {
351
- private fun isSystemAnimation(stackAnimation: StackAnimation): Boolean {
352
- return stackAnimation === StackAnimation.DEFAULT || stackAnimation === StackAnimation.FADE || stackAnimation === StackAnimation.NONE
353
- }
354
-
355
333
  private fun isTransparent(fragment: ScreenStackFragment): Boolean {
356
334
  return (
357
335
  fragment.screen.stackPresentation
@@ -16,7 +16,6 @@ import android.view.animation.Transformation
16
16
  import android.widget.LinearLayout
17
17
  import androidx.appcompat.widget.Toolbar
18
18
  import androidx.coordinatorlayout.widget.CoordinatorLayout
19
- import com.facebook.react.bridge.UiThreadUtil
20
19
  import com.facebook.react.uimanager.PixelUtil
21
20
  import com.google.android.material.appbar.AppBarLayout
22
21
  import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
@@ -86,35 +85,6 @@ class ScreenStackFragment : ScreenFragment {
86
85
  notifyViewAppearTransitionEnd()
87
86
  }
88
87
 
89
- override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
90
- // this means that the fragment will appear with a custom transition, in the case
91
- // of animation: 'none', onViewAnimationStart and onViewAnimationEnd
92
- // won't be called and we need to notify stack directly from here.
93
- // When using the Toolbar back button this is called an extra time with transit = 0 but in
94
- // this case we don't want to notify. The way I found to detect is case is check isHidden.
95
- if (transit == 0 && !isHidden &&
96
- screen.stackAnimation === Screen.StackAnimation.NONE
97
- ) {
98
- if (enter) {
99
- // Android dispatches the animation start event for the fragment that is being added first
100
- // however we want the one being dismissed first to match iOS. It also makes more sense
101
- // from a navigation point of view to have the disappear event first.
102
- // Since there are no explicit relationships between the fragment being added / removed
103
- // the practical way to fix this is delaying dispatching the appear events at the end of
104
- // the frame.
105
- UiThreadUtil.runOnUiThread {
106
- dispatchOnWillAppear()
107
- dispatchOnAppear()
108
- }
109
- } else {
110
- dispatchOnWillDisappear()
111
- dispatchOnDisappear()
112
- notifyViewAppearTransitionEnd()
113
- }
114
- }
115
- return null
116
- }
117
-
118
88
  private fun notifyViewAppearTransitionEnd() {
119
89
  val screenStack = view?.parent
120
90
  if (screenStack is ScreenStack) {
@@ -24,6 +24,7 @@ import com.facebook.react.views.text.ReactTypefaceUtils
24
24
  class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
25
25
  private val mConfigSubviews = ArrayList<ScreenStackHeaderSubview>(3)
26
26
  val toolbar: CustomToolbar
27
+ private var headerTopInset: Int? = null
27
28
  private var mTitle: String? = null
28
29
  private var mTitleColor = 0
29
30
  private var mTitleFontFamily: String? = null
@@ -81,6 +82,15 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
81
82
  super.onAttachedToWindow()
82
83
  mIsAttachedToWindow = true
83
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
+ }
84
94
  onUpdate()
85
95
  }
86
96
 
@@ -159,11 +169,8 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
159
169
  screenFragment?.setToolbar(toolbar)
160
170
  }
161
171
  if (mIsTopInsetEnabled) {
162
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
163
- toolbar.setPadding(0, rootWindowInsets.systemWindowInsetTop, 0, 0)
164
- } else {
165
- // Hacky fallback for old android. Before Marshmallow, the status bar height was always 25
166
- toolbar.setPadding(0, (25 * resources.displayMetrics.density).toInt(), 0, 0)
172
+ headerTopInset.let {
173
+ toolbar.setPadding(0, it ?: 0, 0, 0)
167
174
  }
168
175
  } else {
169
176
  if (toolbar.paddingTop > 0) {
@@ -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
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
82
- UiThreadUtil.runOnUiThread {
83
- val decorView = activity.window.decorView
84
- var systemUiVisibilityFlags = decorView.systemUiVisibility
85
- systemUiVisibilityFlags = if ("dark" == style) {
86
- systemUiVisibilityFlags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
87
- } else {
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
  }