react-native-screens 4.11.0-beta.0 → 4.11.0-beta.2
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 +3 -1
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +18 -12
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +48 -97
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +4 -124
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +2 -2
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +23 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/anim/ScreensAnimation.kt +18 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/views/ChildDrawingOrderStrategyImpl.kt +48 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/views/ChildrenDrawingOrderStrategy.kt +24 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt +99 -0
- package/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp +1 -2
- package/ios/RNSConvert.mm +44 -47
- package/ios/RNSFullWindowOverlay.h +2 -0
- package/ios/RNSFullWindowOverlay.mm +32 -8
- package/ios/RNSScreen.mm +20 -34
- package/ios/RNSScreenStack.mm +6 -8
- package/ios/RNSScreenStackHeaderConfig.mm +120 -223
- package/ios/RNSScreenWindowTraits.mm +19 -37
- package/ios/RNSSearchBar.mm +15 -24
- package/lib/commonjs/components/FullWindowOverlay.js +2 -1
- package/lib/commonjs/components/FullWindowOverlay.js.map +1 -1
- package/lib/commonjs/components/ScreenContentWrapper.windows.js +10 -0
- package/lib/commonjs/components/ScreenContentWrapper.windows.js.map +1 -0
- package/lib/commonjs/components/ScreenFooter.windows.js +11 -0
- package/lib/commonjs/components/ScreenFooter.windows.js.map +1 -0
- package/lib/commonjs/fabric/FullWindowOverlayNativeComponent.js +1 -0
- package/lib/commonjs/fabric/FullWindowOverlayNativeComponent.js.map +1 -1
- package/lib/module/components/FullWindowOverlay.js +2 -1
- package/lib/module/components/FullWindowOverlay.js.map +1 -1
- package/lib/module/components/ScreenContentWrapper.windows.js +4 -0
- package/lib/module/components/ScreenContentWrapper.windows.js.map +1 -0
- package/lib/module/components/ScreenFooter.windows.js +6 -0
- package/lib/module/components/ScreenFooter.windows.js.map +1 -0
- package/lib/module/fabric/FullWindowOverlayNativeComponent.js +3 -0
- package/lib/module/fabric/FullWindowOverlayNativeComponent.js.map +1 -1
- package/lib/typescript/components/FullWindowOverlay.d.ts +4 -2
- package/lib/typescript/components/FullWindowOverlay.d.ts.map +1 -1
- package/lib/typescript/components/ScreenContentWrapper.windows.d.ts +4 -0
- package/lib/typescript/components/ScreenContentWrapper.windows.d.ts.map +1 -0
- package/lib/typescript/components/ScreenFooter.windows.d.ts +6 -0
- package/lib/typescript/components/ScreenFooter.windows.d.ts.map +1 -0
- package/lib/typescript/fabric/FullWindowOverlayNativeComponent.d.ts +3 -1
- package/lib/typescript/fabric/FullWindowOverlayNativeComponent.d.ts.map +1 -1
- package/lib/typescript/native-stack/types.d.ts +1 -1
- package/lib/typescript/types.d.ts +2 -2
- package/native-stack/README.md +2 -2
- package/package.json +1 -1
- package/src/components/FullWindowOverlay.tsx +14 -3
- package/src/components/ScreenContentWrapper.windows.tsx +5 -0
- package/src/components/ScreenFooter.windows.tsx +7 -0
- package/src/fabric/FullWindowOverlayNativeComponent.ts +5 -1
- package/src/native-stack/types.tsx +1 -1
- package/src/types.tsx +2 -2
- package/windows/RNScreens/ModalScreenViewManager.cpp +22 -0
- package/windows/RNScreens/ModalScreenViewManager.h +13 -0
- package/windows/RNScreens/RNScreens.vcxproj +11 -1
- package/windows/RNScreens/RNScreens.vcxproj.filters +10 -0
- package/windows/RNScreens/ReactPackageProvider.cpp +18 -0
- package/windows/RNScreens/Screen.cpp +128 -122
- package/windows/RNScreens/Screen.h +6 -2
- package/windows/RNScreens/ScreenStackHeaderConfig.cpp +25 -1
- package/windows/RNScreens/ScreenStackHeaderConfig.h +10 -1
- package/windows/RNScreens/ScreenStackHeaderConfigViewManager.cpp +42 -1
- package/windows/RNScreens/ScreenStackHeaderConfigViewManager.h +15 -0
- package/windows/RNScreens/ScreenStackHeaderSubview.cpp +43 -0
- package/windows/RNScreens/ScreenStackHeaderSubview.h +24 -0
- package/windows/RNScreens/ScreenStackHeaderSubviewViewManager.cpp +129 -0
- package/windows/RNScreens/ScreenStackHeaderSubviewViewManager.h +77 -0
- package/windows/RNScreens/ScreenViewManager.cpp +45 -16
- package/windows/RNScreens/ScreenViewManager.h +1 -1
- package/windows/RNScreens/SearchBar.cpp +19 -0
- package/windows/RNScreens/SearchBar.h +14 -0
- package/windows/RNScreens/SearchBarViewManager.cpp +88 -0
- package/windows/RNScreens/SearchBarViewManager.h +62 -0
package/RNScreens.podspec
CHANGED
|
@@ -4,6 +4,8 @@ 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
|
+
min_supported_tvos_version = "15.1"
|
|
8
|
+
min_supported_visionos_version = "1.0"
|
|
7
9
|
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/RNScreensTurboModule.cpp", "cpp/RNScreensTurboModule.h"]
|
|
8
10
|
|
|
9
11
|
Pod::Spec.new do |s|
|
|
@@ -16,7 +18,7 @@ Pod::Spec.new do |s|
|
|
|
16
18
|
s.homepage = "https://github.com/software-mansion/react-native-screens"
|
|
17
19
|
s.license = "MIT"
|
|
18
20
|
s.author = { "author" => "author@domain.cn" }
|
|
19
|
-
s.platforms = { :ios => min_supported_ios_version, :tvos =>
|
|
21
|
+
s.platforms = { :ios => min_supported_ios_version, :tvos => min_supported_tvos_version, :visionos => min_supported_visionos_version }
|
|
20
22
|
s.source = { :git => "https://github.com/software-mansion/react-native-screens.git", :tag => "#{s.version}" }
|
|
21
23
|
s.source_files = source_files
|
|
22
24
|
s.project_header_files = "cpp/**/*.h" # Don't expose C++ headers publicly to allow importing framework into Swift files
|
|
@@ -32,7 +32,6 @@ import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
|
|
|
32
32
|
import com.swmansion.rnscreens.events.HeaderHeightChangeEvent
|
|
33
33
|
import com.swmansion.rnscreens.events.SheetDetentChangedEvent
|
|
34
34
|
import com.swmansion.rnscreens.ext.parentAsViewGroup
|
|
35
|
-
import java.lang.ref.WeakReference
|
|
36
35
|
|
|
37
36
|
@SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated.
|
|
38
37
|
class Screen(
|
|
@@ -42,8 +41,6 @@ class Screen(
|
|
|
42
41
|
val fragment: Fragment?
|
|
43
42
|
get() = fragmentWrapper?.fragment
|
|
44
43
|
|
|
45
|
-
var contentWrapper = WeakReference<ScreenContentWrapper>(null)
|
|
46
|
-
|
|
47
44
|
val sheetBehavior: BottomSheetBehavior<Screen>?
|
|
48
45
|
get() = (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior as? BottomSheetBehavior<Screen>
|
|
49
46
|
|
|
@@ -135,8 +132,10 @@ class Screen(
|
|
|
135
132
|
) {
|
|
136
133
|
val height = bottom - top
|
|
137
134
|
|
|
138
|
-
if (
|
|
139
|
-
|
|
135
|
+
if (usesFormSheetPresentation()) {
|
|
136
|
+
if (isSheetFitToContents()) {
|
|
137
|
+
sheetBehavior?.useSingleDetent(height)
|
|
138
|
+
}
|
|
140
139
|
|
|
141
140
|
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
142
141
|
// On old architecture we delay enter transition in order to wait for initial frame.
|
|
@@ -153,7 +152,6 @@ class Screen(
|
|
|
153
152
|
|
|
154
153
|
fun registerLayoutCallbackForWrapper(wrapper: ScreenContentWrapper) {
|
|
155
154
|
wrapper.delegate = this
|
|
156
|
-
this.contentWrapper = WeakReference(wrapper)
|
|
157
155
|
}
|
|
158
156
|
|
|
159
157
|
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
|
|
@@ -187,22 +185,27 @@ class Screen(
|
|
|
187
185
|
}
|
|
188
186
|
|
|
189
187
|
internal fun onBottomSheetBehaviorDidLayout(coordinatorLayoutDidChange: Boolean) {
|
|
190
|
-
if (!usesFormSheetPresentation()) {
|
|
188
|
+
if (!usesFormSheetPresentation() || !isNativeStackScreen) {
|
|
191
189
|
return
|
|
192
190
|
}
|
|
193
|
-
if (coordinatorLayoutDidChange
|
|
191
|
+
if (coordinatorLayoutDidChange) {
|
|
194
192
|
dispatchShadowStateUpdate(width, height, top)
|
|
195
193
|
}
|
|
196
|
-
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
197
|
-
maybeTriggerPostponedTransition()
|
|
198
|
-
}
|
|
199
194
|
|
|
200
195
|
footer?.onParentLayout(coordinatorLayoutDidChange, left, top, right, bottom, container!!.height)
|
|
196
|
+
|
|
197
|
+
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
198
|
+
// When using form sheet presentation we want to delay enter transition **on Paper** in order
|
|
199
|
+
// to wait for initial layout from React, otherwise the animator-based animation will look
|
|
200
|
+
// glitchy. *This seems to not be needed on Fabric*.
|
|
201
|
+
triggerPostponedEnterTransitionIfNeeded()
|
|
202
|
+
}
|
|
201
203
|
}
|
|
202
204
|
|
|
203
|
-
private fun
|
|
205
|
+
private fun triggerPostponedEnterTransitionIfNeeded() {
|
|
204
206
|
if (shouldTriggerPostponedTransitionAfterLayout) {
|
|
205
207
|
shouldTriggerPostponedTransitionAfterLayout = false
|
|
208
|
+
// This will trigger enter transition only if one was requested by ScreenStack
|
|
206
209
|
fragment?.startPostponedEnterTransition()
|
|
207
210
|
}
|
|
208
211
|
}
|
|
@@ -240,6 +243,9 @@ class Screen(
|
|
|
240
243
|
val headerConfig: ScreenStackHeaderConfig?
|
|
241
244
|
get() = children.find { it is ScreenStackHeaderConfig } as? ScreenStackHeaderConfig
|
|
242
245
|
|
|
246
|
+
val contentWrapper: ScreenContentWrapper?
|
|
247
|
+
get() = children.find { it is ScreenContentWrapper } as? ScreenContentWrapper
|
|
248
|
+
|
|
243
249
|
/**
|
|
244
250
|
* While transitioning this property allows to optimize rendering behavior on Android and provide
|
|
245
251
|
* a correct blending options for the animated screen. It is turned on automatically by the
|
|
@@ -7,78 +7,16 @@ import android.view.View
|
|
|
7
7
|
import com.facebook.react.bridge.ReactContext
|
|
8
8
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
9
9
|
import com.swmansion.rnscreens.Screen.StackAnimation
|
|
10
|
-
import com.swmansion.rnscreens.bottomsheet.
|
|
10
|
+
import com.swmansion.rnscreens.bottomsheet.requiresEnterTransitionPostponing
|
|
11
11
|
import com.swmansion.rnscreens.events.StackFinishTransitioningEvent
|
|
12
|
+
import com.swmansion.rnscreens.stack.views.ChildrenDrawingOrderStrategy
|
|
13
|
+
import com.swmansion.rnscreens.stack.views.ReverseFromIndex
|
|
14
|
+
import com.swmansion.rnscreens.stack.views.ReverseOrder
|
|
15
|
+
import com.swmansion.rnscreens.stack.views.ScreensCoordinatorLayout
|
|
12
16
|
import com.swmansion.rnscreens.utils.setTweenAnimations
|
|
13
|
-
import java.util.Collections
|
|
14
17
|
import kotlin.collections.ArrayList
|
|
15
18
|
import kotlin.math.max
|
|
16
19
|
|
|
17
|
-
internal interface ChildDrawingOrderStrategy {
|
|
18
|
-
/**
|
|
19
|
-
* Mutates the list of draw operations **in-place**.
|
|
20
|
-
*/
|
|
21
|
-
fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>)
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Enables the given strategy. When enabled - the strategy **might** mutate the operations
|
|
25
|
-
* list passed to `apply` method.
|
|
26
|
-
*/
|
|
27
|
-
fun enable()
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Disables the given strategy - even when `apply` is called it **must not** produce
|
|
31
|
-
* any side effect (it must not manipulate the drawing operations list passed to `apply` method).
|
|
32
|
-
*/
|
|
33
|
-
fun disable()
|
|
34
|
-
|
|
35
|
-
fun isEnabled(): Boolean
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
internal abstract class ChildDrawingOrderStrategyBase(
|
|
39
|
-
var enabled: Boolean = false,
|
|
40
|
-
) : ChildDrawingOrderStrategy {
|
|
41
|
-
override fun enable() {
|
|
42
|
-
enabled = true
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
override fun disable() {
|
|
46
|
-
enabled = false
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
override fun isEnabled() = enabled
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
internal class SwapLastTwo : ChildDrawingOrderStrategyBase() {
|
|
53
|
-
override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
|
|
54
|
-
if (!isEnabled()) {
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
if (drawingOperations.size >= 2) {
|
|
58
|
-
Collections.swap(drawingOperations, drawingOperations.lastIndex, drawingOperations.lastIndex - 1)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
internal class ReverseOrderInRange(
|
|
64
|
-
val range: IntRange,
|
|
65
|
-
) : ChildDrawingOrderStrategyBase() {
|
|
66
|
-
override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
|
|
67
|
-
if (!isEnabled()) {
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
var startRange = range.start
|
|
72
|
-
var endRange = range.endInclusive
|
|
73
|
-
|
|
74
|
-
while (startRange < endRange) {
|
|
75
|
-
Collections.swap(drawingOperations, startRange, endRange)
|
|
76
|
-
startRange += 1
|
|
77
|
-
endRange -= 1
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
20
|
class ScreenStack(
|
|
83
21
|
context: Context?,
|
|
84
22
|
) : ScreenContainer(context) {
|
|
@@ -88,9 +26,9 @@ class ScreenStack(
|
|
|
88
26
|
private var drawingOps: MutableList<DrawingOp> = ArrayList()
|
|
89
27
|
private var topScreenWrapper: ScreenStackFragmentWrapper? = null
|
|
90
28
|
private var removalTransitionStarted = false
|
|
91
|
-
private var previousChildrenCount = 0
|
|
92
29
|
|
|
93
|
-
private var
|
|
30
|
+
private var childrenDrawingOrderStrategy: ChildrenDrawingOrderStrategy? = null
|
|
31
|
+
private var disappearingTransitioningChildren: MutableList<View> = ArrayList()
|
|
94
32
|
|
|
95
33
|
var goingForward = false
|
|
96
34
|
|
|
@@ -122,14 +60,25 @@ class ScreenStack(
|
|
|
122
60
|
}
|
|
123
61
|
|
|
124
62
|
override fun startViewTransition(view: View) {
|
|
63
|
+
check(view is ScreensCoordinatorLayout) { "[RNScreens] Unexpected type of ScreenStack direct subview ${view.javaClass}" }
|
|
125
64
|
super.startViewTransition(view)
|
|
126
|
-
|
|
65
|
+
if (view.fragment.isRemoving) {
|
|
66
|
+
disappearingTransitioningChildren.add(view)
|
|
67
|
+
}
|
|
68
|
+
if (disappearingTransitioningChildren.isNotEmpty()) {
|
|
69
|
+
childrenDrawingOrderStrategy?.enable()
|
|
70
|
+
}
|
|
127
71
|
removalTransitionStarted = true
|
|
128
72
|
}
|
|
129
73
|
|
|
130
74
|
override fun endViewTransition(view: View) {
|
|
131
75
|
super.endViewTransition(view)
|
|
132
|
-
|
|
76
|
+
|
|
77
|
+
disappearingTransitioningChildren.remove(view)
|
|
78
|
+
|
|
79
|
+
if (disappearingTransitioningChildren.isEmpty()) {
|
|
80
|
+
childrenDrawingOrderStrategy?.disable()
|
|
81
|
+
}
|
|
133
82
|
if (removalTransitionStarted) {
|
|
134
83
|
removalTransitionStarted = false
|
|
135
84
|
dispatchOnFinishTransitioning()
|
|
@@ -172,14 +121,17 @@ class ScreenStack(
|
|
|
172
121
|
var visibleBottom: ScreenFragmentWrapper? = null
|
|
173
122
|
|
|
174
123
|
// reset, to not use previously set strategy by mistake
|
|
175
|
-
|
|
124
|
+
childrenDrawingOrderStrategy = null
|
|
176
125
|
|
|
177
126
|
// Determine new first & last visible screens.
|
|
178
127
|
val notDismissedWrappers =
|
|
179
128
|
screenWrappers
|
|
180
129
|
.asReversed()
|
|
181
130
|
.asSequence()
|
|
182
|
-
.filter {
|
|
131
|
+
.filter {
|
|
132
|
+
!dismissedWrappers.contains(it) &&
|
|
133
|
+
it.screen.activityState !== Screen.ActivityState.INACTIVE
|
|
134
|
+
}
|
|
183
135
|
|
|
184
136
|
newTop = notDismissedWrappers.firstOrNull()
|
|
185
137
|
visibleBottom =
|
|
@@ -202,11 +154,13 @@ class ScreenStack(
|
|
|
202
154
|
// if the previous top screen does not exist anymore and the new top was not on the stack
|
|
203
155
|
// before, probably replace or reset was called, so we play the "close animation".
|
|
204
156
|
// Otherwise it's open animation
|
|
205
|
-
val previousTopScreenRemainsInStack =
|
|
157
|
+
val previousTopScreenRemainsInStack =
|
|
158
|
+
topScreenWrapper?.let { screenWrappers.contains(it) } == true
|
|
206
159
|
val isPushReplace = newTop.screen.replaceAnimation === Screen.ReplaceAnimation.PUSH
|
|
207
160
|
shouldUseOpenAnimation = previousTopScreenRemainsInStack || isPushReplace
|
|
208
161
|
// if the replace animation is `push`, the new top screen provides the animation, otherwise the previous one
|
|
209
|
-
stackAnimation =
|
|
162
|
+
stackAnimation =
|
|
163
|
+
if (shouldUseOpenAnimation) newTop.screen.stackAnimation else topScreenWrapper?.screen?.stackAnimation
|
|
210
164
|
} else {
|
|
211
165
|
// mTopScreen was not present before so newTop is the first screen added to a stack
|
|
212
166
|
// and we don't want the animation when it is entering
|
|
@@ -226,16 +180,16 @@ class ScreenStack(
|
|
|
226
180
|
needsDrawReordering(newTop, stackAnimation) &&
|
|
227
181
|
visibleBottom == null
|
|
228
182
|
) {
|
|
229
|
-
// When using an open animation in which
|
|
230
|
-
// slide_from_bottom), we want to draw the previous
|
|
231
|
-
// which is apparently not the default option. Android always draws the disappearing
|
|
183
|
+
// When using an open animation in which screens overlap (eg. fade_from_bottom or
|
|
184
|
+
// slide_from_bottom), we want to draw the previous screens under the new one,
|
|
185
|
+
// which is apparently not the default option. Android always draws the disappearing views
|
|
232
186
|
// on top of the appearing one. We then reverse the order of the views so the new screen
|
|
233
|
-
// appears on top of the previous
|
|
187
|
+
// appears on top of the previous ones. You can read more about in the comment
|
|
234
188
|
// for the code we use to change that behavior:
|
|
235
189
|
// https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L18
|
|
236
190
|
// Note: This should not be set in case there is only a single screen in stack or animation `none` is used.
|
|
237
191
|
// Atm needsDrawReordering implementation guards that assuming that first screen on stack uses `NONE` animation.
|
|
238
|
-
|
|
192
|
+
childrenDrawingOrderStrategy = ReverseOrder()
|
|
239
193
|
} else if (newTop != null &&
|
|
240
194
|
newTopAlreadyInStack &&
|
|
241
195
|
topScreenWrapper?.isTranslucent() == true &&
|
|
@@ -253,8 +207,8 @@ class ScreenStack(
|
|
|
253
207
|
it.isTranslucent()
|
|
254
208
|
}.count()
|
|
255
209
|
if (dismissedTransparentScreenApproxCount > 1) {
|
|
256
|
-
|
|
257
|
-
|
|
210
|
+
childrenDrawingOrderStrategy =
|
|
211
|
+
ReverseFromIndex(max(stack.lastIndex - dismissedTransparentScreenApproxCount + 1, 0))
|
|
258
212
|
}
|
|
259
213
|
}
|
|
260
214
|
|
|
@@ -267,8 +221,12 @@ class ScreenStack(
|
|
|
267
221
|
// no longer rendered or were dismissed natively.
|
|
268
222
|
stack
|
|
269
223
|
.asSequence()
|
|
270
|
-
.filter { wrapper ->
|
|
271
|
-
|
|
224
|
+
.filter { wrapper ->
|
|
225
|
+
!screenWrappers.contains(wrapper) ||
|
|
226
|
+
dismissedWrappers.contains(
|
|
227
|
+
wrapper,
|
|
228
|
+
)
|
|
229
|
+
}.forEach { wrapper -> transaction.remove(wrapper.fragment) }
|
|
272
230
|
|
|
273
231
|
// Remove all screens underneath visibleBottom && these marked for preload, but keep newTop.
|
|
274
232
|
screenWrappers
|
|
@@ -290,14 +248,12 @@ class ScreenStack(
|
|
|
290
248
|
}
|
|
291
249
|
}
|
|
292
250
|
} else if (newTop != null && !newTop.fragment.isAdded) {
|
|
293
|
-
if (
|
|
294
|
-
// On old architecture the content wrapper might not have received its frame yet,
|
|
295
|
-
// which is required to determine height of the sheet after animation. Therefore
|
|
296
|
-
// we delay the transition and trigger it after views receive the layout.
|
|
251
|
+
if (newTop.screen.requiresEnterTransitionPostponing()) {
|
|
297
252
|
newTop.fragment.postponeEnterTransition()
|
|
298
253
|
}
|
|
299
254
|
transaction.add(id, newTop.fragment)
|
|
300
255
|
}
|
|
256
|
+
|
|
301
257
|
topScreenWrapper = newTop as? ScreenStackFragmentWrapper
|
|
302
258
|
stack.clear()
|
|
303
259
|
stack.addAll(screenWrappers.asSequence().map { it as ScreenStackFragmentWrapper })
|
|
@@ -312,7 +268,8 @@ class ScreenStack(
|
|
|
312
268
|
if (screenWrappers.size > 1 && visibleBottom != null) {
|
|
313
269
|
topScreenWrapper?.let {
|
|
314
270
|
if (it.isTranslucent()) {
|
|
315
|
-
val screenFragmentsBeneathTop =
|
|
271
|
+
val screenFragmentsBeneathTop =
|
|
272
|
+
screenWrappers.slice(0 until screenWrappers.size - 1).asReversed()
|
|
316
273
|
// go from the top of the stack excluding the top screen
|
|
317
274
|
for (fragmentWrapper in screenFragmentsBeneathTop) {
|
|
318
275
|
fragmentWrapper.screen.changeAccessibilityMode(
|
|
@@ -351,13 +308,7 @@ class ScreenStack(
|
|
|
351
308
|
override fun dispatchDraw(canvas: Canvas) {
|
|
352
309
|
super.dispatchDraw(canvas)
|
|
353
310
|
|
|
354
|
-
|
|
355
|
-
if (drawingOps.size < previousChildrenCount) {
|
|
356
|
-
childDrawingOrderStrategy = null
|
|
357
|
-
}
|
|
358
|
-
previousChildrenCount = drawingOps.size
|
|
359
|
-
|
|
360
|
-
childDrawingOrderStrategy?.apply(drawingOps)
|
|
311
|
+
childrenDrawingOrderStrategy?.apply(drawingOps)
|
|
361
312
|
|
|
362
313
|
drawAndRelease()
|
|
363
314
|
}
|
|
@@ -407,7 +358,7 @@ class ScreenStack(
|
|
|
407
358
|
fragmentWrapper: ScreenFragmentWrapper,
|
|
408
359
|
resolvedStackAnimation: StackAnimation?,
|
|
409
360
|
): Boolean {
|
|
410
|
-
val stackAnimation =
|
|
361
|
+
val stackAnimation = resolvedStackAnimation ?: fragmentWrapper.screen.stackAnimation
|
|
411
362
|
// On Android sdk 33 and above the animation is different and requires draw reordering.
|
|
412
363
|
// For React Native 0.70 and lower versions, `Build.VERSION_CODES.TIRAMISU` is not defined yet.
|
|
413
364
|
// Hence, we're comparing numerical version here.
|
|
@@ -4,7 +4,6 @@ import android.animation.Animator
|
|
|
4
4
|
import android.animation.AnimatorSet
|
|
5
5
|
import android.animation.ValueAnimator
|
|
6
6
|
import android.annotation.SuppressLint
|
|
7
|
-
import android.content.Context
|
|
8
7
|
import android.graphics.Color
|
|
9
8
|
import android.graphics.drawable.ColorDrawable
|
|
10
9
|
import android.os.Bundle
|
|
@@ -14,15 +13,11 @@ import android.view.MenuInflater
|
|
|
14
13
|
import android.view.MenuItem
|
|
15
14
|
import android.view.View
|
|
16
15
|
import android.view.ViewGroup
|
|
17
|
-
import android.view.WindowInsets
|
|
18
16
|
import android.view.animation.Animation
|
|
19
|
-
import android.view.animation.AnimationSet
|
|
20
|
-
import android.view.animation.Transformation
|
|
21
17
|
import android.widget.LinearLayout
|
|
22
18
|
import androidx.appcompat.widget.Toolbar
|
|
23
19
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
24
20
|
import com.facebook.react.uimanager.PixelUtil
|
|
25
|
-
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
26
21
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
27
22
|
import com.google.android.material.appbar.AppBarLayout
|
|
28
23
|
import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
|
|
@@ -37,6 +32,7 @@ import com.swmansion.rnscreens.events.ScreenAnimationDelegate
|
|
|
37
32
|
import com.swmansion.rnscreens.events.ScreenDismissedEvent
|
|
38
33
|
import com.swmansion.rnscreens.events.ScreenEventEmitter
|
|
39
34
|
import com.swmansion.rnscreens.ext.recycle
|
|
35
|
+
import com.swmansion.rnscreens.stack.views.ScreensCoordinatorLayout
|
|
40
36
|
import com.swmansion.rnscreens.transition.ExternalBoundaryValuesEvaluator
|
|
41
37
|
import com.swmansion.rnscreens.utils.DeviceUtils
|
|
42
38
|
import com.swmansion.rnscreens.utils.resolveBackgroundColor
|
|
@@ -233,6 +229,8 @@ class ScreenStackFragment :
|
|
|
233
229
|
dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout)
|
|
234
230
|
dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!)
|
|
235
231
|
|
|
232
|
+
// Pre-layout the content for the sake of enter transition.
|
|
233
|
+
|
|
236
234
|
val container = screen.container!!
|
|
237
235
|
coordinatorLayout.measure(
|
|
238
236
|
View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
|
|
@@ -341,7 +339,7 @@ class ScreenStackFragment :
|
|
|
341
339
|
return screenColor
|
|
342
340
|
}
|
|
343
341
|
|
|
344
|
-
val contentWrapper = screen.contentWrapper
|
|
342
|
+
val contentWrapper = screen.contentWrapper
|
|
345
343
|
if (contentWrapper == null) {
|
|
346
344
|
return null
|
|
347
345
|
}
|
|
@@ -471,122 +469,4 @@ class ScreenStackFragment :
|
|
|
471
469
|
}
|
|
472
470
|
return sheetDelegate!!
|
|
473
471
|
}
|
|
474
|
-
|
|
475
|
-
private class ScreensCoordinatorLayout(
|
|
476
|
-
context: Context,
|
|
477
|
-
private val fragment: ScreenStackFragment,
|
|
478
|
-
private val pointerEventsImpl: ReactPointerEventsView,
|
|
479
|
-
// ) : CoordinatorLayout(context), ReactCompoundViewGroup, ReactHitSlopView {
|
|
480
|
-
) : CoordinatorLayout(context),
|
|
481
|
-
ReactPointerEventsView by pointerEventsImpl {
|
|
482
|
-
constructor(context: Context, fragment: ScreenStackFragment) : this(
|
|
483
|
-
context,
|
|
484
|
-
fragment,
|
|
485
|
-
PointerEventsBoxNoneImpl(),
|
|
486
|
-
)
|
|
487
|
-
|
|
488
|
-
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets)
|
|
489
|
-
|
|
490
|
-
private val animationListener: Animation.AnimationListener =
|
|
491
|
-
object : Animation.AnimationListener {
|
|
492
|
-
override fun onAnimationStart(animation: Animation) {
|
|
493
|
-
fragment.onViewAnimationStart()
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
override fun onAnimationEnd(animation: Animation) {
|
|
497
|
-
fragment.onViewAnimationEnd()
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
override fun onAnimationRepeat(animation: Animation) {}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
override fun startAnimation(animation: Animation) {
|
|
504
|
-
// For some reason View##onAnimationEnd doesn't get called for
|
|
505
|
-
// exit transitions so we explicitly attach animation listener.
|
|
506
|
-
// We also have some animations that are an AnimationSet, so we don't wrap them
|
|
507
|
-
// in another set since it causes some visual glitches when going forward.
|
|
508
|
-
// We also set the listener only when going forward, since when going back,
|
|
509
|
-
// there is already a listener for dismiss action added, which would be overridden
|
|
510
|
-
// and also this is not necessary when going back since the lifecycle methods
|
|
511
|
-
// are correctly dispatched then.
|
|
512
|
-
// We also add fakeAnimation to the set of animations, which sends the progress of animation
|
|
513
|
-
val fakeAnimation = ScreensAnimation(fragment).apply { duration = animation.duration }
|
|
514
|
-
|
|
515
|
-
if (animation is AnimationSet && !fragment.isRemoving) {
|
|
516
|
-
animation
|
|
517
|
-
.apply {
|
|
518
|
-
addAnimation(fakeAnimation)
|
|
519
|
-
setAnimationListener(animationListener)
|
|
520
|
-
}.also {
|
|
521
|
-
super.startAnimation(it)
|
|
522
|
-
}
|
|
523
|
-
} else {
|
|
524
|
-
AnimationSet(true)
|
|
525
|
-
.apply {
|
|
526
|
-
addAnimation(animation)
|
|
527
|
-
addAnimation(fakeAnimation)
|
|
528
|
-
setAnimationListener(animationListener)
|
|
529
|
-
}.also {
|
|
530
|
-
super.startAnimation(it)
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* This method implements a workaround for RN's autoFocus functionality. Because of the way
|
|
537
|
-
* autoFocus is implemented it dismisses soft keyboard in fragment transition
|
|
538
|
-
* due to change of visibility of the view at the start of the transition. Here we override the
|
|
539
|
-
* call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the
|
|
540
|
-
* hiding of the keyboard in `ReactEditText.java`.
|
|
541
|
-
*/
|
|
542
|
-
override fun clearFocus() {
|
|
543
|
-
if (visibility != INVISIBLE) {
|
|
544
|
-
super.clearFocus()
|
|
545
|
-
}
|
|
546
|
-
}
|
|
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
|
-
|
|
562
|
-
// override fun reactTagForTouch(touchX: Float, touchY: Float): Int {
|
|
563
|
-
// throw IllegalStateException("Screen wrapper should never be asked for the view tag")
|
|
564
|
-
// }
|
|
565
|
-
//
|
|
566
|
-
// override fun interceptsTouchEvent(touchX: Float, touchY: Float): Boolean {
|
|
567
|
-
// return false
|
|
568
|
-
// }
|
|
569
|
-
//
|
|
570
|
-
// override fun getHitSlopRect(): Rect? {
|
|
571
|
-
// val screen: Screen = fragment.screen
|
|
572
|
-
// // left – The X coordinate of the left side of the rectangle
|
|
573
|
-
// // top – The Y coordinate of the top of the rectangle i
|
|
574
|
-
// // right – The X coordinate of the right side of the rectangle
|
|
575
|
-
// // bottom – The Y coordinate of the bottom of the rectangle
|
|
576
|
-
// return Rect(screen.x.toInt(), -screen.y.toInt(), screen.x.toInt() + screen.width, screen.y.toInt() + screen.height)
|
|
577
|
-
// }
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
private class ScreensAnimation(
|
|
581
|
-
private val mFragment: ScreenFragment,
|
|
582
|
-
) : Animation() {
|
|
583
|
-
override fun applyTransformation(
|
|
584
|
-
interpolatedTime: Float,
|
|
585
|
-
t: Transformation,
|
|
586
|
-
) {
|
|
587
|
-
super.applyTransformation(interpolatedTime, t)
|
|
588
|
-
// interpolated time should be the progress of the current transition
|
|
589
|
-
mFragment.dispatchTransitionProgressEvent(interpolatedTime, !mFragment.isResumed)
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
472
|
}
|
|
@@ -130,13 +130,13 @@ class SheetDelegate(
|
|
|
130
130
|
behavior.apply {
|
|
131
131
|
val height =
|
|
132
132
|
if (screen.isSheetFitToContents()) {
|
|
133
|
-
screen.contentWrapper
|
|
133
|
+
screen.contentWrapper?.let { contentWrapper ->
|
|
134
134
|
contentWrapper.height.takeIf {
|
|
135
135
|
// subtree might not be laid out, e.g. after fragment reattachment
|
|
136
136
|
// and view recreation, however since it is retained by
|
|
137
137
|
// react-native it has its height cached. We want to use it.
|
|
138
138
|
// Otherwise we would have to trigger RN layout manually.
|
|
139
|
-
contentWrapper.
|
|
139
|
+
contentWrapper.isLaidOutOrHasCachedLayout()
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
} else {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
package com.swmansion.rnscreens.bottomsheet
|
|
2
2
|
|
|
3
|
+
import android.view.View
|
|
3
4
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
4
5
|
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
|
|
5
6
|
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
|
|
6
7
|
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
|
|
7
8
|
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
|
|
9
|
+
import com.swmansion.rnscreens.BuildConfig
|
|
8
10
|
import com.swmansion.rnscreens.Screen
|
|
9
11
|
|
|
10
12
|
object SheetUtils {
|
|
@@ -133,3 +135,24 @@ fun Screen.isSheetFitToContents(): Boolean =
|
|
|
133
135
|
sheetDetents.first() == Screen.SHEET_FIT_TO_CONTENTS
|
|
134
136
|
|
|
135
137
|
fun Screen.usesFormSheetPresentation(): Boolean = stackPresentation === Screen.StackPresentation.FORM_SHEET
|
|
138
|
+
|
|
139
|
+
fun Screen.requiresEnterTransitionPostponing(): Boolean {
|
|
140
|
+
// On old architecture the content wrapper might not have received its frame yet,
|
|
141
|
+
// which is required to determine height of the sheet after animation. Therefore
|
|
142
|
+
// we delay the transition and trigger it after views receive the layout.
|
|
143
|
+
// This is used only for formSheet presentation, because we use value animators
|
|
144
|
+
// there. Tween animations have some magic way to make this work (maybe they
|
|
145
|
+
// postpone the transition internally, dunno).
|
|
146
|
+
|
|
147
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED || !this.usesFormSheetPresentation()) {
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
// Assumes that formSheet uses content wrapper
|
|
151
|
+
return !this.isLaidOutOrHasCachedLayout() || this.contentWrapper?.isLaidOutOrHasCachedLayout() != true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The view might not be laid out, but have cached dimensions e.g. when host fragment
|
|
156
|
+
* is reattached to container.
|
|
157
|
+
*/
|
|
158
|
+
fun View.isLaidOutOrHasCachedLayout() = this.isLaidOut || height > 0 || width > 0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.stack.anim
|
|
2
|
+
|
|
3
|
+
import android.view.animation.Animation
|
|
4
|
+
import android.view.animation.Transformation
|
|
5
|
+
import com.swmansion.rnscreens.ScreenFragment
|
|
6
|
+
|
|
7
|
+
internal class ScreensAnimation(
|
|
8
|
+
private val mFragment: ScreenFragment,
|
|
9
|
+
) : Animation() {
|
|
10
|
+
override fun applyTransformation(
|
|
11
|
+
interpolatedTime: Float,
|
|
12
|
+
t: Transformation,
|
|
13
|
+
) {
|
|
14
|
+
super.applyTransformation(interpolatedTime, t)
|
|
15
|
+
// interpolated time should be the progress of the current transition
|
|
16
|
+
mFragment.dispatchTransitionProgressEvent(interpolatedTime, !mFragment.isResumed)
|
|
17
|
+
}
|
|
18
|
+
}
|
package/android/src/main/java/com/swmansion/rnscreens/stack/views/ChildDrawingOrderStrategyImpl.kt
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.stack.views
|
|
2
|
+
|
|
3
|
+
import com.swmansion.rnscreens.ScreenStack
|
|
4
|
+
import java.util.Collections
|
|
5
|
+
|
|
6
|
+
internal abstract class ChildrenDrawingOrderStrategyBase(
|
|
7
|
+
var enabled: Boolean = false,
|
|
8
|
+
) : ChildrenDrawingOrderStrategy {
|
|
9
|
+
override fun enable() {
|
|
10
|
+
enabled = true
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun disable() {
|
|
14
|
+
enabled = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun isEnabled() = enabled
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
internal class ReverseFromIndex(
|
|
22
|
+
val startIndex: Int,
|
|
23
|
+
) : ChildrenDrawingOrderStrategyBase() {
|
|
24
|
+
override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
|
|
25
|
+
if (!isEnabled()) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var currentLeftIndex = startIndex
|
|
30
|
+
var currentRightIndex = drawingOperations.lastIndex
|
|
31
|
+
|
|
32
|
+
while (currentLeftIndex < currentRightIndex) {
|
|
33
|
+
Collections.swap(drawingOperations, currentLeftIndex, currentRightIndex)
|
|
34
|
+
currentLeftIndex += 1
|
|
35
|
+
currentRightIndex -= 1
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
internal class ReverseOrder : ChildrenDrawingOrderStrategyBase() {
|
|
41
|
+
override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
|
|
42
|
+
if (!isEnabled()) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
drawingOperations.reverse()
|
|
47
|
+
}
|
|
48
|
+
}
|
package/android/src/main/java/com/swmansion/rnscreens/stack/views/ChildrenDrawingOrderStrategy.kt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.stack.views
|
|
2
|
+
|
|
3
|
+
import com.swmansion.rnscreens.ScreenStack
|
|
4
|
+
|
|
5
|
+
internal interface ChildrenDrawingOrderStrategy {
|
|
6
|
+
/**
|
|
7
|
+
* Mutates the list of draw operations **in-place**.
|
|
8
|
+
*/
|
|
9
|
+
fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>)
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Enables the given strategy. When enabled - the strategy **might** mutate the operations
|
|
13
|
+
* list passed to `apply` method.
|
|
14
|
+
*/
|
|
15
|
+
fun enable()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Disables the given strategy - even when `apply` is called it **must not** produce
|
|
19
|
+
* any side effect (it must not manipulate the drawing operations list passed to `apply` method).
|
|
20
|
+
*/
|
|
21
|
+
fun disable()
|
|
22
|
+
|
|
23
|
+
fun isEnabled(): Boolean
|
|
24
|
+
}
|