diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js
index fe087cba1..f36b9d35d 100644
--- a/app/actions/views/channel.js
+++ b/app/actions/views/channel.js
@@ -167,13 +167,14 @@ export function loadFilesForPostIfNecessary(postId) {
};
}
-export function loadThreadIfNecessary(rootId) {
+export function loadThreadIfNecessary(rootId, channelId) {
return async (dispatch, getState) => {
const state = getState();
- const {posts} = state.entities.posts;
+ const {posts, postsInChannel} = state.entities.posts;
+ const channelPosts = postsInChannel[channelId];
- if (rootId && !posts[rootId]) {
- getPostThread(rootId)(dispatch, getState);
+ if (rootId && (!posts[rootId] || !channelPosts || !channelPosts[rootId])) {
+ getPostThread(rootId, false)(dispatch, getState);
}
};
}
diff --git a/app/components/search_preview/search_preview.js b/app/components/search_preview/search_preview.js
index 849b9a06f..09eb9d0a1 100644
--- a/app/components/search_preview/search_preview.js
+++ b/app/components/search_preview/search_preview.js
@@ -52,9 +52,17 @@ export default class SearchPreview extends PureComponent {
};
state = {
- showPosts: false
+ showPosts: false,
+ animationEnded: false
};
+ componentWillReceiveProps(nextProps) {
+ const {animationEnded, showPosts} = this.state;
+ if (animationEnded && !showPosts && nextProps.posts.length) {
+ this.setState({showPosts: true});
+ }
+ }
+
handleClose = () => {
this.refs.view.zoomOut().then(() => {
if (this.props.onClose) {
@@ -74,6 +82,7 @@ export default class SearchPreview extends PureComponent {
};
showPostList = () => {
+ this.setState({animationEnded: true});
if (!this.state.showPosts && this.props.posts.length) {
this.setState({showPosts: true});
}
diff --git a/app/screens/channel/channel_post_list/channel_post_list.js b/app/screens/channel/channel_post_list/channel_post_list.js
index 4b2b09f5d..0416aa4cc 100644
--- a/app/screens/channel/channel_post_list/channel_post_list.js
+++ b/app/screens/channel/channel_post_list/channel_post_list.js
@@ -133,7 +133,7 @@ class ChannelPostList extends PureComponent {
const channelId = post.channel_id;
const rootId = (post.root_id || post.id);
- actions.loadThreadIfNecessary(post.root_id);
+ actions.loadThreadIfNecessary(post.root_id, channelId);
actions.selectPost(rootId);
let title;
diff --git a/app/screens/search/search.js b/app/screens/search/search.js
index dc63baebe..5034f4253 100644
--- a/app/screens/search/search.js
+++ b/app/screens/search/search.js
@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import {injectIntl, intlShape} from 'react-intl';
import {
Dimensions,
+ Keyboard,
Platform,
StyleSheet,
Text,
@@ -14,6 +15,7 @@ import {
View
} from 'react-native';
import IonIcon from 'react-native-vector-icons/Ionicons';
+import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
import {General, RequestStatus} from 'mattermost-redux/constants';
@@ -32,6 +34,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
const SECTION_HEIGHT = 20;
const RECENT_LABEL_HEIGHT = 42;
const RECENT_SEPARATOR_HEIGHT = 3;
+const MODIFIER_LABEL_HEIGHT = 58;
const POSTS_PER_PAGE = ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
const SEARCHING = 'searching';
const NO_RESULTS = 'no results';
@@ -99,7 +102,7 @@ class Search extends Component {
setTimeout(() => {
this.refs.list.getWrapperRef().getListRef().scrollToOffset({
animated: true,
- offset: SECTION_HEIGHT + (recentLenght * RECENT_LABEL_HEIGHT) + ((recentLenght - 1) * RECENT_SEPARATOR_HEIGHT)
+ offset: SECTION_HEIGHT + (2 * MODIFIER_LABEL_HEIGHT) + (recentLenght * RECENT_LABEL_HEIGHT) + ((recentLenght + 1) * RECENT_SEPARATOR_HEIGHT)
});
}, 200);
}
@@ -121,7 +124,8 @@ class Search extends Component {
const channel = channels.find((c) => c.id === channelId);
const rootId = (post.root_id || post.id);
- actions.loadThreadIfNecessary(post.root_id);
+ Keyboard.dismiss();
+ actions.loadThreadIfNecessary(rootId, channelId);
actions.selectPost(rootId);
let title;
@@ -149,11 +153,7 @@ class Search extends Component {
}
};
- if (Platform.OS === 'android') {
- navigator.showModal(options);
- } else {
- navigator.push(options);
- }
+ navigator.push(options);
};
handleSelectionChange = (event) => {
@@ -173,6 +173,10 @@ class Search extends Component {
}
};
+ keyModifierExtractor = (item) => {
+ return `modifier-${item.value}`;
+ };
+
keyRecentExtractor = (item) => {
return `recent-${item.terms}`;
};
@@ -205,7 +209,8 @@ class Search extends Component {
const focusedPostId = post.id;
const channelId = post.channel_id;
- actions.getPostThread(focusedPostId);
+ Keyboard.dismiss();
+ actions.getPostThread(focusedPostId, false);
actions.getPostsBefore(channelId, focusedPostId, 0, POSTS_PER_PAGE);
actions.getPostsAfter(channelId, focusedPostId, 0, POSTS_PER_PAGE);
@@ -224,6 +229,40 @@ class Search extends Component {
actions.removeSearchTerms(currentTeamId, item.terms);
};
+ renderModifiers = ({item}) => {
+ const {theme} = this.props;
+ const style = getStyleFromTheme(theme);
+
+ return (
+ preventDoubleTap(this.setModifierValue, this, item.value)}
+ >
+
+
+
+
+
+
+
+ {item.modifier}
+
+
+
+ {item.description}
+
+
+
+
+ );
+ };
+
renderPost = ({item, index}) => {
const {channels, posts, theme} = this.props;
const style = getStyleFromTheme(theme);
@@ -297,15 +336,19 @@ class Search extends Component {
const {title} = section;
const style = getStyleFromTheme(theme);
- return (
-
-
-
- {title}
-
+ if (title) {
+ return (
+
+
+
+ {title}
+
+
-
- );
+ );
+ }
+
+ return ;
};
renderRecentItem = ({item}) => {
@@ -353,6 +396,17 @@ class Search extends Component {
actions.searchPosts(currentTeamId, terms, isOrSearch);
};
+ setModifierValue = (modifier) => {
+ const {value} = this.state;
+ if (!value) {
+ this.handleTextChanged(modifier);
+ } else if (value.endsWith(' ')) {
+ this.handleTextChanged(`${value}${modifier}`);
+ } else {
+ this.handleTextChanged(`${value} ${modifier}`);
+ }
+ };
+
setRecentValue = (recent) => {
const {terms, isOrSearch} = recent;
this.handleTextChanged(terms);
@@ -405,7 +459,28 @@ class Search extends Component {
const {channelName, postId, preview, value} = this.state;
const style = getStyleFromTheme(theme);
- const sections = [];
+ const sections = [{
+ data: [{
+ value: 'from:',
+ modifier: `from:${intl.formatMessage({id: 'mobile.search.from_modifier_title', defaultMessage: 'username'})}`,
+ description: intl.formatMessage({
+ id: 'mobile.search.from_modifier_description',
+ defaultMessage: 'to find posts from specific users'
+ })
+ }, {
+ value: 'in:',
+ modifier: `in:${intl.formatMessage({id: 'mobile.search.in_modifier_title', defaultMessage: 'channel-name'})}`,
+ description: intl.formatMessage({
+ id: 'mobile.search.in_modifier_description',
+ defaultMessage: 'to find posts in specific channels'
+ })
+ }],
+ key: 'modifiers',
+ title: '',
+ renderItem: this.renderModifiers,
+ keyExtractor: this.keyModifierExtractor,
+ ItemSeparatorComponent: this.renderRecentSeparator
+ }];
if (recent.length) {
sections.push({
@@ -520,7 +595,8 @@ class Search extends Component {
style={style.sectionList}
renderSectionHeader={this.renderSectionHeader}
sections={sections}
- keyboardShouldPersistTaps='handled'
+ keyboardShouldPersistTaps='always'
+ keyboardDismissMode='interactive'
stickySectionHeadersEnabled={Platform.OS === 'ios'}
/>
{previewComponent}
@@ -559,6 +635,38 @@ 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,
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 6ace3ca1d..5b2c1a9b1 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -1906,7 +1906,11 @@
"mobile.routes.thread_dm": "Direct Message Thread",
"mobile.routes.user_profile": "Profile",
"mobile.routes.user_profile.send_message": "Send Message",
+ "mobile.search.from_modifier_description": "to find posts from specific users",
+ "mobile.search.from_modifier_title": "username",
"mobile.search.jump": "JUMP",
+ "mobile.search.in_modifier_description": "to find posts in specific channels",
+ "mobile.search.in_modifier_title": "channel-name",
"mobile.search.no_results": "No Results Found",
"mobile.select_team.choose": "Your teams:",
"mobile.select_team.join_open": "Open teams you can join",
diff --git a/yarn.lock b/yarn.lock
index ebc7873cf..032f7197f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3656,7 +3656,7 @@ makeerror@1.0.x:
mattermost-redux@mattermost/mattermost-redux#master:
version "0.0.1"
- resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/09be668d27afd13ea9f3618f4c368c0d0a0db485"
+ resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/1afaf513717108b609c4dc677cf49d474624e0ca"
dependencies:
deep-equal "1.0.1"
harmony-reflect "1.5.1"