react-native-screens 3.7.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -3
- package/android/build.gradle +12 -21
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +55 -40
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +2 -100
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +0 -3
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +1 -1
- package/ios/RNSScreen.h +1 -0
- package/ios/RNSScreen.m +31 -0
- package/ios/RNSScreenContainer.h +2 -0
- package/ios/RNSScreenStack.m +17 -0
- package/ios/RNSScreenStackHeaderConfig.m +45 -2
- package/ios/RNSScreenWindowTraits.h +5 -0
- package/ios/RNSScreenWindowTraits.m +29 -0
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.native.js +50 -12
- package/lib/commonjs/index.native.js.map +1 -1
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.native.js +47 -13
- package/lib/module/index.native.js.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/native-stack/README.md +4 -2
- package/package.json +4 -2
- package/src/index.native.tsx +57 -20
- package/src/index.tsx +6 -0
package/README.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
<img src="https://user-images.githubusercontent.com/16062886/117443651-c13d9500-af38-11eb-888d-b6a0b580760c.png" width="100%" alt="React Native Screens by Software Mansion" >
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
This project aims to expose native navigation container components to React Native. It is not designed to be used as a standalone library but rather as a dependency of a [full-featured navigation library](https://github.com/react-navigation/react-navigation).
|
|
5
4
|
|
|
5
|
+
## Supported platforms
|
|
6
|
+
|
|
7
|
+
- [x] iOS
|
|
8
|
+
- [x] Android
|
|
9
|
+
- [x] tvOS
|
|
10
|
+
- [x] Windows
|
|
11
|
+
- [x] Web
|
|
12
|
+
|
|
6
13
|
## Installation
|
|
7
14
|
|
|
8
15
|
### iOS
|
|
@@ -76,9 +83,10 @@ You can also disable the usage of native screens per navigator with [`detachInac
|
|
|
76
83
|
### Using `createNativeStackNavigator` with React Navigation
|
|
77
84
|
|
|
78
85
|
To take advantage of the native stack navigator primitive for React Navigation that leverages `UINavigationController` on iOS and `Fragment` on Android, please refer:
|
|
86
|
+
|
|
79
87
|
- for React Navigation >= v6 to the [Native Stack Navigator part of React Navigation documentation](https://reactnavigation.org/docs/native-stack-navigator)
|
|
80
|
-
- 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)
|
|
81
|
-
- for older versions to the [README in react-native-screens/createNativeStackNavigator](https://github.com/software-mansion/react-native-screens/tree/master/createNativeStackNavigator)
|
|
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)
|
|
82
90
|
|
|
83
91
|
## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)
|
|
84
92
|
|
|
@@ -93,6 +101,26 @@ This library should work out of the box with all existing react-native libraries
|
|
|
93
101
|
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.
|
|
94
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).
|
|
95
103
|
|
|
104
|
+
## Common problems
|
|
105
|
+
|
|
106
|
+
### Problems with header on iOS
|
|
107
|
+
|
|
108
|
+
- [Focused search bar causes new screens to have incorrect header](https://github.com/software-mansion/react-native-screens/issues/996)
|
|
109
|
+
- [Scrollable content gets cut off by the header with a search bar](https://github.com/software-mansion/react-native-screens/issues/1120)
|
|
110
|
+
- [RefreshControl does not work properly with NativeStackNavigator and largeTitle](https://github.com/software-mansion/react-native-screens/issues/395)
|
|
111
|
+
|
|
112
|
+
#### Solution
|
|
113
|
+
|
|
114
|
+
Use `ScrollView` with prop `contentInsetAdjustmentBehavior=“automatic”` as a main container of the screen and set `headerTranslucent: true` in screen options.
|
|
115
|
+
|
|
116
|
+
### Other problems
|
|
117
|
+
|
|
118
|
+
| Problem | Solution |
|
|
119
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| [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
|
+
| [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
|
+
| [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) |
|
|
123
|
+
|
|
96
124
|
## Contributing
|
|
97
125
|
|
|
98
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!
|
package/android/build.gradle
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
buildscript {
|
|
2
|
+
ext.safeExtGet = {prop, fallback ->
|
|
3
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
4
|
+
}
|
|
2
5
|
repositories {
|
|
3
6
|
google()
|
|
4
7
|
jcenter()
|
|
@@ -6,21 +9,19 @@ buildscript {
|
|
|
6
9
|
}
|
|
7
10
|
dependencies {
|
|
8
11
|
classpath('com.android.tools.build:gradle:4.2.2')
|
|
9
|
-
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin
|
|
12
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.4.10')}"
|
|
13
|
+
classpath "com.diffplug.spotless:spotless-plugin-gradle:5.15.0"
|
|
10
14
|
}
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
// spotless is only accessible within react-native-screens repo
|
|
18
|
+
if (project == rootProject) {
|
|
19
|
+
apply from: 'spotless.gradle'
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
apply plugin: 'com.android.library'
|
|
18
23
|
apply plugin: 'kotlin-android'
|
|
19
24
|
|
|
20
|
-
def safeExtGet(prop, fallback) {
|
|
21
|
-
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
22
|
-
}
|
|
23
|
-
|
|
24
25
|
android {
|
|
25
26
|
compileSdkVersion safeExtGet('compileSdkVersion', 28)
|
|
26
27
|
|
|
@@ -33,6 +34,10 @@ android {
|
|
|
33
34
|
lintOptions {
|
|
34
35
|
abortOnError false
|
|
35
36
|
}
|
|
37
|
+
compileOptions {
|
|
38
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
39
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
40
|
+
}
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
repositories {
|
|
@@ -55,18 +60,4 @@ dependencies {
|
|
|
55
60
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
|
56
61
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
|
|
57
62
|
implementation 'com.google.android.material:material:1.1.0'
|
|
58
|
-
implementation "androidx.core:core-ktx:1.6.0"
|
|
59
|
-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.20"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
plugins.apply("com.diffplug.spotless")
|
|
63
|
-
|
|
64
|
-
spotless {
|
|
65
|
-
kotlin {
|
|
66
|
-
target 'src/**/*.kt'
|
|
67
|
-
ktlint("0.40.0")
|
|
68
|
-
trimTrailingWhitespace()
|
|
69
|
-
indentWithSpaces()
|
|
70
|
-
endWithNewline()
|
|
71
|
-
}
|
|
72
63
|
}
|
|
@@ -11,6 +11,7 @@ import androidx.fragment.app.FragmentActivity
|
|
|
11
11
|
import androidx.fragment.app.FragmentManager
|
|
12
12
|
import androidx.fragment.app.FragmentTransaction
|
|
13
13
|
import com.facebook.react.ReactRootView
|
|
14
|
+
import com.facebook.react.bridge.ReactContext
|
|
14
15
|
import com.facebook.react.modules.core.ChoreographerCompat
|
|
15
16
|
import com.facebook.react.modules.core.ReactChoreographer
|
|
16
17
|
import com.swmansion.rnscreens.Screen.ActivityState
|
|
@@ -176,16 +177,12 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
|
|
|
176
177
|
return transaction
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
private fun attachScreen(screenFragment: ScreenFragment) {
|
|
180
|
-
|
|
180
|
+
private fun attachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) {
|
|
181
|
+
transaction.add(id, screenFragment)
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
private fun
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private fun detachScreen(screenFragment: ScreenFragment) {
|
|
188
|
-
createTransaction().remove(screenFragment).commitNowAllowingStateLoss()
|
|
184
|
+
private fun detachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) {
|
|
185
|
+
transaction.remove(screenFragment)
|
|
189
186
|
}
|
|
190
187
|
|
|
191
188
|
private fun getActivityState(screenFragment: ScreenFragment): ActivityState? {
|
|
@@ -276,10 +273,18 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
|
|
|
276
273
|
// The exception to this rule is `updateImmediately` which is triggered by actions
|
|
277
274
|
// not connected to React view hierarchy changes, but rather internal events
|
|
278
275
|
mNeedUpdate = true
|
|
276
|
+
(context as? ReactContext)?.runOnUiQueueThread {
|
|
277
|
+
// We schedule the update here because LayoutAnimations of `react-native-reanimated`
|
|
278
|
+
// sometimes attach/detach screens after the layout block of `ScreensShadowNode` has
|
|
279
|
+
// already run, and we want to update the container then too. In the other cases,
|
|
280
|
+
// this code will do nothing since it will run after the UIBlock when `mNeedUpdate`
|
|
281
|
+
// will already be false.
|
|
282
|
+
performUpdates()
|
|
283
|
+
}
|
|
279
284
|
}
|
|
280
285
|
|
|
281
286
|
protected fun performUpdatesNow() {
|
|
282
|
-
// we want to update
|
|
287
|
+
// we want to update immediately when the fragment manager is set or native back button
|
|
283
288
|
// dismiss is dispatched or Screen's activityState changes since it is not connected to React
|
|
284
289
|
// view hierarchy changes and will not trigger `onBeforeLayout` method of `ScreensShadowNode`
|
|
285
290
|
mNeedUpdate = true
|
|
@@ -287,7 +292,7 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
|
|
|
287
292
|
}
|
|
288
293
|
|
|
289
294
|
fun performUpdates() {
|
|
290
|
-
if (!mNeedUpdate || !mIsAttached || mFragmentManager == null) {
|
|
295
|
+
if (!mNeedUpdate || !mIsAttached || mFragmentManager == null || mFragmentManager?.isDestroyed == true) {
|
|
291
296
|
return
|
|
292
297
|
}
|
|
293
298
|
mNeedUpdate = false
|
|
@@ -296,43 +301,53 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
|
|
|
296
301
|
}
|
|
297
302
|
|
|
298
303
|
open fun onUpdate() {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
screenFragment.
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
createTransaction().let {
|
|
305
|
+
// detach screens that are no longer active
|
|
306
|
+
val orphaned: MutableSet<Fragment> = HashSet(requireNotNull(mFragmentManager, { "mFragmentManager is null when performing update in ScreenContainer" }).fragments)
|
|
307
|
+
for (screenFragment in mScreenFragments) {
|
|
308
|
+
if (getActivityState(screenFragment) === ActivityState.INACTIVE &&
|
|
309
|
+
screenFragment.isAdded
|
|
310
|
+
) {
|
|
311
|
+
detachScreen(it, screenFragment)
|
|
312
|
+
}
|
|
313
|
+
orphaned.remove(screenFragment)
|
|
306
314
|
}
|
|
307
|
-
orphaned.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
detachScreen(fragment)
|
|
315
|
+
if (orphaned.isNotEmpty()) {
|
|
316
|
+
val orphanedAry = orphaned.toTypedArray()
|
|
317
|
+
for (fragment in orphanedAry) {
|
|
318
|
+
if (fragment is ScreenFragment) {
|
|
319
|
+
if (fragment.screen.container == null) {
|
|
320
|
+
detachScreen(it, fragment)
|
|
321
|
+
}
|
|
315
322
|
}
|
|
316
323
|
}
|
|
317
324
|
}
|
|
318
|
-
|
|
319
|
-
var transitioning = true
|
|
320
|
-
if (topScreen != null) {
|
|
325
|
+
|
|
321
326
|
// if there is an "onTop" screen it means the transition has ended
|
|
322
|
-
transitioning =
|
|
323
|
-
}
|
|
327
|
+
val transitioning = topScreen == null
|
|
324
328
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
329
|
+
// attach newly activated screens
|
|
330
|
+
var addedBefore = false
|
|
331
|
+
val pendingFront: ArrayList<T> = ArrayList()
|
|
332
|
+
|
|
333
|
+
for (screenFragment in mScreenFragments) {
|
|
334
|
+
val activityState = getActivityState(screenFragment)
|
|
335
|
+
if (activityState !== ActivityState.INACTIVE && !screenFragment.isAdded) {
|
|
336
|
+
addedBefore = true
|
|
337
|
+
attachScreen(it, screenFragment)
|
|
338
|
+
} else if (activityState !== ActivityState.INACTIVE && addedBefore) {
|
|
339
|
+
// we detach the screen and then reattach it later to make it appear on front
|
|
340
|
+
detachScreen(it, screenFragment)
|
|
341
|
+
pendingFront.add(screenFragment)
|
|
342
|
+
}
|
|
343
|
+
screenFragment.screen.setTransitioning(transitioning)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
for (screenFragment in pendingFront) {
|
|
347
|
+
attachScreen(it, screenFragment)
|
|
334
348
|
}
|
|
335
|
-
|
|
349
|
+
|
|
350
|
+
it.commitNowAllowingStateLoss()
|
|
336
351
|
}
|
|
337
352
|
}
|
|
338
353
|
|
|
@@ -3,8 +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.Fragment
|
|
7
|
-
import androidx.fragment.app.FragmentManager
|
|
8
6
|
import androidx.fragment.app.FragmentTransaction
|
|
9
7
|
import com.facebook.react.bridge.ReactContext
|
|
10
8
|
import com.facebook.react.uimanager.UIManagerModule
|
|
@@ -20,21 +18,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
20
18
|
private val drawingOpPool: MutableList<DrawingOp> = ArrayList()
|
|
21
19
|
private val drawingOps: MutableList<DrawingOp> = ArrayList()
|
|
22
20
|
private var mTopScreen: ScreenStackFragment? = null
|
|
23
|
-
private val mBackStackListener = FragmentManager.OnBackStackChangedListener {
|
|
24
|
-
if (mFragmentManager?.backStackEntryCount == 0) {
|
|
25
|
-
// when back stack entry count hits 0 it means the user's navigated back using hw back
|
|
26
|
-
// button. As the "fake" transaction we installed on the back stack does nothing we need
|
|
27
|
-
// to handle back navigation on our own.
|
|
28
|
-
mTopScreen?.let { dismiss(it) }
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
private val mLifecycleCallbacks: FragmentManager.FragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
|
|
32
|
-
override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
|
|
33
|
-
if (mTopScreen === f) {
|
|
34
|
-
setupBackHandlerIfNeeded(f)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
21
|
private var mRemovalTransitionStarted = false
|
|
39
22
|
private var isDetachingCurrentScreen = false
|
|
40
23
|
private var reverseLastTwoChildren = false
|
|
@@ -65,28 +48,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
65
48
|
return ScreenStackFragment(screen)
|
|
66
49
|
}
|
|
67
50
|
|
|
68
|
-
override fun onDetachedFromWindow() {
|
|
69
|
-
mFragmentManager?.let {
|
|
70
|
-
it.removeOnBackStackChangedListener(mBackStackListener)
|
|
71
|
-
it.unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks)
|
|
72
|
-
if (!it.isStateSaved && !it.isDestroyed) {
|
|
73
|
-
// State save means that the container where fragment manager was installed has been
|
|
74
|
-
// unmounted.
|
|
75
|
-
// This could happen as a result of dismissing nested stack. In such a case we don't need to
|
|
76
|
-
// reset back stack as it'd result in a crash caused by the fact the fragment manager is no
|
|
77
|
-
// longer attached.
|
|
78
|
-
it.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
super.onDetachedFromWindow()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
override fun onAttachedToWindow() {
|
|
85
|
-
super.onAttachedToWindow()
|
|
86
|
-
val fragmentManager = requireNotNull(mFragmentManager, { "mFragmentManager is null when ScreenStack attached to window" })
|
|
87
|
-
fragmentManager.registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
51
|
override fun startViewTransition(view: View) {
|
|
91
52
|
super.startViewTransition(view)
|
|
92
53
|
mRemovalTransitionStarted = true
|
|
@@ -159,10 +120,8 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
159
120
|
// if the previous top screen does not exist anymore and the new top was not on the stack
|
|
160
121
|
// before, probably replace or reset was called, so we play the "close animation".
|
|
161
122
|
// Otherwise it's open animation
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
newTop.screen.replaceAnimation !== Screen.ReplaceAnimation.POP
|
|
165
|
-
)
|
|
123
|
+
val containsTopScreen = mTopScreen?.let { mScreenFragments.contains(it) } == true
|
|
124
|
+
shouldUseOpenAnimation = containsTopScreen || newTop.screen.replaceAnimation !== Screen.ReplaceAnimation.POP
|
|
166
125
|
stackAnimation = newTop.screen.stackAnimation
|
|
167
126
|
} else if (mTopScreen == null && newTop != null) {
|
|
168
127
|
// mTopScreen was not present before so newTop is the first screen added to a stack
|
|
@@ -278,7 +237,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
278
237
|
mStack.clear()
|
|
279
238
|
mStack.addAll(mScreenFragments)
|
|
280
239
|
it.commitNowAllowingStateLoss()
|
|
281
|
-
mTopScreen?.let { screen -> setupBackHandlerIfNeeded(screen) }
|
|
282
240
|
}
|
|
283
241
|
}
|
|
284
242
|
|
|
@@ -288,61 +246,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
288
246
|
}
|
|
289
247
|
}
|
|
290
248
|
|
|
291
|
-
/**
|
|
292
|
-
* The below method sets up fragment manager's back stack in a way that it'd trigger our back
|
|
293
|
-
* stack change listener when hw back button is clicked.
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* Because back stack by default rolls back the transaction the stack entry is associated with
|
|
297
|
-
* we generate a "fake" transaction that hides and shows the top fragment. As a result when back
|
|
298
|
-
* stack entry is rolled back nothing happens and we are free to handle back navigation on our own
|
|
299
|
-
* in `mBackStackListener`.
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* We pop that "fake" transaction each time we update stack and we add a new one in case the
|
|
303
|
-
* top screen is allowed to be dismissed using hw back button. This way in the listener we can
|
|
304
|
-
* tell if back button was pressed based on the count of the items on back stack. We expect 0
|
|
305
|
-
* items in case hw back is pressed because we try to keep the number of items at 1 by always
|
|
306
|
-
* resetting and adding new items. In case we don't add a new item to back stack we remove
|
|
307
|
-
* listener so that it does not get triggered.
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* It is important that we don't install back handler when stack contains a single screen as in
|
|
311
|
-
* that case we want the parent navigator or activity handler to take over.
|
|
312
|
-
*/
|
|
313
|
-
private fun setupBackHandlerIfNeeded(topScreen: ScreenStackFragment) {
|
|
314
|
-
if (mTopScreen?.isResumed != true) {
|
|
315
|
-
// if the top fragment is not in a resumed state, adding back stack transaction would throw.
|
|
316
|
-
// In such a case we skip installing back handler and use FragmentLifecycleCallbacks to get
|
|
317
|
-
// notified when it gets resumed so that we can install the handler.
|
|
318
|
-
return
|
|
319
|
-
}
|
|
320
|
-
mFragmentManager?.let {
|
|
321
|
-
it.removeOnBackStackChangedListener(mBackStackListener)
|
|
322
|
-
it.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
|
323
|
-
var firstScreen: ScreenStackFragment? = null
|
|
324
|
-
var i = 0
|
|
325
|
-
val size = mStack.size
|
|
326
|
-
while (i < size) {
|
|
327
|
-
val screen = mStack[i]
|
|
328
|
-
if (!mDismissed.contains(screen)) {
|
|
329
|
-
firstScreen = screen
|
|
330
|
-
break
|
|
331
|
-
}
|
|
332
|
-
i++
|
|
333
|
-
}
|
|
334
|
-
if (topScreen !== firstScreen && topScreen.isDismissible) {
|
|
335
|
-
it
|
|
336
|
-
.beginTransaction()
|
|
337
|
-
.show(topScreen)
|
|
338
|
-
.addToBackStack(BACK_STACK_TAG)
|
|
339
|
-
.setPrimaryNavigationFragment(topScreen)
|
|
340
|
-
.commitNowAllowingStateLoss()
|
|
341
|
-
it.addOnBackStackChangedListener(mBackStackListener)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
249
|
// below methods are taken from
|
|
347
250
|
// https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L43
|
|
348
251
|
// and are used to swap the order of drawing views when navigating forward with the transitions
|
|
@@ -418,7 +321,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
418
321
|
}
|
|
419
322
|
|
|
420
323
|
companion object {
|
|
421
|
-
private const val BACK_STACK_TAG = "RN_SCREEN_LAST"
|
|
422
324
|
private fun isSystemAnimation(stackAnimation: StackAnimation): Boolean {
|
|
423
325
|
return stackAnimation === StackAnimation.DEFAULT || stackAnimation === StackAnimation.FADE || stackAnimation === StackAnimation.NONE
|
|
424
326
|
}
|
|
@@ -145,9 +145,6 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
145
145
|
return view
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
val isDismissible: Boolean
|
|
149
|
-
get() = screen.isGestureEnabled
|
|
150
|
-
|
|
151
148
|
fun canNavigateBack(): Boolean {
|
|
152
149
|
val container: ScreenContainer<*>? = screen.container
|
|
153
150
|
check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
|
|
@@ -369,7 +369,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
369
369
|
mDirection = direction
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
private class DebugMenuToolbar(context: Context
|
|
372
|
+
private class DebugMenuToolbar(context: Context) : Toolbar(context) {
|
|
373
373
|
override fun showOverflowMenu(): Boolean {
|
|
374
374
|
(context.applicationContext as ReactApplication)
|
|
375
375
|
.reactNativeHost
|
package/ios/RNSScreen.h
CHANGED
|
@@ -65,6 +65,7 @@ typedef NS_ENUM(NSInteger, RNSWindowTrait) {
|
|
|
65
65
|
|
|
66
66
|
- (instancetype)initWithView:(UIView *)view;
|
|
67
67
|
- (void)notifyFinishTransitioning;
|
|
68
|
+
- (UIViewController *)findChildVCForConfigAndTrait:(RNSWindowTrait)trait includingModals:(BOOL)includingModals;
|
|
68
69
|
|
|
69
70
|
@end
|
|
70
71
|
|
package/ios/RNSScreen.m
CHANGED
|
@@ -564,6 +564,8 @@
|
|
|
564
564
|
_shouldNotify = NO;
|
|
565
565
|
}
|
|
566
566
|
|
|
567
|
+
[self hideHeaderIfNecessary];
|
|
568
|
+
|
|
567
569
|
// as per documentation of these methods
|
|
568
570
|
_goingForward = [self isBeingPresented] || [self isMovingToParentViewController];
|
|
569
571
|
|
|
@@ -575,6 +577,35 @@
|
|
|
575
577
|
}
|
|
576
578
|
}
|
|
577
579
|
|
|
580
|
+
- (void)hideHeaderIfNecessary
|
|
581
|
+
{
|
|
582
|
+
#if !TARGET_OS_TV
|
|
583
|
+
// On iOS >=13, there is a bug when user transitions from screen with active search bar to screen without header
|
|
584
|
+
// In that case default iOS header will be shown. To fix this we hide header when the screens that appears has header
|
|
585
|
+
// hidden and search bar was active on previous screen. We need to do it asynchronously, because default header is
|
|
586
|
+
// added after viewWillAppear.
|
|
587
|
+
if (@available(iOS 13.0, *)) {
|
|
588
|
+
NSUInteger currentIndex = [self.navigationController.viewControllers indexOfObject:self];
|
|
589
|
+
|
|
590
|
+
if (currentIndex > 0 && [self.view.reactSubviews[0] isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
|
591
|
+
UINavigationItem *prevNavigationItem =
|
|
592
|
+
[self.navigationController.viewControllers objectAtIndex:currentIndex - 1].navigationItem;
|
|
593
|
+
RNSScreenStackHeaderConfig *config = ((RNSScreenStackHeaderConfig *)self.view.reactSubviews[0]);
|
|
594
|
+
|
|
595
|
+
BOOL wasSearchBarActive = prevNavigationItem.searchController.active;
|
|
596
|
+
BOOL shouldHideHeader = config.hide;
|
|
597
|
+
|
|
598
|
+
if (wasSearchBarActive && shouldHideHeader) {
|
|
599
|
+
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0);
|
|
600
|
+
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
|
601
|
+
[self.navigationController setNavigationBarHidden:YES animated:NO];
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
#endif
|
|
607
|
+
}
|
|
608
|
+
|
|
578
609
|
- (void)viewWillDisappear:(BOOL)animated
|
|
579
610
|
{
|
|
580
611
|
[super viewWillDisappear:animated];
|
package/ios/RNSScreenContainer.h
CHANGED
package/ios/RNSScreenStack.m
CHANGED
|
@@ -489,6 +489,23 @@
|
|
|
489
489
|
[self setModalViewControllers:modalControllers];
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
// By default, the header buttons that are not inside the native hit area
|
|
493
|
+
// cannot be clicked, so we check it by ourselves
|
|
494
|
+
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
|
495
|
+
{
|
|
496
|
+
if (CGRectContainsPoint(_controller.navigationBar.frame, point)) {
|
|
497
|
+
// headerConfig should be the first subview of the topmost screen
|
|
498
|
+
UIView *headerConfig = [[_reactSubviews.lastObject reactSubviews] firstObject];
|
|
499
|
+
if ([headerConfig isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
|
500
|
+
UIView *headerHitTestResult = [headerConfig hitTest:point withEvent:event];
|
|
501
|
+
if (headerHitTestResult != nil) {
|
|
502
|
+
return headerHitTestResult;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return [super hitTest:point withEvent:event];
|
|
507
|
+
}
|
|
508
|
+
|
|
492
509
|
- (void)layoutSubviews
|
|
493
510
|
{
|
|
494
511
|
[super layoutSubviews];
|
|
@@ -118,6 +118,27 @@
|
|
|
118
118
|
_screenView = nil;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// this method is never invoked by the system since this view
|
|
122
|
+
// is not added to native view hierarchy so we can apply our logic
|
|
123
|
+
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
|
124
|
+
{
|
|
125
|
+
for (RNSScreenStackHeaderSubview *subview in _reactSubviews) {
|
|
126
|
+
if (subview.type == RNSScreenStackHeaderSubviewTypeLeft || subview.type == RNSScreenStackHeaderSubviewTypeRight) {
|
|
127
|
+
// we wrap the headerLeft/Right component in a UIBarButtonItem
|
|
128
|
+
// so we need to use the only subview of it to retrieve the correct view
|
|
129
|
+
UIView *headerComponent = subview.subviews.firstObject;
|
|
130
|
+
// we convert the point to RNSScreenStackView since it always contains the header inside it
|
|
131
|
+
CGPoint convertedPoint = [_screenView.reactSuperview convertPoint:point toView:headerComponent];
|
|
132
|
+
|
|
133
|
+
UIView *hitTestResult = [headerComponent hitTest:convertedPoint withEvent:event];
|
|
134
|
+
if (hitTestResult != nil) {
|
|
135
|
+
return hitTestResult;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return nil;
|
|
140
|
+
}
|
|
141
|
+
|
|
121
142
|
- (void)updateViewControllerIfNeeded
|
|
122
143
|
{
|
|
123
144
|
UIViewController *vc = _screenView.controller;
|
|
@@ -138,10 +159,23 @@
|
|
|
138
159
|
}
|
|
139
160
|
}
|
|
140
161
|
|
|
162
|
+
- (void)layoutNavigationControllerView
|
|
163
|
+
{
|
|
164
|
+
UIViewController *vc = _screenView.controller;
|
|
165
|
+
UINavigationController *navctr = vc.navigationController;
|
|
166
|
+
[navctr.view setNeedsLayout];
|
|
167
|
+
}
|
|
168
|
+
|
|
141
169
|
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
|
142
170
|
{
|
|
143
171
|
[super didSetProps:changedProps];
|
|
144
172
|
[self updateViewControllerIfNeeded];
|
|
173
|
+
// We need to layout navigation controller view after translucent prop changes, because otherwise
|
|
174
|
+
// frame of RNSScreen will not be changed and screen content will remain the same size.
|
|
175
|
+
// For more details look at https://github.com/software-mansion/react-native-screens/issues/1158
|
|
176
|
+
if ([changedProps containsObject:@"translucent"]) {
|
|
177
|
+
[self layoutNavigationControllerView];
|
|
178
|
+
}
|
|
145
179
|
}
|
|
146
180
|
|
|
147
181
|
- (void)didUpdateReactSubviews
|
|
@@ -444,8 +478,10 @@
|
|
|
444
478
|
|
|
445
479
|
[navctr setNavigationBarHidden:shouldHide animated:animated];
|
|
446
480
|
|
|
447
|
-
if (config.direction == UISemanticContentAttributeForceLeftToRight ||
|
|
448
|
-
|
|
481
|
+
if ((config.direction == UISemanticContentAttributeForceLeftToRight ||
|
|
482
|
+
config.direction == UISemanticContentAttributeForceRightToLeft) &&
|
|
483
|
+
// iOS 12 cancels swipe gesture when direction is changed. See #1091
|
|
484
|
+
navctr.view.semanticContentAttribute != config.direction) {
|
|
449
485
|
navctr.view.semanticContentAttribute = config.direction;
|
|
450
486
|
navctr.navigationBar.semanticContentAttribute = config.direction;
|
|
451
487
|
}
|
|
@@ -554,6 +590,13 @@
|
|
|
554
590
|
break;
|
|
555
591
|
}
|
|
556
592
|
case RNSScreenStackHeaderSubviewTypeSearchBar: {
|
|
593
|
+
if (subview.subviews == nil || [subview.subviews count] == 0) {
|
|
594
|
+
RCTLogWarn(
|
|
595
|
+
@"Failed to attach search bar to the header. We recommend using `useLayoutEffect` when managing "
|
|
596
|
+
"searchBar properties dynamically. \n\nSee: github.com/software-mansion/react-native-screens/issues/1188");
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
|
|
557
600
|
if ([subview.subviews[0] isKindOfClass:[RNSSearchBar class]]) {
|
|
558
601
|
#if !TARGET_OS_TV
|
|
559
602
|
if (@available(iOS 11.0, *)) {
|
|
@@ -17,4 +17,9 @@
|
|
|
17
17
|
+ (UIInterfaceOrientationMask)maskFromOrientation:(UIInterfaceOrientation)orientation;
|
|
18
18
|
#endif
|
|
19
19
|
|
|
20
|
+
+ (BOOL)shouldAskScreensForTrait:(RNSWindowTrait)trait
|
|
21
|
+
includingModals:(BOOL)includingModals
|
|
22
|
+
inViewController:(UIViewController *)vc;
|
|
23
|
+
+ (BOOL)shouldAskScreensForScreenOrientationInViewController:(UIViewController *)vc;
|
|
24
|
+
|
|
20
25
|
@end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#import "RNSScreenWindowTraits.h"
|
|
2
2
|
#import "RNSScreen.h"
|
|
3
|
+
#import "RNSScreenContainer.h"
|
|
4
|
+
#import "RNSScreenStack.h"
|
|
3
5
|
|
|
4
6
|
@implementation RNSScreenWindowTraits
|
|
5
7
|
|
|
@@ -190,4 +192,31 @@
|
|
|
190
192
|
}
|
|
191
193
|
#endif
|
|
192
194
|
|
|
195
|
+
// method to be used in Expo for checking if RNScreens have trait set
|
|
196
|
+
+ (BOOL)shouldAskScreensForTrait:(RNSWindowTrait)trait
|
|
197
|
+
includingModals:(BOOL)includingModals
|
|
198
|
+
inViewController:(UIViewController *)vc
|
|
199
|
+
{
|
|
200
|
+
UIViewController *lastViewController = [[vc childViewControllers] lastObject];
|
|
201
|
+
if ([lastViewController conformsToProtocol:@protocol(RNScreensViewControllerDelegate)]) {
|
|
202
|
+
UIViewController *vc = nil;
|
|
203
|
+
if ([lastViewController isKindOfClass:[RNScreensViewController class]]) {
|
|
204
|
+
vc = [(RNScreensViewController *)lastViewController findActiveChildVC];
|
|
205
|
+
} else if ([lastViewController isKindOfClass:[RNScreensNavigationController class]]) {
|
|
206
|
+
vc = [(RNScreensNavigationController *)lastViewController topViewController];
|
|
207
|
+
}
|
|
208
|
+
return [vc isKindOfClass:[RNSScreen class]] &&
|
|
209
|
+
[(RNSScreen *)vc findChildVCForConfigAndTrait:trait includingModals:includingModals] != nil;
|
|
210
|
+
}
|
|
211
|
+
return NO;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// same method as above, but directly for orientation
|
|
215
|
+
+ (BOOL)shouldAskScreensForScreenOrientationInViewController:(UIViewController *)vc
|
|
216
|
+
{
|
|
217
|
+
return [RNSScreenWindowTraits shouldAskScreensForTrait:RNSWindowTraitOrientation
|
|
218
|
+
includingModals:YES
|
|
219
|
+
inViewController:vc];
|
|
220
|
+
}
|
|
221
|
+
|
|
193
222
|
@end
|