Show FlatList when searching in More DM Screen (#1546)
* Set searchbars to autoCapitalize='none' * Show flatlist when searching profiles for more dms * Fix lint errors
This commit is contained in:
parent
d8cf2fb76c
commit
d6ee97c0c7
8 changed files with 292 additions and 16 deletions
240
app/components/custom_flat_list.js
Normal file
240
app/components/custom_flat_list.js
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FlatList, Platform, View} from 'react-native';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class CustomFlatList extends PureComponent {
|
||||
static propTypes = {
|
||||
|
||||
/*
|
||||
* The current theme.
|
||||
*/
|
||||
theme: PropTypes.object.isRequired,
|
||||
|
||||
/*
|
||||
* An array of items to be rendered.
|
||||
*/
|
||||
items: PropTypes.array.isRequired,
|
||||
|
||||
/*
|
||||
* A function or React element used to render the items in the list.
|
||||
*/
|
||||
renderItem: PropTypes.func,
|
||||
|
||||
/*
|
||||
* Whether or not to render "No results" when the list contains no items.
|
||||
*/
|
||||
showNoResults: PropTypes.bool,
|
||||
|
||||
/*
|
||||
* A function to get a unique key for each item in the list. If not provided, the id field of the item will be used as the key.
|
||||
*/
|
||||
keyExtractor: PropTypes.func,
|
||||
|
||||
/*
|
||||
* Any extra data needed to render the list. If this value changes, all items of a list will be re-rendered.
|
||||
*/
|
||||
extraData: PropTypes.object,
|
||||
|
||||
/*
|
||||
* A function called when an item in the list is pressed. Receives the item that was pressed as an argument.
|
||||
*/
|
||||
onRowPress: PropTypes.func,
|
||||
|
||||
/*
|
||||
* A function called when the end of the list is reached. This can be triggered before this list end is reached
|
||||
* by changing onListEndReachedThreshold.
|
||||
*/
|
||||
onListEndReached: PropTypes.func,
|
||||
|
||||
/*
|
||||
* How soon before the end of the list onListEndReached should be called.
|
||||
*/
|
||||
onListEndReachedThreshold: PropTypes.number,
|
||||
|
||||
/*
|
||||
* Whether or not to display the loading text.
|
||||
*/
|
||||
loading: PropTypes.bool,
|
||||
|
||||
/*
|
||||
* The text displayed when loading is set to true.
|
||||
*/
|
||||
loadingText: PropTypes.object,
|
||||
|
||||
/*
|
||||
* How many items to render when the list is first rendered.
|
||||
*/
|
||||
initialNumToRender: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
showNoResults: true,
|
||||
keyExtractor: (item) => {
|
||||
return item.id;
|
||||
},
|
||||
onListEndReached: emptyFunction,
|
||||
onListEndReachedThreshold: 50,
|
||||
loadingText: null,
|
||||
initialNumToRender: 10,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
items: props.items,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.items !== this.props.items) {
|
||||
this.setState({
|
||||
items: nextProps.items,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderItem = ({item}) => {
|
||||
const props = {
|
||||
id: item.id,
|
||||
item,
|
||||
onPress: this.props.onRowPress,
|
||||
};
|
||||
|
||||
// Allow passing in a component like UserListRow or ChannelListRow
|
||||
if (this.props.renderItem.prototype.isReactElement) {
|
||||
const RowComponent = this.props.renderItem;
|
||||
return <RowComponent {...props}/>;
|
||||
}
|
||||
|
||||
return this.props.renderItem(props);
|
||||
};
|
||||
|
||||
renderItemSeparator = () => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return <View style={style.separator}/>;
|
||||
};
|
||||
|
||||
renderFooter = () => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
if (!this.props.loading || !this.props.loadingText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.props.items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.loading}>
|
||||
<FormattedText
|
||||
{...this.props.loadingText}
|
||||
style={style.loadingText}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderEmptyList = () => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
if (this.props.loading) {
|
||||
return (
|
||||
<View style={style.searching}>
|
||||
<Loading/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.showNoResults) {
|
||||
return (
|
||||
<View style={style.noResultContainer}>
|
||||
<FormattedText
|
||||
id='mobile.custom_list.no_results'
|
||||
defaultMessage='No Results'
|
||||
style={style.noResultText}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
style={style.listView}
|
||||
initialNumToRender={this.props.initialNumToRender}
|
||||
ItemSeparatorComponent={this.renderItemSeparator}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
ListEmptyComponent={this.renderEmptyList}
|
||||
onEndReached={this.props.onListEndReached}
|
||||
onEndReachedThreshold={this.props.onListEndReachedThreshold}
|
||||
extraData={this.props.extraData}
|
||||
data={this.state.items}
|
||||
keyExtractor={this.props.keyExtractor}
|
||||
renderItem={this.renderItem}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
}),
|
||||
},
|
||||
loading: {
|
||||
height: 70,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
loadingText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.6),
|
||||
},
|
||||
searching: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
},
|
||||
noResultContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
noResultText: {
|
||||
fontSize: 26,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -473,6 +473,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.changeSearchTerm}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={searchTerm}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -270,6 +270,7 @@ class ChannelAddMembers extends PureComponent {
|
|||
onChangeText={this.searchProfiles}
|
||||
onSearchButtonPress={this.searchProfiles}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ class ChannelMembers extends PureComponent {
|
|||
onChangeText={this.searchProfiles}
|
||||
onSearchButtonPress={this.searchProfiles}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ export default class MoreChannels extends PureComponent {
|
|||
onChangeText={this.searchProfiles}
|
||||
onSearchButtonPress={this.searchProfiles}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
|||
import {getGroupDisplayNameFromUserIds} from 'mattermost-redux/utils/channel_utils';
|
||||
import {displayUsername, filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import CustomFlatList from 'app/components/custom_flat_list';
|
||||
import CustomSectionList from 'app/components/custom_section_list';
|
||||
import UserListRow from 'app/components/custom_list/user_list_row';
|
||||
import Loading from 'app/components/loading';
|
||||
|
|
@ -96,13 +97,21 @@ class MoreDirectMessages extends PureComponent {
|
|||
this.setState({profiles, showNoResults: true});
|
||||
} else if (this.state.searching &&
|
||||
nextProps.searchRequest.status === RequestStatus.SUCCESS) {
|
||||
let results = filterProfilesMatchingTerm(nextProps.profiles, this.state.term);
|
||||
const exactMatches = [];
|
||||
let results = filterProfilesMatchingTerm(nextProps.profiles, this.state.term).filter((p) => {
|
||||
if (p.username === this.state.term || p.username.startsWith(this.state.term)) {
|
||||
exactMatches.push(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (this.state.selectedCount > 0) {
|
||||
results = this.removeCurrentUserFromProfiles(results);
|
||||
}
|
||||
|
||||
this.setState({profiles: results, showNoResults: true});
|
||||
this.setState({profiles: [...exactMatches, ...results], showNoResults: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -466,6 +475,39 @@ class MoreDirectMessages extends PureComponent {
|
|||
}),
|
||||
};
|
||||
|
||||
let listComponent;
|
||||
if (term.length) {
|
||||
listComponent = (
|
||||
<CustomFlatList
|
||||
theme={theme}
|
||||
items={this.state.profiles}
|
||||
renderItem={this.renderItem}
|
||||
showNoResults={showNoResults}
|
||||
extraData={this.state.selectedIds}
|
||||
onRowPress={this.handleSelectUser}
|
||||
loading={isLoading}
|
||||
loadingText={loadingText}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
listComponent = (
|
||||
<CustomSectionList
|
||||
theme={theme}
|
||||
items={this.state.profiles}
|
||||
renderItem={this.renderItem}
|
||||
showNoResults={showNoResults}
|
||||
sectionKeyExtractor={this.sectionKeyExtractor}
|
||||
compareItems={this.compareItems}
|
||||
extraData={this.state.selectedIds}
|
||||
onListEndReached={this.loadMoreProfiles}
|
||||
listScrollRenderAheadDistance={50}
|
||||
onRowPress={this.handleSelectUser}
|
||||
loading={isLoading}
|
||||
loadingText={loadingText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
|
|
@ -484,6 +526,7 @@ class MoreDirectMessages extends PureComponent {
|
|||
onChangeText={this.onSearch}
|
||||
onSearchButtonPress={this.onSearch}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={term}
|
||||
/>
|
||||
<SelectedUsers
|
||||
|
|
@ -495,20 +538,7 @@ class MoreDirectMessages extends PureComponent {
|
|||
onRemove={this.handleRemoveUser}
|
||||
/>
|
||||
</View>
|
||||
<CustomSectionList
|
||||
theme={theme}
|
||||
items={this.state.profiles}
|
||||
renderItem={this.renderItem}
|
||||
showNoResults={showNoResults}
|
||||
sectionKeyExtractor={this.sectionKeyExtractor}
|
||||
compareItems={this.compareItems}
|
||||
extraData={this.state.selectedIds}
|
||||
onListEndReached={this.loadMoreProfiles}
|
||||
listScrollRenderAheadDistance={50}
|
||||
onRowPress={this.handleSelectUser}
|
||||
loading={isLoading}
|
||||
loadingText={loadingText}
|
||||
/>
|
||||
{listComponent}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ export default class ExtensionTeam extends PureComponent {
|
|||
tintColorDelete={changeOpacity(defaultTheme.centerChannelColor, 0.3)}
|
||||
titleCancelColor={defaultTheme.centerChannelColor}
|
||||
onChangeText={this.handleSearch}
|
||||
autoCapitalize='none'
|
||||
value={this.state.term}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -221,6 +221,7 @@ export default class ExtensionChannels extends PureComponent {
|
|||
titleCancelColor={theme.linkColor}
|
||||
onChangeText={this.handleSearch}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={this.state.term}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
Loading…
Reference in a new issue