From 71750da4205908c9a1a48119365d2eef1865fef4 Mon Sep 17 00:00:00 2001 From: Krystof Celba Date: Tue, 23 Jan 2018 02:25:53 +0100 Subject: [PATCH 1/4] Add support for react-native 0.52.0 --- .../controllers/NavigationActivity.java | 6 ++ .../react/JsDevReloadListenerReplacer.java | 65 ++++++++++--------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java b/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java index 946be939b63..54eaaee8fd6 100644 --- a/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java +++ b/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java @@ -39,6 +39,7 @@ import com.reactnativenavigation.screens.NavigationType; import com.reactnativenavigation.screens.Screen; import com.reactnativenavigation.utils.OrientationHelper; +import com.reactnativenavigation.utils.ReflectionUtils; import com.reactnativenavigation.views.SideMenu.Side; import java.util.List; @@ -455,6 +456,11 @@ private void postHandleJsDevReloadEvent() { public void run() { layout.destroy(); modalController.destroy(); + + Object devSupportManager = ReflectionUtils.getDeclaredField(getReactGateway().getReactInstanceManager(), "mDevSupportManager"); + if (ReflectionUtils.getDeclaredField(devSupportManager, "mRedBoxDialog") != null) { + ReflectionUtils.setField(devSupportManager, "mRedBoxDialog", null); + } } }); } diff --git a/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java b/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java index 70e7abdb7a8..d69a860e0a9 100644 --- a/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java +++ b/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java @@ -1,10 +1,12 @@ package com.reactnativenavigation.react; import com.facebook.react.ReactInstanceManager; -import com.facebook.react.bridge.JavaJSExecutor; -import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; import com.reactnativenavigation.utils.ReflectionUtils; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + class JsDevReloadListenerReplacer { private final ReactInstanceManager reactInstanceManager; private final Listener listener; @@ -19,49 +21,50 @@ interface Listener { } void replace() { - ReactInstanceDevCommandsHandler originalHandler = getOriginalHandler(); - DevCommandsHandlerProxy proxy = new DevCommandsHandlerProxy(originalHandler, listener); - replaceInReactInstanceManager(proxy); - replaceInDevSupportManager(proxy); - } + Object originalHandler = getOriginalHandler(); - private void replaceInDevSupportManager(DevCommandsHandlerProxy proxy) { Object devSupportManager = ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevSupportManager"); - ReflectionUtils.setField(devSupportManager, "mReactInstanceCommandsHandler", proxy); - } - private ReactInstanceDevCommandsHandler getOriginalHandler() { - return (ReactInstanceDevCommandsHandler) ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface"); + Object proxy = Proxy.newProxyInstance( + originalHandler.getClass().getClassLoader(), + originalHandler.getClass().getInterfaces(), + new DevCommandsHandlerProxy(originalHandler, listener)); + + if (ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface") == null) { + ReflectionUtils.setField(devSupportManager, "mReactInstanceManagerHelper", proxy); + } else { + ReflectionUtils.setField(reactInstanceManager, "mDevInterface", proxy); + ReflectionUtils.setField(devSupportManager, "mReactInstanceCommandsHandler", proxy); + } } - private void replaceInReactInstanceManager(DevCommandsHandlerProxy proxy) { - ReflectionUtils.setField(reactInstanceManager, "mDevInterface", proxy); + + private Object getOriginalHandler() { + Object devInterface = ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface"); + if (devInterface == null) { + Object devSupportManager = ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevSupportManager"); + devInterface = ReflectionUtils.getDeclaredField(devSupportManager, "mReactInstanceManagerHelper"); + } + return devInterface; } - private static class DevCommandsHandlerProxy implements ReactInstanceDevCommandsHandler { - private ReactInstanceDevCommandsHandler originalReactHandler; + + private static class DevCommandsHandlerProxy implements InvocationHandler { + private Object originalReactHandler; private final Listener listener; - DevCommandsHandlerProxy(ReactInstanceDevCommandsHandler originalReactHandler, Listener listener) { + DevCommandsHandlerProxy(Object originalReactHandler, Listener listener) { this.originalReactHandler = originalReactHandler; this.listener = listener; } @Override - public void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory) { - listener.onJsDevReload(); - originalReactHandler.onReloadWithJSDebugger(proxyExecutorFactory); - } - - @Override - public void onJSBundleLoadedFromServer() { - listener.onJsDevReload(); - originalReactHandler.onJSBundleLoadedFromServer(); - } - - @Override - public void toggleElementInspector() { - originalReactHandler.toggleElementInspector(); + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if (method.getName().equals("onJSBundleLoadedFromServer") || method.getName().equals("onReloadWithJSDebugger")) { + listener.onJsDevReload(); + } + return method.invoke(originalReactHandler, args); } } } From 07784a3e1fddcb1bd8be1eb543702209ac1718e9 Mon Sep 17 00:00:00 2001 From: Krystof Celba Date: Sun, 28 Jan 2018 06:39:44 +0100 Subject: [PATCH 2/4] Refactore a bit 1. Rename `DevCommandsHandlerProxy` to `DevHelperProxy` so it reflects the changes in RN. See https://github.com/facebook/react-native/commit/d19afc73f5048f81656d0b4424232ce6d69a6368 . 2. Add few comments to show which code is used on `RN >= 0.52` and which on `RN <= 0.51`. --- .../controllers/NavigationActivity.java | 2 +- .../react/JsDevReloadListenerReplacer.java | 40 ++++++++++--------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java b/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java index 54eaaee8fd6..f35e3a51d59 100644 --- a/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java +++ b/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java @@ -458,7 +458,7 @@ public void run() { modalController.destroy(); Object devSupportManager = ReflectionUtils.getDeclaredField(getReactGateway().getReactInstanceManager(), "mDevSupportManager"); - if (ReflectionUtils.getDeclaredField(devSupportManager, "mRedBoxDialog") != null) { + if (ReflectionUtils.getDeclaredField(devSupportManager, "mRedBoxDialog") != null) { // RN >= 0.52 ReflectionUtils.setField(devSupportManager, "mRedBoxDialog", null); } } diff --git a/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java b/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java index d69a860e0a9..1f3ae447c92 100644 --- a/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java +++ b/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadListenerReplacer.java @@ -21,50 +21,54 @@ interface Listener { } void replace() { - Object originalHandler = getOriginalHandler(); + Object originalHelper = getOriginalHelper(); Object devSupportManager = ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevSupportManager"); Object proxy = Proxy.newProxyInstance( - originalHandler.getClass().getClassLoader(), - originalHandler.getClass().getInterfaces(), - new DevCommandsHandlerProxy(originalHandler, listener)); + originalHelper.getClass().getClassLoader(), + originalHelper.getClass().getInterfaces(), + new DevHelperProxy(originalHelper, listener)); - if (ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface") == null) { + if (ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface") == null) { // RN >= 0.52 ReflectionUtils.setField(devSupportManager, "mReactInstanceManagerHelper", proxy); - } else { + } else { // RN <= 0.51 ReflectionUtils.setField(reactInstanceManager, "mDevInterface", proxy); ReflectionUtils.setField(devSupportManager, "mReactInstanceCommandsHandler", proxy); } } - private Object getOriginalHandler() { + private Object getOriginalHelper() { Object devInterface = ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface"); - if (devInterface == null) { + + if (devInterface == null) { // RN >= 0.52 Object devSupportManager = ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevSupportManager"); - devInterface = ReflectionUtils.getDeclaredField(devSupportManager, "mReactInstanceManagerHelper"); + return ReflectionUtils.getDeclaredField(devSupportManager, "mReactInstanceManagerHelper"); } - return devInterface; + + return devInterface; // RN <= 0.51 } - private static class DevCommandsHandlerProxy implements InvocationHandler { - private Object originalReactHandler; + private static class DevHelperProxy implements InvocationHandler { + private Object originalReactHelper; private final Listener listener; - DevCommandsHandlerProxy(Object originalReactHandler, Listener listener) { - this.originalReactHandler = originalReactHandler; + DevHelperProxy(Object originalReactHelper, Listener listener) { + this.originalReactHelper = originalReactHelper; this.listener = listener; } @Override - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable { - if (method.getName().equals("onJSBundleLoadedFromServer") || method.getName().equals("onReloadWithJSDebugger")) { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + + if (methodName.equals("onJSBundleLoadedFromServer") || methodName.equals("onReloadWithJSDebugger")) { listener.onJsDevReload(); } - return method.invoke(originalReactHandler, args); + + return method.invoke(originalReactHelper, args); } } } From 12082af4df4e5b731798b1170cb3ee86ceed67aa Mon Sep 17 00:00:00 2001 From: Ioannis Kokkinidis Date: Thu, 1 Feb 2018 01:57:02 +0200 Subject: [PATCH 3/4] Now allowing the custom nav bar to take up the whole space on iOS The custom nav bar on iOS was really just a custom view applied to the title of the navigation bar. There were problems with this approach, because the navigation bar messes up the frame of the title very frequently, especially when the orientation is changing. That behaviour is accepted when the title is a label, but not when we have a custom component. With this pull request the custom nav bar component can take up the full width of the navigation bar, no matter what orientation we're at. --- ios/RCCCustomTitleView.h | 1 + ios/RCCCustomTitleView.m | 54 ++++++++++++++++++++++++++++++++++++++++ ios/RCCViewController.m | 14 +++++++++-- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/ios/RCCCustomTitleView.h b/ios/RCCCustomTitleView.h index a6dd54b4d24..e83e9c58086 100644 --- a/ios/RCCCustomTitleView.h +++ b/ios/RCCCustomTitleView.h @@ -11,5 +11,6 @@ @interface RCCCustomTitleView : UIView -(instancetype)initWithFrame:(CGRect)frame subView:(UIView*)subView alignment:(NSString*)alignment; +- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator; @end diff --git a/ios/RCCCustomTitleView.m b/ios/RCCCustomTitleView.m index 663060bdd8d..281f4a67238 100644 --- a/ios/RCCCustomTitleView.m +++ b/ios/RCCCustomTitleView.m @@ -11,12 +11,14 @@ @interface RCCCustomTitleView () @property (nonatomic, strong) UIView *subView; @property (nonatomic, strong) NSString *subViewAlign; +@property float initialWidth; @end @implementation RCCCustomTitleView -(instancetype)initWithFrame:(CGRect)frame subView:(UIView*)subView alignment:(NSString*)alignment { + _initialWidth = frame.size.width; self = [super initWithFrame:frame]; if (self) { @@ -52,4 +54,56 @@ -(void)layoutSubviews { } } +- (void)setFrame:(CGRect) frame { + float referenceWidth = [self statusBarWidth]; + if (referenceWidth == 0) { + referenceWidth = _initialWidth; + } + float newNavBarWidth = frame.size.width; + BOOL frameNeedsToBeCorrected = newNavBarWidth < referenceWidth || CGRectEqualToRect(self.frame, CGRectZero); + + if (frameNeedsToBeCorrected) { + // first we need to find out the total point diff of the status bar and the nav bar + float navBarHorizontalMargin = referenceWidth - newNavBarWidth; + + CGRect correctedFrame = frame; + + // then we need to place the nav bar half times the horizontal margin to the left + correctedFrame.origin.x = -(navBarHorizontalMargin / 2); + + // and finally set the width so that it's equal to the status bar width + correctedFrame.size.width = referenceWidth; + + [super setFrame:correctedFrame]; + } else if (frame.size.height != self.frame.size.height) { // otherwise + // if only the height has changed + CGRect newHeightFrame = self.frame; + // make sure we update just the height + newHeightFrame.size.height = frame.size.height; + [super setFrame:newHeightFrame]; + } + + // keep a ref to the last frame, so that we avoid setting the frame twice for no reason +// _lastFrame = frame; +} + + +- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + // whenever the orientation changes this runs + // and sets the nav bar item width to the new size width + CGRect newFrame = self.frame; + + if (newFrame.size.width < size.width) { + newFrame.size.width = size.width; + newFrame.origin.x = 0; + } + [super setFrame:newFrame]; +} + +-(float) statusBarWidth { + CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size; + return MAX(statusBarSize.width, statusBarSize.height); +} + + @end diff --git a/ios/RCCViewController.m b/ios/RCCViewController.m index 2086b2d76bd..00831ee40ab 100755 --- a/ios/RCCViewController.m +++ b/ios/RCCViewController.m @@ -643,14 +643,16 @@ -(void)setStyleOnAppearForViewController:(UIViewController*)viewController appea NSDictionary *initialProps = self.navigatorStyle[@"navBarCustomViewInitialProps"]; RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:bridge moduleName:navBarCustomView initialProperties:initialProps]; - RCCCustomTitleView *titleView = [[RCCCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds subView:reactView alignment:self.navigatorStyle[@"navBarComponentAlignment"]]; + RCCCustomTitleView *titleView = [[RCCCustomTitleView alloc] initWithFrame:self.navigationController.navigationBar.bounds + subView:reactView + alignment:self.navigatorStyle[@"navBarComponentAlignment"]]; titleView.backgroundColor = [UIColor clearColor]; reactView.backgroundColor = [UIColor clearColor]; self.navigationItem.titleView = titleView; self.navigationItem.titleView.backgroundColor = [UIColor clearColor]; - self.navigationItem.titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; + self.navigationItem.titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.navigationItem.titleView.clipsToBounds = YES; } } @@ -675,6 +677,14 @@ -(void)setStyleOnAppearForViewController:(UIViewController*)viewController appea #endif } +- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + RCCCustomTitleView* customNavBar = (RCCCustomTitleView*) self.navigationItem.titleView; + if (customNavBar && [customNavBar isKindOfClass:[RCCCustomTitleView class]]) { + [customNavBar viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + } +} + -(void)storeOriginalNavBarImages { From b2ad935891e13e1dbc6870f6dcb3bc945fabc537 Mon Sep 17 00:00:00 2001 From: Ioannis Kokkinidis Date: Thu, 1 Feb 2018 12:09:54 +0200 Subject: [PATCH 4/4] Added mix of native btns and custom component on the Custom TopBar screen of the example project --- example/src/screens/types/CustomTopBar.js | 11 ++++--- .../src/screens/types/CustomTopBarScreen.js | 32 ++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/example/src/screens/types/CustomTopBar.js b/example/src/screens/types/CustomTopBar.js index da3e1c17c0b..0e12374b2c8 100644 --- a/example/src/screens/types/CustomTopBar.js +++ b/example/src/screens/types/CustomTopBar.js @@ -18,8 +18,8 @@ export default class CustomTopBar extends Component { render() { return ( - Alert.alert(this.props.title, 'Thanks for that :)') }> - Press Me + Alert.alert(this.props.title, 'Hello custom btn :)') }> + Custom ); @@ -30,15 +30,16 @@ const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', - alignItems: 'center' + alignItems: 'center', + // backgroundColor: 'yellow' }, button: { alignSelf: 'center', - backgroundColor: 'green' + // backgroundColor: 'green' }, text: { alignSelf: 'center', - color: Platform.OS === 'ios' ? 'black' : 'white' + color: 'white' } }); diff --git a/example/src/screens/types/CustomTopBarScreen.js b/example/src/screens/types/CustomTopBarScreen.js index da2538d5194..3aad405ee2e 100644 --- a/example/src/screens/types/CustomTopBarScreen.js +++ b/example/src/screens/types/CustomTopBarScreen.js @@ -10,11 +10,41 @@ import CustomTopBar from './CustomTopBar'; Navigation.registerComponent('example.CustomTopBar', () => CustomTopBar); export default class CustomTopBarScreen extends Component { + + static navigatorButtons = { + leftButtons: [ + { + title: 'Back', + id: 'helloBtn', + } + ], + rightButtons: [ + { + title: 'Right', + id: 'helloBtn2', + } + ], + }; + componentDidMount() { this.props.navigator.setStyle({ navBarCustomView: 'example.CustomTopBar', navBarComponentAlignment: 'center', - navBarCustomViewInitialProps: {title: 'Hi Custom'} + navBarCustomViewInitialProps: { + title: 'Hi Custom', + navigator: this.props.navigator, + }, + }); + this.props.navigator.setOnNavigatorEvent((e) => { + if (e.type == 'NavBarButtonPress') { // this is the event type for button presses + if (e.id == 'helloBtn') { // this is the same id field from the static navigatorButtons definition + this.props.navigator.pop(); + // alert('Hello left btn'); + } + if (e.id == 'helloBtn2') { // this is the same id field from the static navigatorButtons definition + alert('Hello right btn'); + } + } }); }