Files
mattermost-mobile/app/components/custom_list/index.js
Elias Nahum 8676d063f2 MM-9494 & MM-13888 Tapping execute actions & interactive keyboard dismissal (#2799)
* MM-9494 & MM-13888 Tapping with the keyboard opened executes the action & iOS iteractive keyboard

* Fix tests

* feedback review

* add new line at the end of file

* feedback review and added todo list

* Track interactive dismiss keyboard and set scrollview bounds natively

* Fix snapshots

* Fastlane default to current branch when no BRANCH_TO_BUILD is set

* Set NODE_OPTIONS in ios build script

* Rebind scrollview when channel gets first set of posts

* Keep scrolling momentum on keyboard close

* Update react-native-keyboard-tracking-view

* Fix ScrollView offset with keyboard-tracking

* Fix offset while dragging the keyboard

* Allow action on channel drawer on tablets

* Fix typo

Co-Authored-By: Saturnino Abril <saturnino.abril@gmail.com>

* Fix indentation
2019-05-20 12:04:18 -04:00

280 lines
8.3 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {FlatList, Keyboard, Platform, SectionList, Text, View} from 'react-native';
import {ListTypes} from 'app/constants';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
export const FLATLIST = 'flat';
export const SECTIONLIST = 'section';
const INITIAL_BATCH_TO_RENDER = 15;
const SCROLL_UP_MULTIPLIER = 6;
export default class CustomList extends PureComponent {
static propTypes = {
data: PropTypes.array.isRequired,
extraData: PropTypes.any,
listType: PropTypes.oneOf([FLATLIST, SECTIONLIST]),
loading: PropTypes.bool,
loadingComponent: PropTypes.element,
noResults: PropTypes.element,
refreshing: PropTypes.bool,
onRefresh: PropTypes.func,
onLoadMore: PropTypes.func,
onRowPress: PropTypes.func,
onRowSelect: PropTypes.func,
renderItem: PropTypes.func.isRequired,
selectable: PropTypes.bool,
theme: PropTypes.object.isRequired,
shouldRenderSeparator: PropTypes.bool,
};
static defaultProps = {
listType: FLATLIST,
showNoResults: true,
shouldRenderSeparator: true,
};
constructor(props) {
super(props);
this.contentOffsetY = 0;
this.keyboardDismissProp = Platform.select({
android: {
onScrollBeginDrag: Keyboard.dismiss,
},
ios: {
keyboardDismissMode: 'on-drag',
},
});
this.state = {};
}
handleLayout = (event) => {
const {height} = event.nativeEvent.layout;
this.setState({listHeight: height});
};
handleScroll = (event) => {
const pageOffsetY = event.nativeEvent.contentOffset.y;
if (pageOffsetY > 0) {
const contentHeight = event.nativeEvent.contentSize.height;
const direction = (this.contentOffsetY < pageOffsetY) ?
ListTypes.VISIBILITY_SCROLL_UP :
ListTypes.VISIBILITY_SCROLL_DOWN;
this.contentOffsetY = pageOffsetY;
if (
direction === ListTypes.VISIBILITY_SCROLL_UP &&
(contentHeight - pageOffsetY) < (this.state.listHeight * SCROLL_UP_MULTIPLIER)
) {
this.props.onLoadMore();
}
}
};
keyExtractor = (item) => {
return item.id || item.key || item.value || item;
};
renderEmptyList = () => {
return this.props.noResults || null;
};
renderItem = ({item, index, section}) => {
const {onRowPress, onRowSelect, selectable} = this.props;
const props = {
id: item.id,
item,
selected: item.selected,
selectable,
};
if ('disableSelect' in item) {
props.enabled = !item.disableSelect;
}
if (onRowSelect) {
props.onPress = onRowSelect(section.title, index);
} else {
props.onPress = onRowPress;
}
return this.props.renderItem(props);
};
renderFlatList = () => {
const {data, extraData, theme, onRefresh, refreshing} = this.props;
const style = getStyleFromTheme(theme);
return (
<FlatList
contentContainerStyle={style.container}
data={data}
extraData={extraData}
keyboardShouldPersistTaps='always'
{...this.keyboardDismissProp}
keyExtractor={this.keyExtractor}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
ItemSeparatorComponent={this.renderSeparator}
ListEmptyComponent={this.renderEmptyList}
ListFooterComponent={this.renderFooter}
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
onLayout={this.handleLayout}
onScroll={this.handleScroll}
onRefresh={onRefresh}
refreshing={refreshing}
ref={this.setListRef}
removeClippedSubviews={true}
renderItem={this.renderItem}
scrollEventThrottle={60}
style={style.list}
stickySectionHeadersEnabled={true}
/>
);
};
renderFooter = () => {
const {loading, loadingComponent} = this.props;
if (!loading || !loadingComponent) {
return null;
}
return loadingComponent;
};
renderSectionHeader = ({section}) => {
const style = getStyleFromTheme(this.props.theme);
return (
<View style={style.sectionWrapper}>
<View style={style.sectionContainer}>
<Text style={style.sectionText}>{section.id}</Text>
</View>
</View>
);
};
renderSectionList = () => {
const {data, loading, theme} = this.props;
const style = getStyleFromTheme(theme);
return (
<SectionList
contentContainerStyle={style.container}
extraData={loading}
keyboardShouldPersistTaps='always'
{...this.keyboardDismissProp}
keyExtractor={this.keyExtractor}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
ItemSeparatorComponent={this.renderSeparator}
ListEmptyComponent={this.renderEmptyList}
ListFooterComponent={this.renderFooter}
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
onLayout={this.handleLayout}
onScroll={this.handleScroll}
ref={this.setListRef}
removeClippedSubviews={true}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
scrollEventThrottle={60}
sections={data}
style={style.list}
stickySectionHeadersEnabled={false}
/>
);
};
renderSeparator = () => {
if (!this.props.shouldRenderSeparator) {
return null;
}
const style = getStyleFromTheme(this.props.theme);
return (
<View style={style.separator}/>
);
};
setListRef = (ref) => {
this.list = ref;
};
render() {
const {listType} = this.props;
if (listType === FLATLIST) {
return this.renderFlatList();
}
return this.renderSectionList();
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
list: {
backgroundColor: theme.centerChannelBg,
flex: 1,
...Platform.select({
android: {
marginBottom: 20,
},
}),
},
container: {
flexGrow: 1,
},
separator: {
height: 1,
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
},
listView: {
flex: 1,
backgroundColor: theme.centerChannelBg,
...Platform.select({
android: {
marginBottom: 20,
},
}),
},
loadingText: {
color: changeOpacity(theme.centerChannelColor, 0.6),
},
searching: {
backgroundColor: theme.centerChannelBg,
height: '100%',
position: 'absolute',
width: '100%',
},
sectionContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.07),
paddingLeft: 10,
paddingVertical: 2,
},
sectionWrapper: {
backgroundColor: theme.centerChannelBg,
},
sectionText: {
fontWeight: '600',
color: theme.centerChannelColor,
},
noResultContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
noResultText: {
fontSize: 26,
color: changeOpacity(theme.centerChannelColor, 0.5),
},
};
});