diff --git a/Libraries/CustomComponents/ListView/THFeedListView.js b/Libraries/CustomComponents/ListView/THFeedListView.js deleted file mode 100644 index e473a2e8e..000000000 --- a/Libraries/CustomComponents/ListView/THFeedListView.js +++ /dev/null @@ -1,443 +0,0 @@ -/** - * Copyright (c) 2015, Facebook, Inc. All rights reserved. - * - * Facebook, Inc. ("Facebook") owns all right, title and interest, including - * all intellectual property and other proprietary rights, in and to the React - * Native CustomComponents software (the "Software"). Subject to your - * compliance with these terms, you are hereby granted a non-exclusive, - * worldwide, royalty-free copyright license to (1) use and copy the Software; - * and (2) reproduce and distribute the Software as part of your own software - * ("Your Software"). Facebook reserves all rights not expressly granted to - * you in this license agreement. - * - * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. - * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR - * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * @providesModule THFeedListView - */ -'use strict'; - -var ListViewDataSource = require('ListViewDataSource'); -var React = require('React'); -var RCTScrollViewManager = require('NativeModules').ScrollViewManager; -var ScrollView = require('ScrollView'); -var ScrollResponder = require('ScrollResponder'); -var StaticRenderer = require('StaticRenderer'); -var TimerMixin = require('TimerMixin'); - -var isEmpty = require('isEmpty'); -var logError = require('logError'); -var merge = require('merge'); -var performanceNow = require('performanceNow'); - -var PropTypes = React.PropTypes; - -var DEFAULT_PAGE_SIZE = 1; -var DEFAULT_INITIAL_ROWS = 10; -var DEFAULT_END_REACHED_THRESHOLD = 1000; -var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; -var DEFAULT_TICK_FREQUENCY = 200; -var DEFAULT_ROWS_YIELD_TIME = 100; -var DEFAULT_END_SCROLL_TIMEOUT = 100; -var SCROLLVIEW_REF = 'listviewscroll'; - -/** - * Simpler version of `ListView`, optimized for smooth scrolling in feed. This is - * achieved by skipping unecessary processing caused by race condition (with paging - * and handling view resize callback) and, more importantly, handling heavier - * computation (i.e rendering more rows, possibly fetching more data when end of - * feed is reached) only once current scroll gesture completes. - * - * Internally, listview uses an periodic tick callback that will render ahead each time - * additionl row(s), the number of which is controlled by `pageSize` props, until - * no more rows are available in data source. - * - */ - -var THFeedListView = React.createClass({ - mixins: [ScrollResponder.Mixin, TimerMixin], - - statics: { - DataSource: ListViewDataSource, - }, - - /** - * You must provide a renderRow function. If you omit any of the other render - * functions, ListView will simply skip rendering them. - * - * - renderRow(rowData, sectionID, rowID, highlightRow); - */ - propTypes: { - ...ScrollView.propTypes, - - dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, - /** - * (sectionID, rowID, adjacentRowHighlighted) => renderable - * - * If provided, a renderable component to be rendered as the separator - * below each row but not the last row if there is a section header below. - * Take a sectionID and rowID of the row above and whether its adjacent row - * is highlighted. - */ - renderSeparator: PropTypes.func, - /** - * (rowData, sectionID, rowID, highlightRow) => renderable - * - * Takes a data entry from the data source and its ids and should return - * a renderable component to be rendered as the row. By default the data - * is exactly what was put into the data source, but it's also possible to - * provide custom extractors. ListView can be notified when a row is - * being highlighted by calling highlightRow function. The separators above and - * below will be hidden when a row is highlighted. The highlighted state of - * a row can be reset by calling highlightRow(null). - */ - renderRow: PropTypes.func.isRequired, - /** - * How many rows to render on initial component mount. Use this to make - * it so that the first screen worth of data appears at one time instead of - * over the course of multiple frames. - */ - initialListSize: PropTypes.number, - /** - * Called when all rows have been rendered and the list has been scrolled - * to within onEndReachedThreshold of the bottom. The native scroll - * event is provided. - */ - onEndReached: PropTypes.func, - /** - * Threshold in pixels for onEndReached. - */ - onEndReachedThreshold: PropTypes.number, - /** - * Number of rows to render per event loop. - */ - pageSize: PropTypes.number, - /** - * () => renderable - * - * The header and footer are always rendered (if these props are provided) - * on every render pass. If they are expensive to re-render, wrap them - * in StaticContainer or other mechanism as appropriate. Footer is always - * at the bottom of the list, and header at the top, on every render pass. - */ - renderFooter: PropTypes.func, - renderHeader: PropTypes.func, - /** - * (props) => renderable - * - * A function that returns the scrollable component in which the list rows - * are rendered. Defaults to returning a ScrollView with the given props. - */ - renderScrollComponent: React.PropTypes.func.isRequired, - /** - * (visibleRows, changedRows) => void - * - * Called when the set of visible rows changes. `visibleRows` maps - * { sectionID: { rowID: true }} for all the visible rows, and - * `changedRows` maps { sectionID: { rowID: true | false }} for the rows - * that have changed their visibility, with true indicating visible, and - * false indicating the view has moved out of view. - */ - onChangeVisibleRows: React.PropTypes.func, - /** - * A performance optimization for improving scroll perf of - * large lists, used in conjunction with overflow: 'hidden' on the row - * containers. This is enabled by default. - */ - removeClippedSubviews: React.PropTypes.bool, - }, - - /** - * Exports some data, e.g. for perf investigations or analytics. - */ - getMetrics: function() { - return { - contentLength: this._scrollProperties.contentLength, - totalRows: this.props.dataSource.getRowCount(), - renderedRows: this.state.curRenderedRowsCount, - visibleRows: Object.keys(this._visibleRows).length, - }; - }, - - /** - * Provides a handle to the underlying scroll responder to support operations - * such as scrollTo. - */ - getScrollResponder: function() { - return this.refs[SCROLLVIEW_REF] && - this.refs[SCROLLVIEW_REF].getScrollResponder && - this.refs[SCROLLVIEW_REF].getScrollResponder(); - }, - - getInnerViewNode: function() { - return this.refs[SCROLLVIEW_REF] && - this.refs[SCROLLVIEW_REF].getInnerViewNode(); - }, - - setNativeProps: function(props) { - this.refs[SCROLLVIEW_REF] && - this.refs[SCROLLVIEW_REF].setNativeProps(props); - }, - - scrollTo: function(destY, destX) { - this.getScrollResponder().scrollResponderScrollTo(destX || 0, destY || 0); - }, - - /** - * React life cycle hooks. - */ - - getDefaultProps: function() { - return { - initialListSize: DEFAULT_INITIAL_ROWS, - pageSize: DEFAULT_PAGE_SIZE, - onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD, - }; - }, - - getInitialState: function() { - return { - curRenderedRowsCount: this.props.initialListSize, - highlightedRow: {}, - }; - }, - - componentWillMount: function() { - // this data should never trigger a render pass, so don't put in state - this._scrollProperties = { - visibleLength: null, - contentLength: null, - offset: 0 - }; - this._childFrames = []; - this._visibleRows = {}; - this._prevRenderedRowsCount = 0; - this._lastEndReachedContentLength = null; - this._lastScrollEventTime = 0; // TODO(9622421): use native scroll events when available - this._lastUpdateEventTime = 0; - }, - - componentDidMount: function() { - // potentially trigger fetching additional data as soon as possible. - this._callOnEndReachedIfNeeded(); - - // schedule periodic check to check if we have more rows to render or need to feth more rows - this.setInterval(this.onTick, DEFAULT_TICK_FREQUENCY); - }, - - componentWillReceiveProps: function(nextProps) { - if (this.props.dataSource !== nextProps.dataSource) { - this._prevRenderedRowsCount = 0; - } - if (this.props.initialListSize !== nextProps.initialListSize) { - this.setState((state, props) => { - return { - curRenderedRowsCount: Math.max( - state.curRenderedRowsCount, - props.initialListSize - ), - }; - }); - } - }, - - componentDidUpdate: function () { - this._lastUpdateEventTime = performanceNow(); - }, - - onRowHighlighted: function(sectionID, rowID) { - this.setState({highlightedRow: {sectionID, rowID}}); - }, - - onTick: function() { - if (!this.isMounted()) { - return; - } - if (performanceNow() < this._lastScrollEventTime + DEFAULT_END_SCROLL_TIMEOUT) { - // listview is currently scrolling, skip any work until scroll completes - return; - } - if (performanceNow() < this._lastUpdateEventTime + DEFAULT_ROWS_YIELD_TIME) { - // ensure at least enough time has elapsed between 2 row rendering - return; - } - this._callOnEndReachedIfNeeded(); - this._renderMoreRowsIfAvailable(); - }, - - render: function() { - var dataSource = this.props.dataSource; - var allRowIDs = dataSource.rowIdentities; - var rowCount = 0; - var header = this.props.renderHeader && this.props.renderHeader(); - var footer = this.props.renderFooter && this.props.renderFooter(); - var totalIndex = header ? 1 : 0; - - var bodyComponents = []; - for (var sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { - var sectionID = dataSource.sectionIdentities[sectionIdx]; - var rowIDs = allRowIDs[sectionIdx]; - if (rowIDs.length === 0) { - continue; - } - - for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { - var rowID = rowIDs[rowIdx]; - var comboID = sectionID + '_' + rowID; - var shouldUpdateRow = rowCount >= this._prevRenderedRowsCount && - dataSource.rowShouldUpdate(sectionIdx, rowIdx); - var row = - ; - bodyComponents.push(row); - totalIndex++; - - if (this.props.renderSeparator && - (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) { - var adjacentRowHighlighted = - this.state.highlightedRow.sectionID === sectionID && ( - this.state.highlightedRow.rowID === rowID || - this.state.highlightedRow.rowID === rowIDs[rowIdx + 1] - ); - var separator = this.props.renderSeparator( - sectionID, - rowID, - adjacentRowHighlighted - ); - bodyComponents.push(separator); - totalIndex++; - } - if (++rowCount === this.state.curRenderedRowsCount) { - break; - } - } - if (rowCount >= this.state.curRenderedRowsCount) { - break; - } - } - - var { - renderScrollComponent, - ...props, - } = this.props; - if (!props.scrollEventThrottle) { - props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE; - } - if (props.removeClippedSubviews === undefined) { - props.removeClippedSubviews = true; - } - Object.assign(props, { - onScroll: this._onScroll, - - // Do not pass these events downstream to ScrollView since they will be - // registered in ListView's own ScrollResponder.Mixin - onKeyboardWillShow: undefined, - onKeyboardWillHide: undefined, - onKeyboardDidShow: undefined, - onKeyboardDidHide: undefined, - }); - - // TODO(ide): Use function refs so we can compose with the scroll - // component's original ref instead of clobbering it - return React.cloneElement(renderScrollComponent(props), { - ref: SCROLLVIEW_REF, - onContentSizeChange: this._onContentSizeChange, - onLayout: this._onLayout, - }, header, bodyComponents, footer); - }, - - /** - * Private methods - */ - - _onContentSizeChange: function(width, height) { - var contentLength = !this.props.horizontal ? height : width; - this._scrollProperties.contentLength = contentLength; - this.props.onContentSizeChange && this.props.onContentSizeChange(width, height); - }, - - _onLayout: function(event) { - var {width, height} = event.nativeEvent.layout; - var visibleLength = !this.props.horizontal ? height : width; - this._scrollProperties.visibleLength = visibleLength; - this.props.onLayout && this.props.onLayout(event); - }, - - _onScroll: function(e) { - var data = e.nativeEvent; - var isVertical = !this.props.horizontal; - this._scrollProperties.visibleLength = data.layoutMeasurement[isVertical ? 'height' : 'width']; - this._scrollProperties.contentLength = data.contentSize[isVertical ? 'height' : 'width']; - this._scrollProperties.offset = data.contentOffset[isVertical ? 'y' : 'x']; - this._lastScrollEventTime = performanceNow(); - this.props.onScroll && this.props.onScroll(e); - }, - - _callOnEndReachedIfNeeded: function() { - if (!this.props.onEndReached) { - // skip any processing if parent not interested in end of feed callbacks - return; - } - if (this._getDistanceFromEnd() > this.props.onEndReachedThreshold) { - // Scrolled out of the end zone, so it should be able to trigger again. - this._lastEndReachedContentLength = null; - return; - } - if (this.state.curRenderedRowsCount !== this.props.dataSource.getRowCount()) { - // we have more rows to render, so we haven't reached end of feed - return; - } - if (this._scrollProperties.contentLength && - this._scrollProperties.contentLength === this._lastEndReachedContentLength) { - // we have already invoked end of feed callback for this given position in feed - return; - } - this._lastEndReachedContentLength = this._scrollProperties.contentLength; - this.props.onEndReached(); - }, - - _renderMoreRowsIfAvailable: function() { - if (this.state.curRenderedRowsCount !== this.props.dataSource.getRowCount()) { - this.setState((state, props) => { - var curRenderedRowsCount = Math.min( - state.curRenderedRowsCount + props.pageSize, - props.dataSource.getRowCount() - ); - this._prevRenderedRowsCount = state.curRenderedRowsCount; - return {curRenderedRowsCount}; - }, () => { - this._prevRenderedRowsCount = this.state.curRenderedRowsCount; - }); - } - }, - - _getDistanceFromEnd: function() { - var scrollProperties = this._scrollProperties; - var maxLength = Math.max( - scrollProperties.contentLength, - scrollProperties.visibleLength - ); - return maxLength - scrollProperties.visibleLength - scrollProperties.offset; - }, - -}); - -module.exports = THFeedListView;