Compare commits

..

53 Commits

Author SHA1 Message Date
Krzysztof Magiera
fd7fcfbc5b Bump version -> 2.0.0-beta.2 2020-02-05 16:22:32 +01:00
Krzysztof Magiera
06b928f5c1 Fix showing sw back button on nested stack on Android. (#308)
This change fixes a problem with native stack on Android where we'd display a sw back button on screens that are nested stack navigatodespite the fact those screens were the initial screens in the whole container stack. In #306 we introuced a change that would allow for displaying sw back in nested stacks, however that change did not handle the case where screen is a root screen of a container that is nested and placed as an initial screen in another container.
2020-02-05 16:22:10 +01:00
Krzysztof Magiera
acb038e213 Upgrading to beta -> 2.0.0-beta.1 2020-02-04 22:21:50 +01:00
Krzysztof Magiera
22d4400b93 Render Android back button for root screens of nested stack navi… (#306)
This change allows for root screens of nested stack navigators on Android to display back button in the navigation bar. Navigating back is still possible using hw back because of the way hw back is handled by nested fragment containers. However, despite hw back functioning properly, before this change we would not allow for the soft back button to be rendered in the header. This change adds this possibility to keep software back consistent with the hw back button functionality. This behavior can still be disabled/adjusted using hideBackButton property of the header config component.
2020-02-04 22:20:41 +01:00
Krzysztof Magiera
f78c264419 Fix updating navigation bar for modal transitions. (#305)
This change fixes the problem when navigation bar would not get properly updated for modal transitions. The problem was due to the fact that for modal transitions the animateAlongsideTransition block was not getting trriggered. This was noticeable on iOS versions before 13, as starting with iOS 13 we can use NavigationItem to specify most of the nav bar customization. On older versions however it is required that for a lot of navbar settings we need to update them durion animation. This wasn't the case for navigators presented modally. In case we show a modal we can update nav bar without animation because it actually renders a completely new navigation bar which isn't shared.

On top of that this PR fixes the problem with bridge not being set for header subviews.
2020-02-04 16:45:55 +01:00
Krzysztof Magiera
65bd436cbe Bump version -> 2.0.0-alpha.34 2020-02-03 21:12:27 +01:00
Krzysztof Magiera
a0745762ba Remove references to ScreenStackHeaderTitleView. 2020-02-03 16:30:31 +01:00
Krzysztof Magiera
89658d9361 Delete Title subview and code for handling title view scaling. (#304)
This change removes ScreenStackHeaderTitleView component and code that used to handle title view scaling. There were multiple issues related to scaling toolbar title views and it is not a priority at the moment to us to work on addressing those. The most frequent usecase is to put a fixed-dimensions view (e.g. logo or text) which can be now handled w/o this extra code.
2020-02-03 16:27:00 +01:00
osdnk
074aabc33e Bump version -> 2.0.0-alpha.33 2020-02-02 17:09:15 +01:00
Satyajit Sahoo
eb2a1ae8fd Fix type definition for gestureEnabled (#302) 2020-01-29 23:51:32 +01:00
Krzysztof Magiera
9721353e7b Bump version -> 2.0.0-alpha.32 2020-01-28 11:27:05 +01:00
Krzysztof Magiera
523b3d8f3a Fix native stack navigator wrapper after #254 (#299) 2020-01-27 19:26:51 +01:00
Krzysztof Magiera
f851c26642 Bump version -> 2.0.0-alpha.31 2020-01-27 15:46:49 +01:00
Krzysztof Magiera
f8f5d66bf9 Fix updating native header on iOS while transition is ongoing. (#298)
This change fixes a bug when header property updates happens during or at the same time of stack transition, e.g., when we both pop a screen and change parent VC header properties. When this happens we trigger header update code, but because we used to compare current VC to visibleViewController, which at that moment points to controller that we are transitioning away from, the update would've been prevented. With this change instead of using visibleViewController to compare with updated VC, we check if there is transition happening (using transitransitionCoordinator property), and in such case we use topController which points to the controller we are transitioning to.
2020-01-27 15:34:40 +01:00
osdnk
72037e20fb Bump version -> 2.0.0-alpha.30 2020-01-27 15:32:48 +01:00
Michał Osadnik
69a23f1c9f Remove blocking touch interactions if more than one active scree… (#296)
We believe that it might be better to manage the logic of blocking interactions from JS with setNativeProps and pointerEvents.

The current logic leads to some issues we're facing within React Navigation. There might be a state when more that one screen can be active. E.g. in modals we want to have two screens visible on the stack and have enabled interaction on the bottom one.

New code should not give any issues, because even though we need to handle activation of each screen from JS and this will just introduce an additional native call for enabling / disabling pointer events.

On iOS, we're not using React Native Screens in React Navigation for now and we'd like to focus on it later because right now we're not observing that crucial performance issues.
2020-01-27 11:07:07 +01:00
Janic Duplessis
d32463ee83 Move gestureEnabled config to screen instead of heade… (#254)
When you have 2 screens in a stack with the bottom one with gestureEnabled=false using the back gesture causes the screen to become unresponsive. This seems to be caused by setting interactivePopGestureRecognizer.enabled = NO as soon as the gesture starts. This causes the gesture to cancel immediately and leaves the screen in an unresponsive state.

<Stack><Screen gestureEnabled={false} /><Screen /></Stack>
To fix this instead of using interactivePopGestureRecognizer.enabled we can leverage the existing delegate that we have in RNScreenStack. In gestureRecognizerShouldBegin we can check if the top screen has gestures enabled.

To make this simpler I moved the gestureEnabled config to Screen instead of HeaderConfig. I think it also makes more sense conceptually since the gesture is tied to the screen and not the header. It also simplifies the android code a bit.

This is a breaking change.

Update

This now only moves the config to screen since a separate fix was merged for the bug.
2020-01-24 01:19:38 +01:00
Satyajit Sahoo
2da04f37e6 Fix typo in onDismissed prop name (#288)
The prop is called `onDismissed` according to the code

e00a08a3dc/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.java (L65)
e00a08a3dc/ios/RNSScreen.h (L43)
2020-01-24 01:18:26 +01:00
Krzysztof Magiera
623f9452cb Bump version -> 2.0.0-alpha.29 2020-01-22 21:56:21 +01:00
Krzysztof Magiera
c646a4e7ba Fix rendering title view in native header on Android. (#294)
This change removes the behavior that's been added for an unclear reason. We may consider reverting this one, however I was unable to reproduce a problem when setting header title view layout measurements was necessary. To the contrary it was causing issues in the case when the title header view updates. In such a case we would never update width/height so the updated view might be cropped. In addition we are changing the way we schedule native layout updates. Previously we'd use handler.post but it was causing one frame delay so this change migrates us to use choreographer.
2020-01-22 21:52:56 +01:00
Krzysztof Magiera
a1311cd31d Bump version -> 2.0.0-alpha.28 2020-01-22 15:20:22 +01:00
Krzysztof Magiera
f044334464 Fix focus events in native stack on Android. (#292)
This change fixes the issue with auto focusing text input fields inside native stack. Due to the fact we perform hide and show transactions in order to control back stack the hiding part would propagate visibility change event down the view hierarchy. As a result views would receive that visibility event and blur themselves resulting in textinput not getting the focus when the fragment mounts. It turns out however that for the back stack to function properly we don't need to run hide and show operation within the transaction because show is idempotent. So we change our back stack handling transaction to only call show.
2020-01-22 15:19:54 +01:00
Krzysztof Magiera
1c7ebeba39 Fix NPE on nested stack detach on Android. (#291)
This change fixes crash caused when nested stack is detached due to the parent stack being closed. As a result we may end up in a situation when fragment manager is not yet set despite onDetach callback being call. When fragment manager is not set we can also ignore the operations we should've performed in on detach. This change adds a null check before we call into fragment manager in on detach callback.
2020-01-22 15:16:25 +01:00
Krzysztof Magiera
1493d6f1b8 Bump version -> 2.0.0-alpha.27 2020-01-21 11:51:50 +01:00
Krzysztof Magiera
8cf82d1cbe Fix layout of header items on Android native stack. (#289)
This change fixes the issue when left item added to native stack header on Android would by default be shifted from the screen edge by some amount. This turned out to be the default config of the native toolbar which applies some padding on the left side. WIn this change we reset that padding to always be 0 to let the position be specified from react. Note that the setting we reset does not influence the position of the native back navigation button as Android does not apply the padding in case navigation back icon is rendered.
2020-01-20 22:21:07 +01:00
Krzysztof Magiera
e8403582ae Bump version -> 2.0.0-alpha.26 2020-01-20 10:59:13 +01:00
Tuan Luong
5832593980 [Android] update view when go back to previous screen (#286) 2020-01-20 10:58:19 +01:00
dependabot[bot]
78c7745049 Bump handlebars from 4.1.2 to 4.5.3 (#255)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-01-17 22:15:51 +01:00
dependabot[bot]
350a80c29b Bump handlebars from 4.2.0 to 4.5.3 in /Example (#256)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.2.0 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.2.0...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-01-17 22:15:39 +01:00
Michael Wood
c460341a68 Minor typo/wording tweaks (#264) 2020-01-17 22:15:14 +01:00
Angelika Serwa
3fc74e29ab [android] Fix re-attaching ScreenContainer to window (#272)
Code to reproduce and test: https://snack.expo.io/@angelikaserwa/humiliated-bagel.
Switch to the Settings tab, then go back to the Home tab and press the details button. Nothing happens. It was because after re-attaching we were using a destroyed FragmentManager that was cached inside the ScreenContainer class. 
Then, when we go back from Details to Home screen, using a hardware back button, an exception occured: `The specified child already has a parent. You must call removeView() on the child's parent first`.
I fixed this by calling `removeAllViews()` when detaching container from window and forcing an update on re-attach.
2020-01-17 22:13:57 +01:00
Krzysztof Magiera
518c094657 Revert "[Android] Bottom tab not update" (#285)
This reverts commit ca6319d26e.
2020-01-17 22:11:37 +01:00
Krzysztof Magiera
d71aa2c6ef Bump version -> 2.0.0-alpha.25 2020-01-15 21:26:01 +01:00
Krzysztof Magiera
f21ec66cb4 Fix header transition on Android. (#284)
This change prevents toolbar from updating when there is a transition happening to the view. This fixes the issue where header subviews would disappear when going back from a given screen. The root cause was that the config view manager would trigger subviews removal and re-layout itself. As aresult if we had a custom back button that back button would get removed and the title would move into its place while the screen animation is running. In order to prevent that we hook into View Manager's onDropViewInstance to notify header config that it is about to be destroyed. This in turn prevents any updates to be made to the toolbar config.
2020-01-15 21:25:36 +01:00
Janic Duplessis
27ef6dc900 Fix stack with gestureEnabled=false on iOS (#283)
#254 without breaking changes

Instead of moving gestureEnabled to the screen we find the header config subview.
2020-01-14 15:46:16 -05:00
Krzysztof Magiera
ce819f6356 Bump version -> 2.0.0-alpha.24 2020-01-14 18:28:46 +01:00
Krzysztof Magiera
1ac742610b Fix detaching views rendered under header config component. (#282)
This change fixes the problem when header update would get triggered while the header config subviews are not yet fully unmounted. The root cause of this problem is kind of a mystery, I encouneter a crash when going back using back button from a screen that have custom back button image rendered. Turned out that after implementing removeAll schema the problem no longer occurs. I didn't have enough time to investigate it further but supporting removeAll schema is an improvement regardless so going with this solution for now.
2020-01-14 18:28:19 +01:00
Tuan Luong
28f57240c2 [ios] release view from controller when popped from stack (#258)
This issue is related to #92.

Issue
I got issue when playing video with react-native-video. The video keep playing (still hear sound) when go back.

react-native: 0.61.5
react-native-screens: 2.0.0-alpha.22
Solution
When controller popped, try to assign view to nil to break the cycle.
2020-01-11 10:38:06 -05:00
Tuan Luong
ca6319d26e [Android] Bottom tab not update (#275)
Fixes #259

Issue
Screen doesn't update when switching between tabs.

Problem
When ScreenContainer re-added. mFragmentManager is destroyed.
2020-01-11 10:36:28 -05:00
Krzysztof Magiera
2b1b726723 Bump version -> 2.0.0-alpha.23 2020-01-10 10:07:44 +01:00
Krzysztof Magiera
4ce9469571 Fix bug in modal dismiss logic. (#277)
This change fixes a problem in modal dismiss logic. The problem would occur when we had a nested stack that both have screens pushed and presented modally. In such case when we only wanted to pop one of the screens pushed (even though the screen was obscured by the one presented modally) we'd still run setModalViewControllers method. As a result that method could've find the navigato that is presenting the screens modally and trigger dismiss on them. This was the case because we used to only check in that method whether the top-most unchanged screen is presenting modals. We shouldn't only be checking whether the screen is presenting because it may be presenting modals that belong to a different stack that did not change.

To prevent the issue from happening we make sure that we run dismiss logic only when a change on a give stack occur. We verify that the list of presented VCs is not equal to the list of new controllers before we run push-stack or modal-stack presenting/dismiss logic.

On top of that change I also modified push logic to avoid running animation when the push stack is obscured by modals. This prevents some weird effects when you could see the end of animation after closing the modal immediately after pop done in the hidden stack.
2020-01-10 10:04:45 +01:00
osdnk
cef9b8f393 add onAppear callback 2020-01-09 16:05:14 -05:00
Krzysztof Magiera
ecc4056559 Get rid of lack-of-key warning 2020-01-09 18:10:10 +01:00
Jesse Katsumata
71a02af309 Add missing types (#262)
This issue closes #261 

Added typescript definitions for `<ScreenStack>` and `<ScreenStackHeaderConfig>`
2020-01-07 11:33:03 +01:00
Krzysztof Magiera
c88da09348 Bump version -> 2.0.0-alpha.22 2019-12-20 22:23:47 +01:00
Krzysztof Magiera
9aac0b89f2 Allow for RNN wrapper to customize native stack animations 2019-12-20 22:23:22 +01:00
Krzysztof Magiera
a02dd46eec Bump version -> 2.0.0-alpha.21 2019-12-20 22:00:45 +01:00
Krzysztof Magiera
0919404b2e Add support for customizing back button image. (#253)
For customizing back button image we use platform native functionality that is: `setBackIndicatorImage` on iOS and `setHomeAsUpIndicator` on Android.
The reason we don't do that just by setting left item is that we get a couple of things for free such as handling RTL properly, working accessibility features, handling prop for hiding back button and a couple more.

Unfortunately there are some downsides to that approach too. We need to install the back button as an Image component from the JS side, and the extract the image payload on the native side to set it with the navigator. This is specifically problematic in DEV mode where images are loaded asynchronously over HTTP from the packager. In order for that to work we had to employ a few hacks (more comments on that in the code).
2019-12-20 21:59:34 +01:00
osdnk
e61b8b3bd6 Bump version -> 2.0.0-alpha.20 2019-12-17 17:56:11 +01:00
Michał Osadnik
77d877f0c1 Fix not displaying view in navigation (#252)
fixes #208

For stack overriding reactSetFrame for active does nothing because active is always NO, so there's no regression.

But it fixes in other cases (like stack navigation - not native one).
2019-12-17 17:55:03 +01:00
Kevin Hermawan
15c27e3fe7 Fix README typo (#226) 2019-12-15 15:12:09 +01:00
Krzysztof Magiera
ed478a829d Bump version -> 2.0.0-alpha.19 2019-12-12 23:01:59 +01:00
Krzysztof Magiera
a3335b1384 Use containedTransparentModal in RNN wrapper when requested. (#249)
Previously when transparent card was requested we'd default to transparentModal stack presentation mode. However if user requests contained modal mode we should be using containedTransparentModal instead. This change fixes that behavior.
2019-12-12 23:01:36 +01:00
23 changed files with 514 additions and 273 deletions

View File

@@ -219,7 +219,7 @@ PODS:
- ReactCommon/jscallinvoker (= 0.61.2)
- RNGestureHandler (1.3.0):
- React
- RNScreens (2.0.0-alpha.3):
- RNScreens (2.0.0-alpha.23):
- React
- Yoga (1.14.0)
@@ -256,7 +256,7 @@ DEPENDENCIES:
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
https://github.com/CocoaPods/Specs.git:
- boost-for-react-native
EXTERNAL SOURCES:
@@ -342,9 +342,9 @@ SPEC CHECKSUMS:
React-RCTVibration: fb54c732fd20405a76598e431aa2f8c2bf527de9
ReactCommon: 5848032ed2f274fcb40f6b9ec24067787c42d479
RNGestureHandler: 5329a942fce3d41c68b84c2c2276ce06a696d8b0
RNScreens: a32a406ec4884f0ba383ef835332e8379f75e3da
RNScreens: 55c735f525774e894be67848c250c95a9c3194c0
Yoga: 14927e37bd25376d216b150ab2a561773d57911f
PODFILE CHECKSUM: 1a141b811c7076eb11c48f2e22336181f52531b5
COCOAPODS: 1.7.3
COCOAPODS: 1.8.4

View File

@@ -1633,7 +1633,7 @@ combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.19.0, commander@~2.20.0:
commander@^2.19.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
@@ -1642,6 +1642,11 @@ commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
commander@~2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -2487,9 +2492,9 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
handlebars@^4.0.3:
version "4.2.0"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.2.0.tgz#57ce8d2175b9bbb3d8b3cf3e4217b1aec8ddcb2e"
integrity sha512-Kb4xn5Qh1cxAKvQnzNWZ512DhABzyFNmsaJf3OAkWNa4NkaqWcNI8Tao8Tasi0/F4JD9oyG0YxuFyvyR57d+Gw==
version "4.5.3"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482"
integrity sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==
dependencies:
neo-async "^2.6.0"
optimist "^0.6.1"
@@ -4806,7 +4811,7 @@ react-native-safe-area-view@^0.14.1:
debounce "^1.2.0"
"react-native-screens@file:..":
version "2.0.0-alpha.3"
version "2.0.0-alpha.22"
dependencies:
debounce "^1.2.0"
@@ -5761,11 +5766,11 @@ uglify-es@^3.1.9:
source-map "~0.6.1"
uglify-js@^3.1.4:
version "3.6.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
version "3.7.3"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a"
integrity sha512-7tINm46/3puUA4hCkKYo4Xdts+JDaVC9ZPRcG8Xw9R4nhO/gZgUM3TENq8IF4Vatk8qCig4MzP/c8G4u2BkVQg==
dependencies:
commander "~2.20.0"
commander "~2.20.3"
source-map "~0.6.1"
ultron@1.0.x:

View File

@@ -100,12 +100,12 @@ To do that react-native-screens provides you with two components documented belo
### `<ScreenContainer/>`
This component is a container for one or more `Screen` components.
It does not accept other component types are direct children.
The role of container is to control which of its children screens should be attached to the view hierarchy.
It does that by monitoring `active` property of each of its children.
It it possible to have as many `active` children as you'd like but in order for the component to be the most efficient we should keep the number of active screens to the minimum.
In a case of stack navigator or tabs navigator we only want to have one active screen (the top most view on a stack or the selected tab).
Then for the time of transitioning between views we may want to activate a second screen for the duration of transition, and then go back to just one active screen.
It does not accept other component types as direct children.
The role of the container is to control which of its children screens should be attached to the view hierarchy.
It does that by monitoring the `active` property of each of its children.
It is possible to have as many `active` children as you'd like but in order for the component to be the most efficient we should keep the number of active screens to a minimum.
In a case of a stack navigator or tabs navigator we only want to have one active screen (the top most view on a stack or the selected tab).
While transitioning between views we may want to activate a second screen for the duration of the transition, and then go back to just one active screen.
### `<Screen/>`
@@ -127,11 +127,11 @@ Otherwise the views will be attached as long as the parent container is attached
### `<ScreenStack>`
Screen stack component expects one or more `Screen` components as direct children and renders them in a platform native stack container (for iOS it is `UINavigationController` and for Android inside `Fragment` container). For `Screen` components placed as children of `ScteenStack` the `active` property is ignored and instead the screen that corresponds to the last child is rendered as active. All type of updates done to the list of children are acceptable, when the top element is exchanged the container will use platform default (unless customized) animation to transition between screens.
Screen stack component expects one or more `Screen` components as direct children and renders them in a platform native stack container (for iOS it is `UINavigationController` and for Android inside `Fragment` container). For `Screen` components placed as children of `ScreenStack` the `active` property is ignored and instead the screen that corresponds to the last child is rendered as active. All type of updates done to the list of children are acceptable, when the top element is exchanged the container will use platform default (unless customized) animation to transition between screens.
`StackScreen` extends the capabilities of `Screen` component to allow additional customizations and to make it possible to handle events such as using hardware back or back gesture to dismiss the top screen. Below is the list of additional properties that can be used for `Screen` component:
#### `onDismiss`
#### `onDismissed`
A callback that gets called when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down). The callback takes no arguments.
@@ -155,9 +155,8 @@ Defines how the method that should be used to present the given screen. It is a
The config component is expected to be rendered as a direct children of `<Screen>`. It provides an ability to configure native navigation header that gets rendered as a part of native screen stack. The component acts as a "virtual" element that is not directly rendered under `Screen`. You can use its properties to customize platform native header for the parent screen and also render react-native components that you'd like to be displayed inside the header (e.g. in the title are or on the side).
Along with this component properties that can be used to customize header behavior one can also use one or the below component containers to render custom react-native content in different areas of the native header:
- `ScreenStackHeaderTitleView` react native views rendered as children will appear on the navigation bar in the place of title. Note that title is aligned next to back button on Android while it is centered on iOS.
- `ScreenStackHeaderCenterView` the childern will render in the center of the native navigation bar (on iOS this has the same behavior as `ScreenStackHeaderTitleView` container)
- `ScreenStackHeaderRightView` the children will render on the right hand side of the navigation bar (or on the left hand side in case LTR locales are set on the user's device)
- `ScreenStackHeaderCenterView` the childern will render in the center of the native navigation bar.
- `ScreenStackHeaderRightView` the children will render on the right hand side of the navigation bar (or on the left hand side in case LTR locales are set on the user's device).
- `ScreenStackHeaderLeftView` the children will render on the left hand side of the navigation bar (or on the right hand side in case LTR locales are set on the user's device).
Below is a list of properties that can be set with `ScreenStackHeaderConfig` component:

View File

@@ -16,7 +16,7 @@ import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.uimanager.UIManagerModule;
public class Screen extends ViewGroup implements ReactPointerEventsView {
public class Screen extends ViewGroup {
public enum StackPresentation {
PUSH,
@@ -52,6 +52,7 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
private boolean mTransitioning;
private StackPresentation mStackPresentation = StackPresentation.PUSH;
private StackAnimation mStackAnimation = StackAnimation.DEFAULT;
private boolean mGestureEnabled = true;
public Screen(ReactContext context) {
super(context);
@@ -122,6 +123,10 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
mStackAnimation = stackAnimation;
}
public void setGestureEnabled(boolean gestureEnabled) {
mGestureEnabled = gestureEnabled;
}
public StackAnimation getStackAnimation() {
return mStackAnimation;
}
@@ -130,11 +135,6 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
return mStackPresentation;
}
@Override
public PointerEvents getPointerEvents() {
return mTransitioning ? PointerEvents.NONE : PointerEvents.AUTO;
}
@Override
public void setLayerType(int layerType, @Nullable Paint paint) {
// ignore - layer type is controlled by `transitioning` prop
@@ -169,4 +169,8 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
public boolean isActive() {
return mActive;
}
public boolean isGestureEnabled() {
return mGestureEnabled;
}
}

View File

@@ -25,7 +25,7 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
private final Set<ScreenFragment> mActiveScreenFragments = new HashSet<>();
private final ArrayList<Runnable> mAfterTransitionRunnables = new ArrayList<>(1);
private @Nullable FragmentManager mFragmentManager;
protected @Nullable FragmentManager mFragmentManager;
private @Nullable FragmentTransaction mCurrentTransaction;
private @Nullable FragmentTransaction mProcessingTransaction;
private boolean mNeedUpdate;
@@ -33,6 +33,7 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
private boolean mIsTransitioning;
private boolean mLayoutEnqueued = false;
private final ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
@@ -40,9 +41,9 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
}
};
private final Runnable mLayoutRunnable = new Runnable() {
private final ChoreographerCompat.FrameCallback mLayoutCallback = new ChoreographerCompat.FrameCallback() {
@Override
public void run() {
public void doFrame(long frameTimeNanos) {
mLayoutEnqueued = false;
measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
@@ -66,9 +67,13 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
public void requestLayout() {
super.requestLayout();
if (!mLayoutEnqueued) {
if (!mLayoutEnqueued && mLayoutCallback != null) {
mLayoutEnqueued = true;
post(mLayoutRunnable);
// we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current
// looper loop instead of enqueueing the update in the next loop causing a one frame delay.
ReactChoreographer.getInstance().postFrameCallback(
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
mLayoutCallback);
}
}
@@ -105,6 +110,14 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
markUpdated();
}
protected void removeAllScreens() {
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
mScreenFragments.get(i).getScreen().setContainer(null);
}
mScreenFragments.clear();
markUpdated();
}
@Override
public void startViewTransition(View view) {
super.startViewTransition(view);
@@ -176,16 +189,9 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
return ((FragmentActivity) context).getSupportFragmentManager();
}
protected final FragmentManager getFragmentManager() {
if (mFragmentManager == null) {
mFragmentManager = findFragmentManager();
}
return mFragmentManager;
}
protected FragmentTransaction getOrCreateTransaction() {
if (mCurrentTransaction == null) {
mCurrentTransaction = getFragmentManager().beginTransaction();
mCurrentTransaction = mFragmentManager.beginTransaction();
mCurrentTransaction.setReorderingAllowed(true);
}
return mCurrentTransaction;
@@ -239,6 +245,8 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mIsAttached = true;
mNeedUpdate = true;
mFragmentManager = findFragmentManager();
updateIfNeeded();
}
@@ -246,6 +254,14 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mIsAttached = false;
// fragment manager is destroyed so we can't do anything with it anymore
mFragmentManager = null;
// so we don't add the same screen twice after re-attach
removeAllViews();
mActiveScreenFragments.clear();
// after re-attach we'll update the screen and add views again
markUpdated();
}
@Override

View File

@@ -34,6 +34,11 @@ public class ScreenContainerViewManager extends ViewGroupManager<ScreenContainer
parent.removeScreenAt(index);
}
@Override
public void removeAllViews(ScreenContainer parent) {
parent.removeAllScreens();
}
@Override
public int getChildCount(ScreenContainer parent) {
return parent.getScreenCount();

View File

@@ -22,7 +22,7 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
private final FragmentManager.OnBackStackChangedListener mBackStackListener = new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
if (mFragmentManager.getBackStackEntryCount() == 0) {
// when back stack entry count hits 0 it means the user's navigated back using hw back
// button. As the "fake" transaction we installed on the back stack does nothing we need
// to handle back navigation on our own.
@@ -70,24 +70,24 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
FragmentManager fm = getFragmentManager();
fm.removeOnBackStackChangedListener(mBackStackListener);
getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
if (!fm.isStateSaved()) {
// state save means that the container where fragment manager was installed has been unmounted.
// This could happen as a result of dismissing nested stack. In such a case we don't need to
// reset back stack as it'd result in a crash caused by the fact the fragment manager is no
// longer attached.
fm.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (mFragmentManager != null) {
mFragmentManager.removeOnBackStackChangedListener(mBackStackListener);
mFragmentManager.unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks);
if (!mFragmentManager.isStateSaved()) {
// state save means that the container where fragment manager was installed has been unmounted.
// This could happen as a result of dismissing nested stack. In such a case we don't need to
// reset back stack as it'd result in a crash caused by the fact the fragment manager is no
// longer attached.
mFragmentManager.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
super.onDetachedFromWindow();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false);
mFragmentManager.registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false);
}
@Override
@@ -97,6 +97,12 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
super.removeScreenAt(index);
}
@Override
protected void removeAllScreens() {
mDismissed.clear();
super.removeAllScreens();
}
@Override
protected boolean hasScreen(ScreenFragment screenFragment) {
return super.hasScreen(screenFragment) && !mDismissed.contains(screenFragment);
@@ -224,8 +230,8 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
// notified when it gets resumed so that we can install the handler.
return;
}
getFragmentManager().removeOnBackStackChangedListener(mBackStackListener);
getFragmentManager().popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
mFragmentManager.removeOnBackStackChangedListener(mBackStackListener);
mFragmentManager.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
ScreenStackFragment firstScreen = null;
for (int i = 0, size = mStack.size(); i < size; i++) {
ScreenStackFragment screen = mStack.get(i);
@@ -235,14 +241,13 @@ public class ScreenStack extends ScreenContainer<ScreenStackFragment> {
}
}
if (topScreen != firstScreen && topScreen.isDismissable()) {
getFragmentManager()
mFragmentManager
.beginTransaction()
.hide(topScreen)
.show(topScreen)
.addToBackStack(BACK_STACK_TAG)
.setPrimaryNavigationFragment(topScreen)
.commitAllowingStateLoss();
getFragmentManager().addOnBackStackChangedListener(mBackStackListener);
mFragmentManager.addOnBackStackChangedListener(mBackStackListener);
}
}
}

View File

@@ -11,6 +11,7 @@ import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import com.facebook.react.uimanager.PixelUtil;
import com.google.android.material.appbar.AppBarLayout;
@@ -97,10 +98,35 @@ public class ScreenStackFragment extends ScreenFragment {
}
public boolean isDismissable() {
View child = mScreenView.getChildAt(0);
if (child instanceof ScreenStackHeaderConfig) {
return ((ScreenStackHeaderConfig) child).isDismissable();
return mScreenView.isGestureEnabled();
}
public boolean canNavigateBack() {
ScreenContainer container = mScreenView.getContainer();
if (container instanceof ScreenStack) {
if (((ScreenStack) container).getRootScreen() == getScreen()) {
// this screen is the root of the container, if it is nested we can check parent container
// if it is also a root or not
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof ScreenStackFragment) {
return ((ScreenStackFragment) parentFragment).canNavigateBack();
} else {
return false;
}
} else {
return true;
}
} else {
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
}
}
public void dismiss() {
ScreenContainer container = mScreenView.getContainer();
if (container instanceof ScreenStack) {
((ScreenStack) container).dismiss(this);
} else {
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
}
return true;
}
}

View File

@@ -8,6 +8,7 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.ActionBar;
@@ -15,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.views.text.ReactFontManager;
import java.util.ArrayList;
@@ -28,9 +30,9 @@ public class ScreenStackHeaderConfig extends ViewGroup {
private float mTitleFontSize;
private int mBackgroundColor;
private boolean mIsHidden;
private boolean mGestureEnabled = true;
private boolean mIsBackButtonHidden;
private boolean mIsShadowHidden;
private boolean mDestroyed;
private int mTintColor;
private final Toolbar mToolbar;
@@ -39,7 +41,16 @@ public class ScreenStackHeaderConfig extends ViewGroup {
private OnClickListener mBackClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
getScreenStack().dismiss(getScreenFragment());
ScreenStack stack = getScreenStack();
ScreenStackFragment fragment = getScreenFragment();
if (stack.getRootScreen() == fragment.getScreen()) {
Fragment parentFragment = fragment.getParentFragment();
if (parentFragment instanceof ScreenStackFragment) {
((ScreenStackFragment) parentFragment).dismiss();
}
} else {
fragment.dismiss();
}
}
};
@@ -48,6 +59,10 @@ public class ScreenStackHeaderConfig extends ViewGroup {
setVisibility(View.GONE);
mToolbar = new Toolbar(context);
// reset content insets to be 0 to allow react position custom navbar views. Note that this does
// not affect platform native back button as toolbar does not apply left inset when navigation
// button is specified
mToolbar.setContentInsetsAbsolute(0, 0);
// set primary color as background by default
TypedValue tv = new TypedValue();
@@ -61,6 +76,10 @@ public class ScreenStackHeaderConfig extends ViewGroup {
// no-op
}
public void destroy() {
mDestroyed = true;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -104,17 +123,12 @@ public class ScreenStackHeaderConfig extends ViewGroup {
return null;
}
public boolean isDismissable() {
return mGestureEnabled;
}
public void onUpdate() {
Screen parent = (Screen) getParent();
final ScreenStack stack = getScreenStack();
boolean isRoot = stack == null ? true : stack.getRootScreen() == parent;
boolean isTop = stack == null ? true : stack.getTopScreen() == parent;
if (!mIsAttachedToWindow || !isTop) {
if (!mIsAttachedToWindow || !isTop || mDestroyed) {
return;
}
@@ -134,7 +148,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
ActionBar actionBar = activity.getSupportActionBar();
// hide back button
actionBar.setDisplayHomeAsUpEnabled(isRoot ? false : !mIsBackButtonHidden);
actionBar.setDisplayHomeAsUpEnabled(getScreenFragment().canNavigateBack() ? !mIsBackButtonHidden : false);
// when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites
// navigation click listener. The default behavior set in the wrapper is to call into
@@ -184,6 +198,17 @@ public class ScreenStackHeaderConfig extends ViewGroup {
ScreenStackHeaderSubview view = mConfigSubviews.get(i);
ScreenStackHeaderSubview.Type type = view.getType();
if (type == ScreenStackHeaderSubview.Type.BACK) {
// we special case BACK button header config type as we don't add it as a view into toolbar
// but instead just copy the drawable from imageview that's added as a first child to it.
View firstChild = view.getChildAt(0);
if (!(firstChild instanceof ImageView)) {
throw new JSApplicationIllegalArgumentException("Back button header config view should have Image as first child");
}
actionBar.setHomeAsUpIndicator(((ImageView) firstChild).getDrawable());
continue;
}
Toolbar.LayoutParams params =
new Toolbar.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
@@ -198,11 +223,10 @@ public class ScreenStackHeaderConfig extends ViewGroup {
case RIGHT:
params.gravity = Gravity.RIGHT;
break;
case TITLE:
params.width = LayoutParams.MATCH_PARENT;
mToolbar.setTitle(null);
case CENTER:
params.width = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER_HORIZONTAL;
mToolbar.setTitle(null);
break;
}
@@ -212,7 +236,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
}
private void maybeUpdate() {
if (getParent() != null) {
if (getParent() != null && !mDestroyed) {
onUpdate();
}
}
@@ -230,6 +254,11 @@ public class ScreenStackHeaderConfig extends ViewGroup {
maybeUpdate();
}
public void removeAllConfigSubviews() {
mConfigSubviews.clear();
maybeUpdate();
}
public void addConfigSubview(ScreenStackHeaderSubview child, int index) {
mConfigSubviews.add(index, child);
maybeUpdate();
@@ -276,10 +305,6 @@ public class ScreenStackHeaderConfig extends ViewGroup {
mIsShadowHidden = hideShadow;
}
public void setGestureEnabled(boolean gestureEnabled) {
mGestureEnabled = gestureEnabled;
}
public void setHideBackButton(boolean hideBackButton) {
mIsBackButtonHidden = hideBackButton;
}

View File

@@ -4,11 +4,12 @@ import android.view.View;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nonnull;
@ReactModule(name = ScreenStackHeaderConfigViewManager.REACT_CLASS)
public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenStackHeaderConfig> {
@@ -32,6 +33,16 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
parent.addConfigSubview((ScreenStackHeaderSubview) child, index);
}
@Override
public void onDropViewInstance(@Nonnull ScreenStackHeaderConfig view) {
view.destroy();
}
@Override
public void removeAllViews(ScreenStackHeaderConfig parent) {
parent.removeAllConfigSubviews();
}
@Override
public void removeViewAt(ScreenStackHeaderConfig parent, int index) {
parent.removeConfigSubview(index);
@@ -88,11 +99,6 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
config.setHideShadow(hideShadow);
}
@ReactProp(name = "gestureEnabled", defaultBoolean = true)
public void setGestureEnabled(ScreenStackHeaderConfig config, boolean gestureEnabled) {
config.setGestureEnabled(gestureEnabled);
}
@ReactProp(name = "hideBackButton")
public void setHideBackButton(ScreenStackHeaderConfig config, boolean hideBackButton) {
config.setHideBackButton(hideBackButton);

View File

@@ -9,20 +9,14 @@ import com.facebook.react.views.view.ReactViewGroup;
public class ScreenStackHeaderSubview extends ReactViewGroup {
public class Measurements {
public int width;
public int height;
}
public enum Type {
LEFT,
CENTER,
TITLE,
RIGHT
RIGHT,
BACK
}
private int mReactWidth, mReactHeight;
private final UIManagerModule mUIManager;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@@ -42,29 +36,13 @@ public class ScreenStackHeaderSubview extends ReactViewGroup {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed && (mType == Type.CENTER || mType == Type.TITLE)) {
Measurements measurements = new Measurements();
measurements.width = right - left;
if (mType == Type.CENTER) {
// if we want the view to be centered we need to account for the fact that right and left
// paddings may not be equal.
View parent = (View) getParent();
int parentWidth = parent.getWidth();
int rightPadding = parentWidth - right;
int leftPadding = left;
measurements.width = Math.max(0, parentWidth - 2 * Math.max(rightPadding, leftPadding));
}
measurements.height = bottom - top;
mUIManager.setViewLocalData(getId(), measurements);
}
super.onLayout(changed, left, top, right, bottom);
// no-op
}
private Type mType = Type.RIGHT;
public ScreenStackHeaderSubview(ReactContext context) {
super(context);
mUIManager = context.getNativeModule(UIManagerModule.class);
}
public void setType(Type type) {

View File

@@ -1,8 +1,6 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.view.ReactViewGroup;
@@ -11,15 +9,6 @@ import com.facebook.react.views.view.ReactViewManager;
@ReactModule(name = ScreenStackHeaderSubviewManager.REACT_CLASS)
public class ScreenStackHeaderSubviewManager extends ReactViewManager {
private static class SubviewShadowNode extends LayoutShadowNode {
@Override
public void setLocalData(Object data) {
ScreenStackHeaderSubview.Measurements measurements = (ScreenStackHeaderSubview.Measurements) data;
setStyleWidth(measurements.width);
setStyleHeight(measurements.height);
}
}
protected static final String REACT_CLASS = "RNSScreenStackHeaderSubview";
@Override
@@ -32,21 +21,16 @@ public class ScreenStackHeaderSubviewManager extends ReactViewManager {
return new ScreenStackHeaderSubview(context);
}
@Override
public LayoutShadowNode createShadowNodeInstance(ReactApplicationContext context) {
return new SubviewShadowNode();
}
@ReactProp(name = "type")
public void setType(ScreenStackHeaderSubview view, String type) {
if ("left".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.LEFT);
} else if ("center".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.CENTER);
} else if ("title".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.TITLE);
} else if ("right".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.RIGHT);
} else if ("back".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.BACK);
}
}
}

View File

@@ -57,6 +57,11 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
}
}
@ReactProp(name = "gestureEnabled", defaultBoolean = true)
public void setGestureEnabled(Screen view, boolean gestureEnabled) {
view.setGestureEnabled(gestureEnabled);
}
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {

View File

@@ -12,9 +12,10 @@ import {
ScreenStack,
Screen,
ScreenStackHeaderConfig,
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderLeftView,
ScreenStackHeaderRightView,
ScreenStackHeaderTitleView,
ScreenStackHeaderCenterView,
} from 'react-native-screens';
function renderComponentOrThunk(componentOrThunk, props) {
@@ -29,7 +30,10 @@ class StackView extends React.Component {
this.props.navigation.dispatch(StackActions.pop({ key: route.key }));
};
_onSceneFocus = route => {
_onSceneFocus = (route, descriptor) => {
descriptor.options &&
descriptor.options.onAppear &&
descriptor.options.onAppear();
this.props.navigation.dispatch(
StackActions.completeTransition({ toChildKey: route.key })
);
@@ -48,7 +52,6 @@ class StackView extends React.Component {
headerBackTitle,
headerBackTitleVisible,
headerTintColor,
gestureEnabled,
largeTitle,
headerLargeTitleStyle,
translucent,
@@ -73,7 +76,6 @@ class StackView extends React.Component {
headerBackTitleStyle && headerBackTitleStyle.fontFamily,
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
color: headerTintColor,
gestureEnabled: gestureEnabled === undefined ? true : gestureEnabled,
largeTitle,
largeTitleFontFamily:
headerLargeTitleStyle && headerLargeTitleStyle.fontFamily,
@@ -93,6 +95,15 @@ class StackView extends React.Component {
const children = [];
if (options.backButtonImage) {
children.push(
<ScreenStackHeaderBackButtonImage
key="backImage"
source={options.backButtonImage}
/>
);
}
if (options.headerLeft !== undefined) {
children.push(
<ScreenStackHeaderLeftView key="left">
@@ -130,9 +141,9 @@ class StackView extends React.Component {
headerOptions.title = options.headerTitle;
} else {
children.push(
<ScreenStackHeaderTitleView key="title">
<ScreenStackHeaderCenterView key="center">
{renderComponentOrThunk(options.headerTitle, { scene })}
</ScreenStackHeaderTitleView>
</ScreenStackHeaderCenterView>
);
}
}
@@ -159,11 +170,16 @@ class StackView extends React.Component {
let stackPresentation = 'push';
if (mode === 'modal' || mode === 'containedModal') {
stackPresentation =
transparentCard || options.cardTransparent ? 'transparentModal' : mode;
stackPresentation = mode;
if (transparentCard || options.cardTransparent) {
stackPresentation =
mode === 'containedModal'
? 'containedTransparentModal'
: 'transparentModal';
}
}
let stackAnimation;
let stackAnimation = options.stackAnimation;
if (options.animationEnabled === false) {
stackAnimation = 'none';
}
@@ -175,7 +191,10 @@ class StackView extends React.Component {
style={options.cardStyle}
stackAnimation={stackAnimation}
stackPresentation={stackPresentation}
onAppear={() => this._onSceneFocus(route)}
gestureEnabled={
options.gestureEnabled === undefined ? true : options.gestureEnabled
}
onAppear={() => this._onSceneFocus(route, descriptor)}
onDismissed={() => this._removeScene(route)}>
{this._renderHeaderConfig(index, route, descriptor)}
<SceneView
@@ -206,7 +225,6 @@ const styles = StyleSheet.create({
function createStackNavigator(routeConfigMap, stackConfig = {}) {
const router = StackRouter(routeConfigMap, stackConfig);
// Create a navigator with StackView as the view
let Navigator = createNavigator(StackView, router, stackConfig);
// if (!stackConfig.disableKeyboardHandling) {

View File

@@ -44,6 +44,7 @@ typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
@property (weak, nonatomic) UIView<RNSScreenContainerDelegate> *reactSuperview;
@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic) BOOL active;
@property (nonatomic) BOOL gestureEnabled;
@property (nonatomic) RNSScreenStackAnimation stackAnimation;
@property (nonatomic) RNSScreenStackPresentation stackPresentation;

View File

@@ -26,6 +26,7 @@
_controller = [[RNSScreen alloc] initWithView:self];
_stackPresentation = RNSScreenStackPresentationPush;
_stackAnimation = RNSScreenStackAnimationDefault;
_gestureEnabled = YES;
}
return self;
@@ -33,6 +34,9 @@
- (void)reactSetFrame:(CGRect)frame
{
if (_active) {
[super reactSetFrame:frame];
}
// ignore setFrame call from react, the frame of this view
// is controlled by the UIViewController it is contained in
}
@@ -273,6 +277,7 @@
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(active, BOOL)
RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
RCT_EXPORT_VIEW_PROPERTY(onAppear, RCTDirectEventBlock);

View File

@@ -22,6 +22,7 @@
NSMutableArray<RNSScreenView *> *_reactSubviews;
NSMutableSet<RNSScreenView *> *_dismissedScreens;
NSMutableArray<UIViewController *> *_presentedModals;
__weak UIViewController* recentPopped;
__weak RNSScreenStackManager *_manager;
}
@@ -69,6 +70,10 @@
[_dismissedScreens addObject:[_reactSubviews objectAtIndex:i - 1]];
}
}
if (recentPopped != nil) {
recentPopped.view = nil;
recentPopped = nil;
}
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
@@ -77,7 +82,8 @@
if (operation == UINavigationControllerOperationPush) {
screen = (RNSScreenView *) toVC.view;
} else if (operation == UINavigationControllerOperationPop) {
screen = (RNSScreenView *) fromVC.view;
screen = (RNSScreenView *) fromVC.view;
recentPopped = fromVC;
}
if (screen != nil && (screen.stackAnimation == RNSScreenStackAnimationFade || screen.stackAnimation == RNSScreenStackAnimationNone)) {
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
@@ -96,7 +102,9 @@
RCTRootContentView *rootView = (RCTRootContentView *)parent;
[rootView.touchHandler cancel];
return _controller.viewControllers.count > 1;
RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
return _controller.viewControllers.count > 1 && topScreen.gestureEnabled;
}
- (void)markChildUpdated
@@ -137,12 +145,16 @@
- (void)setModalViewControllers:(NSArray<UIViewController *> *)controllers
{
// when there is no change we return immediately. This check is important because sometime we may
// accidently trigger modal dismiss if we don't verify to run the below code only when an actual
// change in the list of presented modal was made.
if ([_presentedModals isEqualToArray:controllers]) {
return;
}
NSMutableArray<UIViewController *> *newControllers = [NSMutableArray arrayWithArray:controllers];
[newControllers removeObjectsInArray:_presentedModals];
NSMutableArray<UIViewController *> *controllersToRemove = [NSMutableArray arrayWithArray:_presentedModals];
[controllersToRemove removeObjectsInArray:controllers];
// find bottom-most controller that should stay on the stack for the duration of transition
NSUInteger changeRootIndex = 0;
UIViewController *changeRootController = _controller;
@@ -173,9 +185,6 @@
completion:nil];
previous = next;
}
[self->_presentedModals removeAllObjects];
[self->_presentedModals addObjectsFromArray:controllers];
};
if (changeRootController.presentedViewController) {
@@ -185,10 +194,16 @@
} else {
finish();
}
[_presentedModals setArray:controllers];
}
- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
{
// when there is no change we return immediately
if ([_controller.viewControllers isEqualToArray:controllers]) {
return;
}
UIViewController *top = controllers.lastObject;
UIViewController *lastTop = _controller.viewControllers.lastObject;
@@ -198,7 +213,7 @@
// controller is still there
BOOL firstTimePush = ![lastTop isKindOfClass:[RNSScreen class]];
BOOL shouldAnimate = !firstTimePush && ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone;
BOOL shouldAnimate = !firstTimePush && ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone && !_controller.presentedViewController;
if (firstTimePush) {
// nothing pushed yet

View File

@@ -23,7 +23,6 @@
@property (nonatomic) BOOL hideBackButton;
@property (nonatomic) BOOL hideShadow;
@property (nonatomic) BOOL translucent;
@property (nonatomic) BOOL gestureEnabled;
+ (void)willShowViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig*)config;
@@ -34,6 +33,7 @@
@end
typedef NS_ENUM(NSInteger, RNSScreenStackHeaderSubviewType) {
RNSScreenStackHeaderSubviewTypeBackButton,
RNSScreenStackHeaderSubviewTypeLeft,
RNSScreenStackHeaderSubviewTypeRight,
RNSScreenStackHeaderSubviewTypeTitle,

View File

@@ -5,34 +5,41 @@
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <React/RCTShadowView.h>
#import <React/RCTImageLoader.h>
#import <React/RCTImageView.h>
#import <React/RCTImageSource.h>
@interface RNSScreenHeaderItemMeasurements : NSObject
@property (nonatomic, readonly) CGSize headerSize;
@property (nonatomic, readonly) CGFloat leftPadding;
@property (nonatomic, readonly) CGFloat rightPadding;
- (instancetype)initWithHeaderSize:(CGSize)headerSize leftPadding:(CGFloat)leftPadding rightPadding:(CGFloat)rightPadding;
// Some RN private method hacking below. Couldn't figure out better way to access image data
// of a given RCTImageView. See more comments in the code section processing SubviewTypeBackButton
@interface RCTImageView (Private)
- (UIImage*)image;
@end
@implementation RNSScreenHeaderItemMeasurements
- (instancetype)initWithHeaderSize:(CGSize)headerSize leftPadding:(CGFloat)leftPadding rightPadding:(CGFloat)rightPadding
{
if (self = [super init]) {
_headerSize = headerSize;
_leftPadding = leftPadding;
_rightPadding = rightPadding;
}
return self;
}
@interface RCTImageLoader (Private)
- (id<RCTImageCache>)imageCache;
@end
@interface RNSScreenStackHeaderSubview : UIView
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, weak) UIView *reactSuperview;
@property (nonatomic) RNSScreenStackHeaderSubviewType type;
- (instancetype)initWithBridge:(RCTBridge*)bridge;
@end
@implementation RNSScreenStackHeaderSubview
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
}
return self;
}
@end
@implementation RNSScreenStackHeaderConfig {
@@ -45,7 +52,6 @@
self.hidden = YES;
_translucent = YES;
_reactSubviews = [NSMutableArray new];
_gestureEnabled = YES;
}
return self;
}
@@ -81,7 +87,16 @@
{
UIViewController *vc = _screenView.controller;
UINavigationController *nav = (UINavigationController*) vc.parentViewController;
if (vc != nil && nav.visibleViewController == vc) {
UIViewController *nextVC = nav.visibleViewController;
if (nav.transitionCoordinator != nil) {
// if navigator is performing transition instead of allowing to update of `visibleConttroller`
// we look at `topController`. This is because during transitiong the `visibleController` won't
// point to the controller that is going to be revealed after transition. This check fixes the
// problem when config gets updated while the transition is ongoing.
nextVC = nav.topViewController;
}
if (vc != nil && nextVC == vc) {
[RNSScreenStackHeaderConfig updateViewController:self.screenView.controller withConfig:self];
}
}
@@ -150,6 +165,15 @@
[navbar setLargeTitleTextAttributes:largeAttrs];
}
}
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
if (backButtonImage) {
navbar.backIndicatorImage = backButtonImage;
navbar.backIndicatorTransitionMaskImage = backButtonImage;
} else if (navbar.backIndicatorImage) {
navbar.backIndicatorImage = nil;
navbar.backIndicatorTransitionMaskImage = nil;
}
}
}
@@ -164,6 +188,59 @@
}
}
+ (UIImage*)loadBackButtonImageInViewController:(UIViewController *)vc
withConfig:(RNSScreenStackHeaderConfig *)config
{
BOOL hasBackButtonImage = NO;
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
if (subview.type == RNSScreenStackHeaderSubviewTypeBackButton && subview.subviews.count > 0) {
hasBackButtonImage = YES;
RCTImageView *imageView = subview.subviews[0];
UIImage *image = imageView.image;
// IMPORTANT!!!
// image can be nil in DEV MODE ONLY
//
// It is so, because in dev mode images are loaded over HTTP from the packager. In that case
// we first check if image is already loaded in cache and if it is, we take it from cache and
// display immediately. Otherwise we wait for the transition to finish and retry updating
// header config.
// Unfortunately due to some problems in UIKit we cannot update the image while the screen
// transition is ongoing. This results in the settings being reset after the transition is done
// to the state from before the transition.
if (image == nil) {
// in DEV MODE we try to load from cache (we use private API for that as it is not exposed
// publically in headers).
RCTImageSource *source = imageView.imageSources[0];
image = [subview.bridge.imageLoader.imageCache
imageForUrl:source.request.URL.absoluteString
size:source.size
scale:source.scale
resizeMode:imageView.resizeMode];
}
if (image == nil) {
// This will be triggered if the image is not in the cache yet. What we do is we wait until
// the end of transition and run header config updates again. We could potentially wait for
// image on load to trigger, but that would require even more private method hacking.
if (vc.transitionCoordinator) {
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// nothing, we just want completion
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// in order for new back button image to be loaded we need to trigger another change
// in back button props that'd make UIKit redraw the button. Otherwise the changes are
// not reflected. Here we change back button visibility which is then immediately restored
vc.navigationItem.hidesBackButton = YES;
[config updateViewControllerIfNeeded];
}];
}
return [UIImage new];
} else {
return image;
}
}
}
return nil;
}
+ (void)willShowViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
{
[self updateViewController:vc withConfig:config];
@@ -190,10 +267,9 @@
}
[navctr setNavigationBarHidden:shouldHide animated:YES];
navctr.interactivePopGestureRecognizer.enabled = config.gestureEnabled;
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
vc.modalInPresentation = !config.gestureEnabled;
vc.modalInPresentation = !config.screenView.gestureEnabled;
}
#endif
if (shouldHide) {
@@ -201,7 +277,6 @@
}
navitem.title = config.title;
navitem.hidesBackButton = config.hideBackButton;
if (config.backTitle != nil) {
prevItem.backBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:config.backTitle
@@ -290,11 +365,19 @@
appearance.largeTitleTextAttributes = largeAttrs;
}
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
if (backButtonImage) {
[appearance setBackIndicatorImage:backButtonImage transitionMaskImage:backButtonImage];
} else if (appearance.backIndicatorImage) {
[appearance setBackIndicatorImage:nil transitionMaskImage:nil];
}
navitem.standardAppearance = appearance;
navitem.compactAppearance = appearance;
navitem.scrollEdgeAppearance = appearance;
}
#endif
navitem.hidesBackButton = config.hideBackButton;
navitem.leftBarButtonItem = nil;
navitem.rightBarButtonItem = nil;
navitem.titleView = nil;
@@ -312,17 +395,22 @@
}
case RNSScreenStackHeaderSubviewTypeCenter:
case RNSScreenStackHeaderSubviewTypeTitle: {
subview.translatesAutoresizingMaskIntoConstraints = NO;
navitem.titleView = subview;
break;
}
}
}
if (vc.transitionCoordinator != nil && !wasHidden) {
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
} completion:nil];
if (vc.transitionCoordinator != nil
&& vc.transitionCoordinator.presentationStyle == UIModalPresentationNone
&& !wasHidden) {
// when there is an ongoing transition we may need to update navbar setting in animation block
// using animateAlongsideTransition. However, we only do that given the transition is not a modal
// transition (presentationStyle == UIModalPresentationNone) and that the bar was not previously
// hidden. This is because both for modal transitions and transitions from screen with hidden bar
// the transition animation block does not get triggered. This is ok, because with both of those
// types of transitions there is no "shared" navigation bar that needs to be updated in an animated
// way.
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self setAnimatedConfig:vc withConfig:config];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
@@ -371,13 +459,13 @@ RCT_EXPORT_VIEW_PROPERTY(hideShadow, BOOL)
// `hidden` is an UIView property, we need to use different name internally
RCT_REMAP_VIEW_PROPERTY(hidden, hide, BOOL)
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
@end
@implementation RCTConvert (RNSScreenStackHeader)
RCT_ENUM_CONVERTER(RNSScreenStackHeaderSubviewType, (@{
@"back": @(RNSScreenStackHeaderSubviewTypeBackButton),
@"left": @(RNSScreenStackHeaderSubviewTypeLeft),
@"right": @(RNSScreenStackHeaderSubviewTypeRight),
@"title": @(RNSScreenStackHeaderSubviewTypeTitle),
@@ -386,69 +474,6 @@ RCT_ENUM_CONVERTER(RNSScreenStackHeaderSubviewType, (@{
@end
@implementation RNSScreenStackHeaderSubview {
__weak RCTBridge *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (!self.translatesAutoresizingMaskIntoConstraints) {
CGSize size = self.superview.frame.size;
CGFloat right = size.width - self.frame.size.width - self.frame.origin.x;
CGFloat left = self.frame.origin.x;
[_bridge.uiManager
setLocalData:[[RNSScreenHeaderItemMeasurements alloc]
initWithHeaderSize:size
leftPadding:left rightPadding:right]
forView:self];
}
}
- (void)reactSetFrame:(CGRect)frame
{
if (self.translatesAutoresizingMaskIntoConstraints) {
[super reactSetFrame:frame];
}
}
- (CGSize)intrinsicContentSize
{
return UILayoutFittingExpandedSize;
}
@end
@interface RNSScreenStackHeaderSubviewShadow : RCTShadowView
@end
@implementation RNSScreenStackHeaderSubviewShadow
- (void)setLocalData:(RNSScreenHeaderItemMeasurements *)data
{
self.width = (YGValue){data.headerSize.width - data.leftPadding - data.rightPadding, YGUnitPoint};
self.height = (YGValue){data.headerSize.height, YGUnitPoint};
if (data.leftPadding > data.rightPadding) {
self.paddingLeft = (YGValue){0, YGUnitPoint};
self.paddingRight = (YGValue){data.leftPadding - data.rightPadding, YGUnitPoint};
} else {
self.paddingLeft = (YGValue){data.rightPadding - data.leftPadding, YGUnitPoint};
self.paddingRight = (YGValue){0, YGUnitPoint};
}
[self didSetProps:@[@"width", @"height", @"paddingLeft", @"paddingRight"]];
}
@end
@implementation RNSScreenStackHeaderSubviewManager
RCT_EXPORT_MODULE()
@@ -460,9 +485,4 @@ RCT_EXPORT_VIEW_PROPERTY(type, RNSScreenStackHeaderSubviewType)
return [[RNSScreenStackHeaderSubview alloc] initWithBridge:self.bridge];
}
- (RCTShadowView *)shadowView
{
return [RNSScreenStackHeaderSubviewShadow new];
}
@end

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-screens",
"version": "2.0.0-alpha.18",
"version": "2.0.0-beta.2",
"description": "First incomplete navigation solution for your react-native app.",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",

129
src/screens.d.ts vendored
View File

@@ -3,21 +3,144 @@
declare module 'react-native-screens' {
import { ComponentClass } from 'react';
import { ViewProps, Animated } from 'react-native';
import {
ViewProps,
Animated,
NativeSyntheticEvent,
NativeTouchEvent,
ImageProps,
} from 'react-native';
export function useScreens(shouldUseScreens?: boolean): void;
export function enableScreens(shouldEnableScreens?: boolean): void;
export function screensEnabled(): boolean;
export type StackPresentationTypes = 'push' | 'modal' | 'transparentModal';
export type StackAnimationTypes = 'default' | 'fade' | 'flip' | 'none';
export interface ScreenProps extends ViewProps {
active?: 0 | 1 | Animated.AnimatedInterpolation;
onComponentRef?: (view: any) => void;
children?: React.ReactNode;
onAppear?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
/**
*@description A callback that gets called when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down). The callback takes no arguments.
*/
onDismissed?: (e: NativeSyntheticEvent<NativeTouchEvent>) => void;
/**
* @type "push" the new screen will be pushed onto a stack which on iOS means that the default animation will be slide from the side, the animation on Android may vary depending on the OS version and theme.
* @type "modal" the new screen will be presented modally. In addition this allow for a nested stack to be rendered inside such screens
* @type "transparentModal" the new screen will be presented modally but in addition the second to last screen will remain attached to the stack container such that if the top screen is non opaque the content below can still be seen. If "modal" is used instead the below screen will get unmounted as soon as the transition ends.
*/
stackPresentation: StackPresentationTypes;
/**
*@description Allows for the customization of how the given screen should appear/dissapear when pushed or popped at the top of the stack. The followin values are currently supported:
* @type "default" uses a platform default animation
* @type "fade" fades screen in or out
* @type "flip" flips the screen, requires stackPresentation: "modal" (iOS only)
* @type "none" the screen appears/dissapears without an animation
*/
stackAnimation?: StackAnimationTypes;
/**
* @description When set to false the back swipe gesture will be disabled when the parent Screen is on top of the stack. The default value is true.
*/
gestureEnabled?: boolean;
}
export const Screen: ComponentClass<ScreenProps>;
export type ScreenContainerProps = ViewProps;
export const ScreenContainer: ComponentClass<ScreenContainerProps>;
export interface ScreenStackProps extends ViewProps {
transitioning?: number;
progress?: number;
}
export interface ScreenStackHeaderConfigProps extends ViewProps {
/**
*@description String that representing screen title that will get rendered in the middle section of the header. On iOS the title is centered on the header while on Android it is aligned to the left and placed next to back button (if one is present).
*/
title?: string;
/**
*@description When set to true the header will be hidden while the parent Screen is on the top of the stack. The default value is false.
*/
hidden?: boolean;
/**
*@description Controls the color of items rendered on the header. This includes back icon, back text (iOS only) and title text. If you want the title to have different color use titleColor property.
*/
color?: string;
/**
*@description Customize font family to be used for the title.
*/
titleFontFamily?: string;
/**
*@description Customize the size of the font to be used for the title.
*/
titleFontSize?: number;
/**
*@description Allows for setting text color of the title.
*/
titleColor?: string;
/**
*@description Controlls the color of the navigation header.
*/
backgroundColor?: string;
/**
* @description Boolean that allows for disabling drop shadow under navigation header. The default value is true.
*/
hideShadow?: boolean;
/**
* @description If set to true the back button will not be rendered as a part of navigation header.
*/
hideBackButton?: boolean;
/**
* @host (iOS only)
* @description When set to true, it makes native navigation bar on iOS semi transparent with blur effect. It is a common way of presenting navigation bar introduced in iOS 11. The default value is false
*/
translucent?: boolean;
/**
* @host (iOS only)
* @description Allows for controlling the string to be rendered next to back button. By default iOS uses the title of the previous screen.
*/
backTitle?: string;
/**
* @host (iOS only)
* @description Allows for customizing font family to be used for back button title on iOS.
*/
backTitleFontFamily?: string;
/**
* @host (iOS only)
* @description Allows for customizing font size to be used for back button title on iOS.
*/
backTitleFontSize?: number;
/**
* @host (iOS only)
* @description When set to true it makes the title display using the large title effect.
*/
largeTitle?: boolean;
/**
* @host (iOS only)
* @description Customize font family to be used for the large title.
*/
largeTitleFontFamily?: string;
/**
* @host (iOS only)
* @description Customize the size of the font to be used for the large title.
*/
largeTitleFontSize?: number;
/**
* Pass HeaderLeft, HeaderRight and HeaderTitle
*/
children?: React.ReactNode;
}
export const Screen: ComponentClass<ScreenProps>;
export const ScreenContainer: ComponentClass<ScreenContainerProps>;
export const NativeScreen: ComponentClass<ScreenProps>;
export const NativeScreenContainer: ComponentClass<ScreenContainerProps>;
export const ScreenStack: ComponentClass<ScreenStackProps>;
export const ScreenStackHeaderBackButtonImage: ComponentClass<ImageProps>;
export const ScreenStackHeaderLeftView: ComponentClass<ViewProps>;
export const ScreenStackHeaderRightView: ComponentClass<ViewProps>;
export const ScreenStackHeaderConfig: ComponentClass<
ScreenStackHeaderConfigProps
>;
}

View File

@@ -4,6 +4,7 @@ import {
requireNativeComponent,
View,
UIManager,
Image,
StyleSheet,
} from 'react-native';
import { version } from 'react-native/Libraries/Core/ReactNativeVersion';
@@ -146,6 +147,14 @@ const styles = StyleSheet.create({
},
});
const ScreenStackHeaderBackButtonImage = props => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
type="back"
style={styles.headerSubview}>
<Image resizeMode="center" fadeDuration={0} {...props} />
</ScreensNativeModules.NativeScreenStackHeaderSubview>
);
const ScreenStackHeaderRightView = props => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
@@ -162,14 +171,6 @@ const ScreenStackHeaderLeftView = props => (
/>
);
const ScreenStackHeaderTitleView = props => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
type="title"
style={styles.headerSubview}
/>
);
const ScreenStackHeaderCenterView = props => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
@@ -198,9 +199,9 @@ module.exports = {
get ScreenStackHeaderSubview() {
return ScreensNativeModules.NativeScreenStackHeaderSubview;
},
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderRightView,
ScreenStackHeaderLeftView,
ScreenStackHeaderTitleView,
ScreenStackHeaderCenterView,
enableScreens,

View File

@@ -1741,10 +1741,10 @@ commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
commander@~2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commondir@^1.0.1:
version "1.0.1"
@@ -2765,9 +2765,9 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
handlebars@^4.0.3:
version "4.1.2"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
version "4.5.3"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482"
integrity sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==
dependencies:
neo-async "^2.6.0"
optimist "^0.6.1"
@@ -5855,11 +5855,11 @@ uglify-es@^3.1.9:
source-map "~0.6.1"
uglify-js@^3.1.4:
version "3.6.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
version "3.7.3"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a"
integrity sha512-7tINm46/3puUA4hCkKYo4Xdts+JDaVC9ZPRcG8Xw9R4nhO/gZgUM3TENq8IF4Vatk8qCig4MzP/c8G4u2BkVQg==
dependencies:
commander "~2.20.0"
commander "~2.20.3"
source-map "~0.6.1"
ultron@1.0.x: