diff --git a/app/components/autocomplete/at_mention/at_mention.js b/app/components/autocomplete/at_mention/at_mention.js index fc4284aca..1aa29e392 100644 --- a/app/components/autocomplete/at_mention/at_mention.js +++ b/app/components/autocomplete/at_mention/at_mention.js @@ -9,6 +9,7 @@ import {RequestStatus} from 'mattermost-redux/constants'; import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete'; import AtMentionItem from 'app/components/autocomplete/at_mention_item'; +import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider'; import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header'; import SpecialMentionItem from 'app/components/autocomplete/special_mention_item'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -26,6 +27,7 @@ export default class AtMention extends PureComponent { isSearch: PropTypes.bool, matchTerm: PropTypes.string, onChangeText: PropTypes.func.isRequired, + onResultCountChange: PropTypes.func.isRequired, outChannel: PropTypes.array, requestStatus: PropTypes.string.isRequired, teamMembers: PropTypes.array, @@ -55,6 +57,9 @@ export default class AtMention extends PureComponent { mentionComplete: false, sections: [] }); + + this.props.onResultCountChange(0); + return; } else if (matchTerm === null) { // if the terms did not change but is null then we don't need to do anything @@ -113,6 +118,8 @@ export default class AtMention extends PureComponent { this.setState({ sections }); + + this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0)); } } @@ -214,6 +221,7 @@ export default class AtMention extends PureComponent { sections={sections} renderItem={this.renderItem} renderSectionHeader={this.renderSectionHeader} + ItemSeparatorComponent={AutocompleteDivider} initialNumToRender={10} /> ); diff --git a/app/components/autocomplete/at_mention_item/at_mention_item.js b/app/components/autocomplete/at_mention_item/at_mention_item.js index 5aa6b2659..e68956549 100644 --- a/app/components/autocomplete/at_mention_item/at_mention_item.js +++ b/app/components/autocomplete/at_mention_item/at_mention_item.js @@ -10,7 +10,7 @@ import { } from 'react-native'; import ProfilePicture from 'app/components/profile_picture'; -import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; +import {makeStyleSheetFromTheme} from 'app/utils/theme'; export default class AtMentionItem extends PureComponent { static propTypes = { @@ -60,19 +60,14 @@ export default class AtMentionItem extends PureComponent { ); } } + const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { row: { paddingVertical: 8, flexDirection: 'row', alignItems: 'center', - backgroundColor: theme.centerChannelBg, - borderTopWidth: 1, - borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), - borderLeftWidth: 1, - borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2), - borderRightWidth: 1, - borderRightColor: changeOpacity(theme.centerChannelColor, 0.2) + backgroundColor: theme.centerChannelBg }, rowPicture: { marginHorizontal: 8, diff --git a/app/components/autocomplete/autocomplete.js b/app/components/autocomplete/autocomplete.js new file mode 100644 index 000000000..f551266e2 --- /dev/null +++ b/app/components/autocomplete/autocomplete.js @@ -0,0 +1,123 @@ +// 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 { + Platform, + View +} from 'react-native'; + +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; + +import AtMention from './at_mention'; +import ChannelMention from './channel_mention'; +import EmojiSuggestion from './emoji_suggestion'; + +export default class Autocomplete extends PureComponent { + static propTypes = { + onChangeText: PropTypes.func.isRequired, + rootId: PropTypes.string, + isSearch: PropTypes.bool, + theme: PropTypes.object.isRequired, + value: PropTypes.string + }; + + static defaultProps = { + isSearch: false + }; + + state = { + cursorPosition: 0, + atMentionCount: 0, + channelMentionCount: 0, + emojiCount: 0 + }; + + handleSelectionChange = (event) => { + this.setState({ + cursorPosition: event.nativeEvent.selection.end + }); + }; + + handleAtMentionCountChange = (atMentionCount) => { + this.setState({atMentionCount}); + }; + + handleChannelMentionCountChange = (channelMentionCount) => { + this.setState({channelMentionCount}); + }; + + handleEmojiCountChange = (emojiCount) => { + this.setState({emojiCount}); + }; + + render() { + const style = getStyleFromTheme(this.props.theme); + + const containerStyle = [style.base]; + if (this.props.isSearch) { + containerStyle.push(style.searchContainer); + } else { + containerStyle.push(style.container); + } + + // We always need to render something, but we only draw the borders when we have results to show + if (this.state.atMentionCount + this.state.channelMentionCount + this.state.emojiCount > 0) { + containerStyle.push(style.borders); + } + + return ( + + + + + + ); + } +} + +const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { + return { + base: { + left: 0, + overflow: 'hidden', + position: 'absolute', + right: 0 + }, + borders: { + borderWidth: 1, + borderColor: changeOpacity(theme.centerChannelColor, 0.2) + }, + container: { + bottom: 0, + maxHeight: 200 + }, + searchContainer: { + elevation: 5, + flex: 1, + maxHeight: 250, + zIndex: 5, + ...Platform.select({ + android: { + top: 47 + }, + ios: { + top: 64 + } + }) + } + }; +}); diff --git a/app/components/autocomplete/autocomplete_divider/autocomplete_divider.js b/app/components/autocomplete/autocomplete_divider/autocomplete_divider.js new file mode 100644 index 000000000..e094d80e1 --- /dev/null +++ b/app/components/autocomplete/autocomplete_divider/autocomplete_divider.js @@ -0,0 +1,32 @@ +// 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 {View} from 'react-native'; + +import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; + +export default class AutocompleteDivider extends PureComponent { + static propTypes = { + theme: PropTypes.object.isRequired + }; + + render() { + const {theme} = this.props; + const style = getStyleFromTheme(theme); + + return ( + + ); + } +} + +const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { + return { + divider: { + height: 1, + backgroundColor: changeOpacity(theme.centerChannelColor, 0.2) + } + }; +}); diff --git a/app/components/autocomplete/autocomplete_divider/index.js b/app/components/autocomplete/autocomplete_divider/index.js new file mode 100644 index 000000000..af4ab5d1b --- /dev/null +++ b/app/components/autocomplete/autocomplete_divider/index.js @@ -0,0 +1,16 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; + +import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; + +import AutocompleteDivider from './autocomplete_divider'; + +function mapStateToProps(state) { + return { + theme: getTheme(state) + }; +} + +export default connect(mapStateToProps)(AutocompleteDivider); diff --git a/app/components/autocomplete/autocomplete_section_header.js b/app/components/autocomplete/autocomplete_section_header.js index 041b87be0..a5c950d93 100644 --- a/app/components/autocomplete/autocomplete_section_header.js +++ b/app/components/autocomplete/autocomplete_section_header.js @@ -40,11 +40,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { paddingLeft: 8, backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), borderTopWidth: 1, - borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), - borderLeftWidth: 1, - borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2), - borderRightWidth: 1, - borderRightColor: changeOpacity(theme.centerChannelColor, 0.2) + borderTopColor: changeOpacity(theme.centerChannelColor, 0.2) }, sectionText: { fontSize: 12, diff --git a/app/components/autocomplete/channel_mention/channel_mention.js b/app/components/autocomplete/channel_mention/channel_mention.js index ad6eb0605..0aa901397 100644 --- a/app/components/autocomplete/channel_mention/channel_mention.js +++ b/app/components/autocomplete/channel_mention/channel_mention.js @@ -8,6 +8,7 @@ import {SectionList} from 'react-native'; import {RequestStatus} from 'mattermost-redux/constants'; import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete'; +import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider'; import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header'; import ChannelMentionItem from 'app/components/autocomplete/channel_mention_item'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -24,6 +25,7 @@ export default class ChannelMention extends PureComponent { myChannels: PropTypes.array, otherChannels: PropTypes.array, onChangeText: PropTypes.func.isRequired, + onResultCountChange: PropTypes.func.isRequired, privateChannels: PropTypes.array, publicChannels: PropTypes.array, requestStatus: PropTypes.string.isRequired, @@ -53,6 +55,9 @@ export default class ChannelMention extends PureComponent { mentionComplete: false, sections: [] }); + + this.props.onResultCountChange(0); + return; } else if (matchTerm === null) { // if the terms did not change but is null then we don't need to do anything @@ -112,6 +117,8 @@ export default class ChannelMention extends PureComponent { this.setState({ sections }); + + this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0)); } } @@ -178,6 +185,7 @@ export default class ChannelMention extends PureComponent { sections={sections} renderItem={this.renderItem} renderSectionHeader={this.renderSectionHeader} + ItemSeparatorComponent={AutocompleteDivider} initialNumToRender={10} /> ); diff --git a/app/components/autocomplete/channel_mention_item/channel_mention_item.js b/app/components/autocomplete/channel_mention_item/channel_mention_item.js index 119ff5ecd..75389c9dc 100644 --- a/app/components/autocomplete/channel_mention_item/channel_mention_item.js +++ b/app/components/autocomplete/channel_mention_item/channel_mention_item.js @@ -8,7 +8,7 @@ import { TouchableOpacity } from 'react-native'; -import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; +import {makeStyleSheetFromTheme} from 'app/utils/theme'; export default class ChannelMentionItem extends PureComponent { static propTypes = { @@ -46,19 +46,14 @@ export default class ChannelMentionItem extends PureComponent { ); } } + const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { row: { padding: 8, flexDirection: 'row', alignItems: 'center', - backgroundColor: theme.centerChannelBg, - borderTopWidth: 1, - borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), - borderLeftWidth: 1, - borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2), - borderRightWidth: 1, - borderRightColor: changeOpacity(theme.centerChannelColor, 0.2) + backgroundColor: theme.centerChannelBg }, rowDisplayName: { fontSize: 13, diff --git a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js index a380724e3..5c8b701b9 100644 --- a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js +++ b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js @@ -10,8 +10,9 @@ import { View } from 'react-native'; +import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider'; import Emoji from 'app/components/emoji'; -import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; +import {makeStyleSheetFromTheme} from 'app/utils/theme'; const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i; const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i; @@ -25,6 +26,7 @@ export default class EmojiSuggestion extends Component { emojis: PropTypes.array.isRequired, theme: PropTypes.object.isRequired, onChangeText: PropTypes.func.isRequired, + onResultCountChange: PropTypes.func.isRequired, rootId: PropTypes.string, value: PropTypes.string }; @@ -49,6 +51,9 @@ export default class EmojiSuggestion extends Component { matchTerm: null, emojiComplete: false }); + + this.props.onResultCountChange(0); + return; } @@ -69,9 +74,11 @@ export default class EmojiSuggestion extends Component { } this.setState({ - active: data.length, + active: data.length > 0, dataSource: data }); + + this.props.onResultCountChange(data.length); } completeSuggestion = (emoji) => { @@ -137,6 +144,7 @@ export default class EmojiSuggestion extends Component { data={this.state.dataSource} keyExtractor={this.keyExtractor} renderItem={this.renderItem} + ItemSeparatorComponent={AutocompleteDivider} pageSize={10} initialListSize={10} /> @@ -162,13 +170,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 8, - backgroundColor: theme.centerChannelBg, - borderTopWidth: 1, - borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), - borderLeftWidth: 1, - borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2), - borderRightWidth: 1, - borderRightColor: changeOpacity(theme.centerChannelColor, 0.2) + backgroundColor: theme.centerChannelBg } }; }); diff --git a/app/components/autocomplete/index.js b/app/components/autocomplete/index.js index 1b2877272..a0ebdca7c 100644 --- a/app/components/autocomplete/index.js +++ b/app/components/autocomplete/index.js @@ -1,89 +1,16 @@ // 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 { - Platform, - StyleSheet, - View -} from 'react-native'; +import {connect} from 'react-redux'; -import AtMention from './at_mention'; -import ChannelMention from './channel_mention'; -import EmojiSuggestion from './emoji_suggestion'; +import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -export default class Autocomplete extends PureComponent { - static propTypes = { - onChangeText: PropTypes.func.isRequired, - rootId: PropTypes.string, - isSearch: PropTypes.bool, - value: PropTypes.string +import Autocomplete from './autocomplete'; + +function mapStateToProps(state) { + return { + theme: getTheme(state) }; - - static defaultProps = { - isSearch: false - }; - - state = { - cursorPosition: 0 - }; - - handleSelectionChange = (event) => { - this.setState({ - cursorPosition: event.nativeEvent.selection.end - }); - }; - - render() { - const searchContainer = this.props.isSearch ? style.searchContainer : null; - const container = this.props.isSearch ? null : style.container; - return ( - - - - - - - - ); - } } -const style = StyleSheet.create({ - container: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - maxHeight: 200, - overflow: 'hidden' - }, - searchContainer: { - elevation: 5, - flex: 1, - left: 0, - maxHeight: 250, - overflow: 'hidden', - position: 'absolute', - right: 0, - zIndex: 5, - ...Platform.select({ - android: { - top: 47 - }, - ios: { - top: 64 - } - }) - } -}); +export default connect(mapStateToProps, null, null, {withRef: true})(Autocomplete); diff --git a/app/components/autocomplete/special_mention_item.js b/app/components/autocomplete/special_mention_item.js index ebba3ab91..c74be2afa 100644 --- a/app/components/autocomplete/special_mention_item.js +++ b/app/components/autocomplete/special_mention_item.js @@ -70,13 +70,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { paddingVertical: 8, flexDirection: 'row', alignItems: 'center', - backgroundColor: theme.centerChannelBg, - borderTopWidth: 1, - borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), - borderLeftWidth: 1, - borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2), - borderRightWidth: 1, - borderRightColor: changeOpacity(theme.centerChannelColor, 0.2) + backgroundColor: theme.centerChannelBg }, rowPicture: { marginHorizontal: 8, diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js index b46479d88..58881b004 100644 --- a/app/components/post_textbox/post_textbox.js +++ b/app/components/post_textbox/post_textbox.js @@ -199,7 +199,7 @@ class PostTextbox extends PureComponent { this.props.actions.handlePostDraftSelectionChanged(this.props.channelId, cursorPosition); } - this.autocomplete.handleSelectionChange(event); + this.autocomplete.getWrappedInstance().handleSelectionChange(event); }; handleSendMessage = () => { diff --git a/app/screens/search/search.js b/app/screens/search/search.js index 271f6cd60..75b7a716f 100644 --- a/app/screens/search/search.js +++ b/app/screens/search/search.js @@ -150,7 +150,7 @@ class Search extends PureComponent { handleSelectionChange = (event) => { if (this.autocomplete) { - this.autocomplete.handleSelectionChange(event); + this.autocomplete.getWrappedInstance().handleSelectionChange(event); } };