Skip to content

Commit b5535b9

Browse files
committed
initial work on v2
1 parent 4c610c5 commit b5535b9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+905
-110
lines changed

lib/ControlledRefreshableListView.js

Lines changed: 139 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,103 @@
11
var React = require('react-native')
22
var {
33
PropTypes,
4+
StyleSheet,
5+
View,
6+
Platform,
47
} = React
58
var ListView = require('./ListView')
69
var createElementFrom = require('./createElementFrom')
710
var RefreshingIndicator = require('./RefreshingIndicator')
811

912
const SCROLL_EVENT_THROTTLE = 32
1013
const MIN_PULLDOWN_DISTANCE = 40
11-
14+
const REFRESHING_INDICATOR_HEIGHT = 60
1215
const LISTVIEW_REF = 'listview'
1316

17+
/*
18+
* state transitions:
19+
* {isRefreshing: false}
20+
* v - show loading spinner
21+
* {isRefreshing: true, waitingForRelease: true}
22+
* v - reset scroll position, offset scroll top
23+
* {isRefreshing: true, waitingForRelease: false}
24+
* v - hide loading spinner
25+
* {isRefreshing: false}
26+
*/
27+
1428
var ControlledRefreshableListView = React.createClass({
1529
propTypes: {
1630
onRefresh: PropTypes.func.isRequired,
1731
isRefreshing: PropTypes.bool.isRequired,
32+
refreshPrompt: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
1833
refreshDescription: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
19-
refreshingIndictatorComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
34+
refreshingIndicatorComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
2035
minPulldownDistance: PropTypes.number,
2136
ignoreInertialScroll: PropTypes.bool,
2237
scrollEventThrottle: PropTypes.number,
2338
onScroll: PropTypes.func,
24-
renderHeader: PropTypes.func,
25-
renderHeaderWrapper: PropTypes.func,
2639
onResponderGrant: PropTypes.func,
2740
onResponderRelease: PropTypes.func,
41+
renderHeaderWrapper: (props, propName, componentName) => {
42+
if (props[propName]) {
43+
return new Error("The 'renderHeaderWrapper' prop is no longer used");
44+
}
45+
},
46+
refreshingIndictatorComponent: (props, propName, componentName) => {
47+
if (props[propName]) {
48+
return new Error("The 'refreshingIndictatorComponent' prop has been renamed to 'refreshingIndicatorComponent'");
49+
}
50+
},
2851
},
2952
getDefaultProps() {
3053
return {
3154
minPulldownDistance: MIN_PULLDOWN_DISTANCE,
3255
scrollEventThrottle: SCROLL_EVENT_THROTTLE,
3356
ignoreInertialScroll: true,
34-
refreshingIndictatorComponent: RefreshingIndicator,
57+
refreshingIndicatorComponent: RefreshingIndicator,
58+
}
59+
},
60+
getInitialState() {
61+
return {
62+
waitingForRelease: false,
63+
}
64+
},
65+
componentWillReceiveProps(nextProps) {
66+
if (!this.props.isRefreshing && nextProps.isRefreshing && this.isTouching) {
67+
68+
this.waitingForRelease = true
69+
this.setState({waitingForRelease: true})
70+
}
71+
},
72+
componentWillUpdate(nextProps, nextState) {
73+
if (Platform.OS === 'ios') {
74+
if (
75+
this.isReleaseUpdate(this.props, this.state, nextProps, nextState)
76+
) {
77+
this.getScrollResponder().scrollWithoutAnimationTo(
78+
-(this.lastContentInsetTop + REFRESHING_INDICATOR_HEIGHT),
79+
this.lastContentOffsetX
80+
)
81+
}
82+
}
83+
},
84+
componentDidUpdate(prevProps, prevState) {
85+
if (Platform.OS === 'ios') {
86+
if (
87+
this.isReleaseUpdate(prevProps, prevState, this.props, this.state)
88+
) {
89+
this.getScrollResponder().scrollWithoutAnimationTo(
90+
-(this.lastContentInsetTop),
91+
this.lastContentOffsetX
92+
)
93+
}
3594
}
3695
},
3796
handleScroll(e) {
3897
var scrollY = e.nativeEvent.contentInset.top + e.nativeEvent.contentOffset.y
98+
this.lastScrollY = scrollY
99+
this.lastContentInsetTop = e.nativeEvent.contentInset.top
100+
this.lastContentOffsetX = e.nativeEvent.contentOffset.x
39101

40102
if (this.isTouching || (!this.isTouching && !this.props.ignoreInertialScroll)) {
41103
if (scrollY < -this.props.minPulldownDistance) {
@@ -57,50 +119,96 @@ var ControlledRefreshableListView = React.createClass({
57119
},
58120
handleResponderRelease() {
59121
this.isTouching = false
122+
if (this.isWaitingForRelease()) {
123+
this.waitingForRelease = false
124+
this.setState({waitingForRelease: false})
125+
}
60126
if (this.props.onResponderRelease) {
61127
this.props.onResponderRelease.apply(null, arguments)
62128
}
63129
},
130+
getContentContainerStyle() {
131+
if (!this.props.isRefreshing || this.isWaitingForRelease()) return null
132+
133+
return {marginTop: REFRESHING_INDICATOR_HEIGHT}
134+
},
64135
getScrollResponder() {
65136
return this.refs[LISTVIEW_REF].getScrollResponder()
66137
},
67138
setNativeProps(props) {
68139
this.refs[LISTVIEW_REF].setNativeProps(props)
69140
},
70-
renderHeader() {
71-
var description = this.props.refreshDescription
72-
73-
var refreshingIndictator
74-
if (this.props.isRefreshing) {
75-
refreshingIndictator = createElementFrom(this.props.refreshingIndictatorComponent, {description})
76-
} else {
77-
refreshingIndictator = null
78-
}
79-
80-
if (this.props.renderHeaderWrapper) {
81-
return this.props.renderHeaderWrapper(refreshingIndictator)
82-
} else if (this.props.renderHeader) {
83-
console.warn('renderHeader is deprecated. Use renderHeaderWrapper instead.')
84-
return this.props.renderHeader(refreshingIndictator)
85-
} else {
86-
return refreshingIndictator
141+
isWaitingForRelease() {
142+
return this.waitingForRelease || this.props.waitingForRelease
143+
},
144+
isReleaseUpdate(oldProps, oldState, newProps, newState) {
145+
return (
146+
(!oldProps.isRefreshing && newProps.isRefreshing && !this.waitingForRelease) ||
147+
(oldProps.isRefreshing && oldState.waitingForRelease && !newState.waitingForRelease)
148+
)
149+
},
150+
renderRefreshingIndicator() {
151+
var {isRefreshing, refreshDescription, refreshPrompt} = this.props
152+
var refreshingIndicatorProps = {
153+
isRefreshing,
154+
description: refreshDescription,
155+
prompt: refreshPrompt,
156+
pulldownDistance: -(this.lastScrollY || 0)
87157
}
158+
return createElementFrom(this.props.refreshingIndicatorComponent, refreshingIndicatorProps)
88159
},
89160
render() {
90161
return (
91-
<ListView
92-
{...this.props}
93-
ref={LISTVIEW_REF}
94-
onScroll={this.handleScroll}
95-
renderHeader={this.renderHeader}
96-
scrollEventThrottle={this.props.scrollEventThrottle}
97-
onResponderGrant={this.handleResponderGrant}
98-
onResponderRelease={this.handleResponderRelease}
99-
/>
162+
<View style={[stylesheet.container]}>
163+
<View style={[stylesheet.fillParent]}>
164+
{this.renderRefreshingIndicator()}
165+
</View>
166+
<View style={[stylesheet.fillParent]}>
167+
<ListView
168+
{...this.props}
169+
ref={LISTVIEW_REF}
170+
contentContainerStyle={this.getContentContainerStyle()}
171+
onScroll={this.handleScroll}
172+
scrollEventThrottle={this.props.scrollEventThrottle}
173+
onResponderGrant={this.handleResponderGrant}
174+
onResponderRelease={this.handleResponderRelease}
175+
/>
176+
</View>
177+
</View>
100178
)
101179
},
102180
})
103181

182+
var stylesheet = StyleSheet.create({
183+
container: {
184+
// flex: 1,
185+
},
186+
fillParent: {
187+
backgroundColor: 'transparent',
188+
position: 'absolute',
189+
top: 0,
190+
left: 0,
191+
right: 0,
192+
bottom: 0,
193+
},
194+
// offsetParent: {
195+
// position: 'relative',
196+
// },
197+
// positionTopLeft: {
198+
// position: 'absolute',
199+
// top: 0,
200+
// left: 0,
201+
// },
202+
// fill: {
203+
// flex: 1
204+
// },
205+
// center: {
206+
// flex: 1,
207+
// justifyContent: 'space-around',
208+
// alignItems: 'center',
209+
// },
210+
})
211+
104212
ControlledRefreshableListView.DataSource = ListView.DataSource
105213
ControlledRefreshableListView.RefreshingIndicator = RefreshingIndicator
106214

lib/RefreshableListView.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ var RefreshableListView = React.createClass({
1616
minDisplayTime: PropTypes.number,
1717
minBetweenTime: PropTypes.number,
1818
// props passed to child
19+
refreshPrompt: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
1920
refreshDescription: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
20-
refreshingIndictatorComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
21+
refreshingIndicatorComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
2122
minPulldownDistance: PropTypes.number,
22-
renderHeaderWrapper: PropTypes.func,
2323
},
2424
getDefaultProps() {
2525
return {
2626
minDisplayTime: 300,
2727
minBetweenTime: 300,
2828
minPulldownDistance: 40,
29-
refreshingIndictatorComponent: RefreshingIndicator,
29+
refreshingIndicatorComponent: RefreshingIndicator,
3030
}
3131
},
3232
getInitialState() {

lib/RefreshingIndicator.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,32 @@ var RefreshingIndicator = React.createClass({
2020
activityIndicatorComponent: ActivityIndicatorIOS,
2121
}
2222
},
23-
renderActivityIndicator(style) {
23+
renderDescription(styles) {
24+
return (
25+
<Text style={styles.description}>
26+
{this.props.isRefreshing ? this.props.description : this.props.prompt}
27+
</Text>
28+
);
29+
},
30+
renderActivityIndicator(styles) {
31+
if (!this.props.isRefreshing) return null
32+
2433
var activityIndicator = this.props.activityIndicatorComponent
2534

2635
if (isValidElement(activityIndicator)) {
2736
return activityIndicator
2837
} else { // is a component class, not an element
29-
return createElement(activityIndicator, {style})
38+
return createElement(activityIndicator, {style: styles.activityIndicator})
3039
}
3140
},
3241
render() {
3342
var styles = Object.assign({}, stylesheet, this.props.stylesheet)
3443

3544
return (
36-
<View style={[styles.container, styles.wrapper]}>
45+
<View style={[styles.wrapper]}>
3746
<View style={[styles.container, styles.loading, styles.content]}>
38-
<Text style={styles.description}>
39-
{this.props.description}
40-
</Text>
41-
{this.renderActivityIndicator(styles.activityIndicator)}
47+
{this.renderDescription(styles)}
48+
{this.renderActivityIndicator(styles)}
4249
</View>
4350
</View>
4451
)

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"main": "index.js",
66
"scripts": {
77
"lint": "eslint .",
8+
"watch": "watchy -w lib -- bash -c 'cp -a lib/ test/RefreshableListViewTest/node_modules/react-native-refreshable-listview/lib/'",
89
"jest": "BABEL_JEST_STAGE=0 jest $JEST_ARGS",
910
"test": "npm run lint && npm run jest"
1011
},
@@ -57,6 +58,7 @@
5758
"jest-cli": "^0.4.1",
5859
"react": "^0.13.1",
5960
"source-map-support": "^0.2.10",
61+
"watchy": "^0.6.4",
6062
"xtend": "^4.0.0"
6163
},
6264
"dependencies": {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[ignore]
2+
3+
# We fork some components by platform.
4+
.*/*.web.js
5+
.*/*.android.js
6+
7+
# Some modules have their own node_modules with overlap
8+
.*/node_modules/node-haste/.*
9+
10+
# Ignore react-tools where there are overlaps, but don't ignore anything that
11+
# react-native relies on
12+
.*/node_modules/react-tools/src/React.js
13+
.*/node_modules/react-tools/src/renderers/shared/event/EventPropagators.js
14+
.*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js
15+
.*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js
16+
17+
18+
# Ignore commoner tests
19+
.*/node_modules/commoner/test/.*
20+
21+
# See https://github.com/facebook/flow/issues/442
22+
.*/react-tools/node_modules/commoner/lib/reader.js
23+
24+
# Ignore jest
25+
.*/react-native/node_modules/jest-cli/.*
26+
27+
[include]
28+
29+
[libs]
30+
node_modules/react-native/Libraries/react-native/react-native-interface.js
31+
32+
[options]
33+
module.system=haste
34+
35+
munge_underscores=true
36+
37+
suppress_type=$FlowIssue
38+
suppress_type=$FlowFixMe
39+
suppress_type=$FixMe
40+
41+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-4]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
42+
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-4]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
43+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
44+
45+
[version]
46+
0.14.0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#Sun Sep 20 11:47:02 AEST 2015
Binary file not shown.
Binary file not shown.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<module external.linked.project.id="RefreshableListViewTest" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
3+
<component name="FacetManager">
4+
<facet type="java-gradle" name="Java-Gradle">
5+
<configuration>
6+
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
7+
<option name="BUILDABLE" value="false" />
8+
</configuration>
9+
</facet>
10+
</component>
11+
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
12+
<exclude-output />
13+
<content url="file://$MODULE_DIR$">
14+
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
15+
</content>
16+
<orderEntry type="inheritedJdk" />
17+
<orderEntry type="sourceFolder" forTests="false" />
18+
</component>
19+
</module>

0 commit comments

Comments
 (0)