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...",