diff --git a/app/components/channel_intro/channel_intro.js b/app/components/channel_intro/channel_intro.js
new file mode 100644
index 000000000..863a4a016
--- /dev/null
+++ b/app/components/channel_intro/channel_intro.js
@@ -0,0 +1,333 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PropTypes, PureComponent} from 'react';
+import {
+ StyleSheet,
+ Text,
+ View
+} from 'react-native';
+import {getFullName} from 'mattermost-redux/utils/user_utils';
+import {Constants} from 'mattermost-redux/constants';
+import {injectIntl, intlShape} from 'react-intl';
+
+import ProfilePicture from 'app/components/profile_picture';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+class ChannelIntro extends PureComponent {
+ static propTypes = {
+ currentChannel: PropTypes.object.isRequired,
+ currentChannelMembers: PropTypes.array.isRequired,
+ currentUser: PropTypes.object.isRequired,
+ intl: intlShape.isRequired,
+ theme: PropTypes.object.isRequired
+ };
+
+ getDisplayName = (member) => {
+ if (!member) {
+ return null;
+ }
+
+ const displayName = getFullName(member);
+
+ if (!displayName) {
+ return member.username;
+ }
+
+ return displayName;
+ };
+
+ buildProfiles = () => {
+ const {currentChannelMembers, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return currentChannelMembers.map((member) => (
+
+
+
+ ));
+ }
+
+ buildNames = () => {
+ const {currentChannelMembers, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ const names = currentChannelMembers.map((member) => this.getDisplayName(member));
+
+ return {names.join(', ')};
+ }
+
+ buildDMContent = () => {
+ const {currentChannelMembers, intl, theme} = this.props;
+ const style = getStyleSheet(theme);
+ const teammate = this.getDisplayName(currentChannelMembers[0]);
+
+ return (
+
+ {intl.formatMessage({
+ id: 'mobile.intro_messages.DM',
+ defaultMessage: 'This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.'
+ }, {
+ teammate
+ })}
+
+ );
+ }
+
+ buildGMContent = () => {
+ const {intl, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+
+ {intl.formatMessage({
+ id: 'intro_messages.group_message',
+ defaultMessage: 'This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.'
+ })}
+
+ );
+ }
+
+ buildOpenChannelContent = () => {
+ const {currentChannel, currentChannelMembers, currentUser, intl, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ const date = intl.formatDate(currentChannel.create_at, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ });
+
+ let mainMessageIntl;
+ if (currentChannel.creator_id) {
+ const creator = currentChannel.creator_id === currentUser.id ? currentUser : currentChannelMembers[currentChannel.creator_id];
+ const creatorName = this.getDisplayName(creator);
+ mainMessageIntl = {
+ id: 'intro_messages.creator',
+ defaultMessage: 'This is the start of the {name} {type}, created by {creator} on {date}.',
+ values: {
+ name: currentChannel.display_name,
+ creator: creatorName,
+ date,
+ type: intl.formatMessage({
+ id: 'intro_messages.channel',
+ defaultMessage: 'channel'
+ })
+ }
+ };
+ } else {
+ mainMessageIntl = {
+ id: 'intro_messages.noCreator',
+ defaultMessage: 'This is the start of the {name} {type}, created on {date}.',
+ values: {
+ name: currentChannel.display_name,
+ date,
+ type: intl.formatMessage({
+ id: 'intro_messages.channel',
+ defaultMessage: 'channel'
+ })
+ }
+ };
+ }
+
+ const mainMessage = intl.formatMessage({
+ id: mainMessageIntl.id,
+ defaultMessage: mainMessageIntl.defaultMessage
+ }, mainMessageIntl.values);
+
+ const anyMemberMessage = intl.formatMessage({
+ id: 'intro_messages.anyMember',
+ defaultMessage: ' Any member can join and read this channel.'
+ });
+
+ return (
+
+
+ {intl.formatMessage({
+ id: 'intro_messages.beginning',
+ defaultMessage: 'Beginning of {name}'
+ }, {
+ name: currentChannel.display_name
+ })}
+
+
+ {`${mainMessage} ${anyMemberMessage}`}
+
+
+ );
+ }
+
+ buildPrivateChannelContent = () => {
+ const {currentChannel, currentChannelMembers, currentUser, intl, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ const creator = currentChannel.creator_id === currentUser.id ? currentUser : currentChannelMembers[currentChannel.creator_id];
+ const creatorName = this.getDisplayName(creator);
+ const date = intl.formatDate(currentChannel.create_at, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ });
+
+ const mainMessage = intl.formatMessage({
+ id: 'intro_messages.creator',
+ defaultMessage: 'This is the start of the {name} {type}, created by {creator} on {date}.'
+ }, {
+ name: currentChannel.display_name,
+ creator: creatorName,
+ date,
+ type: intl.formatMessage({
+ id: 'intro_messages.group',
+ defaultMessage: 'private channel'
+ })
+ });
+
+ const onlyInvitedMessage = intl.formatMessage({
+ id: 'intro_messages.onlyInvited',
+ defaultMessage: ' Only invited members can see this private channel.'
+ });
+
+ return (
+
+
+ {intl.formatMessage({
+ id: 'intro_messages.beginning',
+ defaultMessage: 'Beginning of {name}'
+ }, {
+ name: currentChannel.display_name
+ })}
+
+
+ {`${mainMessage} ${onlyInvitedMessage}`}
+
+
+ );
+ }
+
+ buildTownSquareContent = () => {
+ const {currentChannel, intl, theme} = this.props;
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+ {intl.formatMessage({
+ id: 'intro_messages.beginning',
+ defaultMessage: 'Beginning of {name}'
+ }, {
+ name: currentChannel.display_name
+ })}
+
+
+ {intl.formatMessage({
+ id: 'mobile.intro_messages.default_welcome',
+ defaultMessage: 'Welcome to {name}!'
+ }, {
+ name: currentChannel.display_name
+ })}
+
+
+ {intl.formatMessage({
+ id: 'mobile.intro_messages.default_message',
+ defaultMessage: 'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'
+ })}
+
+
+ );
+ }
+
+ buildContent = () => {
+ const {currentChannel} = this.props;
+
+ switch (currentChannel.type) {
+ default:
+ case Constants.DM_CHANNEL:
+ return this.buildDMContent();
+
+ case Constants.GM_CHANNEL:
+ return this.buildGMContent();
+
+ case Constants.OPEN_CHANNEL: {
+ if (currentChannel.name === Constants.DEFAULT_CHANNEL) {
+ return this.buildTownSquareContent();
+ }
+
+ return this.buildOpenChannelContent();
+ }
+
+ case Constants.PRIVATE_CHANNEL:
+ return this.buildPrivateChannelContent();
+
+ }
+ }
+
+ render() {
+ const {theme} = this.props;
+
+ const style = getStyleSheet(theme);
+
+ return (
+
+
+ {this.buildProfiles()}
+
+
+ {this.buildNames()}
+
+
+ {this.buildContent()}
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return StyleSheet.create({
+ channelTitle: {
+ color: theme.centerChannelColor,
+ fontSize: 17,
+ fontWeight: '600',
+ marginBottom: 12
+ },
+ channelWelcome: {
+ color: theme.centerChannelColor,
+ marginBottom: 12
+ },
+ container: {
+ marginTop: 60,
+ marginHorizontal: 12,
+ marginBottom: 12
+ },
+ displayName: {
+ color: theme.centerChannelColor,
+ fontSize: 15,
+ fontWeight: '600'
+ },
+ message: {
+ color: changeOpacity(theme.centerChannelColor, 0.8),
+ lineHeight: 18
+ },
+ namesContainer: {
+ marginBottom: 12
+ },
+ profile: {
+ marginRight: 12
+ },
+ profilesContainer: {
+ flexDirection: 'row',
+ marginBottom: 12
+ }
+ });
+});
+
+export default injectIntl(ChannelIntro);
diff --git a/app/components/channel_intro/index.js b/app/components/channel_intro/index.js
new file mode 100644
index 000000000..04c642e0e
--- /dev/null
+++ b/app/components/channel_intro/index.js
@@ -0,0 +1,43 @@
+// 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 {Constants} from 'mattermost-redux/constants';
+import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
+import {getCurrentUser, getProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
+
+import {getTheme} from 'app/selectors/preferences';
+
+import ChannelIntro from './channel_intro';
+
+function mapStateToProps(state) {
+ const currentChannel = getCurrentChannel(state);
+ const currentUser = getCurrentUser(state);
+
+ let currentChannelMembers = [];
+ if (currentChannel.type === Constants.DM_CHANNEL) {
+ const otherChannelMember = currentChannel.name.split('__').find((m) => m.id !== currentUser.id);
+ currentChannelMembers.push(state.entities.users.profiles[otherChannelMember]);
+ }
+
+ if (currentChannel.type === Constants.GM_CHANNEL) {
+ currentChannelMembers = getProfilesInCurrentChannel(state);
+ }
+
+ return {
+ currentChannel,
+ currentChannelMembers,
+ currentUser,
+ theme: getTheme(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ // placeholder for invite and set header actions
+ return {
+ actions: bindActionCreators({}, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ChannelIntro);
diff --git a/app/components/post_list/index.js b/app/components/post_list/index.js
index f05db9712..0f02eaf7d 100644
--- a/app/components/post_list/index.js
+++ b/app/components/post_list/index.js
@@ -9,8 +9,9 @@ import PostList from './post_list';
function mapStateToProps(state, ownProps) {
return {
- theme: getTheme(state),
- ...ownProps
+ ...ownProps,
+ channelIsLoading: state.views.channel.loading,
+ theme: getTheme(state)
};
}
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index 14c7ae7ab..3d90222f2 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -4,30 +4,24 @@
import React, {Component, PropTypes} from 'react';
import {
ListView,
- StyleSheet
+ StyleSheet,
+ View
} from 'react-native';
+import {Constants} from 'mattermost-redux/constants';
+import {addDatesToPostList} from 'mattermost-redux/utils/post_utils';
+import ChannelIntro from 'app/components/channel_intro';
import Post from 'app/components/post';
import DateHeader from './date_header';
import LoadMorePosts from './load_more_posts';
import NewMessagesDivider from './new_messages_divider';
-import {Constants} from 'mattermost-redux/constants';
-import {addDatesToPostList} from 'mattermost-redux/utils/post_utils';
-
-const style = StyleSheet.create({
- container: {
- transform: [{rotate: '180deg'}]
- },
- row: {
- transform: [{rotate: '180deg'}]
- }
-});
-
const LOAD_MORE_POSTS = 'load-more-posts';
export default class PostList extends Component {
static propTypes = {
+ channel: PropTypes.object.isRequired,
+ channelIsLoading: PropTypes.bool.isRequired,
posts: PropTypes.array.isRequired,
theme: PropTypes.object.isRequired,
loadMore: PropTypes.func,
@@ -78,6 +72,22 @@ export default class PostList extends Component {
}
};
+ renderChannelIntro = () => {
+ const {channel, channelIsLoading, posts} = this.props;
+
+ const firstPostHasRendered = channel.total_msg_count ? posts.length > 0 : true;
+ const messageCount = channel.total_msg_count - posts.length;
+ if (channelIsLoading || !firstPostHasRendered || messageCount > Constants.POST_CHUNK_SIZE) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+ }
+
renderRow = (row) => {
if (row instanceof Date) {
return this.renderDateHeader(row);
@@ -132,6 +142,7 @@ export default class PostList extends Component {
);
} else {
diff --git a/app/store/index.js b/app/store/index.js
index 9a7ce7713..ce393c60a 100644
--- a/app/store/index.js
+++ b/app/store/index.js
@@ -83,7 +83,7 @@ export default function configureStore(initialState) {
// check to see if the logout request was successful
store.subscribe(() => {
const state = store.getState();
- if (state.requests.users.logout.status === RequestStatus.SUCCESS && !purging) {
+ if ((state.requests.users.logout.status === RequestStatus.SUCCESS || state.requests.users.logout.status === RequestStatus.FAILURE) && !purging) {
purging = true;
persistor.purge();
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 7288a5568..14b18ba03 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -1689,6 +1689,9 @@
"mobile.file_upload.camera": "Take Photo or Video",
"mobile.file_upload.library": "Photo Library",
"mobile.file_upload.more": "More",
+ "mobile.intro_messages.default_welcome": "Welcome to {name}!",
+ "mobile.intro_messages.default_message": "This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.",
+ "mobile.intro_messages.DM": "This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.",
"mobile.loading_channels": "Loading Channels...",
"mobile.loading_members": "Loading Members...",
"mobile.loading_posts": "Loading Messages...",