diff --git a/Example/.buckconfig b/Example/.buckconfig new file mode 100644 index 000000000..934256cb2 --- /dev/null +++ b/Example/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/Example/.flowconfig b/Example/.flowconfig index 66b57e096..ee340094c 100644 --- a/Example/.flowconfig +++ b/Example/.flowconfig @@ -42,6 +42,12 @@ # Ignore Website .*/website/.* +# Ignore generators +.*/local-cli/generator.* + +# Ignore BUCK generated folders +.*\.buckd/ + .*/node_modules/is-my-json-valid/test/.*\.json .*/node_modules/iconv-lite/encodings/tables/.*\.json .*/node_modules/y18n/test/.*\.json @@ -59,6 +65,7 @@ .*/node_modules/isemail/.*\.json .*/node_modules/tr46/.*\.json + [include] [libs] @@ -86,4 +93,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9 suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] -0.22.0 +^0.22.0 diff --git a/Example/.gitignore b/Example/.gitignore index 94fc86711..42c9490e5 100644 --- a/Example/.gitignore +++ b/Example/.gitignore @@ -32,3 +32,9 @@ local.properties # node_modules/ npm-debug.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore diff --git a/Example/Example.js b/Example/Example.js index 97783a69f..1c1d2e494 100644 --- a/Example/Example.js +++ b/Example/Example.js @@ -59,7 +59,7 @@ export default class Example extends React.Component { - + alert("Right button")} rightTitle="Right" /> diff --git a/Example/android/app/BUCK b/Example/android/app/BUCK new file mode 100644 index 000000000..bdec72cd9 --- /dev/null +++ b/Example/android/app/BUCK @@ -0,0 +1,66 @@ +import re + +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `cp ~/.android/debug.keystore keystores/debug.keystore` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = 'all-libs', + exported_deps = lib_deps +) + +android_library( + name = 'app-code', + srcs = glob([ + 'src/main/java/**/*.java', + ]), + deps = [ + ':all-libs', + ':build_config', + ':res', + ], +) + +android_build_config( + name = 'build_config', + package = 'com.example', +) + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.example', +) + +android_binary( + name = 'app', + package_type = 'debug', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//android/keystores:debug', + deps = [ + ':app-code', + ], +) diff --git a/Example/android/app/build.gradle b/Example/android/app/build.gradle index 5f726247a..156630953 100644 --- a/Example/android/app/build.gradle +++ b/Example/android/app/build.gradle @@ -9,7 +9,7 @@ import com.android.build.OutputFile * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "react.gradle"` line. + * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle @@ -59,7 +59,7 @@ import com.android.build.OutputFile * ] */ -apply from: "react.gradle" +apply from: "../../node_modules/react-native/react.gradle" /** * Set this to true to create two separate APKs instead of one: @@ -124,3 +124,10 @@ dependencies { compile "com.android.support:appcompat-v7:23.0.1" compile "com.facebook.react:react-native:+" // From node_modules } + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/Example/package.json b/Example/package.json index 72672d1a2..728fb489f 100644 --- a/Example/package.json +++ b/Example/package.json @@ -7,9 +7,9 @@ }, "dependencies": { "react": "^0.14.7", - "react-native": "^0.22.2", + "react-native": "^0.24.0", "react-native-button": "^1.2.1", - "react-native-drawer": "^1.16.7", + "react-native-drawer": "^2.0.0", "react-native-modalbox": "^1.3.0", "react-native-router-flux": "file:../" } diff --git a/README.md b/README.md index 46c386bf7..7831b4d20 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ class App extends React.Component { } } ``` -Alternatively you could define all your scenes during compile time and use it later within Router: -``` +Alternatively you could define all your scenes during compile time and use it later within `Router`: +```javascript const scenes = Actions.create( @@ -67,21 +67,21 @@ class App extends React.Component { ``` 2. In any app screen: - * import {Actions} from 'react-native-router-flux' - * Actions.ACTION_NAME(PARAMS) will call the appropriate action and params will be passed to the scene. - * Actions.pop() will pop the current screen. - * Actions.refresh(PARAMS) will update the properties of the current screen. + * `import {Actions} from 'react-native-router-flux'` + * `Actions.ACTION_NAME(PARAMS)` will call the appropriate action and params will be passed to the scene. + * `Actions.pop()` will pop the current screen. + * `Actions.refresh(PARAMS)` will update the properties of the current screen. ## Available imports -- Router -- Scene -- Modal -- TabBar -- getInitialState -- Reducer -- DefaultRenderer -- Switch -- Actions +- `Router` +- `Scene` +- `Modal` +- `TabBar` +- `getInitialState` +- `Reducer` +- `DefaultRenderer` +- `Switch` +- `Actions` ## Configuration @@ -132,7 +132,7 @@ class App extends React.Component { | sceneStyle | View style | { flex: 1 } | optional style override for the Scene's component | | other props | | | all properties that will be passed to your component instance | | getSceneStyle | function | optional | Optionally override the styles for NavigationCard's Animated.View rendering the scene. | - +| renderTitle | function | optional | Optionally closure to render the title ## Example ![launch](https://cloud.githubusercontent.com/assets/1321329/11692367/7337cfe2-9e9f-11e5-8515-e8b7a9f230ec.gif) @@ -237,22 +237,159 @@ To display a modal use `Modal` as root renderer, so it will render the first ele ## Redux/Flux This component doesn't depend on any redux/flux library. It uses new React Native Navigation API and provide own reducer for its navigation state. -You may provide your own reducer if needed. To avoid the creation of initial state, you may pass a reducer creator. Example to print all actions: +You may provide your own reducer if needed. To avoid the creation of initial state, you may pass a reducer creator. + +The following example will dispatch the `focus` action when a new scene comes into focus. The current scene will be available to components via the `props.scene` property. + +##### Step 1 + +First create a reducer for the routing actions that will be dispatched by RNRF. + ```javascript -// remember to add the 'Reducer' to your imports along with Router, Scene, ... like so -// import { Reducer } from 'react-native-router-flux' -const reducerCreate = params=>{ - const defaultReducer = Reducer(params); - return (state, action)=>{ - console.log("ACTION:", action); - return defaultReducer(state, action); - } +// reducers/routes.js + +const initialState = { + scene: {}, }; -// within your App render() method -return ; +export default function reducer(state = initialState, action = {}) { + switch (action.type) { + // focus action is dispatched when a new screen comes into focus + case "focus": + return { + ...state, + scene: action.scene, + }; + + // ...other actions + + default: + return state; + } +} +``` + +##### Step 2 + +Combine this reducer with the rest of the reducers from your app. + +```javascript +// reducers/index.js + +import { combineReducers } from 'redux'; +import routes from './routes'; +// ... other reducers + +export default combineReducers({ + routes, + // ... other reducers +}); + +``` + +##### Step 3 + +Connect the `Router` to your redux dispatching system. + +```javascript +// routes.js + +import { Actions, Router, Reducer } from 'react-native-router-flux'; +import { connect } from 'react-redux'; +// other imports... + + +const scenes = Actions.create({ + + {/* create scenes */} + +}); + +class Routes extends React.Component { + static propTypes = { + dispatch: PropTypes.func, + }; + + reducerCreate(params) { + const defaultReducer = Reducer(params); + return (state, action) => { + this.props.dispatch(action) + return defaultReducer(state, action); + }; + } + + render () { + return ( + + ); + } +} +export default connect()(Routes); ``` + +##### Step 4 + +Create your store, wrap your routes with the redux `Provider` component. + + +```js +// app.js + +import Routes from './routes'; +import { Provider } from 'react-redux'; +import { createStore, applyMiddleware, compose } from 'redux'; +import reducers from './reducers'; +// other imports... + +// create store... +const middleware = [/* ...your middleware (i.e. thunk) */]; +const store = compose( + applyMiddleware(...middleware) +)(createStore)(reducers); + + +class App extends React.Component { + render () { + return ( + + + + ); + } +} + +export default App; +``` + +##### Step 5 + +Now you can access the current scene from any connected component. + +```js +// components/MyComponent.js +import React, { PropTypes, Text } from 'react-native'; +import { connect } from 'react-redux'; + +class MyComponent extends React.Component { + static propTypes = { + routes: PropTypes.object, + }; + + render () { + return ( + + The current scene is titled {this.props.routes.scene.title}. + + ); + } +} + +export default connect(({routes}) => ({routes}))(MyComponent); +``` + ## Tabbar Every tab has its own navigation bar. However, if you do not set its parent `` with `hideNavBar={true}`, the tabs' navigation bar will be overrided by their parient. @@ -263,12 +400,12 @@ Your scene `component` class could implement _static_ renderNavigationBar(props) New feature for 3.x release is custom scene renderer that should be used together with tabs={true} property. It allows to select `tab` scene to show depending from app state. It could be useful for authentication, restricted scenes, etc. Usually you should wrap `Switch` with redux `connect` to pass application state to it: Following example chooses scene depending from sessionID using Redux: -``` - ({profile:state.profile}))(Switch)} tabs={true} - selector={props=>props.profile.sessionID ? "main" : "signUp"}> - - - +```javascript +({profile:state.profile}))(Switch)} tabs={true} + selector={props=>props.profile.sessionID ? "main" : "signUp"}> + + + ``` ## Drawer (side menu) integration diff --git a/package.json b/package.json index 61a3ad961..42c15b0e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-router-flux", - "version": "3.22.0", + "version": "3.24.1", "description": "React Native Router using Flux architecture", "repository": { "type": "git", @@ -53,7 +53,7 @@ "react": "^0.14.8", "react-addons-test-utils": "^0.14.7", "react-dom": "^0.14.7", - "react-native": "^0.22.2", + "react-native": "^0.24.0", "react-native-mock": "0.0.6", "sinon": "^1.17.3" } diff --git a/src/DefaultRenderer.js b/src/DefaultRenderer.js index 5b29bae8a..55f1d7837 100644 --- a/src/DefaultRenderer.js +++ b/src/DefaultRenderer.js @@ -9,10 +9,14 @@ import React, {Component, Animated, PropTypes, StyleSheet, View, NavigationExperimental} from "react-native"; const { AnimatedView: NavigationAnimatedView, - Card: NavigationCard, - RootContainer: NavigationRootContainer, - Header: NavigationHeader, - } = NavigationExperimental; + Card: NavigationCard +} = NavigationExperimental; + +const { + CardStackPanResponder: NavigationCardStackPanResponder, + CardStackStyleInterpolator: NavigationCardStackStyleInterpolator +} = NavigationCard; + import TabBar from "./TabBar"; import NavBar from "./NavBar"; import Actions from './Actions'; @@ -59,13 +63,13 @@ export default class DefaultRenderer extends Component { return null; } let Component = navigationState.component; - if (navigationState.tabs && !Component){ + if (navigationState.tabs && !Component) { Component = TabBar; } if (Component) { return ( - + ) } @@ -75,7 +79,6 @@ export default class DefaultRenderer extends Component { let applyAnimation = selected.applyAnimation || navigationState.applyAnimation; let style = selected.style || navigationState.style; - let direction = selected.direction || navigationState.direction || "horizontal"; let optionals = {}; if (applyAnimation) { @@ -85,7 +88,11 @@ export default class DefaultRenderer extends Component { if (duration === null || duration === undefined) duration = navigationState.duration; if (duration !== null && duration !== undefined) { optionals.applyAnimation = function (pos, navState) { - Animated.timing(pos, {toValue: navState.index, duration}).start(); + if (duration === 0) { + pos.setValue(navState.index); + } else { + Animated.timing(pos, {toValue: navState.index, duration}).start(); + } }; } } @@ -95,7 +102,6 @@ export default class DefaultRenderer extends Component { navigationState={navigationState} style={[styles.animatedView, style]} renderOverlay={this._renderHeader} - direction={direction} renderScene={this._renderCard} {...optionals} /> @@ -104,25 +110,40 @@ export default class DefaultRenderer extends Component { _renderHeader(/*NavigationSceneRendererProps*/ props) { return state.title} - />; + {...props} + getTitle={state => state.title} + />; } _renderCard(/*NavigationSceneRendererProps*/ props) { - const { key, direction, panHandlers, getSceneStyle } = props.scene.navigationState; + const {key, direction, getSceneStyle} = props.scene.navigationState; + let {panHandlers, animationStyle} = props.scene.navigationState; + + // Since we always need to pass a style for the direction, we can avoid #526 + let style = {}; + if (getSceneStyle) style = getSceneStyle(props); - const optionals = {}; - if (getSceneStyle) optionals.style = getSceneStyle(props); + const isVertical = direction === "vertical"; + + if (typeof(animationStyle) === 'undefined') { + animationStyle = (isVertical ? + NavigationCardStackStyleInterpolator.forVertical(props) : + NavigationCardStackStyleInterpolator.forHorizontal(props)); + } + + if (typeof(panHandlers) === 'undefined') { + panHandlers = panHandlers || (isVertical ? + NavigationCardStackPanResponder.forVertical(props) : + NavigationCardStackPanResponder.forHorizontal(props)); + } return ( ); } @@ -136,6 +157,6 @@ export default class DefaultRenderer extends Component { const styles = StyleSheet.create({ animatedView: { flex: 1, - backgroundColor:"transparent" + backgroundColor: "transparent" }, }); diff --git a/src/NavBar.js b/src/NavBar.js index 011802509..b9fc95884 100644 --- a/src/NavBar.js +++ b/src/NavBar.js @@ -54,7 +54,7 @@ export default class NavBar extends React.Component { if (selected.component && selected.component.renderNavigationBar){ return selected.component.renderNavigationBar({...this.props,...selected}); } - if (state.hideNavBar || child.hideNavBar || selected.hideNavBar){ + if (child.hideNavBar || selected.hideNavBar) { return null; } @@ -65,8 +65,8 @@ export default class NavBar extends React.Component { {state.children.map(this._renderTitle, this)} - {renderLeftButton() || renderBackButton()} - {renderRightButton()} + {renderLeftButton(selected) || renderBackButton(selected)} + {renderRightButton(selected)} ); } @@ -135,6 +135,8 @@ export default class NavBar extends React.Component { } _renderTitle(childState: NavigationState, index:number) { + const title = childState.renderTitle ? + childState.renderTitle():this.props.getTitle ? this.props.getTitle(childState) : childState.title; return ( - {this.props.getTitle ? this.props.getTitle(childState) : childState.title } + {title} ); }