diff --git a/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js b/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js new file mode 100644 index 000000000..06a39c7cc --- /dev/null +++ b/Examples/UIExplorer/PullToRefreshLayoutAndroidExample.android.js @@ -0,0 +1,127 @@ +/** +* The examples provided by Facebook are for non-commercial testing and +* evaluation purposes only. +* +* Facebook reserves all rights not expressly granted. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL +* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*/ +'use strict'; + +const React = require('react-native'); +const { + ScrollView, + StyleSheet, + PullToRefreshLayoutAndroid, + Text, + TouchableWithoutFeedback, + View, +} = React; + +const styles = StyleSheet.create({ + row: { + borderColor: 'grey', + borderWidth: 1, + padding: 20, + backgroundColor: '#3a5795', + margin: 5, + }, + text: { + alignSelf: 'center', + color: '#fff', + + }, + layout: { + flex: 1, + }, + scrollview: { + flex: 1, + }, +}); + +const Row = React.createClass({ + _onClick: function() { + this.props.onClick(this.props.data); + }, + render: function() { + return ( + + + + {this.props.data.text + ' (' + this.props.data.clicks + ' clicks)'} + + + + ); + }, +}); +const PullToRefreshLayoutAndroidExample = React.createClass({ + statics: { + title: '', + description: 'Container that adds pull-to-refresh support to its child view.' + }, + + getInitialState() { + return { + isRefreshing: false, + loaded: 0, + rowData: Array.from(new Array(20)).map( + (val, i) => {return {text: 'Initial row' + i, clicks: 0}}), + }; + }, + + _onClick(row) { + row.clicks++; + this.setState({ + rowData: this.state.rowData, + }); + }, + + render() { + const rows = this.state.rowData.map((row) => { + return ; + }); + return ( + + + {rows} + + + ); + }, + + _onRefresh() { + this.setState({isRefreshing: true}); + setTimeout(() => { + // prepend 10 items + const rowData = Array.from(new Array(10)) + .map((val, i) => {return { + text: 'Loaded row' + (+this.state.loaded + i), + clicks: 0, + }}) + .concat(this.state.rowData); + + this.setState({ + loaded: this.state.loaded + 10, + isRefreshing: false, + rowData: rowData, + }); + }, 5000); + }, + +}); + + +module.exports = PullToRefreshLayoutAndroidExample; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 5a370a01c..7d57332c2 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -27,6 +27,7 @@ var COMPONENTS = [ require('./ProgressBarAndroidExample'), require('./ScrollViewSimpleExample'), require('./SwitchAndroidExample'), + require('./PullToRefreshLayoutAndroidExample.android'), require('./TextExample.android'), require('./TextInputExample.android'), require('./ToolbarAndroidExample'), diff --git a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js new file mode 100644 index 000000000..a1d69c353 --- /dev/null +++ b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.android.js @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PullToRefreshLayoutAndroid + */ +'use strict'; + +var React = require('React'); +var RefreshLayoutConsts = require('NativeModules').UIManager.AndroidSwipeRefreshLayout.Constants; +var View = require('View'); + +var onlyChild = require('onlyChild'); +var processColor = require('processColor'); +var requireNativeComponent = require('requireNativeComponent'); + +var NATIVE_REF = 'native_swiperefreshlayout'; + +/** + * React view that supports a single scrollable child view (e.g. `ScrollView`). When this child + * view is at `scrollY: 0`, swiping down triggers an `onRefresh` event. + */ +var PullToRefreshLayoutAndroid = React.createClass({ + statics: { + SIZE: RefreshLayoutConsts.SIZE, + }, + + propTypes: { + ...View.propTypes, + /** + * Whether the pull to refresh functionality is enabled + */ + enabled: React.PropTypes.bool, + /** + * The colors (at least one) that will be used to draw the refresh indicator + */ + colors: React.PropTypes.arrayOf(React.PropTypes.string), + /** + * The background color of the refresh indicator + */ + progressBackgroundColor: React.PropTypes.string, + /** + * Whether the view should be indicating an active refresh + */ + refreshing: React.PropTypes.bool, + /** + * Size of the refresh indicator, see PullToRefreshLayoutAndroid.SIZE + */ + size: React.PropTypes.oneOf(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE), + }, + + getInnerViewNode: function() { + return this.refs[NATIVE_REF]; + }, + + render: function() { + return ( + + {onlyChild(this.props.children)} + + ); + }, + + _onRefresh: function() { + this.props.onRefresh && this.props.onRefresh(); + this.refs[NATIVE_REF].setNativeProps({refreshing: !!this.props.refreshing}); + } +}); + +var NativePullToRefresh = requireNativeComponent( + 'AndroidSwipeRefreshLayout', + PullToRefreshLayoutAndroid +); + +module.exports = PullToRefreshLayoutAndroid; diff --git a/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js new file mode 100644 index 000000000..eea4dbae7 --- /dev/null +++ b/Libraries/PullToRefresh/PullToRefreshLayoutAndroid.ios.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PullToRefreshLayoutAndroid + */ + 'use strict'; + + module.exports = require('UnimplementedView'); diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 206b5f785..29ff95205 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -37,6 +37,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { SliderIOS: require('SliderIOS'), SnapshotViewIOS: require('SnapshotViewIOS'), Switch: require('Switch'), + PullToRefreshLayoutAndroid: require('PullToRefreshLayoutAndroid'), SwitchAndroid: require('SwitchAndroid'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 115c33ea6..a0e90db20 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -39,6 +39,7 @@ import com.facebook.react.views.textinput.ReactTextInputManager; import com.facebook.react.views.toolbar.ReactToolbarManager; import com.facebook.react.views.view.ReactViewManager; import com.facebook.react.views.viewpager.ReactViewPagerManager; +import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager; /** * Package defining basic modules and view managers. @@ -78,6 +79,7 @@ public class MainReactPackage implements ReactPackage { new ReactViewManager(), new ReactViewPagerManager(), new ReactTextInlineImageViewManager(), - new ReactVirtualTextViewManager()); + new ReactVirtualTextViewManager(), + new SwipeRefreshLayoutManager()); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java new file mode 100644 index 000000000..f458c0ab0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.swiperefresh; + +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.MotionEvent; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.uimanager.events.NativeGestureUtil; + +/** + * Basic extension of {@link SwipeRefreshLayout} with ReactNative-specific functionality. + */ +public class ReactSwipeRefreshLayout extends SwipeRefreshLayout { + + public ReactSwipeRefreshLayout(ReactContext reactContext) { + super(reactContext); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (super.onInterceptTouchEvent(ev)) { + NativeGestureUtil.notifyNativeGestureStarted(this, ev); + return true; + } + return false; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java new file mode 100644 index 000000000..d8325d2bc --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.swiperefresh; + +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +public class RefreshEvent extends Event { + + protected RefreshEvent(int viewTag, long timestampMs) { + super(viewTag, timestampMs); + } + + @Override + public String getEventName() { + return "topRefresh"; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), null); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java new file mode 100644 index 000000000..1520dc1f9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.swiperefresh; + +import javax.annotation.Nullable; + +import java.util.Map; + +import android.graphics.Color; +import android.os.SystemClock; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.ReactProp; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.ViewProps; + +/** + * ViewManager for {@link ReactSwipeRefreshLayout} which allows the user to "pull to refresh" a + * child view. Emits an {@code onRefresh} event when this happens. + */ +public class SwipeRefreshLayoutManager extends ViewGroupManager { + + @Override + protected ReactSwipeRefreshLayout createViewInstance(ThemedReactContext reactContext) { + return new ReactSwipeRefreshLayout(reactContext); + } + + @Override + public String getName() { + return "AndroidSwipeRefreshLayout"; + } + + @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true) + public void setEnabled(ReactSwipeRefreshLayout view, boolean enabled) { + view.setEnabled(enabled); + } + + @ReactProp(name = "colors") + public void setColors(ReactSwipeRefreshLayout view, @Nullable ReadableArray colors) { + if (colors != null) { + int[] colorValues = new int[colors.size()]; + for (int i = 0; i < colors.size(); i++) { + colorValues[i] = colors.getInt(i); + } + view.setColorSchemeColors(colorValues); + } else { + view.setColorSchemeColors(); + } + } + + @ReactProp(name = "progressBackgroundColor", defaultInt = Color.TRANSPARENT, customType = "Color") + public void setProgressBackgroundColor(ReactSwipeRefreshLayout view, int color) { + view.setProgressBackgroundColorSchemeColor(color); + } + + @ReactProp(name = "size", defaultInt = SwipeRefreshLayout.DEFAULT) + public void setSize(ReactSwipeRefreshLayout view, int size) { + view.setSize(size); + } + + @ReactProp(name = "refreshing") + public void setRefreshing(ReactSwipeRefreshLayout view, boolean refreshing) { + view.setRefreshing(refreshing); + } + + @Override + protected void addEventEmitters( + final ThemedReactContext reactContext, + final ReactSwipeRefreshLayout view) { + view.setOnRefreshListener( + new OnRefreshListener() { + @Override + public void onRefresh() { + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher() + .dispatchEvent(new RefreshEvent(view.getId(), SystemClock.uptimeMillis())); + } + }); + } + + @Nullable + @Override + public Map getExportedViewConstants() { + return MapBuilder.of( + "SIZE", + MapBuilder.of("DEFAULT", SwipeRefreshLayout.DEFAULT, "LARGE", SwipeRefreshLayout.LARGE)); + } + + @Override + public Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.builder() + .put("topRefresh", MapBuilder.of("registrationName", "onRefresh")) + .build(); + } +}