Skip to content

Commit a6e67f4

Browse files
authored
feat: add optional navigators (#624)
Changed the behavior of active prop to contain 3 options (values): - 0: Screen is inactive - 1: Screen is active, but is not visible and is not the first responder, or is transitioning - 2: Screen is active and on top Added new prop `shouldUseActivityState` to differentiate between different implementations for other libraries to choose their implementation of the JS side.
1 parent e3f07b1 commit a6e67f4

File tree

9 files changed

+97
-60
lines changed

9 files changed

+97
-60
lines changed

Example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ PODS:
304304
- React
305305
- RNReanimated (1.10.1):
306306
- React
307-
- RNScreens (2.12.0):
307+
- RNScreens (2.13.0):
308308
- React-Core
309309
- Yoga (1.14.0)
310310
- YogaKit (1.18.1):
@@ -484,7 +484,7 @@ SPEC CHECKSUMS:
484484
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
485485
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
486486
RNReanimated: c2bb7438b57a3d987bb2e4e6e4bca94787e30b02
487-
RNScreens: 4b488fdf9537bbce829e2910c8cce1d17f6d9be8
487+
RNScreens: 13f23e5498fb4aa749d19a5eda7f8792fbb9d01f
488488
Yoga: 7d2edc5b410474191962e6dee88ee67f9b328b6b
489489
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
490490

android/src/main/java/com/swmansion/rnscreens/Screen.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public enum ReplaceAnimation {
3636
POP
3737
}
3838

39+
public enum ActivityState {
40+
INACTIVE,
41+
TRANSITIONING_OR_BELOW_TOP,
42+
ON_TOP
43+
}
44+
3945
private static OnAttachStateChangeListener sShowSoftKeyboardOnAttach = new OnAttachStateChangeListener() {
4046

4147
@Override
@@ -54,7 +60,7 @@ public void onViewDetachedFromWindow(View view) {
5460

5561
private @Nullable ScreenFragment mFragment;
5662
private @Nullable ScreenContainer mContainer;
57-
private boolean mActive;
63+
private ActivityState mActivityState;
5864
private boolean mTransitioning;
5965
private StackPresentation mStackPresentation = StackPresentation.PUSH;
6066
private ReplaceAnimation mReplaceAnimation = ReplaceAnimation.POP;
@@ -222,18 +228,18 @@ protected void setFragment(ScreenFragment fragment) {
222228
return mContainer;
223229
}
224230

225-
public void setActive(boolean active) {
226-
if (active == mActive) {
231+
public void setActivityState(ActivityState activityState) {
232+
if (activityState == mActivityState) {
227233
return;
228234
}
229-
mActive = active;
235+
mActivityState = activityState;
230236
if (mContainer != null) {
231237
mContainer.notifyChildUpdate();
232238
}
233239
}
234240

235-
public boolean isActive() {
236-
return mActive;
241+
public ActivityState getActivityState() {
242+
return mActivityState;
237243
}
238244

239245
public boolean isGestureEnabled() {

android/src/main/java/com/swmansion/rnscreens/ScreenContainer.java

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,12 @@ protected void tryCommitTransaction() {
208208
mProcessingTransaction.runOnCommit(new Runnable() {
209209
@Override
210210
public void run() {
211-
if (mProcessingTransaction == transaction) {
212-
// we need to take into account that commit is initiated with some other transaction while
213-
// the previous one is still processing. In this case mProcessingTransaction gets overwritten
214-
// and we don't want to set it to null until the second transaction is finished.
215-
mProcessingTransaction = null;
216-
}
211+
if (mProcessingTransaction == transaction) {
212+
// we need to take into account that commit is initiated with some other transaction while
213+
// the previous one is still processing. In this case mProcessingTransaction gets overwritten
214+
// and we don't want to set it to null until the second transaction is finished.
215+
mProcessingTransaction = null;
216+
}
217217
}
218218
});
219219
mCurrentTransaction.commitAllowingStateLoss();
@@ -235,8 +235,8 @@ private void detachScreen(ScreenFragment screenFragment) {
235235
getOrCreateTransaction().remove(screenFragment);
236236
}
237237

238-
protected boolean isScreenActive(ScreenFragment screenFragment) {
239-
return screenFragment.getScreen().isActive();
238+
protected Screen.ActivityState getActivityState(ScreenFragment screenFragment) {
239+
return screenFragment.getScreen().getActivityState();
240240
}
241241

242242
protected boolean hasScreen(ScreenFragment screenFragment) {
@@ -335,8 +335,7 @@ protected void performUpdate() {
335335
Set<Fragment> orphaned = new HashSet<>(mFragmentManager.getFragments());
336336
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
337337
ScreenFragment screenFragment = mScreenFragments.get(i);
338-
boolean isActive = isScreenActive(screenFragment);
339-
if (!isActive && screenFragment.isAdded()) {
338+
if (getActivityState(screenFragment) == Screen.ActivityState.INACTIVE && screenFragment.isAdded()) {
340339
detachScreen(screenFragment);
341340
}
342341
orphaned.remove(screenFragment);
@@ -352,28 +351,30 @@ protected void performUpdate() {
352351
}
353352
}
354353

355-
// detect if we are "transitioning" based on the number of active screens
356-
int activeScreens = 0;
354+
boolean transitioning = true;
355+
357356
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
358-
if (isScreenActive(mScreenFragments.get(i))) {
359-
activeScreens += 1;
357+
ScreenFragment screenFragment = mScreenFragments.get(i);
358+
if (getActivityState(screenFragment) == Screen.ActivityState.ON_TOP) {
359+
// if there is an "onTop" screen it means the transition has ended
360+
transitioning = false;
360361
}
361362
}
362-
boolean transitioning = activeScreens > 1;
363363

364364
// attach newly activated screens
365365
boolean addedBefore = false;
366366
for (int i = 0, size = mScreenFragments.size(); i < size; i++) {
367367
ScreenFragment screenFragment = mScreenFragments.get(i);
368-
boolean isActive = isScreenActive(screenFragment);
369-
if (isActive && !screenFragment.isAdded()) {
368+
Screen.ActivityState activityState = getActivityState(screenFragment);
369+
if (activityState != Screen.ActivityState.INACTIVE && !screenFragment.isAdded()) {
370370
addedBefore = true;
371371
attachScreen(screenFragment);
372-
} else if (isActive && addedBefore) {
372+
} else if (activityState != Screen.ActivityState.INACTIVE && addedBefore) {
373373
moveToFront(screenFragment);
374374
}
375375
screenFragment.getScreen().setTransitioning(transitioning);
376376
}
377+
377378
tryCommitTransaction();
378379
}
379380
}

android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@ protected Screen createViewInstance(ThemedReactContext reactContext) {
2626
return new Screen(reactContext);
2727
}
2828

29-
@ReactProp(name = "active", defaultFloat = 0)
30-
public void setActive(Screen view, float active) {
31-
view.setActive(active != 0);
29+
@ReactProp(name = "activityState")
30+
public void setActivityState(Screen view, int activityState) {
31+
if (activityState == 0) {
32+
view.setActivityState(Screen.ActivityState.INACTIVE);
33+
} else if (activityState == 1) {
34+
view.setActivityState(Screen.ActivityState.TRANSITIONING_OR_BELOW_TOP);
35+
} else if (activityState == 2) {
36+
view.setActivityState(Screen.ActivityState.ON_TOP);
37+
}
3238
}
3339

3440
@ReactProp(name = "stackPresentation")

ios/RNSScreen.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ typedef NS_ENUM(NSInteger, RNSScreenReplaceAnimation) {
2828
RNSScreenReplaceAnimationPush,
2929
};
3030

31+
typedef NS_ENUM(NSInteger, RNSActivityState) {
32+
RNSActivityStateInactive = 0,
33+
RNSActivityStateTransitioningOrBelowTop = 1,
34+
RNSActivityStateOnTop = 2
35+
};
36+
3137
@interface RCTConvert (RNSScreen)
3238

3339
+ (RNSScreenStackPresentation)RNSScreenStackPresentation:(id)json;
@@ -56,7 +62,7 @@ typedef NS_ENUM(NSInteger, RNSScreenReplaceAnimation) {
5662
@property (weak, nonatomic) UIView<RNSScreenContainerDelegate> *reactSuperview;
5763
@property (nonatomic, retain) UIViewController *controller;
5864
@property (nonatomic, readonly) BOOL dismissed;
59-
@property (nonatomic) BOOL active;
65+
@property (nonatomic) int activityState;
6066
@property (nonatomic) BOOL gestureEnabled;
6167
@property (nonatomic) RNSScreenStackAnimation stackAnimation;
6268
@property (nonatomic) RNSScreenStackPresentation stackPresentation;

ios/RNSScreen.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ - (void)updateBounds
5858
[_bridge.uiManager setSize:self.bounds.size forView:self];
5959
}
6060

61-
- (void)setActive:(BOOL)active
61+
- (void)setActivityState:(int)activityState
6262
{
63-
if (active != _active) {
64-
_active = active;
63+
if (activityState != _activityState) {
64+
_activityState = activityState;
6565
[_reactSuperview markChildUpdated];
6666
}
6767
}
@@ -469,7 +469,7 @@ @implementation RNSScreenManager
469469

470470
RCT_EXPORT_MODULE()
471471

472-
RCT_EXPORT_VIEW_PROPERTY(active, BOOL)
472+
RCT_EXPORT_VIEW_PROPERTY(activityState, int)
473473
RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
474474
RCT_EXPORT_VIEW_PROPERTY(replaceAnimation, RNSScreenReplaceAnimation)
475475
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)

ios/RNSScreenContainer.m

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ - (UIViewController *)childViewControllerForStatusBarHidden
4242
- (UIViewController *)findActiveChildVC
4343
{
4444
for (UIViewController *childVC in self.childViewControllers) {
45-
// condition should be changed when activityState is introduced to check for activityState == 2
46-
if ([childVC isKindOfClass:[RNSScreen class]] && ((RNSScreenView *)((RNSScreen *)childVC.view)).active) {
45+
if ([childVC isKindOfClass:[RNSScreen class]] && ((RNSScreenView *)((RNSScreen *)childVC.view)).activityState == RNSActivityStateOnTop) {
4746
return childVC;
4847
}
4948
}
@@ -146,59 +145,60 @@ - (void)attachScreen:(RNSScreenView *)screen atIndex:(NSInteger)index
146145
- (void)updateContainer
147146
{
148147
_needUpdate = NO;
149-
BOOL activeScreenRemoved = NO;
148+
BOOL screenRemoved = NO;
150149
// remove screens that are no longer active
151150
NSMutableSet *orphaned = [NSMutableSet setWithSet:_activeScreens];
152151
for (RNSScreenView *screen in _reactSubviews) {
153-
if (!screen.active && [_activeScreens containsObject:screen]) {
154-
activeScreenRemoved = YES;
152+
if (screen.activityState == RNSActivityStateInactive && [_activeScreens containsObject:screen]) {
153+
screenRemoved = YES;
155154
[self detachScreen:screen];
156155
}
157156
[orphaned removeObject:screen];
158157
}
159158
for (RNSScreenView *screen in orphaned) {
160-
activeScreenRemoved = YES;
159+
screenRemoved = YES;
161160
[self detachScreen:screen];
162161
}
163162

164163
// detect if new screen is going to be activated
165-
BOOL activeScreenAdded = NO;
164+
BOOL screenAdded = NO;
166165
for (RNSScreenView *screen in _reactSubviews) {
167-
if (screen.active && ![_activeScreens containsObject:screen]) {
168-
activeScreenAdded = YES;
166+
if (screen.activityState != RNSActivityStateInactive && ![_activeScreens containsObject:screen]) {
167+
screenAdded = YES;
169168
}
170169
}
171170

172-
173-
if (activeScreenAdded) {
171+
if (screenAdded) {
174172
// add new screens in order they are placed in subviews array
175173
NSInteger index = 0;
176174
for (RNSScreenView *screen in _reactSubviews) {
177-
if (screen.active) {
178-
if ([_activeScreens containsObject:screen]) {
175+
if (screen.activityState != RNSActivityStateInactive) {
176+
if ([_activeScreens containsObject:screen] && screen.activityState == RNSActivityStateTransitioningOrBelowTop) {
179177
// for screens that were already active we want to mimick the effect UINavigationController
180178
// has when willMoveToWindow:nil is triggered before the animation starts
181179
[self prepareDetach:screen];
182-
// disable interactions for the duration of transition
183-
screen.userInteractionEnabled = NO;
184-
} else {
180+
} else if (![_activeScreens containsObject:screen]) {
185181
[self attachScreen:screen atIndex:index];
186182
}
187183
index += 1;
188184
}
189185
}
190186
}
191187

192-
// if we are down to one active screen it means the transitioning is over and we want to notify
193-
// the transition has finished
194-
if ((activeScreenRemoved || activeScreenAdded) && _activeScreens.count == 1) {
195-
RNSScreenView *singleActiveScreen = [_activeScreens anyObject];
196-
// restore interactions
197-
singleActiveScreen.userInteractionEnabled = YES;
198-
[singleActiveScreen notifyFinishTransitioning];
188+
if (screenRemoved || screenAdded) {
189+
// we disable interaction for the duration of the transition until one of the screens changes its state to "onTop"
190+
self.userInteractionEnabled = NO;
191+
192+
for (RNSScreenView *screen in _reactSubviews) {
193+
if (screen.activityState == RNSActivityStateOnTop) {
194+
// if there is an "onTop" screen it means the transition has ended so we restore interactions
195+
self.userInteractionEnabled = YES;
196+
[screen notifyFinishTransitioning];
197+
}
198+
}
199199
}
200200

201-
if ((activeScreenRemoved || activeScreenAdded) && _controller.presentedViewController == nil && _controller.presentingViewController == nil) {
201+
if ((screenRemoved || screenAdded) && _controller.presentedViewController == nil && _controller.presentingViewController == nil) {
202202
// if user has reachability enabled (one hand use) and the window is slided down the below
203203
// method will force it to slide back up as it is expected to happen with UINavController when
204204
// we push or pop views.

src/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ declare module 'react-native-screens' {
4949

5050
export interface ScreenProps extends ViewProps {
5151
active?: 0 | 1 | Animated.AnimatedInterpolation;
52+
activityState?: 0 | 1 | 2 | Animated.AnimatedInterpolation;
5253
children?: React.ReactNode;
5354
/**
5455
* @description All children screens should have the same value of their "enabled" prop as their container.
@@ -252,4 +253,5 @@ declare module 'react-native-screens' {
252253
export const ScreenStackHeaderRightView: ComponentClass<ViewProps>;
253254
export const ScreenStackHeaderCenterView: ComponentClass<ViewProps>;
254255
export const ScreenStackHeaderConfig: ComponentClass<ScreenStackHeaderConfigProps>;
256+
export const shouldUseActivityState: boolean;
255257
}

src/index.native.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ function enableScreens(shouldEnableScreens = true) {
2727
}
2828
}
2929

30+
// const that tells if the library should use new implementation, will be undefined for older versions
31+
const shouldUseActivityState = true;
32+
3033
// we should remove this at some point
3134
function useScreens(shouldUseScreens = true) {
3235
console.warn('Method `useScreens` is deprecated, please use `enableScreens`');
@@ -110,8 +113,20 @@ class Screen extends React.Component {
110113

111114
// When using RN from master version is 0.0.0
112115
if (version.minor >= 57 || version.minor === 0) {
113-
const { enabled, ...rest } = this.props;
114-
return <AnimatedNativeScreen {...rest} ref={this.setRef} />;
116+
let { enabled, active, activityState, ...rest } = this.props;
117+
if (active !== undefined && activityState === undefined) {
118+
console.warn(
119+
'It appears that you are using old version of react-navigation library. Please update @react-navigation/bottom-tabs, @react-navigation/stack and @react-navigation/drawer to version 5.10.0 or above to take full advantage of new functionality added to react-native-screens'
120+
);
121+
activityState = active !== 0 ? 2 : 0; // in the new version, we need one of the screens to have value of 2 after the transition
122+
}
123+
return (
124+
<AnimatedNativeScreen
125+
{...rest}
126+
activityState={activityState}
127+
ref={this.setRef}
128+
/>
129+
);
115130
} else {
116131
// On RN version below 0.57 we need to wrap screen's children with an
117132
// additional View because of a bug fixed in react-native/pull/20658 which
@@ -214,4 +229,5 @@ module.exports = {
214229
enableScreens,
215230
useScreens,
216231
screensEnabled,
232+
shouldUseActivityState,
217233
};

0 commit comments

Comments
 (0)