diff --git a/package.json b/package.json
index e36327f..6228f40 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
- "name": "react-native-ultimate-listview",
- "version": "3.3.0",
+ "name": "@bang88/react-native-ultimate-listview",
+ "version": "4.1.0",
"description": "A high performance FlatList providing customised pull-to-refresh | auto-pagination & infinite-scrolling | gridview layout | swipeable-row. The truly ultimate version that I have done the most tricky part for you, just simply follow the instructions shown below to put it in your app.",
"main": "index.js",
"scripts": {
diff --git a/src/refreshableScrollView.android.js b/src/refreshableScrollView.android.js
index 42dee12..b476d84 100644
--- a/src/refreshableScrollView.android.js
+++ b/src/refreshableScrollView.android.js
@@ -2,7 +2,6 @@ import React from 'react'
import {
ActivityIndicator,
Animated,
- AsyncStorage,
Dimensions,
Easing,
ScrollView,
@@ -13,7 +12,6 @@ import {
import dateFormat from './util'
const { width, height } = Dimensions.get('window')
-const DATE_KEY = 'ultimateRefreshDate'
const RefreshStatus = {
pullToRefresh: 0,
releaseToRefresh: 1,
@@ -25,7 +23,7 @@ const PaginationStatus = {
allLoaded: 2
}
-export default class RefreshableScrollView extends ScrollView {
+export default class RefreshableScrollView extends React.Component {
static defaultProps = {
horizontal: false,
scrollEnabled: true,
@@ -61,25 +59,6 @@ export default class RefreshableScrollView extends ScrollView {
}
}
- async componentDidMount() {
- console.warn('The advancedRefreshView is not ready for Android at this moment. \n\nIf the items are less than the height of device screen, the refreshView will not disappear. \n\nPlease consider setting the refreshableMode={Platform.OS === "ios" ? "advanced" : "basic"}, or feel free to send me a PR to resolve this problem. \n\nThanks a lot.')
- try {
- let result = await AsyncStorage.getItem(DATE_KEY)
- if (result) {
- result = parseInt(result, 10)
- this.setState({
- date: dateFormat(new Date(result), this.props.dateFormat)
- })
- } else {
- this.setState({
- date: dateFormat(new Date(), this.props.dateFormat)
- })
- }
- } catch (err) {
- console.log(err)
- }
- }
-
onScroll = (event) => {
// console.log('onScroll()');
const { y } = event.nativeEvent.contentOffset
@@ -98,7 +77,8 @@ export default class RefreshableScrollView extends ScrollView {
Animated.timing(this.state.arrowAngle, {
toValue: 1,
duration: 50,
- easing: Easing.inOut(Easing.quad)
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
}).start()
}
} else if (this.state.refreshStatus !== RefreshStatus.pullToRefresh) {
@@ -109,7 +89,8 @@ export default class RefreshableScrollView extends ScrollView {
Animated.timing(this.state.arrowAngle, {
toValue: 0,
duration: 50,
- easing: Easing.inOut(Easing.quad)
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
}).start()
}
}
@@ -190,11 +171,11 @@ export default class RefreshableScrollView extends ScrollView {
})
}, 1000)
- AsyncStorage.setItem(DATE_KEY, now.toString())
Animated.timing(this.state.arrowAngle, {
toValue: 0,
duration: 50,
- easing: Easing.inOut(Easing.quad)
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
}).start()
}
}
@@ -216,7 +197,7 @@ export default class RefreshableScrollView extends ScrollView {
{this.renderSpinner()}
{this.state.refreshTitle}
- {this.props.displayDate &&
+ {this.props.displayDate && this.state.date &&
{this.props.dateTitle + this.state.date}
diff --git a/src/refreshableScrollView.ios.js b/src/refreshableScrollView.ios.js
index e29399b..bf8e192 100644
--- a/src/refreshableScrollView.ios.js
+++ b/src/refreshableScrollView.ios.js
@@ -1,8 +1,8 @@
import React from 'react'
-import { ActivityIndicator, Animated, AsyncStorage, Easing, ScrollView, StyleSheet, Text, View } from 'react-native'
+import { ActivityIndicator, Animated, Easing, ScrollView, StyleSheet, Text, View } from 'react-native'
+
import dateFormat from './util'
-const DATE_KEY = 'ultimateRefreshDate'
const RefreshStatus = {
pullToRefresh: 0,
releaseToRefresh: 1,
@@ -14,7 +14,7 @@ const PaginationStatus = {
allLoaded: 2
}
-export default class RefreshableScrollView extends ScrollView {
+export default class RefreshableScrollView extends React.Component {
static defaultProps = {
horizontal: false,
scrollEnabled: true,
@@ -49,24 +49,6 @@ export default class RefreshableScrollView extends ScrollView {
}
}
- async componentDidMount() {
- try {
- let result = await AsyncStorage.getItem(DATE_KEY)
- if (result) {
- result = parseInt(result, 10)
- this.setState({
- date: dateFormat(new Date(result), this.props.dateFormat)
- })
- } else {
- this.setState({
- date: dateFormat(new Date(), this.props.dateFormat)
- })
- }
- } catch (err) {
- console.log(err)
- }
- }
-
onScroll = (event) => {
// console.log('onScroll()');
const { y } = event.nativeEvent.contentOffset
@@ -82,7 +64,8 @@ export default class RefreshableScrollView extends ScrollView {
Animated.timing(this.state.arrowAngle, {
toValue: 1,
duration: 50,
- easing: Easing.inOut(Easing.quad)
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
}).start()
} else {
this.setState({
@@ -92,7 +75,8 @@ export default class RefreshableScrollView extends ScrollView {
Animated.timing(this.state.arrowAngle, {
toValue: 0,
duration: 50,
- easing: Easing.inOut(Easing.quad)
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
}).start()
}
}
@@ -159,11 +143,11 @@ export default class RefreshableScrollView extends ScrollView {
refreshTitle: this.props.refreshableTitlePull,
date: dateFormat(now, this.props.dateFormat)
})
- AsyncStorage.setItem(DATE_KEY, now.toString())
Animated.timing(this.state.arrowAngle, {
toValue: 0,
duration: 50,
- easing: Easing.inOut(Easing.quad)
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
}).start()
this._scrollview.scrollTo({ x: 0, y: 0, animated: true })
}
@@ -184,7 +168,7 @@ export default class RefreshableScrollView extends ScrollView {
{this.renderSpinner()}
{this.state.refreshTitle}
- {this.props.displayDate &&
+ {this.props.displayDate && this.state.date &&
{this.props.dateTitle + this.state.date}
}
@@ -224,6 +208,7 @@ export default class RefreshableScrollView extends ScrollView {
onScroll={this.onScroll}
onScrollEndDrag={this.onScrollEndDrag}
onScrollBeginDrag={this.onScrollBeginDrag}
+ contentInset={{ top: 80 }}
>
{this.renderRefreshHeader()}
{this.props.children}
diff --git a/src/refreshableScrollView.web.js b/src/refreshableScrollView.web.js
new file mode 100644
index 0000000..bf8e192
--- /dev/null
+++ b/src/refreshableScrollView.web.js
@@ -0,0 +1,249 @@
+import React from 'react'
+import { ActivityIndicator, Animated, Easing, ScrollView, StyleSheet, Text, View } from 'react-native'
+
+import dateFormat from './util'
+
+const RefreshStatus = {
+ pullToRefresh: 0,
+ releaseToRefresh: 1,
+ refreshing: 2
+}
+const PaginationStatus = {
+ firstLoad: 0,
+ waiting: 1,
+ allLoaded: 2
+}
+
+export default class RefreshableScrollView extends React.Component {
+ static defaultProps = {
+ horizontal: false,
+ scrollEnabled: true,
+ header: null,
+ refreshable: true,
+ refreshableTitlePull: 'Pull to refresh',
+ refreshableTitleRefreshing: 'Loading...',
+ refreshableTitleRelease: 'Release to load',
+ customRefreshView: null,
+ displayDate: false,
+ dateFormat: 'yyyy-MM-dd hh:mm',
+ dateTitle: 'Last updated: ',
+ arrowImageSource: require('./downArrow.png'),
+ arrowImageStyle: undefined,
+ refreshViewStyle: undefined,
+ dateStyle: undefined,
+ refreshViewHeight: 80,
+ insideOfUltimateListView: false
+ }
+
+ _offsetY = 0
+ _isRefreshing = false
+ _dragFlag = false
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ arrowAngle: new Animated.Value(0),
+ refreshStatus: RefreshStatus.pullToRefresh,
+ refreshTitle: this.props.refreshableTitlePull,
+ date: this.props.date
+ }
+ }
+
+ onScroll = (event) => {
+ // console.log('onScroll()');
+ const { y } = event.nativeEvent.contentOffset
+ this._offsetY = y
+ if (this._dragFlag) {
+ if (!this._isRefreshing) {
+ const height = this.props.refreshViewHeight
+ if (y <= -height) {
+ this.setState({
+ refreshStatus: RefreshStatus.releaseToRefresh,
+ refreshTitle: this.props.refreshableTitleRelease
+ })
+ Animated.timing(this.state.arrowAngle, {
+ toValue: 1,
+ duration: 50,
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
+ }).start()
+ } else {
+ this.setState({
+ refreshStatus: RefreshStatus.pullToRefresh,
+ refreshTitle: this.props.refreshableTitlePull
+ })
+ Animated.timing(this.state.arrowAngle, {
+ toValue: 0,
+ duration: 50,
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
+ }).start()
+ }
+ }
+ }
+ if (this.props.onScroll) {
+ this.props.onScroll(event)
+ }
+ }
+
+ onScrollBeginDrag = (event) => {
+ // console.log('onScrollBeginDrag()');
+ this._dragFlag = true
+ this._offsetY = event.nativeEvent.contentOffset.y
+ if (this.props.onScrollBeginDrag) {
+ this.props.onScrollBeginDrag(event)
+ }
+ }
+
+ onScrollEndDrag = (event) => {
+ // console.log('onScrollEndDrag()');
+ this._dragFlag = false
+ const { y } = event.nativeEvent.contentOffset
+ this._offsetY = y
+ const height = this.props.refreshViewHeight
+ if (!this._isRefreshing) {
+ if (this.state.refreshStatus === RefreshStatus.releaseToRefresh) {
+ this._isRefreshing = true
+ this.setState({
+ refreshStatus: RefreshStatus.refreshing,
+ refreshTitle: this.props.refreshableTitleRefreshing
+ })
+ this._scrollview.scrollTo({ x: 0, y: -height, animated: true })
+ if (this.props.insideOfUltimateListView) {
+ this.props.onRefresh()
+ } else {
+ this.props.onRefresh(() => {
+ this.onRefreshEnd()
+ })
+ }
+ }
+ } else if (y <= 0) {
+ this._scrollview.scrollTo({ x: 0, y: -height, animated: true })
+ }
+ if (this.props.onScrollEndDrag) {
+ this.props.onScrollEndDrag(event)
+ }
+ }
+
+ scrollTo = (option) => {
+ this._scrollview.scrollTo(option)
+ }
+
+ scrollToEnd = (option) => {
+ this._scrollview.scrollToEnd(option)
+ }
+
+ onRefreshEnd = () => {
+ // console.log('onRefreshEnd()');
+ if (this.state.refreshStatus === RefreshStatus.refreshing) {
+ this._isRefreshing = false
+ const now = new Date().getTime()
+ this.setState({
+ refreshStatus: RefreshStatus.pullToRefresh,
+ refreshTitle: this.props.refreshableTitlePull,
+ date: dateFormat(now, this.props.dateFormat)
+ })
+ Animated.timing(this.state.arrowAngle, {
+ toValue: 0,
+ duration: 50,
+ easing: Easing.inOut(Easing.quad),
+ useNativeDriver: true,
+ }).start()
+ this._scrollview.scrollTo({ x: 0, y: 0, animated: true })
+ }
+ }
+
+ renderRefreshHeader() {
+ if (this.props.customRefreshView) {
+ return (
+
+ {this.props.customRefreshView(this.state.refreshStatus, this._offsetY)}
+
+ )
+ }
+
+ return (
+
+
+ {this.renderSpinner()}
+ {this.state.refreshTitle}
+
+ {this.props.displayDate && this.state.date &&
+ {this.props.dateTitle + this.state.date}
+ }
+
+ )
+ }
+
+ renderSpinner() {
+ if (this.state.refreshStatus === RefreshStatus.refreshing) {
+ return (
+
+ )
+ }
+ return (
+
+ )
+ }
+
+ render() {
+ return (
+ this._scrollview = c}
+ {...this.props}
+ scrollEventThrottle={16}
+ onScroll={this.onScroll}
+ onScrollEndDrag={this.onScrollEndDrag}
+ onScrollBeginDrag={this.onScrollBeginDrag}
+ contentInset={{ top: 80 }}
+ >
+ {this.renderRefreshHeader()}
+ {this.props.children}
+
+ )
+ }
+}
+
+const defaultHeaderStyles = StyleSheet.create({
+ header: {
+ position: 'absolute',
+ top: -80,
+ left: 0,
+ right: 0,
+ height: 80,
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ status: {
+ flexDirection: 'row',
+ alignItems: 'center'
+ },
+ arrow: {
+ width: 23,
+ height: 23,
+ marginRight: 10,
+ opacity: 0.4
+ },
+ statusTitle: {
+ fontSize: 13,
+ color: '#333333'
+ },
+ date: {
+ fontSize: 11,
+ color: '#333333',
+ marginTop: 5
+ }
+})
diff --git a/src/ultimateListView.js b/src/ultimateListView.js
index c824ebe..5a74e3a 100644
--- a/src/ultimateListView.js
+++ b/src/ultimateListView.js
@@ -7,6 +7,7 @@ import {
RefreshControl,
ScrollView,
StyleSheet,
+ Button,
Text,
View
} from 'react-native'
@@ -16,8 +17,9 @@ import RefreshableScrollView from './refreshableScrollView'
const { width, height } = Dimensions.get('window')
const PaginationStatus = {
firstLoad: 0,
- waiting: 1,
- allLoaded: 2
+ waiting: 1, // Free process with user's doing-nothing
+ allLoaded: 2,
+ fetching: 3 // Waiting for fetch in `onRefresh()`
}
export default class UltimateListView extends Component {
@@ -36,6 +38,7 @@ export default class UltimateListView extends Component {
paginationFetchingView: null,
paginationAllLoadedView: null,
paginationWaitingView: null,
+ paginationLoadMoreView: null,
emptyView: null,
separator: null,
@@ -98,6 +101,7 @@ export default class UltimateListView extends Component {
paginationFetchingView: PropTypes.func,
paginationAllLoadedView: PropTypes.func,
paginationWaitingView: PropTypes.func,
+ paginationLoadMoreView: PropTypes.func,
emptyView: PropTypes.func,
separator: PropTypes.func,
@@ -161,7 +165,7 @@ export default class UltimateListView extends Component {
componentDidMount() {
this.mounted = true
if (this.props.firstLoader) {
- this.props.onFetch(this.getPage(), this.postRefresh, this.endFetch)
+ this.props.onFetch(1, this.postRefresh, this.endFetch)
}
}
@@ -170,26 +174,37 @@ export default class UltimateListView extends Component {
}
onRefresh = () => {
- console.log('onRefresh()')
if (this.mounted) {
this.setState({
- isRefreshing: true
+ isRefreshing: true,
+ paginationStatus: PaginationStatus.fetching
+ }, () => {
+ this.props.onFetch(1, this.postRefresh, this.endFetch)
})
- this.setPage(1)
- this.props.onFetch(this.getPage(), this.postRefresh, this.endFetch)
}
}
- onPaginate = () => {
+ /**
+ * onRefreshEnd
+ *
+ * @description Scroll to top before `onRefresh()` ending
+ * * */
+ onRefreshEnd = () => {
+ if (this.props.refreshableMode === 'advanced' && this._flatList._listRef._scrollRef.onRefreshEnd) {
+ this._flatList._listRef._scrollRef.onRefreshEnd()
+ } else if (this._flatList._listRef._scrollRef.scrollTo && this.state.isRefreshing) {
+ this._flatList._listRef._scrollRef.scrollTo({ x: 0, y: 0, animated: true })
+ }
+ }
+
+ onPaginate = async () => {
if (this.state.paginationStatus !== PaginationStatus.allLoaded && !this.state.isRefreshing) {
- console.log('onPaginate()')
- this.setState({ paginationStatus: PaginationStatus.waiting })
+ await this.setState({ paginationStatus: PaginationStatus.fetching })
this.props.onFetch(this.getPage() + 1, this.postPaginate, this.endFetch)
}
}
- onEndReached = () => {
- // console.log('onEndReached()');
+ onEndReached = ({ distanceFromEnd }) => {
if (this.props.pagination && this.props.autoPagination && this.state.paginationStatus === PaginationStatus.waiting) {
this.onPaginate()
}
@@ -231,17 +246,16 @@ export default class UltimateListView extends Component {
if (rows.length < pageLimit) {
paginationStatus = PaginationStatus.allLoaded
}
- this.updateRows(rows, paginationStatus)
+ this.setPage(1)
+ this.updateRows(rows.slice(), paginationStatus)
}
}
endFetch = () => {
- // console.log('endRefresh()');
if (this.mounted) {
- this.setState({ isRefreshing: false })
- if (this.props.refreshableMode === 'advanced' && this._flatList._listRef._scrollRef.onRefreshEnd) {
- this._flatList._listRef._scrollRef.onRefreshEnd()
- }
+ this.onRefreshEnd()
+
+ this.setState({ isRefreshing: false, paginationStatus: PaginationStatus.allLoaded, dataSource: [] })
}
}
@@ -260,6 +274,8 @@ export default class UltimateListView extends Component {
}
updateRows = (rows, paginationStatus) => {
+ this.onRefreshEnd()
+
if (rows) {
this.setRows(rows)
this.setState({
@@ -274,17 +290,13 @@ export default class UltimateListView extends Component {
paginationStatus
})
}
-
- if (this.props.refreshableMode === 'advanced') {
- this.endFetch()
- }
}
- updateDataSource(rows = []) {
+ updateDataSource(rows = [], callback) {
this.setRows(rows)
this.setState({
dataSource: rows
- })
+ }, callback)
}
paginationFetchingView = () => {
@@ -317,11 +329,11 @@ export default class UltimateListView extends Component {
return null
}
- paginationWaitingView = (paginateCallback) => {
+ paginationWaitingView = () => {
if (this.props.pagination) {
- if (this.props.autoPagination) {
+ if (!this.state.isRefreshing) {
if (this.props.paginationWaitingView) {
- return this.props.paginationWaitingView(paginateCallback)
+ return this.props.paginationWaitingView()
}
return (
@@ -339,6 +351,22 @@ export default class UltimateListView extends Component {
return null
}
+ paginationLoadMoreView = (paginateCallback) => {
+ if (this.props.pagination) {
+ if (!this.state.isRefreshing) {
+ if (this.props.paginationLoadMoreView) {
+ return this.props.paginationLoadMoreView(paginateCallback)
+ }
+
+ return (
+
+ )
+ }
+ }
+
+ return null
+ }
+
renderHeader = () => {
if (this.props.header) {
return this.props.header()
@@ -370,17 +398,17 @@ export default class UltimateListView extends Component {
renderEmptyView = () => {
if (this.state.paginationStatus !== PaginationStatus.firstLoad && this.props.emptyView) {
return this.props.emptyView()
+ } else if (this.state.paginationStatus === PaginationStatus.firstLoad) {
+ return this.paginationFetchingView()
}
return null
}
renderFooter = () => {
- if (this.state.paginationStatus === PaginationStatus.firstLoad) {
- return this.paginationFetchingView()
- } else if (this.state.paginationStatus === PaginationStatus.waiting && this.props.autoPagination === false) {
- return this.paginationWaitingView(this.onPaginate)
- } else if (this.state.paginationStatus === PaginationStatus.waiting && this.props.autoPagination === true) {
+ if (this.state.paginationStatus === PaginationStatus.waiting && this.props.autoPagination === false) {
+ return this.paginationLoadMoreView(this.onPaginate)
+ } else if (this.state.paginationStatus === PaginationStatus.fetching) {
return this.paginationWaitingView()
} else if (this.getRows().length !== 0 && this.state.paginationStatus === PaginationStatus.allLoaded) {
return this.paginationAllLoadedView()
@@ -435,11 +463,13 @@ export default class UltimateListView extends Component {
const { numColumns } = this.props
return (
this._flatList = ref}
+ paginationStatus={this.state.paginationStatus}// force update pure-component
data={this.state.dataSource}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}