diff --git a/app/components/autocomplete/at_mention/at_mention.js b/app/components/autocomplete/at_mention/at_mention.js index 9f35032fa8..1e8aa87d54 100644 --- a/app/components/autocomplete/at_mention/at_mention.js +++ b/app/components/autocomplete/at_mention/at_mention.js @@ -9,7 +9,6 @@ import {RequestStatus} from '@mm-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 GroupMentionItem from 'app/components/autocomplete/at_mention_group/at_mention_group'; @@ -67,7 +66,7 @@ export default class AtMention extends PureComponent { mentionComplete: false, sections: [], }); - + this.props.onResultCountChange(0); return; } @@ -208,12 +207,14 @@ export default class AtMention extends PureComponent { }; renderSectionHeader = ({section}) => { + const isFirstSection = section.id === this.state.sections[0].id; return ( ); }; @@ -270,7 +271,6 @@ export default class AtMention extends PureComponent { sections={sections} renderItem={this.renderItem} renderSectionHeader={this.renderSectionHeader} - ItemSeparatorComponent={AutocompleteDivider} initialNumToRender={10} nestedScrollEnabled={nestedScrollEnabled} /> @@ -282,6 +282,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { listView: { backgroundColor: theme.centerChannelBg, + borderRadius: 4, }, }; }); 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 0def8606be..1ef9938bf0 100644 --- a/app/components/autocomplete/at_mention_item/at_mention_item.js +++ b/app/components/autocomplete/at_mention_item/at_mention_item.js @@ -12,7 +12,7 @@ import ProfilePicture from 'app/components/profile_picture'; import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import {BotTag, GuestTag} from 'app/components/tag'; import TouchableWithFeedback from 'app/components/touchable_with_feedback'; -import {makeStyleSheetFromTheme} from 'app/utils/theme'; +import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; import FormattedText from 'app/components/formatted_text'; export default class AtMentionItem extends PureComponent { @@ -28,6 +28,7 @@ export default class AtMentionItem extends PureComponent { theme: PropTypes.object.isRequired, isLandscape: PropTypes.bool.isRequired, isCurrentUser: PropTypes.bool.isRequired, + showFullName: PropTypes.string, }; static defaultProps = { @@ -40,11 +41,24 @@ export default class AtMentionItem extends PureComponent { onPress(username); }; + renderNameBlock = () => { + let name = ''; + const {showFullName, firstName, lastName, nickname} = this.props; + const hasNickname = nickname.length > 0; + + if (showFullName === 'true') { + name += `${firstName} ${lastName} `; + } + + if (hasNickname) { + name += `(${nickname})`; + } + + return name; + } + render() { const { - firstName, - lastName, - nickname, userId, username, theme, @@ -55,46 +69,52 @@ export default class AtMentionItem extends PureComponent { } = this.props; const style = getStyleFromTheme(theme); - const hasFullName = firstName.length > 0 && lastName.length > 0; - const hasNickname = nickname.length > 0; + const name = this.renderNameBlock(); return ( - - + + + + + + + {name} + {isCurrentUser && + } + + + {` @${username}`} + - {`@${username}`} - - - {hasFullName && {' - '}} - - {hasFullName && `${firstName} ${lastName}`} - {hasNickname && ` (${nickname}) `} - {isCurrentUser && - } - ); } @@ -103,24 +123,28 @@ export default class AtMentionItem extends PureComponent { const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { row: { + height: 40, paddingVertical: 8, + paddingTop: 4, + paddingHorizontal: 16, flexDirection: 'row', alignItems: 'center', - backgroundColor: theme.centerChannelBg, }, rowPicture: { - marginHorizontal: 8, - width: 20, + marginRight: 10, + marginLeft: 2, + width: 24, alignItems: 'center', justifyContent: 'center', }, - rowUsername: { - fontSize: 13, + rowFullname: { + fontSize: 15, color: theme.centerChannelColor, }, - rowFullname: { + rowUsername: { color: theme.centerChannelColor, - opacity: 0.6, + fontSize: 15, + opacity: 0.56, flex: 1, }, }; diff --git a/app/components/autocomplete/at_mention_item/index.js b/app/components/autocomplete/at_mention_item/index.js index ce297025da..6fe8e84cc3 100644 --- a/app/components/autocomplete/at_mention_item/index.js +++ b/app/components/autocomplete/at_mention_item/index.js @@ -6,6 +6,7 @@ import {connect} from 'react-redux'; import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users'; import {getTheme} from '@mm-redux/selectors/entities/preferences'; +import {getConfig} from '@mm-redux/selectors/entities/general'; import AtMentionItem from './at_mention_item'; @@ -14,12 +15,13 @@ import {isGuest} from 'app/utils/users'; function mapStateToProps(state, ownProps) { const user = getUser(state, ownProps.userId); - + const config = getConfig(state); return { firstName: user.first_name, lastName: user.last_name, nickname: user.nickname, username: user.username, + showFullName: config.ShowFullName, isBot: Boolean(user.is_bot), isGuest: isGuest(user), theme: getTheme(state), diff --git a/app/components/autocomplete/autocomplete.js b/app/components/autocomplete/autocomplete.js index c05ca56a52..3b202cfc62 100644 --- a/app/components/autocomplete/autocomplete.js +++ b/app/components/autocomplete/autocomplete.js @@ -36,8 +36,9 @@ export default class Autocomplete extends PureComponent { valueEvent: PropTypes.string, cursorPositionEvent: PropTypes.string, nestedScrollEnabled: PropTypes.bool, - expandDown: PropTypes.bool, onVisible: PropTypes.func, + offsetY: PropTypes.number, + onKeyboardOffsetChanged: PropTypes.func, style: ViewPropTypes.style, }; @@ -47,6 +48,8 @@ export default class Autocomplete extends PureComponent { enableDateSuggestion: false, nestedScrollEnabled: false, onVisible: emptyFunction, + onKeyboardOffsetChanged: emptyFunction, + offsetY: 80, }; static getDerivedStateFromProps(props, state) { @@ -149,10 +152,12 @@ export default class Autocomplete extends PureComponent { keyboardDidShow = (e) => { const {height} = e.endCoordinates; this.setState({keyboardOffset: height}); + this.props.onKeyboardOffsetChanged(height); }; keyboardDidHide = () => { this.setState({keyboardOffset: 0}); + this.props.onKeyboardOffsetChanged(0); }; maxListHeight() { @@ -166,42 +171,37 @@ export default class Autocomplete extends PureComponent { offset = 90; } - maxHeight = this.props.deviceHeight - offset - this.state.keyboardOffset; + maxHeight = (this.props.deviceHeight / 2) - offset; } return maxHeight; } render() { - const {theme, isSearch, expandDown} = this.props; + const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state; + const {theme, isSearch, offsetY} = this.props; const style = getStyleFromTheme(theme); - + const maxListHeight = this.maxListHeight(); const wrapperStyles = []; - const containerStyles = []; + const containerStyles = [style.borders]; + + if (Platform.OS === 'ios') { + wrapperStyles.push(style.shadow); + } + if (isSearch) { - wrapperStyles.push(style.base, style.searchContainer); - containerStyles.push(style.content); + wrapperStyles.push(style.base, style.searchContainer, {height: maxListHeight}); } else { - const containerStyle = expandDown ? style.containerExpandDown : style.container; + const containerStyle = {bottom: offsetY}; containerStyles.push(style.base, containerStyle); } - // We always need to render something, but we only draw the borders when we have results to show - const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state; - if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) { - if (this.props.isSearch) { - wrapperStyles.push(style.bordersSearch); - } else { - containerStyles.push(style.borders); - } + // Hide when there are no active autocompletes + if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount === 0) { + wrapperStyles.push(style.hidden); + containerStyles.push(style.hidden); } - if (this.props.style) { - containerStyles.push(this.props.style); - } - - const maxListHeight = this.maxListHeight(); - return ( { return { base: { - left: 0, - overflow: 'hidden', + left: 8, position: 'absolute', - right: 0, + right: 8, }, borders: { borderWidth: 1, borderColor: changeOpacity(theme.centerChannelColor, 0.2), - borderBottomWidth: 0, + overflow: 'hidden', + borderRadius: 4, }, - bordersSearch: { - borderWidth: 1, - borderColor: changeOpacity(theme.centerChannelColor, 0.2), - }, - container: { - bottom: 0, - }, - containerExpandDown: { - top: 0, - }, - content: { - flex: 1, + hidden: { + display: 'none', }, searchContainer: { - flex: 1, ...Platform.select({ android: { - top: 46, + top: 42, }, ios: { - top: 44, + top: 55, }, }), }, + shadow: { + shadowColor: '#000', + shadowOpacity: 0.12, + shadowRadius: 8, + shadowOffset: { + width: 0, + height: 8, + }, + }, }; }); diff --git a/app/components/autocomplete/autocomplete_divider/autocomplete_divider.js b/app/components/autocomplete/autocomplete_divider/autocomplete_divider.js deleted file mode 100644 index e8ddcccc0b..0000000000 --- a/app/components/autocomplete/autocomplete_divider/autocomplete_divider.js +++ /dev/null @@ -1,32 +0,0 @@ -// 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 {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 deleted file mode 100644 index 8e7799ef4f..0000000000 --- a/app/components/autocomplete/autocomplete_divider/index.js +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {connect} from 'react-redux'; - -import {getTheme} from '@mm-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 0fb6f6d806..d86d057676 100644 --- a/app/components/autocomplete/autocomplete_section_header.js +++ b/app/components/autocomplete/autocomplete_section_header.js @@ -16,6 +16,7 @@ export default class AutocompleteSectionHeader extends PureComponent { loading: PropTypes.bool, theme: PropTypes.object.isRequired, isLandscape: PropTypes.bool.isRequired, + isFirstSection: PropTypes.bool, }; static defaultProps = { @@ -23,12 +24,17 @@ export default class AutocompleteSectionHeader extends PureComponent { }; render() { - const {defaultMessage, id, loading, theme, isLandscape} = this.props; + const {defaultMessage, id, loading, theme, isLandscape, isFirstSection} = this.props; const style = getStyleFromTheme(theme); + const sectionStyles = [style.section, padding(isLandscape)]; + + if (!isFirstSection) { + sectionStyles.push(style.borderTop); + } return ( - + { return { - section: { - justifyContent: 'center', - paddingHorizontal: 8, - backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), + borderTop: { borderTopWidth: 1, borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), + }, + section: { + justifyContent: 'center', + position: 'relative', + top: -1, flexDirection: 'row', }, sectionText: { fontSize: 12, - color: changeOpacity(theme.centerChannelColor, 0.7), - paddingVertical: 7, + fontWeight: '600', + textTransform: 'uppercase', + color: changeOpacity(theme.centerChannelColor, 0.56), + paddingTop: 16, + paddingBottom: 8, + paddingHorizontal: 16, flex: 1, }, sectionWrapper: { diff --git a/app/components/autocomplete/channel_mention/channel_mention.js b/app/components/autocomplete/channel_mention/channel_mention.js index e72509682a..8a9ab874af 100644 --- a/app/components/autocomplete/channel_mention/channel_mention.js +++ b/app/components/autocomplete/channel_mention/channel_mention.js @@ -184,6 +184,7 @@ export default class ChannelMention extends PureComponent { }; renderSectionHeader = ({section}) => { + const isFirstSection = section.id === this.state.sections[0].id; return ( ); }; @@ -235,6 +237,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { listView: { backgroundColor: theme.centerChannelBg, + borderRadius: 4, }, }; }); 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 4619f5ff49..d3f9731e6e 100644 --- a/app/components/autocomplete/channel_mention_item/channel_mention_item.js +++ b/app/components/autocomplete/channel_mention_item/channel_mention_item.js @@ -5,14 +5,15 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { Text, + View, } from 'react-native'; +import VectorIcon from 'app/components/vector_icon.js'; import {General} from '@mm-redux/constants'; -import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider'; import {BotTag, GuestTag} from 'app/components/tag'; import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import TouchableWithFeedback from 'app/components/touchable_with_feedback'; -import {makeStyleSheetFromTheme} from 'app/utils/theme'; +import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; export default class ChannelMentionItem extends PureComponent { static propTypes = { @@ -49,8 +50,12 @@ export default class ChannelMentionItem extends PureComponent { } = this.props; const style = getStyleFromTheme(theme); - + let iconName = 'public'; let component; + if (type === General.PRIVATE_CHANNEL) { + iconName = 'private'; + } + if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) { if (!displayName) { return null; @@ -79,11 +84,19 @@ export default class ChannelMentionItem extends PureComponent { - {displayName} - {` (~${name})`} + + + {displayName} + {` ~${name}`} + ); } @@ -91,7 +104,6 @@ export default class ChannelMentionItem extends PureComponent { return ( {component} - ); } @@ -99,19 +111,26 @@ export default class ChannelMentionItem extends PureComponent { const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { + icon: { + fontSize: 18, + marginRight: 11, + color: theme.centerChannelColor, + opacity: 0.56, + }, row: { - padding: 8, + paddingHorizontal: 16, + height: 40, flexDirection: 'row', alignItems: 'center', - backgroundColor: theme.centerChannelBg, }, rowDisplayName: { - fontSize: 13, + fontSize: 15, color: theme.centerChannelColor, }, rowName: { + fontSize: 15, color: theme.centerChannelColor, - opacity: 0.6, + opacity: 0.56, }, }; }); diff --git a/app/components/autocomplete/date_suggestion/date_suggestion.js b/app/components/autocomplete/date_suggestion/date_suggestion.js index e7b56c5b7c..925151fad8 100644 --- a/app/components/autocomplete/date_suggestion/date_suggestion.js +++ b/app/components/autocomplete/date_suggestion/date_suggestion.js @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, {PureComponent} from 'react'; -import {Dimensions, Platform, StyleSheet} from 'react-native'; +import {Dimensions, Platform, View} from 'react-native'; import PropTypes from 'prop-types'; import {CalendarList, LocaleConfig} from 'react-native-calendars'; import {intlShape} from 'react-intl'; @@ -10,7 +10,7 @@ import {intlShape} from 'react-intl'; import {memoizeResult} from '@mm-redux/utils/helpers'; import {DATE_MENTION_SEARCH_REGEX, ALL_SEARCH_FLAGS_REGEX} from 'app/constants/autocomplete'; -import {changeOpacity} from 'app/utils/theme'; +import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; export default class DateSuggestion extends PureComponent { static propTypes = { @@ -37,6 +37,7 @@ export default class DateSuggestion extends PureComponent { this.state = { mentionComplete: false, + active: false, sections: [], }; } @@ -45,18 +46,37 @@ export default class DateSuggestion extends PureComponent { this.setCalendarLocale(); } + onLayout = (e) => { + this.setState({calendarWidth: e.nativeEvent.layout.width}); + }; + componentDidUpdate(prevProps) { - const {locale, matchTerm} = this.props; + const {locale, matchTerm, enableDateSuggestion} = this.props; + const {mentionComplete} = this.state; if ((matchTerm !== prevProps.matchTerm && matchTerm === null) || this.state.mentionComplete) { this.resetComponent(); } + if (matchTerm === null || mentionComplete || !enableDateSuggestion) { + this.setCalendarActive(false); + return; + } + + if (matchTerm !== null) { + this.props.onResultCountChange(1); + this.setCalendarActive(true); + } + if (locale !== prevProps.locale) { this.setCalendarLocale(); } } + setCalendarActive = (active) => { + this.setState({active}); + } + completeMention = (day) => { const mention = day.dateString; const {cursorPosition, onChangeText, value} = this.props; @@ -118,10 +138,11 @@ export default class DateSuggestion extends PureComponent { }; render() { - const {mentionComplete} = this.state; - const {matchTerm, enableDateSuggestion, theme} = this.props; + const {active, calendarWidth} = this.state; + const {theme} = this.props; + const styles = getStyleFromTheme(theme); - if (matchTerm === null || mentionComplete || !enableDateSuggestion) { + if (!active) { // If we are not in an active state or the mention has been completed return null so nothing is rendered // other components are not blocked. return null; @@ -131,22 +152,29 @@ export default class DateSuggestion extends PureComponent { const calendarStyle = calendarTheme(theme); return ( - + > + {Boolean(calendarWidth) && + + } + ); } } @@ -197,9 +225,13 @@ const calendarTheme = memoizeResult((theme) => ({ }, })); -const styles = StyleSheet.create({ - calList: { - height: 1700, - paddingTop: 5, - }, +const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { + return { + calList: { + paddingTop: 5, + width: '100%', + borderRadius: 4, + backgroundColor: theme.centerChannelBg, + }, + }; }); diff --git a/app/components/autocomplete/emoji_suggestion/__snapshots__/emoji_suggestion.test.js.snap b/app/components/autocomplete/emoji_suggestion/__snapshots__/emoji_suggestion.test.js.snap index 81e7c8d8d0..06b6ff3db3 100644 --- a/app/components/autocomplete/emoji_suggestion/__snapshots__/emoji_suggestion.test.js.snap +++ b/app/components/autocomplete/emoji_suggestion/__snapshots__/emoji_suggestion.test.js.snap @@ -4,7 +4,6 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 1`] = `n exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = ` { const style = getStyleFromTheme(this.props.theme); - + const completeSuggestion = () => this.completeSuggestion(item); return ( this.completeSuggestion(item)} - style={style.row} - type={'opacity'} + onPress={completeSuggestion} + underlayColor={changeOpacity(this.props.theme.buttonBg, 0.08)} + type={'native'} > - - + + + + + {`:${item}:`} - {`:${item}:`} ); }; @@ -199,12 +200,14 @@ export default class EmojiSuggestion extends PureComponent { return values; }, []); const data = results.sort(sorter); + this.props.onResultCountChange(data.length); this.setState({ active: data.length > 0, dataSource: data, }); }, 100); } else { + this.props.onResultCountChange(emojis.length); this.setState({ active: emojis.length > 0, dataSource: emojis.sort(sorter), @@ -229,7 +232,6 @@ export default class EmojiSuggestion extends PureComponent { data={this.state.dataSource} keyExtractor={this.keyExtractor} renderItem={this.renderItem} - ItemSeparatorComponent={AutocompleteDivider} pageSize={10} initialListSize={10} nestedScrollEnabled={nestedScrollEnabled} @@ -244,7 +246,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { marginRight: 5, }, emojiName: { - fontSize: 13, + fontSize: 15, color: theme.centerChannelColor, }, emojiText: { @@ -252,14 +254,17 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { fontWeight: 'bold', }, listView: { + paddingTop: 16, backgroundColor: theme.centerChannelBg, + borderRadius: 4, }, row: { - height: 40, flexDirection: 'row', alignItems: 'center', - paddingHorizontal: 8, - backgroundColor: theme.centerChannelBg, + overflow: 'hidden', + paddingBottom: 8, + paddingHorizontal: 16, + height: 40, }, }; }); diff --git a/app/components/autocomplete/slash_suggestion/slash_suggestion.js b/app/components/autocomplete/slash_suggestion/slash_suggestion.js index 91a23780f6..de79bd61ea 100644 --- a/app/components/autocomplete/slash_suggestion/slash_suggestion.js +++ b/app/components/autocomplete/slash_suggestion/slash_suggestion.js @@ -8,7 +8,6 @@ import { Platform, } from 'react-native'; -import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; import SlashSuggestionItem from './slash_suggestion_item'; @@ -211,7 +210,6 @@ export default class SlashSuggestion extends PureComponent { data={this.state.dataSource} keyExtractor={this.keyExtractor} renderItem={this.renderItem} - ItemSeparatorComponent={AutocompleteDivider} pageSize={10} initialListSize={10} nestedScrollEnabled={nestedScrollEnabled} @@ -225,6 +223,8 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { listView: { flex: 1, backgroundColor: theme.centerChannelBg, + paddingTop: 8, + borderRadius: 4, }, }; }); diff --git a/app/components/autocomplete/slash_suggestion/slash_suggestion_item.js b/app/components/autocomplete/slash_suggestion/slash_suggestion_item.js index 00adc7ad8d..98d3a466df 100644 --- a/app/components/autocomplete/slash_suggestion/slash_suggestion_item.js +++ b/app/components/autocomplete/slash_suggestion/slash_suggestion_item.js @@ -3,11 +3,12 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {Text} from 'react-native'; +import {Image, Text, View} from 'react-native'; -import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; -import TouchableWithFeedback from 'app/components/touchable_with_feedback'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing'; +import TouchableWithFeedback from '@components/touchable_with_feedback'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import slashIcon from '@assets/images/autocomplete/slash_command.png'; export default class SlashSuggestionItem extends PureComponent { static propTypes = { @@ -39,11 +40,30 @@ export default class SlashSuggestionItem extends PureComponent { return ( - {`${suggestion} ${hint}`} - {description} + + + + + + {`${suggestion.substring(1)} ${hint}`} + + {description} + + + ); } @@ -51,32 +71,38 @@ export default class SlashSuggestionItem extends PureComponent { const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { - row: { - paddingVertical: 8, + icon: { + fontSize: 24, + backgroundColor: changeOpacity(theme.centerChannelColor, 0.08), + width: 35, + height: 35, + marginRight: 12, + borderRadius: 4, justifyContent: 'center', - paddingHorizontal: 8, - backgroundColor: theme.centerChannelBg, - borderLeftWidth: 1, - borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2), - borderRightWidth: 1, - borderRightColor: changeOpacity(theme.centerChannelColor, 0.2), + alignItems: 'center', + marginTop: 8, }, - rowDisplayName: { - fontSize: 13, - color: theme.centerChannelColor, + iconColor: { + tintColor: theme.centerChannelColor, }, - rowName: { - color: theme.centerChannelColor, - opacity: 0.6, + container: { + flexDirection: 'row', + alignItems: 'center', + paddingBottom: 8, + paddingHorizontal: 16, + overflow: 'hidden', + }, + suggestionContainer: { + flex: 1, }, suggestionDescription: { - fontSize: 11, - color: changeOpacity(theme.centerChannelColor, 0.6), + fontSize: 12, + color: changeOpacity(theme.centerChannelColor, 0.56), }, suggestionName: { - fontSize: 13, + fontSize: 15, color: theme.centerChannelColor, - marginBottom: 5, + marginBottom: 4, }, }; }); diff --git a/app/components/autocomplete/special_mention_item.js b/app/components/autocomplete/special_mention_item.js index 090afa6bf9..4054b49439 100644 --- a/app/components/autocomplete/special_mention_item.js +++ b/app/components/autocomplete/special_mention_item.js @@ -42,25 +42,27 @@ export default class SpecialMentionItem extends PureComponent { return ( - - + + + + + + {`@${completeHandle}`} + {' - '} + + - - {`@${completeHandle}`} - {' - '} - - ); } @@ -68,10 +70,11 @@ export default class SpecialMentionItem extends PureComponent { const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { return { row: { + height: 40, paddingVertical: 8, + paddingHorizontal: 9, flexDirection: 'row', alignItems: 'center', - backgroundColor: theme.centerChannelBg, }, rowPicture: { marginHorizontal: 8, @@ -81,10 +84,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { }, rowIcon: { color: changeOpacity(theme.centerChannelColor, 0.7), - fontSize: 14, + fontSize: 18, }, rowUsername: { - fontSize: 13, + fontSize: 15, color: theme.centerChannelColor, }, rowFullname: { diff --git a/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap b/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap index 72f9370698..3b312dc24c 100644 --- a/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap +++ b/app/components/edit_channel_info/__snapshots__/edit_channel_info.test.js.snap @@ -309,18 +309,28 @@ exports[`EditChannelInfo should match snapshot 1`] = ` - - + `; diff --git a/app/components/edit_channel_info/edit_channel_info.js b/app/components/edit_channel_info/edit_channel_info.js index edbecc2f72..4ea8a9ccfd 100644 --- a/app/components/edit_channel_info/edit_channel_info.js +++ b/app/components/edit_channel_info/edit_channel_info.js @@ -9,11 +9,10 @@ import { View, } from 'react-native'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scrollview'; -import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view'; import {General} from '@mm-redux/constants'; -import Autocomplete from 'app/components/autocomplete'; +import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete'; import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; import Loading from 'app/components/loading'; @@ -71,6 +70,7 @@ export default class EditChannelInfo extends PureComponent { this.state = { keyboardVisible: false, + keyboardPosition: 0, }; } @@ -174,6 +174,10 @@ export default class EditChannelInfo extends PureComponent { this.setState({keyboardVisible: false}); } + onKeyboardOffsetChanged = (keyboardPosition) => { + this.setState({keyboardPosition}); + } + onHeaderFocus = () => { if (this.state.keyboardVisible) { this.scrollHeaderToTop(); @@ -201,8 +205,13 @@ export default class EditChannelInfo extends PureComponent { error, saving, } = this.props; - const {keyboardVisible} = this.state; - + const {keyboardVisible, keyboardPosition} = this.state; + const bottomStyle = { + bottom: Platform.select({ + ios: keyboardPosition, + android: 0, + }), + }; const style = getStyleSheet(theme); const displayHeaderOnly = channelType === General.DM_CHANNEL || @@ -354,16 +363,18 @@ export default class EditChannelInfo extends PureComponent { - + - + ); } @@ -375,6 +386,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { position: undefined, }, autocompleteContainer: { + position: 'absolute', + width: '100%', + flex: 1, justifyContent: 'flex-end', }, container: { diff --git a/app/components/post_draft/draft_input/draft_input.js b/app/components/post_draft/draft_input/draft_input.js index 18481386c3..3836b22dcd 100644 --- a/app/components/post_draft/draft_input/draft_input.js +++ b/app/components/post_draft/draft_input/draft_input.js @@ -415,16 +415,6 @@ export default class DraftInput extends PureComponent { theme={theme} registerTypingAnimation={registerTypingAnimation} /> - {Platform.OS === 'android' && - - } + {Platform.OS === 'android' && + + } ); } @@ -513,4 +513,4 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { borderTopColor: changeOpacity(theme.centerChannelColor, 0.20), }, }; -}); \ No newline at end of file +}); diff --git a/app/screens/channel/channel.ios.js b/app/screens/channel/channel.ios.js index 31ad4c01fc..40089f8a7f 100644 --- a/app/screens/channel/channel.ios.js +++ b/app/screens/channel/channel.ios.js @@ -64,15 +64,6 @@ export default class ChannelIOS extends ChannelBase { updateNativeScrollView={this.updateNativeScrollView} registerTypingAnimation={this.registerTypingAnimation} /> - - - {LocalConfig.EnableMobileClientUpgrade && } ); @@ -102,6 +93,15 @@ export default class ChannelIOS extends ChannelBase { valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE} /> } + + + ); diff --git a/app/screens/edit_post/__snapshots__/edit_post.test.js.snap b/app/screens/edit_post/__snapshots__/edit_post.test.js.snap index 51ce60621e..c0d001ee22 100644 --- a/app/screens/edit_post/__snapshots__/edit_post.test.js.snap +++ b/app/screens/edit_post/__snapshots__/edit_post.test.js.snap @@ -87,6 +87,7 @@ exports[`EditPost should match snapshot 1`] = ` cursorPosition={0} maxHeight={200} nestedScrollEnabled={true} + offsetY={8} onChangeText={[Function]} onVisible={[Function]} style={ diff --git a/app/screens/edit_post/edit_post.js b/app/screens/edit_post/edit_post.js index 6974514864..6c7687ea81 100644 --- a/app/screens/edit_post/edit_post.js +++ b/app/screens/edit_post/edit_post.js @@ -281,6 +281,7 @@ export default class EditPost extends PureComponent { value={message} nestedScrollEnabled={true} onVisible={this.onAutocompleteVisible} + offsetY={8} style={style.autocomplete} /> diff --git a/app/screens/thread/__snapshots__/thread.ios.test.js.snap b/app/screens/thread/__snapshots__/thread.ios.test.js.snap index 1ae9d69f86..39823cb2ba 100644 --- a/app/screens/thread/__snapshots__/thread.ios.test.js.snap +++ b/app/screens/thread/__snapshots__/thread.ios.test.js.snap @@ -48,18 +48,6 @@ exports[`thread should match snapshot, has root post 1`] = ` scrollViewNativeID="threadPostList" /> - - - + + + `; @@ -96,6 +96,18 @@ exports[`thread should match snapshot, no root post, loading 1`] = ` style={Object {}} /> + + + `; @@ -166,5 +178,17 @@ exports[`thread should match snapshot, render footer 3`] = ` style={Object {}} /> + + + `; diff --git a/app/screens/thread/thread.ios.js b/app/screens/thread/thread.ios.js index 82b4974876..f683134134 100644 --- a/app/screens/thread/thread.ios.js +++ b/app/screens/thread/thread.ios.js @@ -56,16 +56,6 @@ export default class ThreadIOS extends ThreadBase { scrollViewNativeID={SCROLLVIEW_NATIVE_ID} /> - - - ); @@ -101,6 +91,16 @@ export default class ThreadIOS extends ThreadBase { {content} {postDraft} + + + ); } diff --git a/assets/base/images/autocomplete/slash_command.png b/assets/base/images/autocomplete/slash_command.png new file mode 100644 index 0000000000..a6111db5b4 Binary files /dev/null and b/assets/base/images/autocomplete/slash_command.png differ diff --git a/assets/base/images/autocomplete/slash_command@2x.png b/assets/base/images/autocomplete/slash_command@2x.png new file mode 100644 index 0000000000..fc26022205 Binary files /dev/null and b/assets/base/images/autocomplete/slash_command@2x.png differ diff --git a/assets/base/images/autocomplete/slash_command@3x.png b/assets/base/images/autocomplete/slash_command@3x.png new file mode 100644 index 0000000000..a0252a593f Binary files /dev/null and b/assets/base/images/autocomplete/slash_command@3x.png differ