diff --git a/app/components/inverted_flat_list/index.js b/app/components/inverted_flat_list/index.js
index d6e379efd..f07f8fc7d 100644
--- a/app/components/inverted_flat_list/index.js
+++ b/app/components/inverted_flat_list/index.js
@@ -3,7 +3,9 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {FlatList, Platform, RefreshControl, ScrollView, StyleSheet, View} from 'react-native';
+import {FlatList, Platform, ScrollView, StyleSheet, View} from 'react-native';
+
+import RefreshList from 'app/components/refresh_list';
import VirtualList from './virtual_list';
@@ -64,7 +66,7 @@ export default class InvertibleFlatList extends PureComponent {
+
+
+
+
+
);
body = ({messageComponent});
} else if (message.length) {
diff --git a/app/components/post_body_additional_content/index.js b/app/components/post_body_additional_content/index.js
index 4229087a3..1921d14c0 100644
--- a/app/components/post_body_additional_content/index.js
+++ b/app/components/post_body_additional_content/index.js
@@ -41,7 +41,6 @@ function makeMapStateToProps() {
const link = getFirstLink(ownProps.message);
return {
- ...ownProps,
...getDimensions(state),
config,
link,
diff --git a/app/components/post_body_additional_content/post_body_additional_content.js b/app/components/post_body_additional_content/post_body_additional_content.js
index b92e1c0cf..79e228a79 100644
--- a/app/components/post_body_additional_content/post_body_additional_content.js
+++ b/app/components/post_body_additional_content/post_body_additional_content.js
@@ -98,7 +98,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
return null;
}
- const {link, openGraphData, showLinkPreviews} = this.props;
+ const {link, openGraphData, showLinkPreviews, theme} = this.props;
const attachments = this.getSlackAttachment();
if (attachments) {
return attachments;
@@ -109,6 +109,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
);
}
@@ -244,22 +245,22 @@ export default class PostBodyAdditionalContent extends PureComponent {
};
render() {
- const {link, openGraphData} = this.props;
+ const {link, openGraphData, postProps} = this.props;
const {linkLoaded, linkLoadError} = this.state;
- let isYouTube = false;
- let isImage = false;
- let isOpenGraph = false;
+ const {attachments} = postProps;
- if (link) {
- isYouTube = isYoutubeLink(link);
- isImage = isImageLink(link);
- isOpenGraph = Boolean(openGraphData && openGraphData.description);
+ if (!link && !attachments) {
+ return null;
+ }
- if (((isImage && !isOpenGraph) || isYouTube) && !linkLoadError) {
- const embed = this.generateToggleableEmbed(isImage, isYouTube);
- if (embed && (linkLoaded || isYouTube)) {
- return embed;
- }
+ const isYouTube = isYoutubeLink(link);
+ const isImage = isImageLink(link);
+ const isOpenGraph = Boolean(openGraphData && openGraphData.description);
+
+ if (((isImage && !isOpenGraph) || isYouTube) && !linkLoadError) {
+ const embed = this.generateToggleableEmbed(isImage, isYouTube);
+ if (embed && (linkLoaded || isYouTube)) {
+ return embed;
}
}
diff --git a/app/components/post_list/index.js b/app/components/post_list/index.js
index 52601c54c..d9b3d664f 100644
--- a/app/components/post_list/index.js
+++ b/app/components/post_list/index.js
@@ -9,9 +9,8 @@ import {getTheme} from 'app/selectors/preferences';
import PostList from './post_list';
-function mapStateToProps(state, ownProps) {
+function mapStateToProps(state) {
return {
- ...ownProps,
theme: getTheme(state)
};
}
diff --git a/app/components/post_list/load_more_posts.js b/app/components/post_list/load_more_posts.js
index 354194fe7..693904d60 100644
--- a/app/components/post_list/load_more_posts.js
+++ b/app/components/post_list/load_more_posts.js
@@ -2,6 +2,7 @@
// See License.txt for license information.
import React, {PureComponent} from 'react';
+import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {
TouchableOpacity,
@@ -13,7 +14,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
import FormattedText from 'app/components/formatted_text';
-export default class LoadMorePosts extends PureComponent {
+class LoadMorePosts extends PureComponent {
static propTypes = {
loading: PropTypes.bool.isRequired,
loadMore: PropTypes.func,
@@ -72,3 +73,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
}
};
});
+
+function mapStateToProps(state, ownProps) {
+ return {
+ loading: state.views.channel.loadingPosts[ownProps.channelId] || false
+ };
+}
+
+export default connect(mapStateToProps)(LoadMorePosts);
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index 7cd51c661..e068db993 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -28,7 +28,6 @@ export default class PostList extends PureComponent {
channelId: PropTypes.string,
currentUserId: PropTypes.string,
indicateNewMessages: PropTypes.bool,
- isLoadingMore: PropTypes.bool,
isSearchResult: PropTypes.bool,
lastViewedAt: PropTypes.number,
loadMore: PropTypes.func,
@@ -36,17 +35,26 @@ export default class PostList extends PureComponent {
onPostPress: PropTypes.func,
onRefresh: PropTypes.func,
posts: PropTypes.array.isRequired,
- refreshing: PropTypes.bool,
renderReplies: PropTypes.bool,
showLoadMore: PropTypes.bool,
shouldRenderReplyButton: PropTypes.bool,
theme: PropTypes.object.isRequired
};
+ static defaultProps = {
+ loadMore: () => true
+ };
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.channelId !== this.props.channelId && this.refs.list) {
+ // When switching channels make sure we start from the bottom
+ this.refs.list.scrollToOffset({y: 0, animated: false});
+ }
+ }
+
getPostsWithDates = () => {
const {posts, indicateNewMessages, currentUserId, lastViewedAt, showLoadMore} = this.props;
const list = addDatesToPostList(posts, {indicateNewMessages, currentUserId, lastViewedAt});
-
if (showLoadMore) {
return [...list, LOAD_MORE_POSTS];
}
@@ -58,18 +66,8 @@ export default class PostList extends PureComponent {
if (item instanceof Date) {
return item.getTime();
}
- if (item === General.START_OF_NEW_MESSAGES || item === LOAD_MORE_POSTS) {
- return item;
- }
- return item.id;
- };
-
- loadMorePosts = () => {
- const {loadMore, isLoadingMore} = this.props;
- if (typeof loadMore === 'function' && !isLoadingMore) {
- loadMore();
- }
+ return item.id || item;
};
onRefresh = () => {
@@ -89,9 +87,10 @@ export default class PostList extends PureComponent {
};
renderChannelIntro = () => {
- const {channelId, navigator, refreshing, showLoadMore} = this.props;
+ const {channelId, navigator, showLoadMore} = this.props;
- if (channelId && !showLoadMore && !refreshing) {
+ // FIXME: Only show the channel intro when we are at the very start of the channel
+ if (channelId && !showLoadMore) {
return (
@@ -124,10 +123,7 @@ export default class PostList extends PureComponent {
}
if (item === LOAD_MORE_POSTS && this.props.showLoadMore) {
return (
-
+
);
}
@@ -149,7 +145,8 @@ export default class PostList extends PureComponent {
return (
+ );
+ }
+}
diff --git a/app/components/retry_bar_indicator/index.js b/app/components/retry_bar_indicator/index.js
new file mode 100644
index 000000000..b6c0c247f
--- /dev/null
+++ b/app/components/retry_bar_indicator/index.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {RequestStatus} from 'mattermost-redux/constants';
+import {getConnection} from 'app/selectors/device';
+
+import RetryBarIndicator from './retry_bar_indicator';
+
+function mapStateToProps(state) {
+ const {websocket: websocketRequest} = state.requests.general;
+ const networkOnline = getConnection(state);
+ const webSocketOnline = websocketRequest.status === RequestStatus.SUCCESS;
+
+ let failed = state.views.channel.retryFailed && webSocketOnline;
+ if (!networkOnline) {
+ failed = false;
+ }
+
+ return {
+ failed
+ };
+}
+export default connect(mapStateToProps)(RetryBarIndicator);
diff --git a/app/components/retry_bar_indicator/retry_bar_indicator.js b/app/components/retry_bar_indicator/retry_bar_indicator.js
new file mode 100644
index 000000000..31f632d94
--- /dev/null
+++ b/app/components/retry_bar_indicator/retry_bar_indicator.js
@@ -0,0 +1,65 @@
+// 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 {
+ Animated,
+ StyleSheet
+} from 'react-native';
+import FormattedText from 'app/components/formatted_text';
+
+const {View: AnimatedView} = Animated;
+
+export default class RetryBarIndicator extends PureComponent {
+ static propTypes = {
+ failed: PropTypes.bool
+ };
+
+ state = {
+ retryMessageHeight: new Animated.Value(0)
+ };
+
+ componentWillReceiveProps(nextProps) {
+ if (this.props.failed !== nextProps.failed) {
+ this.toggleRetryMessage(nextProps.failed);
+ }
+ }
+
+ toggleRetryMessage = (show = true) => {
+ const value = show ? 38 : 0;
+ Animated.timing(this.state.retryMessageHeight, {
+ toValue: value,
+ duration: 350
+ }).start();
+ };
+
+ render() {
+ const {retryMessageHeight} = this.state;
+ const refreshIndicatorDimensions = {
+ height: retryMessageHeight
+ };
+
+ return (
+
+
+
+ );
+ }
+}
+
+const style = StyleSheet.create({
+ refreshIndicator: {
+ alignItems: 'center',
+ backgroundColor: '#fb8000',
+ flexDirection: 'row',
+ paddingHorizontal: 10,
+ position: 'absolute',
+ top: 0,
+ overflow: 'hidden',
+ width: '100%'
+ }
+});
diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js
index 34d126317..1c238e7c3 100644
--- a/app/screens/channel/channel.js
+++ b/app/screens/channel/channel.js
@@ -10,7 +10,6 @@ import {
View
} from 'react-native';
-import {RequestStatus} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import ChannelDrawer from 'app/components/channel_drawer';
@@ -49,8 +48,7 @@ class Channel extends PureComponent {
currentTeamId: PropTypes.string,
currentChannelId: PropTypes.string,
theme: PropTypes.object.isRequired,
- webSocketRequest: PropTypes.object,
- channelsRequestStatus: PropTypes.string
+ channelsRequestFailed: PropTypes.bool
};
componentWillMount() {
@@ -64,14 +62,6 @@ class Channel extends PureComponent {
}
componentDidMount() {
- const {startPeriodicStatusUpdates} = this.props.actions;
-
- try {
- startPeriodicStatusUpdates();
- } catch (error) {
- // We don't care about the error
- }
-
// Mark current channel as read when opening app while logged in
if (this.props.currentChannelId) {
this.props.actions.markChannelAsRead(this.props.currentChannelId);
@@ -126,19 +116,21 @@ class Channel extends PureComponent {
});
handleConnectionChange = (isConnected) => {
- const {actions, webSocketRequest} = this.props;
- const {closeWebSocket, connection, initWebSocket} = actions;
+ const {actions} = this.props;
+ const {
+ closeWebSocket,
+ connection,
+ initWebSocket,
+ startPeriodicStatusUpdates,
+ stopPeriodicStatusUpdates
+ } = actions;
if (isConnected) {
- if (!webSocketRequest || webSocketRequest.status === RequestStatus.NOT_STARTED) {
- try {
- initWebSocket(Platform.OS);
- } catch (error) {
- // We don't care if it fails
- }
- }
+ initWebSocket(Platform.OS);
+ startPeriodicStatusUpdates();
} else {
closeWebSocket(true);
+ stopPeriodicStatusUpdates();
}
connection(isConnected);
};
@@ -168,7 +160,7 @@ class Channel extends PureComponent {
render() {
const {
- channelsRequestStatus,
+ channelsRequestFailed,
currentChannelId,
intl,
navigator,
@@ -178,7 +170,7 @@ class Channel extends PureComponent {
const style = getStyleFromTheme(theme);
if (!currentChannelId) {
- if (channelsRequestStatus === RequestStatus.FAILURE) {
+ if (channelsRequestFailed) {
return (
-
+
-
-
+
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 4bda2925e..a156315db 100644
--- a/app/screens/channel/channel_post_list/channel_post_list.js
+++ b/app/screens/channel/channel_post_list/channel_post_list.js
@@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {injectIntl, intlShape} from 'react-intl';
import {
- Animated,
Platform,
StyleSheet,
View
@@ -13,11 +12,9 @@ import {
import {General} from 'mattermost-redux/constants';
-import FormattedText from 'app/components/formatted_text';
import PostList from 'app/components/post_list';
import PostListRetry from 'app/components/post_list_retry';
-
-const {View: AnimatedView} = Animated;
+import RetryBarIndicator from 'app/components/retry_bar_indicator';
class ChannelPostList extends PureComponent {
static propTypes = {
@@ -30,13 +27,11 @@ class ChannelPostList extends PureComponent {
}).isRequired,
channelDisplayName: PropTypes.string,
channelId: PropTypes.string.isRequired,
- channelIsRefreshing: PropTypes.bool,
channelRefreshingFailed: PropTypes.bool,
channelType: PropTypes.string,
currentUserId: PropTypes.string,
intl: intlShape.isRequired,
lastViewedAt: PropTypes.number,
- loadingPosts: PropTypes.bool,
navigator: PropTypes.object,
posts: PropTypes.array.isRequired,
postVisibility: PropTypes.number,
@@ -45,8 +40,6 @@ class ChannelPostList extends PureComponent {
};
static defaultProps = {
- posts: [],
- loadingPosts: false,
postVisibility: 15
};
@@ -54,29 +47,18 @@ class ChannelPostList extends PureComponent {
super(props);
this.state = {
- retryMessageHeight: new Animated.Value(0),
visiblePosts: this.getVisiblePosts(props),
showLoadMore: props.posts.length >= props.postVisibility
};
}
- componentDidMount() {
- this.mounted = true;
- }
-
componentWillReceiveProps(nextProps) {
- const {channelRefreshingFailed: nextChannelRefreshingFailed, posts: nextPosts} = nextProps;
+ const {posts: nextPosts} = nextProps;
- if (nextChannelRefreshingFailed && nextPosts.length) {
- this.toggleRetryMessage();
- } else if (!nextChannelRefreshingFailed || !nextPosts.length) {
- this.toggleRetryMessage(false);
- }
-
- const showLoadMore = nextProps.posts.length >= nextProps.postVisibility;
+ const showLoadMore = nextPosts.length >= nextProps.postVisibility;
let visiblePosts = this.state.visiblePosts;
- if (nextProps.posts !== this.props.posts || nextProps.postVisibility !== this.props.postVisibility) {
+ if (nextPosts !== this.props.posts || nextProps.postVisibility !== this.props.postVisibility) {
visiblePosts = this.getVisiblePosts(nextProps);
}
@@ -86,22 +68,10 @@ class ChannelPostList extends PureComponent {
});
}
- componentWillUnmount() {
- this.mounted = false;
- }
-
getVisiblePosts = (props) => {
return props.posts.slice(0, props.postVisibility);
};
- toggleRetryMessage = (show = true) => {
- const value = show ? 38 : 0;
- Animated.timing(this.state.retryMessageHeight, {
- toValue: value,
- duration: 350
- }).start();
- };
-
goToThread = (post) => {
const {actions, channelId, channelDisplayName, channelType, intl, navigator, theme} = this.props;
const rootId = (post.root_id || post.id);
@@ -156,18 +126,15 @@ class ChannelPostList extends PureComponent {
const {
actions,
channelId,
- channelIsRefreshing,
channelRefreshingFailed,
currentUserId,
lastViewedAt,
- loadingPosts,
navigator,
posts,
theme
} = this.props;
const {
- retryMessageHeight,
showLoadMore,
visiblePosts
} = this.state;
@@ -185,7 +152,6 @@ class ChannelPostList extends PureComponent {
);
}
- const refreshIndicatorDimensions = {
- height: retryMessageHeight
- };
-
return (
{component}
-
-
-
+
);
}
@@ -222,16 +177,6 @@ class ChannelPostList extends PureComponent {
const style = StyleSheet.create({
container: {
flex: 1
- },
- refreshIndicator: {
- alignItems: 'center',
- backgroundColor: '#fb8000',
- flexDirection: 'row',
- paddingHorizontal: 10,
- position: 'absolute',
- top: 0,
- overflow: 'hidden',
- width: '100%'
}
});
diff --git a/app/screens/channel/channel_post_list/index.js b/app/screens/channel/channel_post_list/index.js
index f8a5d4f7f..811536d2a 100644
--- a/app/screens/channel/channel_post_list/index.js
+++ b/app/screens/channel/channel_post_list/index.js
@@ -7,10 +7,9 @@ import {connect} from 'react-redux';
import {selectPost} from 'mattermost-redux/actions/posts';
import {RequestStatus} from 'mattermost-redux/constants';
import {makeGetPostsInChannel} from 'mattermost-redux/selectors/entities/posts';
-import {getMyCurrentChannelMembership, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
+import {getCurrentChannelId, getMyCurrentChannelMembership, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
-import {getConnection} from 'app/selectors/device';
import {getTheme} from 'app/selectors/preferences';
import ChannelPostList from './channel_post_list';
@@ -19,45 +18,23 @@ function makeMapStateToProps() {
const getChannel = makeGetChannel();
const getPostsInChannel = makeGetPostsInChannel();
- return function mapStateToProps(state, ownProps) {
- const channelId = ownProps.channelId;
- const {getPosts, getPostsRetryAttempts, getPostsSince, getPostsSinceRetryAttempts} = state.requests.posts;
+ return function mapStateToProps(state) {
+ const channelId = getCurrentChannelId(state);
const posts = getPostsInChannel(state, channelId) || [];
- const {websocket: websocketRequest} = state.requests.general;
- const networkOnline = getConnection(state);
- const webSocketOnline = websocketRequest.status === RequestStatus.SUCCESS;
-
- let getPostsStatus;
- if (getPostsRetryAttempts > 0) {
- getPostsStatus = getPosts.status;
- } else if (getPostsSinceRetryAttempts > 1) {
- getPostsStatus = getPostsSince.status;
- }
-
- let channelIsRefreshing = state.views.channel.refreshing;
- let channelRefreshingFailed = getPostsStatus === RequestStatus.FAILURE && webSocketOnline;
- if (!networkOnline) {
- channelIsRefreshing = false;
- channelRefreshingFailed = false;
- }
-
+ const channelRefreshingFailed = state.views.channel.retryFailed;
const channel = getChannel(state, {id: channelId}) || {};
return {
channelId,
- channelIsRefreshing,
channelRefreshingFailed,
currentUserId: getCurrentUserId(state),
channelType: channel.type,
channelDisplayName: channel.display_name,
posts,
postVisibility: state.views.channel.postVisibility[channelId],
- loadingPosts: state.views.channel.loadingPosts[channelId],
lastViewedAt: getMyCurrentChannelMembership(state).last_viewed_at,
- networkOnline,
totalMessageCount: channel.total_msg_count,
- theme: getTheme(state),
- ...ownProps
+ theme: getTheme(state)
};
};
}
@@ -74,4 +51,54 @@ function mapDispatchToProps(dispatch) {
};
}
-export default connect(makeMapStateToProps, mapDispatchToProps)(ChannelPostList);
+function areStatesEqual(next, prev) {
+ const nextChannelId = next.entities.channels.currentChannelId;
+ const prevChannelId = prev.entities.channels.currentChannelId;
+ if (prevChannelId !== nextChannelId) {
+ return false;
+ }
+ const nextVisible = next.views.channel.postVisibility[nextChannelId];
+ const prevVisible = prev.views.channel.postVisibility[nextChannelId];
+ const nextPostsIds = next.entities.posts.postsInChannel[nextChannelId];
+ const prevPostsIds = prev.entities.posts.postsInChannel[nextChannelId];
+
+ // When we don't have posts and we failed to get the post after tha max retry attempts
+ const prevRetryFailed = prev.views.channel.retryFailed;
+ const nextRetryFailed = next.views.channel.retryFailed;
+ if (prevRetryFailed !== nextRetryFailed && nextRetryFailed && !nextPostsIds) {
+ return false;
+ }
+
+ // if we don't have post and visibility is not set we don't need to re-render
+ if (!nextPostsIds || !nextVisible || !prevVisible) {
+ return true;
+ }
+
+ const nextVisiblePostsIds = nextPostsIds.slice(0, nextVisible);
+ const prevVisiblePostsIds = prevPostsIds ? prevPostsIds.slice(0, prevVisible) : [];
+
+ // if we have a different amount of post we should re-render
+ if (nextVisiblePostsIds.length !== prevVisiblePostsIds.length) {
+ return false;
+ }
+
+ const {status: nextStatus} = next.requests.posts.getPosts;
+ const {status: prevStatus} = prev.requests.posts.getPosts;
+
+ // if we are requesting post for the first time we should re-render
+ if (prevStatus === RequestStatus.STARTED && nextStatus === RequestStatus.SUCCESS &&
+ nextPostsIds.length !== prevPostsIds.length) {
+ return false;
+ }
+
+ // if at least one post id changed we need to re-render
+ for (let i = 0; i <= nextVisiblePostsIds.length; i++) {
+ if (nextVisiblePostsIds[i] && nextVisiblePostsIds[i] !== prevVisiblePostsIds[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export default connect(makeMapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(ChannelPostList);
diff --git a/app/screens/channel/channel_search_button.js b/app/screens/channel/channel_search_button/channel_search_button.js
similarity index 70%
rename from app/screens/channel/channel_search_button.js
rename to app/screens/channel/channel_search_button/channel_search_button.js
index ec9615eaa..6a1cb3506 100644
--- a/app/screens/channel/channel_search_button.js
+++ b/app/screens/channel/channel_search_button/channel_search_button.js
@@ -6,8 +6,6 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {bindActionCreators} from 'redux';
-import {connect} from 'react-redux';
import {
StyleSheet,
TouchableOpacity,
@@ -15,15 +13,11 @@ import {
} from 'react-native';
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
-import {clearSearch} from 'mattermost-redux/actions/search';
-
-import {handlePostDraftChanged} from 'app/actions/views/channel';
-import {getTheme} from 'app/selectors/preferences';
-import {preventDoubleTap} from 'app/utils/tap';
+import {wrapWithPreventDoubleTap} from 'app/utils/tap';
const SEARCH = 'search';
-class ChannelSearchButton extends PureComponent {
+export default class ChannelSearchButton extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
clearSearch: PropTypes.func.isRequired,
@@ -33,7 +27,7 @@ class ChannelSearchButton extends PureComponent {
theme: PropTypes.object
};
- handlePress = async () => {
+ handlePress = wrapWithPreventDoubleTap(async () => {
const {actions, navigator, theme} = this.props;
await actions.clearSearch();
@@ -52,7 +46,7 @@ class ChannelSearchButton extends PureComponent {
theme
}
});
- };
+ });
render() {
const {
@@ -61,7 +55,7 @@ class ChannelSearchButton extends PureComponent {
return (
preventDoubleTap(this.handlePress, this)}
+ onPress={this.handlePress}
style={style.container}
>
@@ -89,21 +83,3 @@ const style = StyleSheet.create({
zIndex: 30
}
});
-
-function mapStateToProps(state, ownProps) {
- return {
- theme: getTheme(state),
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- clearSearch,
- handlePostDraftChanged
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChannelSearchButton);
diff --git a/app/screens/channel/channel_search_button/index.js b/app/screens/channel/channel_search_button/index.js
new file mode 100644
index 000000000..7e8c1033e
--- /dev/null
+++ b/app/screens/channel/channel_search_button/index.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {clearSearch} from 'mattermost-redux/actions/search';
+
+import {handlePostDraftChanged} from 'app/actions/views/channel';
+
+import ChannelSearchButton from './channel_search_button';
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ clearSearch,
+ handlePostDraftChanged
+ }, dispatch)
+ };
+}
+
+export default connect(null, mapDispatchToProps)(ChannelSearchButton);
diff --git a/app/screens/channel/channel_title.js b/app/screens/channel/channel_title.js
deleted file mode 100644
index 6a35fca16..000000000
--- a/app/screens/channel/channel_title.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {connect} from 'react-redux';
-import {
- Text,
- TouchableOpacity,
- View
-} from 'react-native';
-import Icon from 'react-native-vector-icons/FontAwesome';
-
-import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
-
-import {getTheme} from 'app/selectors/preferences';
-
-function ChannelTitle(props) {
- const channelName = props.displayName || props.currentChannel.display_name;
- let icon;
- if (channelName) {
- icon = (
-
- );
- }
-
- return (
-
-
-
- {channelName}
-
- {icon}
-
-
- );
-}
-
-ChannelTitle.propTypes = {
- currentChannel: PropTypes.object,
- displayName: PropTypes.string,
- onPress: PropTypes.func,
- theme: PropTypes.object
-};
-
-ChannelTitle.defaultProps = {
- currentChannel: {},
- displayName: null,
- theme: {}
-};
-
-function mapStateToProps(state) {
- return {
- currentChannel: getCurrentChannel(state) || {},
- displayName: state.views.channel.displayName,
- theme: getTheme(state)
- };
-}
-
-export default connect(mapStateToProps)(ChannelTitle);
diff --git a/app/screens/channel/channel_title/channel_title.js b/app/screens/channel/channel_title/channel_title.js
new file mode 100644
index 000000000..c2c2526cd
--- /dev/null
+++ b/app/screens/channel/channel_title/channel_title.js
@@ -0,0 +1,88 @@
+// 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 {
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import Icon from 'react-native-vector-icons/FontAwesome';
+
+import {makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class ChannelTitle extends PureComponent {
+ static propTypes = {
+ currentChannelName: PropTypes.string,
+ displayName: PropTypes.string,
+ onPress: PropTypes.func,
+ theme: PropTypes.object
+ };
+
+ static defaultProps = {
+ currentChannel: {},
+ displayName: null,
+ theme: {}
+ };
+
+ render() {
+ const {currentChannelName, displayName, onPress, theme} = this.props;
+ const channelName = displayName || currentChannelName;
+ const style = getStyle(theme);
+ let icon;
+ if (channelName) {
+ icon = (
+
+ );
+ }
+
+ return (
+
+
+
+ {channelName}
+
+ {icon}
+
+
+ );
+ }
+}
+
+const getStyle = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ flexDirection: 'row'
+ },
+ wrapper: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'center',
+ marginHorizontal: 15
+ },
+ icon: {
+ color: theme.sidebarHeaderTextColor,
+ marginHorizontal: 6
+ },
+ text: {
+ color: theme.sidebarHeaderTextColor,
+ fontSize: 17,
+ fontWeight: 'bold'
+ }
+ };
+});
diff --git a/app/screens/channel/channel_title/index.js b/app/screens/channel/channel_title/index.js
new file mode 100644
index 000000000..beed6c218
--- /dev/null
+++ b/app/screens/channel/channel_title/index.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
+
+import {getTheme} from 'app/selectors/preferences';
+
+import ChannelTitle from './channel_title';
+
+function mapStateToProps(state) {
+ const currentChannel = getCurrentChannel(state);
+
+ return {
+ currentChannelName: currentChannel ? currentChannel.display_name : '',
+ displayName: state.views.channel.displayName,
+ theme: getTheme(state)
+ };
+}
+
+export default connect(mapStateToProps)(ChannelTitle);
diff --git a/app/screens/channel/index.js b/app/screens/channel/index.js
index 39eed76b2..3a14c106e 100644
--- a/app/screens/channel/index.js
+++ b/app/screens/channel/index.js
@@ -4,6 +4,13 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
+import {viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
+import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'mattermost-redux/actions/users';
+import {init as initWebSocket, close as closeWebSocket} from 'mattermost-redux/actions/websocket';
+import {RequestStatus} from 'mattermost-redux/constants';
+import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
+import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
+
import {
loadChannelsIfNecessary,
loadProfilesAndTeamMembersForDMSidebar,
@@ -13,29 +20,16 @@ import {connection} from 'app/actions/device';
import {selectFirstAvailableTeam} from 'app/actions/views/select_team';
import {getTheme} from 'app/selectors/preferences';
-import {viewChannel, markChannelAsRead} from 'mattermost-redux/actions/channels';
-import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'mattermost-redux/actions/users';
-import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
-import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
-
-import {
- init as initWebSocket,
- close as closeWebSocket
-} from 'mattermost-redux/actions/websocket';
-
import Channel from './channel';
-function mapStateToProps(state, ownProps) {
- const {websocket} = state.requests.general;
+function mapStateToProps(state) {
const {myChannels: channelsRequest} = state.requests.channels;
return {
- ...ownProps,
currentTeamId: getCurrentTeamId(state),
currentChannelId: getCurrentChannelId(state),
theme: getTheme(state),
- webSocketRequest: websocket,
- channelsRequestStatus: channelsRequest.status
+ channelsRequestFailed: channelsRequest.status === RequestStatus.FAILURE
};
}
@@ -57,4 +51,27 @@ function mapDispatchToProps(dispatch) {
};
}
-export default connect(mapStateToProps, mapDispatchToProps)(Channel);
+function areStatesEqual(next, prev) {
+ // When switching teams
+ if (next.entities.teams.currentTeamId !== prev.entities.teams.currentTeamId) {
+ return false;
+ }
+
+ // When we have a new channel after switching teams
+ const prevChannelId = prev.entities.channels.currentChannelId;
+ const nextChannelId = next.entities.channels.currentChannelId;
+ if (nextChannelId !== prevChannelId) {
+ return false;
+ }
+
+ // When getting the channels for a team and the request fails
+ const prevStatus = prev.requests.channels.myChannels.status;
+ const nextStatus = next.requests.channels.myChannels.status;
+ if (!nextChannelId && prevStatus === RequestStatus.STARTED && nextStatus === RequestStatus.FAILURE) {
+ return false;
+ }
+
+ return true;
+}
+
+export default connect(mapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(Channel);
diff --git a/yarn.lock b/yarn.lock
index 78a6c62ab..c6fb464d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3878,7 +3878,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/85db14af9a6d8c402213b0eb3c372f1ac6adad34"
+ resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/6932c3fd4064434c5b30cff9898d724cdc687648"
dependencies:
deep-equal "1.0.1"
harmony-reflect "1.5.1"