diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js
index 4c2cd15a1..f43a71571 100644
--- a/app/actions/views/channel.js
+++ b/app/actions/views/channel.js
@@ -325,6 +325,13 @@ export function setChannelLoading(loading = true) {
};
}
+export function setChannelRefreshing(loading = true) {
+ return {
+ type: ViewTypes.SET_CHANNEL_REFRESHING,
+ loading
+ };
+}
+
export function setPostTooltipVisible(visible = true) {
return {
type: ViewTypes.POST_TOOLTIP_VISIBLE,
diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js
index 438f2f657..d265c8a54 100644
--- a/app/components/channel_drawer/channel_drawer.js
+++ b/app/components/channel_drawer/channel_drawer.js
@@ -206,9 +206,12 @@ export default class ChannelDrawer extends PureComponent {
} = actions;
markChannelAsRead(channel.id, currentChannelId);
- setChannelLoading();
- viewChannel(currentChannelId);
- setChannelDisplayName(channel.display_name);
+
+ if (channel.id !== currentChannelId) {
+ setChannelLoading();
+ viewChannel(currentChannelId);
+ setChannelDisplayName(channel.display_name);
+ }
this.closeChannelDrawer();
diff --git a/app/components/channel_drawer/channels_list/channels_list.js b/app/components/channel_drawer/channels_list/channels_list.js
index bab3d24ce..fbd48eb0b 100644
--- a/app/components/channel_drawer/channels_list/channels_list.js
+++ b/app/components/channel_drawer/channels_list/channels_list.js
@@ -5,7 +5,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
Platform,
- Text,
TouchableHighlight,
View
} from 'react-native';
@@ -13,22 +12,17 @@ import {injectIntl, intlShape} from 'react-intl';
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
-import Badge from 'app/components/badge';
import SearchBar from 'app/components/search_bar';
-import {preventDoubleTap} from 'app/utils/tap';
+import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import FilteredList from './filtered_list';
import List from './list';
+import SwitchTeams from './switch_teams';
class ChannelsList extends React.PureComponent {
static propTypes = {
- channels: PropTypes.object.isRequired,
- channelMembers: PropTypes.object,
- currentChannel: PropTypes.object,
- currentTeam: PropTypes.object.isRequired,
intl: intlShape.isRequired,
- myTeamMembers: PropTypes.object.isRequired,
navigator: PropTypes.object,
onJoinChannel: PropTypes.func.isRequired,
onSearchEnds: PropTypes.func.isRequired,
@@ -38,11 +32,6 @@ class ChannelsList extends React.PureComponent {
theme: PropTypes.object.isRequired
};
- static defaultProps = {
- currentTeam: {},
- currentChannel: {}
- };
-
constructor(props) {
super(props);
this.firstUnreadChannel = null;
@@ -66,7 +55,7 @@ class ChannelsList extends React.PureComponent {
this.refs.search_bar.cancel();
};
- openSettingsModal = () => {
+ openSettingsModal = wrapWithPreventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
navigator.showModal({
@@ -88,7 +77,7 @@ class ChannelsList extends React.PureComponent {
}]
}
});
- };
+ });
onSearch = (term) => {
this.setState({term});
@@ -107,33 +96,30 @@ class ChannelsList extends React.PureComponent {
render() {
const {
- currentChannel,
- currentTeam,
intl,
- myTeamMembers,
+ navigator,
onShowTeams,
theme
} = this.props;
- if (!currentChannel) {
- return {'Loading'};
- }
-
const {searching, term} = this.state;
- const teamMembers = Object.values(myTeamMembers);
- const showMembers = teamMembers.length > 1;
const styles = getStyleSheet(theme);
let settings;
let list;
if (searching) {
- const listProps = {...this.props, onSelectChannel: this.onSelectChannel, styles, term};
- list = ;
+ list = (
+
+ );
} else {
settings = (
preventDoubleTap(this.openSettingsModal)}
+ onPress={this.openSettingsModal}
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
>
);
- const listProps = {...this.props, onSelectChannel: this.onSelectChannel, styles};
- list =
;
+ list = (
+
+ );
}
const title = (
@@ -174,67 +165,18 @@ class ChannelsList extends React.PureComponent {
);
- let badge;
- let switcher;
- if (showMembers && !searching) {
- let mentionCount = 0;
- let messageCount = 0;
- teamMembers.forEach((m) => {
- if (m.team_id !== currentTeam.id) {
- mentionCount = mentionCount + (m.mention_count || 0);
- messageCount = messageCount + (m.msg_count || 0);
- }
- });
-
- let badgeCount = 0;
- if (mentionCount) {
- badgeCount = mentionCount;
- } else if (messageCount) {
- badgeCount = -1;
- }
-
- if (badgeCount) {
- badge = (
-
- );
- }
-
- switcher = (
- preventDoubleTap(onShowTeams)}
- underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
- >
-
-
-
-
- {currentTeam.display_name.substr(0, 2).toUpperCase()}
-
-
-
- );
- }
-
return (
- {switcher}
+
{title}
{settings}
- {badge}
{list}
@@ -249,9 +191,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
backgroundColor: theme.sidebarBg,
flex: 1
},
- extraPadding: {
- paddingBottom: 5
- },
statusBar: {
backgroundColor: theme.sidebarHeaderBg,
...Platform.select({
@@ -303,7 +242,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 18,
fontWeight: '300'
},
- titleContainer: {
+ titleContainer: { // These aren't used by this component, but they are passed down to the list component
alignItems: 'center',
flex: 1,
flexDirection: 'row',
@@ -330,43 +269,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
}
})
},
- switcherContainer: {
- alignItems: 'center',
- backgroundColor: theme.sidebarHeaderTextColor,
- borderRadius: 2,
- flexDirection: 'row',
- height: 32,
- justifyContent: 'center',
- marginLeft: 6,
- marginRight: 10,
- paddingHorizontal: 6
- },
- switcherDivider: {
- backgroundColor: theme.sidebarHeaderBg,
- height: 15,
- marginHorizontal: 6,
- width: 1
- },
- switcherTeam: {
- color: theme.sidebarHeaderBg,
- fontFamily: 'OpenSans',
- fontSize: 14
- },
- badge: {
- backgroundColor: theme.mentionBj,
- borderColor: theme.sidebarHeaderBg,
- borderRadius: 10,
- borderWidth: 1,
- flexDirection: 'row',
- padding: 3,
- position: 'absolute',
- left: 5,
- top: 0
- },
- mention: {
- color: theme.mentionColor,
- fontSize: 10
- },
divider: {
backgroundColor: changeOpacity(theme.sidebarText, 0.1),
height: 1
diff --git a/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js b/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js
index faf776404..44dbd1dcb 100644
--- a/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js
+++ b/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js
@@ -13,7 +13,6 @@ import {
import {injectIntl, intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
-import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity} from 'app/utils/theme';
import {General} from 'mattermost-redux/constants';
@@ -34,7 +33,6 @@ class FilteredList extends Component {
currentTeam: PropTypes.object.isRequired,
currentUserId: PropTypes.string,
currentChannel: PropTypes.object,
- groupChannels: PropTypes.array,
groupChannelMemberDetails: PropTypes.object,
intl: intlShape.isRequired,
teammateNameDisplay: PropTypes.string,
@@ -325,7 +323,7 @@ class FilteredList extends Component {
return (
preventDoubleTap(action, this)}
+ onPress={action}
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
>
{
+ createPrivateChannel = wrapWithPreventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
navigator.showModal({
@@ -171,7 +171,7 @@ class List extends Component {
closeButton: this.closeButton
}
});
- };
+ });
buildChannels = (props) => {
const {canCreatePrivateChannels, styles} = props;
@@ -233,7 +233,15 @@ class List extends Component {
return data;
};
- showDirectMessagesModal = () => {
+ scrollToTop = () => {
+ this.refs.list.scrollToOffset({
+ x: 0,
+ y: 0,
+ animated: true
+ });
+ }
+
+ showDirectMessagesModal = wrapWithPreventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
navigator.showModal({
@@ -255,9 +263,9 @@ class List extends Component {
}]
}
});
- };
+ });
- showMoreChannelsModal = () => {
+ showMoreChannelsModal = wrapWithPreventDoubleTap(() => {
const {intl, navigator, theme} = this.props;
navigator.showModal({
@@ -276,14 +284,14 @@ class List extends Component {
closeButton: this.closeButton
}
});
- };
+ });
renderSectionAction = (styles, action) => {
const {theme} = this.props;
return (
preventDoubleTap(action, this)}
+ onPress={action}
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
>
this.refs.list.scrollToOffset({x: 0, y: 0, animated: true})}
+ onPress={this.scrollToTop}
text={(
{
+ const {
+ currentTeam,
+ teamMembers
+ } = props;
+
+ let mentionCount = 0;
+ let messageCount = 0;
+ Object.values(teamMembers).forEach((m) => {
+ if (m.team_id !== currentTeam.id) {
+ mentionCount = mentionCount + (m.mention_count || 0);
+ messageCount = messageCount + (m.msg_count || 0);
+ }
+ });
+
+ let badgeCount;
+ if (mentionCount) {
+ badgeCount = mentionCount;
+ } else if (messageCount) {
+ badgeCount = -1;
+ } else {
+ badgeCount = 0;
+ }
+
+ return badgeCount;
+ };
+
+ showTeams = wrapWithPreventDoubleTap(() => {
+ this.props.showTeams();
+ });
+
+ render() {
+ const {
+ currentTeam,
+ searching,
+ teamMembers,
+ theme
+ } = this.props;
+
+ const {
+ badgeCount
+ } = this.state;
+
+ if (searching || teamMembers.length < 2) {
+ return null;
+ }
+
+ const styles = getStyleSheet(theme);
+
+ let badge;
+ if (badgeCount) {
+ badge = (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ {currentTeam.display_name.substr(0, 2).toUpperCase()}
+
+
+
+ {badge}
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return {
+ switcherContainer: {
+ alignItems: 'center',
+ backgroundColor: theme.sidebarHeaderTextColor,
+ borderRadius: 2,
+ flexDirection: 'row',
+ height: 32,
+ justifyContent: 'center',
+ marginLeft: 6,
+ marginRight: 10,
+ paddingHorizontal: 6
+ },
+ switcherDivider: {
+ backgroundColor: theme.sidebarHeaderBg,
+ height: 15,
+ marginHorizontal: 6,
+ width: 1
+ },
+ switcherTeam: {
+ color: theme.sidebarHeaderBg,
+ fontFamily: 'OpenSans',
+ fontSize: 14
+ },
+ badge: {
+ backgroundColor: theme.mentionBj,
+ borderColor: theme.sidebarHeaderBg,
+ borderRadius: 10,
+ borderWidth: 1,
+ flexDirection: 'row',
+ padding: 3,
+ position: 'absolute',
+ left: -5,
+ top: -5
+ },
+ mention: {
+ color: theme.mentionColor,
+ fontSize: 10
+ }
+ };
+});
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index c34c8720c..e9927f483 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -35,6 +35,7 @@ export default class PostList extends PureComponent {
loadMore: PropTypes.func,
navigator: PropTypes.object,
onPostPress: PropTypes.func,
+ onRefresh: PropTypes.func,
posts: PropTypes.array.isRequired,
refreshing: PropTypes.bool,
renderReplies: PropTypes.bool,
@@ -78,11 +79,19 @@ export default class PostList extends PureComponent {
};
onRefresh = () => {
- const {actions, channel} = this.props;
+ const {
+ actions,
+ channel,
+ onRefresh
+ } = this.props;
if (Object.keys(channel).length) {
actions.refreshChannelWithRetry(channel.id);
}
+
+ if (onRefresh) {
+ onRefresh();
+ }
};
renderChannelIntro = () => {
diff --git a/app/components/root/index.js b/app/components/root/index.js
index 65967dab6..68509e281 100644
--- a/app/components/root/index.js
+++ b/app/components/root/index.js
@@ -2,26 +2,19 @@
// See License.txt for license information.
import {connect} from 'react-redux';
-import DeviceInfo from 'react-native-device-info';
import {Client, Client4} from 'mattermost-redux/client';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
+import {getCurrentLocale} from 'app/selectors/i18n';
import {getTheme} from 'app/selectors/preferences';
import {removeProtocol} from 'app/utils/url';
import Root from './root';
function mapStateToProps(state, ownProps) {
- const users = state.entities.users;
- const {currentUserId} = users;
-
- let locale = DeviceInfo.getDeviceLocale().split('-')[0];
- if (currentUserId && users.profiles[currentUserId]) {
- locale = users.profiles[currentUserId].locale;
- }
-
+ const locale = getCurrentLocale(state);
Client.setLocale(locale);
Client4.setAcceptLanguage(locale);
diff --git a/app/components/search_bar/search_bar.ios.js b/app/components/search_bar/search_bar.ios.js
index 4536f0ec2..32cc4d841 100644
--- a/app/components/search_bar/search_bar.ios.js
+++ b/app/components/search_bar/search_bar.ios.js
@@ -1,12 +1,12 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React, {Component} from 'react';
+import React, {PureComponent} from 'react';
import {InteractionManager, Keyboard} from 'react-native';
import PropTypes from 'prop-types';
import Search from './search_box';
-export default class SearchBarIos extends Component {
+export default class SearchBarIos extends PureComponent {
static propTypes = {
onCancelButtonPress: PropTypes.func,
onChangeText: PropTypes.func,
diff --git a/app/constants/view.js b/app/constants/view.js
index 14bf61412..799e13467 100644
--- a/app/constants/view.js
+++ b/app/constants/view.js
@@ -34,6 +34,7 @@ const ViewTypes = keyMirror({
ADD_FILE_TO_FETCH_CACHE: null,
SET_CHANNEL_LOADER: null,
+ SET_CHANNEL_REFRESHING: null,
SET_CHANNEL_DISPLAY_NAME: null,
POST_TOOLTIP_VISIBLE: null,
diff --git a/app/i18n/index.js b/app/i18n/index.js
index 44620e4f3..9289829ef 100644
--- a/app/i18n/index.js
+++ b/app/i18n/index.js
@@ -32,7 +32,7 @@ import ru from 'assets/i18n/ru.json';
import zhCN from 'assets/i18n/zh-CN.json';
import zhTW from 'assets/i18n/zh-TW.json';
-const DEFAULT_LOCALE = 'en';
+export const DEFAULT_LOCALE = 'en';
const TRANSLATIONS = {
de,
diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js
index be6bb1b0c..0312249d7 100644
--- a/app/reducers/views/channel.js
+++ b/app/reducers/views/channel.js
@@ -2,7 +2,11 @@
// See License.txt for license information.
import {combineReducers} from 'redux';
-import {ChannelTypes, FileTypes, PostTypes} from 'mattermost-redux/action_types';
+import {
+ ChannelTypes,
+ FileTypes,
+ PostTypes
+} from 'mattermost-redux/action_types';
import {ViewTypes} from 'app/constants';
@@ -181,6 +185,18 @@ function loading(state = false, action) {
}
}
+function refreshing(state = false, action) {
+ switch (action.type) {
+ case PostTypes.GET_POSTS_SUCCESS:
+ case PostTypes.GET_POSTS_FAILURE:
+ return false;
+ case ViewTypes.SET_CHANNEL_REFRESHING:
+ return action.loading;
+ default:
+ return state;
+ }
+}
+
function tooltipVisible(state = false, action) {
switch (action.type) {
case ViewTypes.POST_TOOLTIP_VISIBLE:
@@ -236,6 +252,7 @@ export default combineReducers({
displayName,
drafts,
loading,
+ refreshing,
tooltipVisible,
postVisibility,
loadingPosts
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 485ced103..0b51841ab 100644
--- a/app/screens/channel/channel_post_list/channel_post_list.js
+++ b/app/screens/channel/channel_post_list/channel_post_list.js
@@ -51,7 +51,9 @@ class ChannelPostList extends PureComponent {
super(props);
this.state = {
- retryMessageHeight: new Animated.Value(0)
+ retryMessageHeight: new Animated.Value(0),
+ visiblePosts: this.getVisiblePosts(props),
+ showLoadMore: false
};
}
@@ -83,12 +85,22 @@ class ChannelPostList extends PureComponent {
this.setState({
showLoadMore
});
+
+ if (nextProps.posts !== this.props.posts || nextProps.postVisibility !== this.props.postVisibility) {
+ this.setState({
+ visiblePosts: this.getVisiblePosts(nextProps)
+ });
+ }
}
componentWillUnmount() {
this.mounted = false;
}
+ getVisiblePosts = (props) => {
+ return props.posts.slice(0, props.posts.postVisibility);
+ }
+
shouldMarkChannelAsLoaded = (postsCount, channelHasMessages, channelRefreshingFailed) => {
if (postsCount || channelHasMessages || channelRefreshingFailed) {
this.channelLoaded();
@@ -159,8 +171,13 @@ class ChannelPostList extends PureComponent {
this.props.actions.loadPostsIfNecessaryWithRetry(channelId);
};
+ loadPostsRetry = () => {
+ this.loadPosts(this.props.channel.id);
+ };
+
render() {
const {
+ actions,
channel,
channelIsLoading,
channelIsRefreshing,
@@ -169,17 +186,20 @@ class ChannelPostList extends PureComponent {
myMember,
navigator,
posts,
- postVisibility,
theme
} = this.props;
- const {retryMessageHeight} = this.state;
+ const {
+ retryMessageHeight,
+ showLoadMore,
+ visiblePosts
+ } = this.state;
let component;
if (!posts.length && channelRefreshingFailed) {
component = (
this.loadPosts(channel.id)}
+ retry={this.loadPostsRetry}
theme={theme}
/>
);
@@ -188,11 +208,12 @@ class ChannelPostList extends PureComponent {
} else {
component = (
+
{component}
{
+ return currentUser ? currentUser.locale : '';
+ }
+);
+
+// Not a proper selector since the device locale isn't in the redux store
+export function getCurrentLocale(state) {
+ const userLocale = getCurrentUserLocale(state);
+ if (userLocale) {
+ return userLocale;
+ }
+
+ const deviceLocale = DeviceInfo.getDeviceLocale().split('-')[0];
+ if (deviceLocale) {
+ return deviceLocale;
+ }
+
+ return DEFAULT_LOCALE;
+}
diff --git a/app/store/index.js b/app/store/index.js
index 7c45644cc..c22ed359d 100644
--- a/app/store/index.js
+++ b/app/store/index.js
@@ -48,6 +48,26 @@ export default function configureAppStore(initialState) {
['typing']
);
+ const channelViewBlackList = {loading: true, refreshing: true, tooltipVisible: true, postVisibility: true, loadingPosts: true};
+ const channelViewBlackListFilter = createTransform(
+ (inboundState) => {
+ const channel = {};
+
+ for (const channelKey of Object.keys(inboundState.channel)) {
+ if (channelViewBlackList[channelKey]) {
+ channel[channelKey] = inboundState.channel[channelKey];
+ }
+ }
+
+ return {
+ ...inboundState,
+ channel
+ };
+ },
+ null,
+ {whitelist: ['views']} // Only run this filter the views state (or any other entry that ends up being named views)
+ );
+
const setTransformer = createTransform(
(inboundState, key) => {
if (key === 'entities') {
@@ -165,7 +185,8 @@ export default function configureAppStore(initialState) {
transforms: [
setTransformer,
viewsBlackListFilter,
- typingBlackListFilter
+ typingBlackListFilter,
+ channelViewBlackListFilter
]
}
};