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.
Files changed (102) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +47 -7
  3. package/android/build.gradle +1 -2
  4. package/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt +71 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +7 -0
  6. package/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt +29 -0
  7. package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -1
  8. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +35 -52
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +1 -1
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +83 -34
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +38 -33
  12. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +77 -42
  13. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +25 -9
  14. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +8 -0
  15. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +7 -1
  16. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt +1 -0
  17. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +10 -0
  18. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +72 -11
  19. package/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt +107 -0
  20. package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +155 -0
  21. package/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt +67 -0
  22. package/android/src/main/res/anim/rns_default_enter_in.xml +18 -0
  23. package/android/src/main/res/anim/rns_default_enter_out.xml +19 -0
  24. package/android/src/main/res/anim/rns_default_exit_in.xml +17 -0
  25. package/android/src/main/res/anim/rns_default_exit_out.xml +18 -0
  26. package/android/src/main/res/anim/rns_fade_in.xml +7 -0
  27. package/android/src/main/res/anim/rns_fade_out.xml +7 -0
  28. package/android/src/main/res/anim/rns_no_animation_20.xml +6 -0
  29. package/createNativeStackNavigator/README.md +12 -0
  30. package/ios/RNSScreen.h +10 -0
  31. package/ios/RNSScreen.m +38 -0
  32. package/ios/RNSScreenContainer.m +5 -0
  33. package/ios/RNSScreenStack.m +29 -13
  34. package/ios/RNSScreenStackAnimator.m +45 -14
  35. package/ios/RNSScreenStackHeaderConfig.m +4 -1
  36. package/ios/RNSScreenWindowTraits.h +1 -0
  37. package/ios/RNSScreenWindowTraits.m +20 -0
  38. package/ios/UIViewController+RNScreens.m +10 -0
  39. package/lib/commonjs/index.js +17 -1
  40. package/lib/commonjs/index.js.map +1 -1
  41. package/lib/commonjs/index.native.js +66 -18
  42. package/lib/commonjs/index.native.js.map +1 -1
  43. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +67 -0
  44. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -0
  45. package/lib/commonjs/native-stack/views/HeaderConfig.js +46 -4
  46. package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
  47. package/lib/commonjs/native-stack/views/NativeStackView.js +33 -4
  48. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  49. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +60 -0
  50. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
  51. package/lib/commonjs/reanimated/ReanimatedScreen.js +7 -79
  52. package/lib/commonjs/reanimated/ReanimatedScreen.js.map +1 -1
  53. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +61 -0
  54. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -0
  55. package/lib/commonjs/reanimated/index.js +2 -2
  56. package/lib/commonjs/reanimated/index.js.map +1 -1
  57. package/lib/commonjs/utils.js +20 -0
  58. package/lib/commonjs/utils.js.map +1 -0
  59. package/lib/module/index.js +1 -0
  60. package/lib/module/index.js.map +1 -1
  61. package/lib/module/index.native.js +65 -19
  62. package/lib/module/index.native.js.map +1 -1
  63. package/lib/module/native-stack/utils/useBackPressSubscription.js +50 -0
  64. package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -0
  65. package/lib/module/native-stack/views/HeaderConfig.js +46 -5
  66. package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
  67. package/lib/module/native-stack/views/NativeStackView.js +33 -4
  68. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  69. package/lib/module/reanimated/ReanimatedNativeStackScreen.js +40 -0
  70. package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
  71. package/lib/module/reanimated/ReanimatedScreen.js +6 -73
  72. package/lib/module/reanimated/ReanimatedScreen.js.map +1 -1
  73. package/lib/module/reanimated/ReanimatedScreenProvider.js +49 -0
  74. package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -0
  75. package/lib/module/reanimated/index.js +1 -1
  76. package/lib/module/reanimated/index.js.map +1 -1
  77. package/lib/module/utils.js +8 -0
  78. package/lib/module/utils.js.map +1 -0
  79. package/lib/typescript/index.d.ts +1 -0
  80. package/lib/typescript/native-stack/types.d.ts +34 -2
  81. package/lib/typescript/native-stack/utils/useBackPressSubscription.d.ts +16 -0
  82. package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +5 -0
  83. package/lib/typescript/reanimated/ReanimatedScreen.d.ts +5 -2
  84. package/lib/typescript/reanimated/ReanimatedScreenProvider.d.ts +2 -0
  85. package/lib/typescript/reanimated/index.d.ts +1 -1
  86. package/lib/typescript/types.d.ts +101 -1
  87. package/lib/typescript/utils.d.ts +2 -0
  88. package/native-stack/README.md +70 -8
  89. package/package.json +2 -1
  90. package/reanimated/package.json +6 -0
  91. package/src/index.native.tsx +94 -36
  92. package/src/index.tsx +4 -0
  93. package/src/native-stack/types.tsx +34 -2
  94. package/src/native-stack/utils/useBackPressSubscription.tsx +66 -0
  95. package/src/native-stack/views/HeaderConfig.tsx +46 -3
  96. package/src/native-stack/views/NativeStackView.tsx +33 -4
  97. package/src/reanimated/ReanimatedNativeStackScreen.tsx +61 -0
  98. package/src/reanimated/ReanimatedScreen.tsx +6 -84
  99. package/src/reanimated/ReanimatedScreenProvider.tsx +42 -0
  100. package/src/reanimated/index.tsx +1 -1
  101. package/src/types.tsx +101 -1
  102. package/src/utils.ts +12 -0
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
@@ -35,6 +35,26 @@ protected void onCreate(Bundle savedInstanceState) {
35
35
 
36
36
  For people that must handle cases like this, there is [a more detailed discussion of the difficulties in a series of related comments](https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704633).
37
37
 
38
+ <details>
39
+ <summary>Need to use a custom Kotlin version?</summary>
40
+ <br>
41
+
42
+ Since `v3.6.0` `react-native-screens` has been rewritten with Kotlin. Kotlin version used in this library defaults to `1.4.10`.
43
+
44
+ If you need to use a different Kotlin version, set `kotlinVersion` ext property in your project's `android/build.gradle` and the library will use this version accordingly:
45
+
46
+ ```
47
+ buildscript {
48
+ ext {
49
+ ...
50
+ kotlinVersion = "1.4.10"
51
+ }
52
+ }
53
+ ```
54
+
55
+ **Disclaimer**: `react-native-screens` requires Kotlin `1.3.50` or higher.
56
+ </details>
57
+
38
58
  ### Windows
39
59
 
40
60
  Installation on Windows should be completely handled with auto-linking when using React Native Windows 0.63+. For earlier versions, you must [manually link](https://microsoft.github.io/react-native-windows/docs/native-modules-using) the native module.
@@ -68,6 +88,24 @@ Just make sure that the version of [react-navigation](https://github.com/react-n
68
88
 
69
89
  You are all set 🎉 – when screens are enabled in your application code react-navigation will automatically use them instead of relying on plain React Native Views.
70
90
 
91
+ ### Experimental support for `react-freeze`
92
+
93
+ > You have to use React Native 0.64 or higher, react-navigation 5.x or 6.x and react-native-screens >= v3.9.0
94
+
95
+ Since `v3.9.0`, `react-native-screens` comes with experimental support for [`react-freeze`](https://github.com/software-mansion-labs/react-freeze). It uses the React `Suspense` mechanism to prevent parts of the React component tree from rendering, while keeping its state untouched.
96
+
97
+ To benefit from this feature, enable it in your entry file (e.g. `App.js`) with this snippet:
98
+
99
+ ```js
100
+ import { enableFreeze } from 'react-native-screens';
101
+
102
+ enableFreeze(true);
103
+ ```
104
+
105
+ Want to know more? Check out [react-freeze README](https://github.com/software-mansion-labs/react-freeze#readme)
106
+
107
+ Found a bug? File an issue [here](https://github.com/software-mansion/react-native-screens/issues) or directly in [react-freeze repository](https://github.com/software-mansion-labs/react-freeze/issues).
108
+
71
109
  ### Disabling `react-native-screens`
72
110
 
73
111
  If, for whatever reason, you'd like to disable native screens support and use plain React Native Views add the following code in your entry file (e.g. `App.js`):
@@ -85,8 +123,8 @@ You can also disable the usage of native screens per navigator with [`detachInac
85
123
  To take advantage of the native stack navigator primitive for React Navigation that leverages `UINavigationController` on iOS and `Fragment` on Android, please refer:
86
124
 
87
125
  - for React Navigation >= v6 to the [Native Stack Navigator part of React Navigation documentation](https://reactnavigation.org/docs/native-stack-navigator)
88
- - 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)
89
- - 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)
90
128
 
91
129
  ## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)
92
130
 
@@ -94,12 +132,12 @@ React-native-navigation library already uses native containers for rendering nav
94
132
 
95
133
  ## Interop with other libraries
96
134
 
97
- 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).
98
136
 
99
137
  ## Guide for navigation library authors
100
138
 
101
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.
102
- 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).
103
141
 
104
142
  ## Common problems
105
143
 
@@ -120,10 +158,11 @@ Use `ScrollView` with prop `contentInsetAdjustmentBehavior=“automatic”` as a
120
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) |
121
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) |
122
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) |
123
162
 
124
163
  ## Contributing
125
164
 
126
- 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!
127
166
 
128
167
  ## License
129
168
 
@@ -131,7 +170,8 @@ React native screens library is licensed under [The MIT License](LICENSE).
131
170
 
132
171
  ## Credits
133
172
 
134
- 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)
135
174
 
175
+ [![shopify](https://avatars1.githubusercontent.com/u/8085?v=3&s=100 'Shopify.com')](https://shopify.com)
136
176
  [![expo](https://avatars2.githubusercontent.com/u/12504344?v=3&s=100 'Expo.io')](https://expo.io)
137
- [![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)
@@ -4,7 +4,6 @@ buildscript {
4
4
  }
5
5
  repositories {
6
6
  google()
7
- jcenter()
8
7
  mavenCentral()
9
8
  }
10
9
  dependencies {
@@ -50,7 +49,6 @@ repositories {
50
49
  mavenCentral()
51
50
  mavenLocal()
52
51
  google()
53
- jcenter()
54
52
  }
55
53
 
56
54
  dependencies {
@@ -60,4 +58,5 @@ dependencies {
60
58
  implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
61
59
  implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
62
60
  implementation 'com.google.android.material:material:1.1.0'
61
+ implementation "androidx.core:core-ktx:1.5.0"
63
62
  }
@@ -0,0 +1,71 @@
1
+ package com.swmansion.rnscreens
2
+
3
+ import android.content.Context
4
+ import androidx.activity.OnBackPressedCallback
5
+ import androidx.appcompat.widget.SearchView
6
+ import androidx.fragment.app.Fragment
7
+
8
+ class CustomSearchView(context: Context, fragment: Fragment) : SearchView(context) {
9
+ /*
10
+ CustomSearchView uses some variables from SearchView. They are listed below with links to documentation
11
+ isIconified - https://developer.android.com/reference/android/widget/SearchView#setIconified(boolean)
12
+ maxWidth - https://developer.android.com/reference/android/widget/SearchView#setMaxWidth(int)
13
+ setOnSearchClickListener - https://developer.android.com/reference/android/widget/SearchView#setOnSearchClickListener(android.view.View.OnClickListener)
14
+ setOnCloseListener - https://developer.android.com/reference/android/widget/SearchView#setOnCloseListener(android.widget.SearchView.OnCloseListener)
15
+ */
16
+ private var mCustomOnCloseListener: OnCloseListener? = null
17
+ private var mCustomOnSearchClickedListener: OnClickListener? = null
18
+
19
+ private var mOnBackPressedCallback: OnBackPressedCallback =
20
+ object : OnBackPressedCallback(true) {
21
+ override fun handleOnBackPressed() {
22
+ isIconified = true
23
+ }
24
+ }
25
+ private val backPressOverrider = FragmentBackPressOverrider(fragment, mOnBackPressedCallback)
26
+ var overrideBackAction: Boolean
27
+ set(value) {
28
+ backPressOverrider.overrideBackAction = value
29
+ }
30
+ get() = backPressOverrider.overrideBackAction
31
+
32
+ fun focus() {
33
+ isIconified = false
34
+ requestFocusFromTouch()
35
+ }
36
+
37
+ override fun setOnCloseListener(listener: OnCloseListener?) {
38
+ mCustomOnCloseListener = listener
39
+ }
40
+
41
+ override fun setOnSearchClickListener(listener: OnClickListener?) {
42
+ mCustomOnSearchClickedListener = listener
43
+ }
44
+
45
+ override fun onAttachedToWindow() {
46
+ super.onAttachedToWindow()
47
+ if (!isIconified) {
48
+ backPressOverrider.maybeAddBackCallback()
49
+ }
50
+ }
51
+
52
+ override fun onDetachedFromWindow() {
53
+ super.onDetachedFromWindow()
54
+ backPressOverrider.removeBackCallbackIfAdded()
55
+ }
56
+
57
+ init {
58
+ super.setOnSearchClickListener { v ->
59
+ mCustomOnSearchClickedListener?.onClick(v)
60
+ backPressOverrider.maybeAddBackCallback()
61
+ }
62
+
63
+ super.setOnCloseListener {
64
+ val result = mCustomOnCloseListener?.onClose() ?: false
65
+ backPressOverrider.removeBackCallbackIfAdded()
66
+ result
67
+ }
68
+
69
+ maxWidth = Integer.MAX_VALUE
70
+ }
71
+ }
@@ -0,0 +1,7 @@
1
+ package com.swmansion.rnscreens
2
+
3
+ import android.content.Context
4
+ import androidx.appcompat.widget.Toolbar
5
+
6
+ // This class is used to store config closer to search bar
7
+ open class CustomToolbar(context: Context, val config: ScreenStackHeaderConfig) : Toolbar(context)
@@ -0,0 +1,29 @@
1
+ package com.swmansion.rnscreens
2
+
3
+ import androidx.activity.OnBackPressedCallback
4
+ import androidx.fragment.app.Fragment
5
+
6
+ class FragmentBackPressOverrider(
7
+ private val fragment: Fragment,
8
+ private val mOnBackPressedCallback: OnBackPressedCallback
9
+ ) {
10
+ private var mIsBackCallbackAdded: Boolean = false
11
+ var overrideBackAction: Boolean = true
12
+
13
+ fun maybeAddBackCallback() {
14
+ if (!mIsBackCallbackAdded && overrideBackAction) {
15
+ fragment.activity?.onBackPressedDispatcher?.addCallback(
16
+ fragment,
17
+ mOnBackPressedCallback
18
+ )
19
+ mIsBackCallbackAdded = true
20
+ }
21
+ }
22
+
23
+ fun removeBackCallbackIfAdded() {
24
+ if (mIsBackCallbackAdded) {
25
+ mOnBackPressedCallback.remove()
26
+ mIsBackCallbackAdded = false
27
+ }
28
+ }
29
+ }
@@ -15,6 +15,7 @@ class RNScreensPackage : ReactPackage {
15
15
  ScreenViewManager(),
16
16
  ScreenStackViewManager(),
17
17
  ScreenStackHeaderConfigViewManager(),
18
- ScreenStackHeaderSubviewManager()
18
+ ScreenStackHeaderSubviewManager(),
19
+ SearchBarManager()
19
20
  )
20
21
  }
@@ -1,18 +1,13 @@
1
1
  package com.swmansion.rnscreens
2
2
 
3
3
  import android.annotation.SuppressLint
4
- import android.content.Context
5
4
  import android.content.pm.ActivityInfo
6
5
  import android.graphics.Paint
7
- import android.os.Build
8
6
  import android.os.Parcelable
9
7
  import android.util.SparseArray
10
- import android.view.View
11
8
  import android.view.ViewGroup
12
9
  import android.view.WindowManager
13
- import android.view.inputmethod.InputMethodManager
14
10
  import android.webkit.WebView
15
- import android.widget.TextView
16
11
  import com.facebook.react.bridge.GuardedRunnable
17
12
  import com.facebook.react.bridge.ReactContext
18
13
  import com.facebook.react.uimanager.UIManagerModule
@@ -34,6 +29,8 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
34
29
  private var mStatusBarHidden: Boolean? = null
35
30
  private var mStatusBarTranslucent: Boolean? = null
36
31
  private var mStatusBarColor: Int? = null
32
+ private var mNavigationBarColor: Int? = null
33
+ private var mNavigationBarHidden: Boolean? = null
37
34
  var isStatusBarAnimated: Boolean? = null
38
35
  private var mNativeBackButtonDismissalEnabled = true
39
36
 
@@ -51,16 +48,6 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
51
48
  layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION)
52
49
  }
53
50
 
54
- override fun onAnimationStart() {
55
- super.onAnimationStart()
56
- fragment?.onViewAnimationStart()
57
- }
58
-
59
- override fun onAnimationEnd() {
60
- super.onAnimationEnd()
61
- fragment?.onViewAnimationEnd()
62
- }
63
-
64
51
  override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
65
52
  // do nothing, react native will keep the view hierarchy so no need to serialize/deserialize
66
53
  // view's states. The side effect of restoring is that TextInput components would trigger
@@ -87,28 +74,6 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
87
74
  }
88
75
  }
89
76
 
90
- override fun onAttachedToWindow() {
91
- super.onAttachedToWindow()
92
- // This method implements a workaround for RN's autoFocus functionality. Because of the way
93
- // autoFocus is implemented it sometimes gets triggered before native text view is mounted. As
94
- // a result Android ignores calls for opening soft keyboard and here we trigger it manually
95
- // again after the screen is attached.
96
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
97
- var view = focusedChild
98
- if (view != null) {
99
- while (view is ViewGroup) {
100
- view = view.focusedChild
101
- }
102
- if (view is TextView) {
103
- val textView = view
104
- if (textView.showSoftInputOnFocus) {
105
- textView.addOnAttachStateChangeListener(sShowSoftKeyboardOnAttach)
106
- }
107
- }
108
- }
109
- }
110
- }
111
-
112
77
  val headerConfig: ScreenStackHeaderConfig?
113
78
  get() {
114
79
  val child = getChildAt(0)
@@ -183,6 +148,13 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
183
148
  fragment?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) }
184
149
  }
185
150
 
151
+ // Accepts one of 4 accessibility flags
152
+ // developer.android.com/reference/android/view/View#attr_android:importantForAccessibility
153
+ fun changeAccessibilityMode(mode: Int) {
154
+ this.importantForAccessibility = mode
155
+ this.headerConfig?.toolbar?.importantForAccessibility = mode
156
+ }
157
+
186
158
  var statusBarStyle: String?
187
159
  get() = mStatusBarStyle
188
160
  set(statusBarStyle) {
@@ -229,6 +201,31 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
229
201
  fragment?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) }
230
202
  }
231
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
+
232
229
  var nativeBackButtonDismissalEnabled: Boolean
233
230
  get() = mNativeBackButtonDismissalEnabled
234
231
  set(enableNativeBackButtonDismissal) {
@@ -252,20 +249,6 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
252
249
  }
253
250
 
254
251
  enum class WindowTraits {
255
- ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED
256
- }
257
-
258
- companion object {
259
- private val sShowSoftKeyboardOnAttach: OnAttachStateChangeListener =
260
- object : OnAttachStateChangeListener {
261
- override fun onViewAttachedToWindow(view: View) {
262
- val inputMethodManager =
263
- view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
264
- inputMethodManager.showSoftInput(view, 0)
265
- view.removeOnAttachStateChangeListener(this)
266
- }
267
-
268
- override fun onViewDetachedFromWindow(view: View) {}
269
- }
252
+ ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED, NAVIGATION_BAR_COLOR, NAVIGATION_BAR_HIDDEN
270
253
  }
271
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
 
@@ -2,6 +2,7 @@ package com.swmansion.rnscreens
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.app.Activity
5
+ import android.content.Context
5
6
  import android.os.Bundle
6
7
  import android.view.LayoutInflater
7
8
  import android.view.View
@@ -37,6 +38,17 @@ open class ScreenFragment : Fragment {
37
38
  // due to progress value being already 0.0f
38
39
  private var mProgress = -1f
39
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
+
40
52
  constructor() {
41
53
  throw IllegalStateException(
42
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."
@@ -61,7 +73,7 @@ open class ScreenFragment : Fragment {
61
73
  container: ViewGroup?,
62
74
  savedInstanceState: Bundle?
63
75
  ): View? {
64
- val wrapper = context?.let { FrameLayout(it) }
76
+ val wrapper = context?.let { ScreensFrameLayout(it) }
65
77
 
66
78
  val params = FrameLayout.LayoutParams(
67
79
  ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
@@ -71,6 +83,23 @@ open class ScreenFragment : Fragment {
71
83
  return wrapper
72
84
  }
73
85
 
86
+ private class ScreensFrameLayout(
87
+ context: Context,
88
+ ) : FrameLayout(context) {
89
+ /**
90
+ * This method implements a workaround for RN's autoFocus functionality. Because of the way
91
+ * autoFocus is implemented it dismisses soft keyboard in fragment transition
92
+ * due to change of visibility of the view at the start of the transition. Here we override the
93
+ * call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the
94
+ * hiding of the keyboard in `ReactEditText.java`.
95
+ */
96
+ override fun clearFocus() {
97
+ if (visibility != INVISIBLE) {
98
+ super.clearFocus()
99
+ }
100
+ }
101
+ }
102
+
74
103
  open fun onContainerUpdate() {
75
104
  updateWindowTraits()
76
105
  }
@@ -123,33 +152,52 @@ open class ScreenFragment : Fragment {
123
152
  val childScreenContainers: List<ScreenContainer<*>>
124
153
  get() = mChildScreenContainers
125
154
 
126
- 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() {
127
174
  dispatchEvent(ScreenLifecycleEvent.WillAppear, this)
128
175
 
129
176
  dispatchTransitionProgress(0.0f, false)
130
177
  }
131
178
 
132
- fun dispatchOnAppear() {
179
+ private fun dispatchOnAppear() {
133
180
  dispatchEvent(ScreenLifecycleEvent.Appear, this)
134
181
 
135
182
  dispatchTransitionProgress(1.0f, false)
136
183
  }
137
184
 
138
- protected fun dispatchOnWillDisappear() {
185
+ private fun dispatchOnWillDisappear() {
139
186
  dispatchEvent(ScreenLifecycleEvent.WillDisappear, this)
140
187
 
141
188
  dispatchTransitionProgress(0.0f, true)
142
189
  }
143
190
 
144
- protected fun dispatchOnDisappear() {
191
+ private fun dispatchOnDisappear() {
145
192
  dispatchEvent(ScreenLifecycleEvent.Disappear, this)
146
193
 
147
194
  dispatchTransitionProgress(1.0f, true)
148
195
  }
149
196
 
150
197
  private fun dispatchEvent(event: ScreenLifecycleEvent, fragment: ScreenFragment) {
151
- if (fragment is ScreenStackFragment) {
198
+ if (fragment is ScreenStackFragment && fragment.canDispatchEvent(event)) {
152
199
  fragment.screen.let {
200
+ fragment.setLastEventDispatched(event)
153
201
  val lifecycleEvent: Event<*> = when (event) {
154
202
  ScreenLifecycleEvent.WillAppear -> ScreenWillAppearEvent(it.id)
155
203
  ScreenLifecycleEvent.Appear -> ScreenAppearEvent(it.id)
@@ -169,12 +217,7 @@ open class ScreenFragment : Fragment {
169
217
  for (sc in mChildScreenContainers) {
170
218
  if (sc.screenCount > 0) {
171
219
  sc.topScreen?.let {
172
- if (it.stackAnimation !== Screen.StackAnimation.NONE || isRemoving) {
173
- // we do not dispatch events in child when it has `none` animation
174
- // and we are going forward since then they will be dispatched in child via
175
- // `onCreateAnimation` of ScreenStackFragment
176
- sc.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) }
177
- }
220
+ sc.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) }
178
221
  }
179
222
  }
180
223
  }
@@ -220,31 +263,37 @@ open class ScreenFragment : Fragment {
220
263
  }
221
264
 
222
265
  fun onViewAnimationStart() {
223
- // onViewAnimationStart is triggered from View#onAnimationStart method of the fragment's root
224
- // view. We override Screen#onAnimationStart and an appropriate method of the StackFragment's
225
- // root view in order to achieve this.
226
- if (isResumed) {
227
- // Android dispatches the animation start event for the fragment that is being added first
228
- // however we want the one being dismissed first to match iOS. It also makes more sense from
229
- // a navigation point of view to have the disappear event first.
230
- // Since there are no explicit relationships between the fragment being added / removed the
231
- // practical way to fix this is delaying dispatching the appear events at the end of the
232
- // frame.
233
- UiThreadUtil.runOnUiThread { dispatchOnWillAppear() }
234
- } else {
235
- dispatchOnWillDisappear()
236
- }
266
+ dispatchViewAnimationEvent(false)
237
267
  }
238
268
 
239
269
  open fun onViewAnimationEnd() {
240
- // onViewAnimationEnd is triggered from View#onAnimationEnd method of the fragment's root view.
241
- // We override Screen#onAnimationEnd and an appropriate method of the StackFragment's root view
242
- // in order to achieve this.
243
- if (isResumed) {
244
- // See the comment in onViewAnimationStart for why this event is delayed.
245
- UiThreadUtil.runOnUiThread { dispatchOnAppear() }
246
- } else {
247
- 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
+ }
248
297
  }
249
298
  }
250
299