react-native-screens 4.10.0-beta.3 → 4.11.0-beta.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/RNScreens.podspec +2 -2
- package/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt +9 -6
- package/android/src/fabric/java/com/swmansion/rnscreens/NativeProxy.kt +2 -1
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +47 -16
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +3 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt +9 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenModalFragment.kt +2 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +16 -19
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +18 -2
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +6 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +19 -2
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt +1 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +1 -1
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +9 -4
- package/android/src/versioned/pointerevents/77/com/swmansion/rnscreens/{ScreensCoordinatorLayoutPointerEventsImpl.kt → PointerEventsBoxNoneImpl.kt} +1 -1
- package/android/src/versioned/pointerevents/latest/com/swmansion/rnscreens/{ScreensCoordinatorLayoutPointerEventsImpl.kt → PointerEventsBoxNoneImpl.kt} +1 -1
- package/common/cpp/react/renderer/components/rnscreens/RNSModalScreenShadowNode.cpp +1 -3
- package/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderConfigComponentDescriptor.h +7 -3
- package/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderSubviewComponentDescriptor.h +1 -1
- package/cpp/RNSScreenRemovalListener.cpp +3 -1
- package/ios/RNSConvert.mm +2 -0
- package/ios/RNSEnums.h +2 -1
- package/ios/RNSScreen.mm +22 -0
- package/ios/RNSScreenStack.mm +3 -1
- package/ios/RNSScreenStackHeaderConfig.h +1 -1
- package/ios/RNSScreenStackHeaderConfig.mm +31 -31
- package/lib/commonjs/components/Screen.js +9 -2
- package/lib/commonjs/components/Screen.js.map +1 -1
- package/lib/commonjs/fabric/ModalScreenNativeComponent.js.map +1 -1
- package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
- package/lib/commonjs/gesture-handler/ScreenGestureDetector.js +3 -1
- package/lib/commonjs/gesture-handler/ScreenGestureDetector.js.map +1 -1
- package/lib/commonjs/native-stack/utils/getDefaultHeaderHeight.js +2 -2
- package/lib/commonjs/native-stack/utils/getDefaultHeaderHeight.js.map +1 -1
- package/lib/module/components/Screen.js +9 -2
- package/lib/module/components/Screen.js.map +1 -1
- package/lib/module/fabric/ModalScreenNativeComponent.js.map +1 -1
- package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
- package/lib/module/gesture-handler/ScreenGestureDetector.js +3 -1
- package/lib/module/gesture-handler/ScreenGestureDetector.js.map +1 -1
- package/lib/module/native-stack/utils/getDefaultHeaderHeight.js +2 -2
- package/lib/module/native-stack/utils/getDefaultHeaderHeight.js.map +1 -1
- package/lib/typescript/components/Screen.d.ts.map +1 -1
- package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts +1 -1
- package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts.map +1 -1
- package/lib/typescript/fabric/ScreenNativeComponent.d.ts +1 -1
- package/lib/typescript/fabric/ScreenNativeComponent.d.ts.map +1 -1
- package/lib/typescript/gesture-handler/ScreenGestureDetector.d.ts.map +1 -1
- package/lib/typescript/native-stack/types.d.ts +12 -5
- package/lib/typescript/native-stack/types.d.ts.map +1 -1
- package/lib/typescript/native-stack/utils/getDefaultHeaderHeight.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +13 -6
- package/lib/typescript/types.d.ts.map +1 -1
- package/native-stack/README.md +7 -2
- package/package.json +1 -1
- package/src/components/Screen.tsx +17 -9
- package/src/fabric/ModalScreenNativeComponent.ts +1 -0
- package/src/fabric/ScreenNativeComponent.ts +1 -0
- package/src/gesture-handler/ScreenGestureDetector.tsx +3 -1
- package/src/native-stack/types.tsx +12 -5
- package/src/native-stack/utils/getDefaultHeaderHeight.tsx +4 -2
- package/src/types.tsx +14 -6
package/RNScreens.podspec
CHANGED
|
@@ -3,7 +3,7 @@ require "json"
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
4
|
|
|
5
5
|
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
|
|
6
|
-
|
|
6
|
+
min_supported_ios_version = new_arch_enabled ? "15.1" : "15.1"
|
|
7
7
|
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/RNScreensTurboModule.cpp", "cpp/RNScreensTurboModule.h"]
|
|
8
8
|
|
|
9
9
|
Pod::Spec.new do |s|
|
|
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
s.homepage = "https://github.com/software-mansion/react-native-screens"
|
|
17
17
|
s.license = "MIT"
|
|
18
18
|
s.author = { "author" => "author@domain.cn" }
|
|
19
|
-
s.platforms = { :ios =>
|
|
19
|
+
s.platforms = { :ios => min_supported_ios_version, :tvos => "11.0", :visionos => "1.0" }
|
|
20
20
|
s.source = { :git => "https://github.com/software-mansion/react-native-screens.git", :tag => "#{s.version}" }
|
|
21
21
|
s.source_files = source_files
|
|
22
22
|
s.project_header_files = "cpp/**/*.h" # Don't expose C++ headers publicly to allow importing framework into Swift files
|
|
@@ -14,8 +14,9 @@ abstract class FabricEnabledViewGroup(
|
|
|
14
14
|
) : ViewGroup(context) {
|
|
15
15
|
private var mStateWrapper: StateWrapper? = null
|
|
16
16
|
|
|
17
|
-
private var
|
|
18
|
-
private var
|
|
17
|
+
private var lastWidth = 0f
|
|
18
|
+
private var lastHeight = 0f
|
|
19
|
+
private var lastHeaderHeight = 0f
|
|
19
20
|
|
|
20
21
|
fun setStateWrapper(wrapper: StateWrapper?) {
|
|
21
22
|
mStateWrapper = wrapper
|
|
@@ -42,14 +43,16 @@ abstract class FabricEnabledViewGroup(
|
|
|
42
43
|
// Check incoming state values. If they're already the correct value, return early to prevent
|
|
43
44
|
// infinite UpdateState/SetState loop.
|
|
44
45
|
val delta = 0.9f
|
|
45
|
-
if (abs(
|
|
46
|
-
abs(
|
|
46
|
+
if (abs(lastWidth - realWidth) < delta &&
|
|
47
|
+
abs(lastHeight - realHeight) < delta &&
|
|
48
|
+
abs(lastHeaderHeight - realHeaderHeight) < delta
|
|
47
49
|
) {
|
|
48
50
|
return
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
lastWidth = realWidth
|
|
54
|
+
lastHeight = realHeight
|
|
55
|
+
lastHeaderHeight = realHeaderHeight
|
|
53
56
|
val map: WritableMap =
|
|
54
57
|
WritableNativeMap().apply {
|
|
55
58
|
putDouble("frameWidth", realWidth.toDouble())
|
|
@@ -41,7 +41,8 @@ class NativeProxy {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Called from native
|
|
44
|
+
// Called from native. Currently this method is called from MountingCoordinator thread,
|
|
45
|
+
// which usually is not UI thread.
|
|
45
46
|
@DoNotStrip
|
|
46
47
|
public fun notifyScreenRemoved(screenTag: Int) {
|
|
47
48
|
// Since RN 0.78 the screenTag we receive as argument here might not belong to a screen
|
|
@@ -27,6 +27,7 @@ import com.google.android.material.shape.CornerFamily
|
|
|
27
27
|
import com.google.android.material.shape.MaterialShapeDrawable
|
|
28
28
|
import com.google.android.material.shape.ShapeAppearanceModel
|
|
29
29
|
import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
|
|
30
|
+
import com.swmansion.rnscreens.bottomsheet.useSingleDetent
|
|
30
31
|
import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
|
|
31
32
|
import com.swmansion.rnscreens.events.HeaderHeightChangeEvent
|
|
32
33
|
import com.swmansion.rnscreens.events.SheetDetentChangedEvent
|
|
@@ -103,6 +104,9 @@ class Screen(
|
|
|
103
104
|
field = value
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
private val isNativeStackScreen: Boolean
|
|
108
|
+
get() = container is ScreenStack
|
|
109
|
+
|
|
106
110
|
init {
|
|
107
111
|
// we set layout params as WindowManager.LayoutParams to workaround the issue with TextInputs
|
|
108
112
|
// not displaying modal menus (e.g., copy/paste or selection). The missing menus are due to the
|
|
@@ -132,11 +136,7 @@ class Screen(
|
|
|
132
136
|
val height = bottom - top
|
|
133
137
|
|
|
134
138
|
if (isSheetFitToContents()) {
|
|
135
|
-
sheetBehavior?.
|
|
136
|
-
if (it.maxHeight != height) {
|
|
137
|
-
it.maxHeight = height
|
|
138
|
-
}
|
|
139
|
-
}
|
|
139
|
+
sheetBehavior?.useSingleDetent(height)
|
|
140
140
|
|
|
141
141
|
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
142
142
|
// On old architecture we delay enter transition in order to wait for initial frame.
|
|
@@ -173,23 +173,31 @@ class Screen(
|
|
|
173
173
|
r: Int,
|
|
174
174
|
b: Int,
|
|
175
175
|
) {
|
|
176
|
-
|
|
176
|
+
// In case of form sheet we get layout notification a bit later, in `onBottomSheetBehaviorDidLayout`
|
|
177
|
+
// after the attached behaviour laid out this view.
|
|
178
|
+
if (changed && isNativeStackScreen && !usesFormSheetPresentation()) {
|
|
177
179
|
val width = r - l
|
|
178
180
|
val height = b - t
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
updateScreenSizeFabric(width, height, t)
|
|
182
|
-
} else {
|
|
183
|
-
updateScreenSizePaper(width, height)
|
|
184
|
-
}
|
|
182
|
+
dispatchShadowStateUpdate(width, height, t)
|
|
185
183
|
|
|
186
|
-
|
|
184
|
+
// FormSheet has no header in current model.
|
|
187
185
|
notifyHeaderHeightChange(t)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
internal fun onBottomSheetBehaviorDidLayout(coordinatorLayoutDidChange: Boolean) {
|
|
190
|
+
if (!usesFormSheetPresentation()) {
|
|
191
|
+
return
|
|
192
192
|
}
|
|
193
|
+
if (coordinatorLayoutDidChange && isNativeStackScreen) {
|
|
194
|
+
dispatchShadowStateUpdate(width, height, top)
|
|
195
|
+
}
|
|
196
|
+
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
197
|
+
maybeTriggerPostponedTransition()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
footer?.onParentLayout(coordinatorLayoutDidChange, left, top, right, bottom, container!!.height)
|
|
193
201
|
}
|
|
194
202
|
|
|
195
203
|
private fun maybeTriggerPostponedTransition() {
|
|
@@ -214,6 +222,21 @@ class Screen(
|
|
|
214
222
|
)
|
|
215
223
|
}
|
|
216
224
|
|
|
225
|
+
/**
|
|
226
|
+
* @param offsetY ignored on old architecture
|
|
227
|
+
*/
|
|
228
|
+
private fun dispatchShadowStateUpdate(
|
|
229
|
+
width: Int,
|
|
230
|
+
height: Int,
|
|
231
|
+
offsetY: Int,
|
|
232
|
+
) {
|
|
233
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
234
|
+
updateScreenSizeFabric(width, height, offsetY)
|
|
235
|
+
} else {
|
|
236
|
+
updateScreenSizePaper(width, height)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
217
240
|
val headerConfig: ScreenStackHeaderConfig?
|
|
218
241
|
get() = children.find { it is ScreenStackHeaderConfig } as? ScreenStackHeaderConfig
|
|
219
242
|
|
|
@@ -237,7 +260,10 @@ class Screen(
|
|
|
237
260
|
)
|
|
238
261
|
}
|
|
239
262
|
|
|
240
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Whether this screen allows to see the content underneath it.
|
|
265
|
+
*/
|
|
266
|
+
fun isTranslucent(): Boolean =
|
|
241
267
|
when (stackPresentation) {
|
|
242
268
|
StackPresentation.TRANSPARENT_MODAL,
|
|
243
269
|
StackPresentation.FORM_SHEET,
|
|
@@ -487,6 +513,11 @@ class Screen(
|
|
|
487
513
|
isStable: Boolean,
|
|
488
514
|
) {
|
|
489
515
|
dispatchSheetDetentChanged(detentIndex, isStable)
|
|
516
|
+
// There is no need to update shadow state for transient sheet states -
|
|
517
|
+
// we are unsure of the exact sheet position anyway.
|
|
518
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && isStable) {
|
|
519
|
+
updateScreenSizeFabric(width, height, top)
|
|
520
|
+
}
|
|
490
521
|
}
|
|
491
522
|
|
|
492
523
|
private fun dispatchSheetDetentChanged(
|
|
@@ -128,6 +128,9 @@ open class ScreenFragment :
|
|
|
128
128
|
ScreenWindowTraits.trySetWindowTraits(screen, activity, tryGetContext())
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// Plain ScreenFragments can not be translucent
|
|
132
|
+
override fun isTranslucent() = false
|
|
133
|
+
|
|
131
134
|
override fun tryGetActivity(): Activity? {
|
|
132
135
|
activity?.let { return it }
|
|
133
136
|
val context = screen.context
|
|
@@ -26,6 +26,15 @@ interface ScreenFragmentWrapper :
|
|
|
26
26
|
|
|
27
27
|
fun onViewAnimationEnd()
|
|
28
28
|
|
|
29
|
+
// Fragment information
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether this screen fragment makes it possible to see content underneath it
|
|
33
|
+
* (not fully opaque or does not fill full screen).
|
|
34
|
+
*/
|
|
35
|
+
fun isTranslucent(): Boolean
|
|
36
|
+
|
|
37
|
+
|
|
29
38
|
// Helpers
|
|
30
39
|
fun tryGetActivity(): Activity?
|
|
31
40
|
|
|
@@ -175,21 +175,18 @@ class ScreenStack(
|
|
|
175
175
|
childDrawingOrderStrategy = null
|
|
176
176
|
|
|
177
177
|
// Determine new first & last visible screens.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
.firstOrNull()
|
|
191
|
-
?.takeUnless { it === newTop }
|
|
192
|
-
}
|
|
178
|
+
val notDismissedWrappers =
|
|
179
|
+
screenWrappers
|
|
180
|
+
.asReversed()
|
|
181
|
+
.asSequence()
|
|
182
|
+
.filter { !dismissedWrappers.contains(it) && it.screen.activityState !== Screen.ActivityState.INACTIVE }
|
|
183
|
+
|
|
184
|
+
newTop = notDismissedWrappers.firstOrNull()
|
|
185
|
+
visibleBottom =
|
|
186
|
+
notDismissedWrappers
|
|
187
|
+
.dropWhile { it.isTranslucent() }
|
|
188
|
+
.firstOrNull()
|
|
189
|
+
?.takeUnless { it === newTop }
|
|
193
190
|
|
|
194
191
|
var shouldUseOpenAnimation = true
|
|
195
192
|
var stackAnimation: StackAnimation? = null
|
|
@@ -241,8 +238,8 @@ class ScreenStack(
|
|
|
241
238
|
childDrawingOrderStrategy = SwapLastTwo()
|
|
242
239
|
} else if (newTop != null &&
|
|
243
240
|
newTopAlreadyInStack &&
|
|
244
|
-
topScreenWrapper?.
|
|
245
|
-
newTop.
|
|
241
|
+
topScreenWrapper?.isTranslucent() == true &&
|
|
242
|
+
newTop.isTranslucent() == false
|
|
246
243
|
) {
|
|
247
244
|
// In case where we dismiss multiple transparent views we want to ensure
|
|
248
245
|
// that they are drawn in correct order - Android swaps them by default,
|
|
@@ -253,7 +250,7 @@ class ScreenStack(
|
|
|
253
250
|
.asSequence()
|
|
254
251
|
.takeWhile {
|
|
255
252
|
it !== newTop &&
|
|
256
|
-
it.
|
|
253
|
+
it.isTranslucent()
|
|
257
254
|
}.count()
|
|
258
255
|
if (dismissedTransparentScreenApproxCount > 1) {
|
|
259
256
|
childDrawingOrderStrategy =
|
|
@@ -314,7 +311,7 @@ class ScreenStack(
|
|
|
314
311
|
private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenFragmentWrapper?) {
|
|
315
312
|
if (screenWrappers.size > 1 && visibleBottom != null) {
|
|
316
313
|
topScreenWrapper?.let {
|
|
317
|
-
if (it.
|
|
314
|
+
if (it.isTranslucent()) {
|
|
318
315
|
val screenFragmentsBeneathTop = screenWrappers.slice(0 until screenWrappers.size - 1).asReversed()
|
|
319
316
|
// go from the top of the stack excluding the top screen
|
|
320
317
|
for (fragmentWrapper in screenFragmentsBeneathTop) {
|
|
@@ -86,6 +86,8 @@ class ScreenStackFragment :
|
|
|
86
86
|
)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
override fun isTranslucent(): Boolean = screen.isTranslucent()
|
|
90
|
+
|
|
89
91
|
override fun removeToolbar() {
|
|
90
92
|
appBarLayout?.let {
|
|
91
93
|
toolbar?.let { toolbar ->
|
|
@@ -380,7 +382,7 @@ class ScreenStackFragment :
|
|
|
380
382
|
// If the screen is a transparent modal with hidden header we don't want to update the toolbar
|
|
381
383
|
// menu because it may erase the menu of the previous screen (which is still visible in these
|
|
382
384
|
// circumstances). See here: https://github.com/software-mansion/react-native-screens/issues/2271
|
|
383
|
-
if (!screen.
|
|
385
|
+
if (!screen.isTranslucent() || screen.headerConfig?.isHeaderHidden == false) {
|
|
384
386
|
updateToolbarMenu(menu)
|
|
385
387
|
}
|
|
386
388
|
return super.onPrepareOptionsMenu(menu)
|
|
@@ -480,7 +482,7 @@ class ScreenStackFragment :
|
|
|
480
482
|
constructor(context: Context, fragment: ScreenStackFragment) : this(
|
|
481
483
|
context,
|
|
482
484
|
fragment,
|
|
483
|
-
|
|
485
|
+
PointerEventsBoxNoneImpl(),
|
|
484
486
|
)
|
|
485
487
|
|
|
486
488
|
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets)
|
|
@@ -543,6 +545,20 @@ class ScreenStackFragment :
|
|
|
543
545
|
}
|
|
544
546
|
}
|
|
545
547
|
|
|
548
|
+
override fun onLayout(
|
|
549
|
+
changed: Boolean,
|
|
550
|
+
l: Int,
|
|
551
|
+
t: Int,
|
|
552
|
+
r: Int,
|
|
553
|
+
b: Int,
|
|
554
|
+
) {
|
|
555
|
+
super.onLayout(changed, l, t, r, b)
|
|
556
|
+
|
|
557
|
+
if (fragment.screen.usesFormSheetPresentation()) {
|
|
558
|
+
fragment.screen.onBottomSheetBehaviorDidLayout(changed)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
546
562
|
// override fun reactTagForTouch(touchX: Float, touchY: Float): Int {
|
|
547
563
|
// throw IllegalStateException("Screen wrapper should never be asked for the view tag")
|
|
548
564
|
// }
|
|
@@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
|
|
|
15
15
|
import com.facebook.react.ReactApplication
|
|
16
16
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
|
|
17
17
|
import com.facebook.react.bridge.ReactContext
|
|
18
|
+
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
18
19
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
19
20
|
import com.facebook.react.views.text.ReactTypefaceUtils
|
|
20
21
|
import com.swmansion.rnscreens.events.HeaderAttachedEvent
|
|
@@ -23,7 +24,11 @@ import kotlin.math.max
|
|
|
23
24
|
|
|
24
25
|
class ScreenStackHeaderConfig(
|
|
25
26
|
context: Context,
|
|
26
|
-
|
|
27
|
+
private val pointerEventsImpl: ReactPointerEventsView
|
|
28
|
+
) : FabricEnabledHeaderConfigViewGroup(context), ReactPointerEventsView by pointerEventsImpl {
|
|
29
|
+
|
|
30
|
+
constructor(context: Context): this(context, pointerEventsImpl = PointerEventsBoxNoneImpl())
|
|
31
|
+
|
|
27
32
|
private val configSubviews = ArrayList<ScreenStackHeaderSubview>(3)
|
|
28
33
|
val toolbar: CustomToolbar
|
|
29
34
|
var isHeaderHidden = false // named this way to avoid conflict with platform's isHidden
|
|
@@ -10,6 +10,14 @@ class ScreenStackHeaderSubview(
|
|
|
10
10
|
) : FabricEnabledHeaderSubviewViewGroup(context) {
|
|
11
11
|
private var reactWidth = 0
|
|
12
12
|
private var reactHeight = 0
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Semantics: true iff we **believe** that SurfaceMountingManager has measured this view during mount item
|
|
16
|
+
* execution. We recognize this case by checking measure mode in `onMeasure`. If Androidx
|
|
17
|
+
* happens to use `EXACTLY` for both dimensions this property might convey invalid information.
|
|
18
|
+
*/
|
|
19
|
+
private var isReactSizeSet = false
|
|
20
|
+
|
|
13
21
|
var type = Type.RIGHT
|
|
14
22
|
|
|
15
23
|
val config: ScreenStackHeaderConfig?
|
|
@@ -22,9 +30,10 @@ class ScreenStackHeaderSubview(
|
|
|
22
30
|
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
|
|
23
31
|
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
|
|
24
32
|
) {
|
|
25
|
-
// dimensions provided by react
|
|
33
|
+
// dimensions provided by react (with high probability)
|
|
26
34
|
reactWidth = MeasureSpec.getSize(widthMeasureSpec)
|
|
27
35
|
reactHeight = MeasureSpec.getSize(heightMeasureSpec)
|
|
36
|
+
isReactSizeSet = true
|
|
28
37
|
val parent = parent
|
|
29
38
|
if (parent != null) {
|
|
30
39
|
forceLayout()
|
|
@@ -44,7 +53,15 @@ class ScreenStackHeaderSubview(
|
|
|
44
53
|
if (changed && BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
45
54
|
val width = r - l
|
|
46
55
|
val height = b - t
|
|
47
|
-
|
|
56
|
+
|
|
57
|
+
// When setting subviews via `setOptions` from `useEffect` hook in a component, the first
|
|
58
|
+
// frame received might be computed by native layout & completely invalid (zero height).
|
|
59
|
+
// RN layout is the source of subview **size** (not origin) & we need to avoid sending
|
|
60
|
+
// this native size to ST. Doing otherwise might lead to problems.
|
|
61
|
+
// See: https://github.com/software-mansion/react-native-screens/pull/2812
|
|
62
|
+
if (isReactSizeSet) {
|
|
63
|
+
updateSubviewFrameState(width, height, l, t)
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
}
|
|
50
67
|
|
|
@@ -61,6 +61,7 @@ class ScreenStackViewManager :
|
|
|
61
61
|
index: Int,
|
|
62
62
|
): View = parent.getScreenAt(index)
|
|
63
63
|
|
|
64
|
+
// Old architecture only.
|
|
64
65
|
override fun createShadowNodeInstance(context: ReactApplicationContext): LayoutShadowNode = ScreensShadowNode(context)
|
|
65
66
|
|
|
66
67
|
override fun needsCustomLayoutForChildren() = true
|
|
@@ -126,7 +126,7 @@ open class ScreenViewManager :
|
|
|
126
126
|
when (presentation) {
|
|
127
127
|
"push" -> Screen.StackPresentation.PUSH
|
|
128
128
|
"formSheet" -> Screen.StackPresentation.FORM_SHEET
|
|
129
|
-
"modal", "containedModal", "fullScreenModal" ->
|
|
129
|
+
"modal", "containedModal", "fullScreenModal", "pageSheet" ->
|
|
130
130
|
Screen.StackPresentation.MODAL
|
|
131
131
|
"transparentModal", "containedTransparentModal" ->
|
|
132
132
|
Screen.StackPresentation.TRANSPARENT_MODAL
|
|
@@ -130,10 +130,15 @@ class SheetDelegate(
|
|
|
130
130
|
behavior.apply {
|
|
131
131
|
val height =
|
|
132
132
|
if (screen.isSheetFitToContents()) {
|
|
133
|
-
screen.contentWrapper
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
screen.contentWrapper.get()?.let { contentWrapper ->
|
|
134
|
+
contentWrapper.height.takeIf {
|
|
135
|
+
// subtree might not be laid out, e.g. after fragment reattachment
|
|
136
|
+
// and view recreation, however since it is retained by
|
|
137
|
+
// react-native it has its height cached. We want to use it.
|
|
138
|
+
// Otherwise we would have to trigger RN layout manually.
|
|
139
|
+
contentWrapper.isLaidOut || contentWrapper.height > 0
|
|
140
|
+
}
|
|
141
|
+
}
|
|
137
142
|
} else {
|
|
138
143
|
(screen.sheetDetents.first() * containerHeight).toInt()
|
|
139
144
|
}
|
|
@@ -3,7 +3,7 @@ package com.swmansion.rnscreens
|
|
|
3
3
|
import com.facebook.react.uimanager.PointerEvents
|
|
4
4
|
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
5
5
|
|
|
6
|
-
internal class
|
|
6
|
+
internal class PointerEventsBoxNoneImpl() : ReactPointerEventsView {
|
|
7
7
|
// We set pointer events to BOX_NONE, because we don't want the ScreensCoordinatorLayout
|
|
8
8
|
// to be target of react gestures and effectively prevent interaction with screens
|
|
9
9
|
// underneath the current screen (useful in `modal` & `formSheet` presentation).
|
|
@@ -3,7 +3,7 @@ package com.swmansion.rnscreens
|
|
|
3
3
|
import com.facebook.react.uimanager.PointerEvents
|
|
4
4
|
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
5
5
|
|
|
6
|
-
internal class
|
|
6
|
+
internal class PointerEventsBoxNoneImpl() : ReactPointerEventsView {
|
|
7
7
|
// We set pointer events to BOX_NONE, because we don't want the ScreensCoordinatorLayout
|
|
8
8
|
// to be target of react gestures and effectively prevent interaction with screens
|
|
9
9
|
// underneath the current screen (useful in `modal` & `formSheet` presentation).
|
|
@@ -7,9 +7,7 @@ extern const char RNSModalScreenComponentName[] = "RNSModalScreen";
|
|
|
7
7
|
|
|
8
8
|
Point RNSModalScreenShadowNode::getContentOriginOffset(
|
|
9
9
|
bool /*includeTransform*/) const {
|
|
10
|
-
|
|
11
|
-
auto contentOffset = stateData.contentOffset;
|
|
12
|
-
return {contentOffset.x, contentOffset.y};
|
|
10
|
+
return getStateData().contentOffset;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
} // namespace react
|
|
@@ -34,17 +34,21 @@ class RNSScreenStackHeaderConfigComponentDescriptor final
|
|
|
34
34
|
shadowNode.getState());
|
|
35
35
|
auto stateData = state->getData();
|
|
36
36
|
|
|
37
|
-
if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
|
|
38
|
-
layoutableShadowNode.setSize(stateData.frameSize);
|
|
39
37
|
#ifdef ANDROID
|
|
38
|
+
if (stateData.frameSize.width != 0) {
|
|
39
|
+
layoutableShadowNode.setSize({stateData.frameSize.width, YGUndefined});
|
|
40
40
|
layoutableShadowNode.setPadding({
|
|
41
41
|
stateData.paddingStart,
|
|
42
42
|
0,
|
|
43
43
|
stateData.paddingEnd,
|
|
44
44
|
0,
|
|
45
45
|
});
|
|
46
|
-
#endif // ANDROID
|
|
47
46
|
}
|
|
47
|
+
#else
|
|
48
|
+
if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
|
|
49
|
+
layoutableShadowNode.setSize(stateData.frameSize);
|
|
50
|
+
}
|
|
51
|
+
#endif // ANDROID
|
|
48
52
|
|
|
49
53
|
ConcreteComponentDescriptor::adopt(shadowNode);
|
|
50
54
|
#if !defined(ANDROID) && !defined(NDEBUG)
|
|
@@ -32,7 +32,7 @@ class RNSScreenStackHeaderSubviewComponentDescriptor final
|
|
|
32
32
|
|
|
33
33
|
auto state = std::static_pointer_cast<
|
|
34
34
|
const RNSScreenStackHeaderSubviewShadowNode::ConcreteState>(
|
|
35
|
-
shadowNode.
|
|
35
|
+
shadowNode.getState());
|
|
36
36
|
auto stateData = state->getData();
|
|
37
37
|
|
|
38
38
|
if (!isSizeEmpty(stateData.frameSize)) {
|
|
@@ -8,9 +8,11 @@ std::optional<MountingTransaction> RNSScreenRemovalListener::pullTransaction(
|
|
|
8
8
|
const TransactionTelemetry &telemetry,
|
|
9
9
|
ShadowViewMutationList mutations) const {
|
|
10
10
|
for (const ShadowViewMutation &mutation : mutations) {
|
|
11
|
+
// When using RNSModalScreen on Android it should be added here.
|
|
11
12
|
if (mutation.type == ShadowViewMutation::Type::Remove &&
|
|
12
13
|
mutation.oldChildShadowView.componentName != nullptr &&
|
|
13
|
-
strcmp(mutation.oldChildShadowView.componentName, "RNSScreen") ==
|
|
14
|
+
std::strcmp(mutation.oldChildShadowView.componentName, "RNSScreen") ==
|
|
15
|
+
0) {
|
|
14
16
|
// We call the listener function even if this screen has not been owned
|
|
15
17
|
// by RNSScreenStack as since RN 0.78 we do not have enough information
|
|
16
18
|
// here. This final filter is applied later in NativeProxy.
|
package/ios/RNSConvert.mm
CHANGED
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
return RNSScreenStackPresentationFullScreenModal;
|
|
50
50
|
case FormSheet:
|
|
51
51
|
return RNSScreenStackPresentationFormSheet;
|
|
52
|
+
case PageSheet:
|
|
53
|
+
return RNSScreenStackPresentationPageSheet;
|
|
52
54
|
case ContainedModal:
|
|
53
55
|
return RNSScreenStackPresentationContainedModal;
|
|
54
56
|
case TransparentModal:
|
package/ios/RNSEnums.h
CHANGED
|
@@ -5,7 +5,8 @@ typedef NS_ENUM(NSInteger, RNSScreenStackPresentation) {
|
|
|
5
5
|
RNSScreenStackPresentationContainedModal,
|
|
6
6
|
RNSScreenStackPresentationContainedTransparentModal,
|
|
7
7
|
RNSScreenStackPresentationFullScreenModal,
|
|
8
|
-
RNSScreenStackPresentationFormSheet
|
|
8
|
+
RNSScreenStackPresentationFormSheet,
|
|
9
|
+
RNSScreenStackPresentationPageSheet,
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
|
package/ios/RNSScreen.mm
CHANGED
|
@@ -246,8 +246,29 @@ RNS_IGNORE_SUPER_CALL_END
|
|
|
246
246
|
}
|
|
247
247
|
#else
|
|
248
248
|
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
249
|
+
#endif
|
|
250
|
+
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_17_0) && \
|
|
251
|
+
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_17_0 && !TARGET_OS_TV
|
|
252
|
+
if (@available(iOS 18.0, *)) {
|
|
253
|
+
UISheetPresentationController *sheetController = _controller.sheetPresentationController;
|
|
254
|
+
if (sheetController != nil) {
|
|
255
|
+
sheetController.prefersPageSizing = true;
|
|
256
|
+
} else {
|
|
257
|
+
RCTLogError(
|
|
258
|
+
@"[RNScreens] sheetPresentationController is null when attempting to set prefersPageSizing for modal");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
#endif
|
|
262
|
+
break;
|
|
263
|
+
|
|
264
|
+
case RNSScreenStackPresentationPageSheet:
|
|
265
|
+
#if !TARGET_OS_TV
|
|
266
|
+
_controller.modalPresentationStyle = UIModalPresentationPageSheet;
|
|
267
|
+
#else
|
|
268
|
+
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
249
269
|
#endif
|
|
250
270
|
break;
|
|
271
|
+
|
|
251
272
|
case RNSScreenStackPresentationFullScreenModal:
|
|
252
273
|
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
253
274
|
break;
|
|
@@ -2024,6 +2045,7 @@ RCT_ENUM_CONVERTER(
|
|
|
2024
2045
|
@"modal" : @(RNSScreenStackPresentationModal),
|
|
2025
2046
|
@"fullScreenModal" : @(RNSScreenStackPresentationFullScreenModal),
|
|
2026
2047
|
@"formSheet" : @(RNSScreenStackPresentationFormSheet),
|
|
2048
|
+
@"pageSheet" : @(RNSScreenStackPresentationPageSheet),
|
|
2027
2049
|
@"containedModal" : @(RNSScreenStackPresentationContainedModal),
|
|
2028
2050
|
@"transparentModal" : @(RNSScreenStackPresentationTransparentModal),
|
|
2029
2051
|
@"containedTransparentModal" : @(RNSScreenStackPresentationContainedTransparentModal)
|
package/ios/RNSScreenStack.mm
CHANGED
|
@@ -78,11 +78,13 @@ namespace react = facebook::react;
|
|
|
78
78
|
BOOL isNotDismissingModal = screenController.presentedViewController == nil ||
|
|
79
79
|
(screenController.presentedViewController != nil &&
|
|
80
80
|
![screenController.presentedViewController isBeingDismissed]);
|
|
81
|
+
BOOL isPresentingSearchController =
|
|
82
|
+
[screenController.presentedViewController isKindOfClass:UISearchController.class];
|
|
81
83
|
|
|
82
84
|
// Calculate header height during simple transition from one screen to another.
|
|
83
85
|
// If RNSScreen includes a navigation controller of type RNSNavigationController, it should not calculate
|
|
84
86
|
// header height, as it could have nested stack.
|
|
85
|
-
if (![screenController hasNestedStack] && isNotDismissingModal) {
|
|
87
|
+
if (![screenController hasNestedStack] && (isPresentingSearchController || isNotDismissingModal)) {
|
|
86
88
|
[screenController calculateAndNotifyHeaderHeightChangeIsModal:NO];
|
|
87
89
|
}
|
|
88
90
|
|