diff --git a/app/screens/search/__snapshots__/modifier.test.js.snap b/app/screens/search/__snapshots__/modifier.test.js.snap
new file mode 100644
index 000000000..7593d5e62
--- /dev/null
+++ b/app/screens/search/__snapshots__/modifier.test.js.snap
@@ -0,0 +1,82 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Search RecentItem should match snapshot and respond to events 1`] = `
+
+
+
+
+
+
+
+
+ channel-name
+
+
+
+ to find posts in specific channels
+
+
+
+
+`;
diff --git a/app/screens/search/__snapshots__/recent_item.test.js.snap b/app/screens/search/__snapshots__/recent_item.test.js.snap
new file mode 100644
index 000000000..f93e66303
--- /dev/null
+++ b/app/screens/search/__snapshots__/recent_item.test.js.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Search RecentItem should match snapshot and respond to events 1`] = `
+
+
+
+ test
+
+
+
+
+
+
+`;
diff --git a/app/screens/search/modifier.js b/app/screens/search/modifier.js
new file mode 100644
index 000000000..bd7a58501
--- /dev/null
+++ b/app/screens/search/modifier.js
@@ -0,0 +1,96 @@
+// 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 {Text, TouchableHighlight, View} from 'react-native';
+import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
+
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export const MODIFIER_LABEL_HEIGHT = 58;
+
+export default class Modifier extends PureComponent {
+ static propTypes = {
+ item: PropTypes.object.isRequired,
+ setModifierValue: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired,
+ };
+
+ handlePress = () => {
+ const {item, setModifierValue} = this.props;
+
+ setModifierValue(item.value);
+ };
+
+ render() {
+ const {item, theme} = this.props;
+ const style = getStyleFromTheme(theme);
+
+ return (
+
+
+
+
+
+
+
+
+ {item.modifier}
+
+
+
+ {item.description}
+
+
+
+
+ );
+ }
+}
+
+const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
+ return {
+ modifierItemContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ height: MODIFIER_LABEL_HEIGHT,
+ },
+ modifierItemWrapper: {
+ flex: 1,
+ flexDirection: 'column',
+ paddingHorizontal: 16,
+ },
+ modifierItemLabelContainer: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ },
+ modifierLabelIconContainer: {
+ alignItems: 'center',
+ marginRight: 5,
+ },
+ modifierLabelIcon: {
+ fontSize: 16,
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ },
+ modifierItemLabel: {
+ fontSize: 14,
+ color: theme.centerChannelColor,
+ },
+ modifierItemDescription: {
+ fontSize: 12,
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ marginTop: 5,
+ },
+ };
+});
diff --git a/app/screens/search/modifier.test.js b/app/screens/search/modifier.test.js
new file mode 100644
index 000000000..3de89a480
--- /dev/null
+++ b/app/screens/search/modifier.test.js
@@ -0,0 +1,34 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {shallow} from 'enzyme';
+
+import Preferences from 'mattermost-redux/constants/preferences';
+
+import Modifier from './modifier';
+
+describe('Search RecentItem', () => {
+ const item = {
+ value: 'in:',
+ modifier: 'channel-name',
+ description: 'to find posts in specific channels',
+ };
+
+ const baseProps = {
+ item,
+ setModifierValue: jest.fn(),
+ theme: Preferences.THEMES.default,
+ };
+
+ test('should match snapshot and respond to events', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.getElement()).toMatchSnapshot();
+ wrapper.find('TouchableHighlight').first().props().onPress();
+ expect(baseProps.setModifierValue).toHaveBeenCalledTimes(1);
+ expect(baseProps.setModifierValue).toHaveBeenCalledWith(item.value);
+ });
+});
diff --git a/app/screens/search/recent_item.js b/app/screens/search/recent_item.js
new file mode 100644
index 000000000..7e0574842
--- /dev/null
+++ b/app/screens/search/recent_item.js
@@ -0,0 +1,88 @@
+// 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 {Text, TouchableHighlight, TouchableOpacity, View} from 'react-native';
+import IonIcon from 'react-native-vector-icons/Ionicons';
+
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export const RECENT_LABEL_HEIGHT = 42;
+
+export default class RecentItem extends PureComponent {
+ static propTypes = {
+ item: PropTypes.object.isRequired,
+ removeSearchTerms: PropTypes.func.isRequired,
+ setRecentValue: PropTypes.func.isRequired,
+ theme: PropTypes.object.isRequired,
+ };
+
+ handlePress = () => {
+ const {item, setRecentValue} = this.props;
+
+ setRecentValue(item);
+ };
+
+ handleRemove = () => {
+ const {item, removeSearchTerms} = this.props;
+ removeSearchTerms(item);
+ };
+
+ render() {
+ const {item, theme} = this.props;
+ const style = getStyleFromTheme(theme);
+
+ return (
+
+
+
+ {item.terms}
+
+
+
+
+
+
+ );
+ }
+}
+
+const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
+ return {
+ recentItemContainer: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ height: RECENT_LABEL_HEIGHT,
+ },
+ recentItemLabel: {
+ color: theme.centerChannelColor,
+ fontSize: 14,
+ height: 20,
+ flex: 1,
+ paddingHorizontal: 16,
+ },
+ recentRemove: {
+ alignItems: 'center',
+ height: RECENT_LABEL_HEIGHT,
+ justifyContent: 'center',
+ width: 50,
+ },
+ };
+});
diff --git a/app/screens/search/recent_item.test.js b/app/screens/search/recent_item.test.js
new file mode 100644
index 000000000..2639bec96
--- /dev/null
+++ b/app/screens/search/recent_item.test.js
@@ -0,0 +1,36 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {shallow} from 'enzyme';
+
+import Preferences from 'mattermost-redux/constants/preferences';
+
+import RecentItem from './recent_item';
+
+describe('Search RecentItem', () => {
+ const item = {
+ terms: 'test',
+ };
+
+ const baseProps = {
+ item,
+ removeSearchTerms: jest.fn(),
+ setRecentValue: jest.fn(),
+ theme: Preferences.THEMES.default,
+ };
+
+ test('should match snapshot and respond to events', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.getElement()).toMatchSnapshot();
+ wrapper.find('TouchableHighlight').first().props().onPress();
+ expect(baseProps.setRecentValue).toHaveBeenCalledTimes(1);
+ expect(baseProps.setRecentValue).toHaveBeenCalledWith(item);
+ wrapper.find('TouchableOpacity').first().props().onPress();
+ expect(baseProps.setRecentValue).toHaveBeenCalledTimes(1);
+ expect(baseProps.setRecentValue).toHaveBeenCalledWith(item);
+ });
+});
diff --git a/app/screens/search/search.js b/app/screens/search/search.js
index 318cfa81b..0985b2515 100644
--- a/app/screens/search/search.js
+++ b/app/screens/search/search.js
@@ -9,11 +9,8 @@ import {
Platform,
SectionList,
Text,
- TouchableHighlight,
- TouchableOpacity,
View,
} from 'react-native';
-import IonIcon from 'react-native-vector-icons/Ionicons';
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
import {RequestStatus} from 'mattermost-redux/constants';
@@ -37,12 +34,12 @@ import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ChannelDisplayName from './channel_display_name';
+import Modifier, {MODIFIER_LABEL_HEIGHT} from './modifier';
+import RecentItem, {RECENT_LABEL_HEIGHT} from './recent_item';
import SearchResultPost from './search_result_post';
const SECTION_HEIGHT = 20;
-const RECENT_LABEL_HEIGHT = 42;
const RECENT_SEPARATOR_HEIGHT = 3;
-const MODIFIER_LABEL_HEIGHT = 58;
const SCROLL_UP_MULTIPLIER = 6;
const SEARCHING = 'searching';
const NO_RESULTS = 'no results';
@@ -97,6 +94,7 @@ export default class Search extends PureComponent {
cursorPosition: 0,
value: props.initialValue,
managedConfig: {},
+ recent: props.recent,
};
}
@@ -118,12 +116,13 @@ export default class Search extends PureComponent {
}, 150);
}
- componentDidUpdate(prevProps) {
- const {searchingStatus: status, recent, enableDateSuggestion} = this.props;
+ componentDidUpdate(prevProps, prevState) {
+ const {searchingStatus: status, enableDateSuggestion} = this.props;
+ const {recent} = this.state;
const {searchingStatus: prevStatus} = prevProps;
const shouldScroll = prevStatus !== status &&
- (status === RequestStatus.SUCCESS || status === RequestStatus.STARTED) &&
- !this.props.isSearchGettingMore && !prevProps.isSearchGettingMore;
+ (status === RequestStatus.SUCCESS || status === RequestStatus.FAILURE) &&
+ !this.props.isSearchGettingMore && !prevProps.isSearchGettingMore && prevState.recent.length === recent.length;
if (this.props.isLandscape !== prevProps.isLandscape) {
this.refs.searchBar.blur();
@@ -131,17 +130,21 @@ export default class Search extends PureComponent {
if (shouldScroll) {
setTimeout(() => {
- const recentLabelsHeight = (recent.length + (Platform.OS === 'ios' ? 1 : 0)) * RECENT_LABEL_HEIGHT;
- const recentSeparatorsHeight = (recent.length + (Platform.OS === 'ios' ? 0 : 2)) * RECENT_SEPARATOR_HEIGHT;
+ const recentLabelsHeight = recent.length * RECENT_LABEL_HEIGHT;
+ const recentSeparatorsHeight = (recent.length - 1) * RECENT_SEPARATOR_HEIGHT;
const modifiersCount = enableDateSuggestion ? 5 : 2;
+ const modifiersHeight = modifiersCount * MODIFIER_LABEL_HEIGHT;
+ const modifiersSeparatorHeight = (modifiersCount - 1) * RECENT_SEPARATOR_HEIGHT;
+ const offset = modifiersHeight + modifiersSeparatorHeight + SECTION_HEIGHT + recentLabelsHeight + recentSeparatorsHeight;
+
+ Keyboard.dismiss();
if (this.refs.list) {
this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle
animated: true,
- offset: SECTION_HEIGHT + (modifiersCount * MODIFIER_LABEL_HEIGHT) +
- recentLabelsHeight + recentSeparatorsHeight,
+ offset,
});
}
- }, 100);
+ }, 250);
}
}
@@ -325,6 +328,14 @@ export default class Search extends PureComponent {
removeSearchTerms = preventDoubleTap((item) => {
const {actions, currentTeamId} = this.props;
+ const recent = [...this.state.recent];
+ const index = recent.indexOf(item);
+
+ if (index !== -1) {
+ recent.splice(index, 1);
+ this.setState({recent});
+ }
+
actions.removeSearchTerms(currentTeamId, item.terms);
});
@@ -343,35 +354,13 @@ export default class Search extends PureComponent {
renderModifiers = ({item}) => {
const {theme} = this.props;
- const style = getStyleFromTheme(theme);
return (
- this.setModifierValue(item.value)}
- >
-
-
-
-
-
-
-
- {item.modifier}
-
-
-
- {item.description}
-
-
-
-
+
);
};
@@ -455,34 +444,14 @@ export default class Search extends PureComponent {
renderRecentItem = ({item}) => {
const {theme} = this.props;
- const style = getStyleFromTheme(theme);
return (
- this.setRecentValue(item)}
- >
-
-
- {item.terms}
-
- this.removeSearchTerms(item)}
- style={style.recentRemove}
- >
-
-
-
-
+
);
};
@@ -537,10 +506,12 @@ export default class Search extends PureComponent {
}
};
- search = (terms, isOrSearch) => {
+ search = (text, isOrSearch) => {
const {actions, currentTeamId, viewArchivedChannels} = this.props;
+ const recent = [...this.state.recent];
+ const terms = text.trim();
- this.handleTextChanged(`${terms.trim()} `);
+ this.handleTextChanged(`${terms} `);
// Trigger onSelectionChanged Manually when submitting
this.handleSelectionChange({
@@ -553,7 +524,7 @@ export default class Search extends PureComponent {
// timezone offset in seconds
const params = {
- terms: terms.trim(),
+ terms,
is_or_search: isOrSearch,
time_zone_offset: this.props.timezoneOffsetInSeconds,
page: 0,
@@ -561,6 +532,12 @@ export default class Search extends PureComponent {
include_deleted_channels: viewArchivedChannels,
};
actions.searchPostsWithParams(currentTeamId, params, true);
+ if (!recent.find((r) => r.terms === terms)) {
+ recent.push({
+ terms,
+ });
+ this.setState({recent});
+ }
};
setModifierValue = preventDoubleTap((modifier) => {
@@ -593,7 +570,6 @@ export default class Search extends PureComponent {
const {
isLandscape,
postIds,
- recent,
searchingStatus,
theme,
isSearchGettingMore,
@@ -602,6 +578,7 @@ export default class Search extends PureComponent {
const {intl} = this.context;
const {
cursorPosition,
+ recent,
value,
} = this.state;
const style = getStyleFromTheme(theme);
@@ -825,38 +802,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
fontSize: 12,
fontWeight: '600',
},
- modifierItemContainer: {
- alignItems: 'center',
- flex: 1,
- flexDirection: 'row',
- height: MODIFIER_LABEL_HEIGHT,
- },
- modifierItemWrapper: {
- flex: 1,
- flexDirection: 'column',
- paddingHorizontal: 16,
- },
- modifierItemLabelContainer: {
- alignItems: 'center',
- flexDirection: 'row',
- },
- modifierLabelIconContainer: {
- alignItems: 'center',
- marginRight: 5,
- },
- modifierLabelIcon: {
- fontSize: 16,
- color: changeOpacity(theme.centerChannelColor, 0.5),
- },
- modifierItemLabel: {
- fontSize: 14,
- color: theme.centerChannelColor,
- },
- modifierItemDescription: {
- fontSize: 12,
- color: changeOpacity(theme.centerChannelColor, 0.5),
- marginTop: 5,
- },
recentItemContainer: {
alignItems: 'center',
flex: 1,