RN-68 RN: Show channel intro message (#480)
This commit is contained in:
parent
756a08703f
commit
65af36ce1d
7 changed files with 425 additions and 17 deletions
333
app/components/channel_intro/channel_intro.js
Normal file
333
app/components/channel_intro/channel_intro.js
Normal file
|
|
@ -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) => (
|
||||
<View
|
||||
key={member.id}
|
||||
style={style.profile}
|
||||
>
|
||||
<ProfilePicture
|
||||
user={member}
|
||||
size={64}
|
||||
statusBorderWidth={2}
|
||||
statusSize={25}
|
||||
statusIconSize={15}
|
||||
/>
|
||||
</View>
|
||||
));
|
||||
}
|
||||
|
||||
buildNames = () => {
|
||||
const {currentChannelMembers, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const names = currentChannelMembers.map((member) => this.getDisplayName(member));
|
||||
|
||||
return <Text style={style.displayName}>{names.join(', ')}</Text>;
|
||||
}
|
||||
|
||||
buildDMContent = () => {
|
||||
const {currentChannelMembers, intl, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
const teammate = this.getDisplayName(currentChannelMembers[0]);
|
||||
|
||||
return (
|
||||
<Text style={style.message}>
|
||||
{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
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
buildGMContent = () => {
|
||||
const {intl, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<Text style={style.message}>
|
||||
{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.'
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<View>
|
||||
<Text style={style.channelTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'intro_messages.beginning',
|
||||
defaultMessage: 'Beginning of {name}'
|
||||
}, {
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.message}>
|
||||
{`${mainMessage} ${anyMemberMessage}`}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<View>
|
||||
<Text style={style.channelTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'intro_messages.beginning',
|
||||
defaultMessage: 'Beginning of {name}'
|
||||
}, {
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.message}>
|
||||
{`${mainMessage} ${onlyInvitedMessage}`}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
buildTownSquareContent = () => {
|
||||
const {currentChannel, intl, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text style={style.channelTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'intro_messages.beginning',
|
||||
defaultMessage: 'Beginning of {name}'
|
||||
}, {
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.channelWelcome}>
|
||||
{intl.formatMessage({
|
||||
id: 'mobile.intro_messages.default_welcome',
|
||||
defaultMessage: 'Welcome to {name}!'
|
||||
}, {
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.message}>
|
||||
{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.'
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<View style={style.container}>
|
||||
<View style={style.profilesContainer}>
|
||||
{this.buildProfiles()}
|
||||
</View>
|
||||
<View style={style.namesContainer}>
|
||||
{this.buildNames()}
|
||||
</View>
|
||||
<View style={style.contentContainer}>
|
||||
{this.buildContent()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
43
app/components/channel_intro/index.js
Normal file
43
app/components/channel_intro/index.js
Normal file
|
|
@ -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);
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={style.row}>
|
||||
<ChannelIntro/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderRow = (row) => {
|
||||
if (row instanceof Date) {
|
||||
return this.renderDateHeader(row);
|
||||
|
|
@ -132,6 +142,7 @@ export default class PostList extends Component {
|
|||
<ListView
|
||||
style={style.container}
|
||||
dataSource={this.state.dataSource.cloneWithRows(this.getPostsWithLoadMore())}
|
||||
renderFooter={this.renderChannelIntro}
|
||||
renderRow={this.renderRow}
|
||||
onEndReached={this.loadMore}
|
||||
enableEmptySections={true}
|
||||
|
|
@ -143,3 +154,12 @@ export default class PostList extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
transform: [{rotate: '180deg'}]
|
||||
},
|
||||
row: {
|
||||
transform: [{rotate: '180deg'}]
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -115,7 +115,14 @@ export default class ChannelPostList extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {applicationInitializing, channelIsLoading, posts, postsRequests, theme} = this.props;
|
||||
const {
|
||||
applicationInitializing,
|
||||
channel,
|
||||
channelIsLoading,
|
||||
posts,
|
||||
postsRequests,
|
||||
theme
|
||||
} = this.props;
|
||||
let component;
|
||||
if (!applicationInitializing && !channelIsLoading && posts && (postsRequests.getPosts.status !== RequestStatus.STARTED || !this.state.didInitialPostsLoad)) {
|
||||
component = (
|
||||
|
|
@ -129,6 +136,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
indicateNewMessages={true}
|
||||
currentUserId={this.props.myMember.user_id}
|
||||
lastViewedAt={this.state.lastViewedAt}
|
||||
channel={channel}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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...",
|
||||
|
|
|
|||
Loading…
Reference in a new issue