From e0eb79656eca6156e147cac1d24931842014280a Mon Sep 17 00:00:00 2001 From: Chris Duarte Date: Fri, 20 Jan 2017 12:17:28 -0800 Subject: [PATCH] Add channel-info (#164) Adding channel info screen. --- app/actions/navigation/index.js | 9 ++ app/components/formatted_date.js | 36 +++++ app/navigation/routes.js | 4 + app/scenes/channel/channel.js | 4 +- app/scenes/channel/channel_container.js | 4 +- .../channel/channel_header/channel_header.js | 13 +- app/scenes/channel_info/channel_info.js | 132 ++++++++++++++++++ .../channel_info/channel_info_container.js | 40 ++++++ .../channel_info/channel_info_header.js | 116 +++++++++++++++ app/scenes/channel_info/channel_info_row.js | 106 ++++++++++++++ app/scenes/channel_info/index.js | 6 + app/scenes/index.js | 4 +- assets/base/i18n/en.json | 6 +- service/selectors/entities/channels.js | 12 ++ service/selectors/entities/general.js | 6 + service/selectors/entities/teams.js | 9 ++ 16 files changed, 499 insertions(+), 8 deletions(-) create mode 100644 app/components/formatted_date.js create mode 100644 app/scenes/channel_info/channel_info.js create mode 100644 app/scenes/channel_info/channel_info_container.js create mode 100644 app/scenes/channel_info/channel_info_header.js create mode 100644 app/scenes/channel_info/channel_info_row.js create mode 100644 app/scenes/channel_info/index.js create mode 100644 service/selectors/entities/general.js diff --git a/app/actions/navigation/index.js b/app/actions/navigation/index.js index 3de878331..1e5acb990 100644 --- a/app/actions/navigation/index.js +++ b/app/actions/navigation/index.js @@ -66,3 +66,12 @@ export function goToFlaggedPosts() { }, getState); }; } + +export function goToChannelInfo() { + return async (dispatch, getState) => { + dispatch({ + type: NavigationTypes.NAVIGATION_PUSH, + route: Routes.ChannelInfo + }, getState); + }; +} diff --git a/app/components/formatted_date.js b/app/components/formatted_date.js new file mode 100644 index 000000000..afc4bbefc --- /dev/null +++ b/app/components/formatted_date.js @@ -0,0 +1,36 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React, {Component, PropTypes} from 'react'; +import {injectIntl, intlShape} from 'react-intl'; +import {Text} from 'react-native'; + +class FormattedDate extends Component { + static propTypes = { + intl: intlShape.isRequired, + value: PropTypes.any.isRequired, + format: PropTypes.string, + children: PropTypes.func + }; + + render() { + const { + intl, + value, + children, + ...props + } = this.props; + + Reflect.deleteProperty(props, 'format'); + + const formattedDate = intl.formatDate(value, this.props); + + if (typeof children === 'function') { + return children(formattedDate); + } + + return {formattedDate}; + } +} + +export default injectIntl(FormattedDate); diff --git a/app/navigation/routes.js b/app/navigation/routes.js index ab60c4512..6372e92e8 100644 --- a/app/navigation/routes.js +++ b/app/navigation/routes.js @@ -22,5 +22,9 @@ export default { }, Search: { key: 'Search' + }, + ChannelInfo: { + key: 'ChannelInfo', + title: {id: 'mobile.routes.channelInfo', defaultMessage: 'Info'} } }; diff --git a/app/scenes/channel/channel.js b/app/scenes/channel/channel.js index bdf56ec0c..4f51f6602 100644 --- a/app/scenes/channel/channel.js +++ b/app/scenes/channel/channel.js @@ -23,7 +23,8 @@ export default class Channel extends React.PureComponent { loadProfilesAndTeamMembersForDMSidebar: React.PropTypes.func.isRequired, selectInitialChannel: React.PropTypes.func.isRequired, openChannelDrawer: React.PropTypes.func.isRequired, - handlePostDraftChanged: React.PropTypes.func.isRequired + handlePostDraftChanged: React.PropTypes.func.isRequired, + goToChannelInfo: React.PropTypes.func.isRequired }).isRequired, currentTeam: React.PropTypes.object, currentChannel: React.PropTypes.object, @@ -110,6 +111,7 @@ export default class Channel extends React.PureComponent { currentChannel={currentChannel} openLeftDrawer={this.openChannelDrawer} openRightDrawer={this.openRightSidebar} + goToChannelInfo={this.props.actions.goToChannelInfo} /> - - {displayName} - + + + + {displayName} + + + + + + true} + defaultMessage='Favorite' + detail={isFavorite} + icon='star-o' + textId='mobile.routes.channelInfo.favorite' + togglable={true} + /> + + + + true} + defaultMessage='Notification Preferences' + icon='bell-o' + textId='channel_header.notificationPreferences' + /> + + + + true} + defaultMessage='Manage Members' + detail={currentChannelMemberCount} + icon='users' + textId='channel_header.manageMembers' + /> + + + + true} + defaultMessage='Add Members' + icon='user-plus' + textId='channel_header.addMembers' + /> + + + + true} + defaultMessage='Leave Channel' + icon='sign-out' + textId='navbar.leave' + /> + + true} + defaultMessage='Delete Channel' + icon='trash' + iconColor='#DA4A4A' + textId='mobile.routes.channelInfo.delete_channel' + textColor='#DA4A4A' + /> + + + + ); + } +} diff --git a/app/scenes/channel_info/channel_info_container.js b/app/scenes/channel_info/channel_info_container.js new file mode 100644 index 000000000..efd5d6aef --- /dev/null +++ b/app/scenes/channel_info/channel_info_container.js @@ -0,0 +1,40 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + +import {getChannelStats} from 'service/actions/channels'; +import {getCurrentChannel, getCurrentChannelStats, getChannelsByCategory} from 'service/selectors/entities/channels'; +import {getTheme} from 'service/selectors/entities/preferences'; +import {getUser} from 'service/selectors/entities/users'; + +import ChannelInfo from './channel_info'; + +function mapStateToProps(state, ownProps) { + const currentChannel = getCurrentChannel(state); + const currentChannelCreator = getUser(state, currentChannel.creator_id); + const currentChannelCreatorName = currentChannelCreator && currentChannelCreator.username; + const currentChannelMemberCount = getCurrentChannelStats(state) && getCurrentChannelStats(state).member_count; + const favoriteChannels = getChannelsByCategory(state).favoriteChannels.map((f) => f.id); + const isFavorite = favoriteChannels.indexOf(currentChannel.id) > -1; + + return { + ...ownProps, + currentChannel, + currentChannelCreatorName, + currentChannelMemberCount, + isFavorite, + theme: getTheme(state) + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getChannelStats + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(ChannelInfo); diff --git a/app/scenes/channel_info/channel_info_header.js b/app/scenes/channel_info/channel_info_header.js new file mode 100644 index 000000000..c9f45b8ec --- /dev/null +++ b/app/scenes/channel_info/channel_info_header.js @@ -0,0 +1,116 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React, {PropTypes} from 'react'; +import { + StyleSheet, + Text, + View +} from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; + +import FormattedDate from 'app/components/formatted_date'; +import FormattedText from 'app/components/formatted_text'; + +const style = StyleSheet.create({ + container: { + backgroundColor: '#fff', + marginBottom: 40, + padding: 15 + }, + channelName: { + marginLeft: 5, + fontSize: 15, + fontWeight: '600' + }, + channelNameContainer: { + flexDirection: 'row', + alignItems: 'center', + paddingBottom: 10 + }, + createdBy: { + flexDirection: 'row', + fontSize: 11, + opacity: 0.5, + marginTop: 5 + }, + detail: { + fontSize: 13 + }, + header: { + fontSize: 12, + opacity: 0.5, + marginBottom: 10 + }, + section: { + marginTop: 15 + }, + url: { + fontSize: 11, + opacity: 0.5, + marginBottom: 10 + } +}); + +function channelInfoHeader(props) { + const {createAt, creator, displayName, header, purpose} = props; + return ( + + + + {displayName} + + {purpose.length > 0 && + + + {purpose} + + } + {header.length > 0 && + + + {header} + + } + {creator && + + + + + } + + ); +} + +channelInfoHeader.propTypes = { + createAt: PropTypes.number.isRequired, + creator: PropTypes.string, + displayName: PropTypes.string.isRequired, + header: PropTypes.string, + purpose: PropTypes.string +}; + +export default channelInfoHeader; diff --git a/app/scenes/channel_info/channel_info_row.js b/app/scenes/channel_info/channel_info_row.js new file mode 100644 index 000000000..bbd3627d0 --- /dev/null +++ b/app/scenes/channel_info/channel_info_row.js @@ -0,0 +1,106 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React, {PropTypes} from 'react'; +import { + StyleSheet, + Switch, + Text, + TouchableHighlight, + View +} from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; + +import FormattedText from 'app/components/formatted_text'; + +const style = StyleSheet.create({ + container: { + backgroundColor: '#fff', + paddingHorizontal: 15, + flexDirection: 'row', + alignItems: 'center' + }, + detail: { + marginHorizontal: 15, + color: 'rgba(0, 0, 0, 0.5)', + fontSize: 15 + }, + label: { + flex: 1, + marginLeft: 15, + fontSize: 15, + paddingVertical: 15 + }, + leftIcon: { + width: 17 + } +}); + +function createTouchableComponent(children, action) { + return ( + + {children} + + ); +} + +function channelInfoRow(props) { + const {action, defaultMessage, detail, icon, iconColor, textColor, textId, togglable} = props; + + const RowComponent = ( + + + + {detail} + {togglable ? + : + + } + + ); + + if (togglable) { + return RowComponent; + } + + return createTouchableComponent(RowComponent, action); +} + +channelInfoRow.propTypes = { + action: PropTypes.func.isRequired, + defaultMessage: PropTypes.string.isRequired, + detail: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.bool + ]), + icon: PropTypes.string.isRequired, + iconColor: PropTypes.string, + textId: PropTypes.string.isRequired, + togglable: PropTypes.bool, + textColor: PropTypes.string +}; + +channelInfoRow.defaultProps = { + iconColor: 'rgba(0, 0, 0, 0.7)', + textColor: '#000', + togglable: false +}; + +export default channelInfoRow; diff --git a/app/scenes/channel_info/index.js b/app/scenes/channel_info/index.js new file mode 100644 index 000000000..1d5f9af26 --- /dev/null +++ b/app/scenes/channel_info/index.js @@ -0,0 +1,6 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ChannelInfoContainer from './channel_info_container'; + +export default ChannelInfoContainer; diff --git a/app/scenes/index.js b/app/scenes/index.js index 065861fbe..96fb53b6d 100644 --- a/app/scenes/index.js +++ b/app/scenes/index.js @@ -7,6 +7,7 @@ import Root from './root/root_container.js'; import Search from './search/search_container.js'; import SelectServer from './select_server/select_server_container.js'; import SelectTeam from './select_team/select_team_container.js'; +import ChannelInfo from './channel_info'; const scenes = { Root, @@ -14,7 +15,8 @@ const scenes = { Login, SelectTeam, ChannelView: Channel, // Special case the name for this one to avoid ambiguity - Search + Search, + ChannelInfo }; export function getComponentForScene(key) { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 3c57ecb90..0709b14bf 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -958,7 +958,7 @@ "backstage_sidebar.integrations.outgoing_webhooks": "Outgoing Webhooks", "calling_screen": "Calling", "center_panel.recent": "Click here to jump to recent messages. ", - "chanel_header.addMembers": "Add Members", + "channel_header.addMembers": "Add Members", "change_url.close": "Close", "change_url.endWithLetter": "Must end with a letter or number", "change_url.invalidUrl": "Invalid URL", @@ -1489,6 +1489,10 @@ "mobile.routes.login": "Login", "mobile.routes.postsList": "Posts List", "mobile.routes.selectTeam": "Select Team", + "mobile.routes.channelInfo": "Info", + "mobile.routes.channelInfo.createdBy": "Created by {creator} on ", + "mobile.routes.channelInfo.delete_channel": "Delete Channel", + "mobile.routes.channelInfo.favorite": "Favorite", "more_channels.close": "Close", "more_channels.create": "Create New Channel", "more_channels.createClick": "Click 'Create New Channel' to make a new one", diff --git a/service/selectors/entities/channels.js b/service/selectors/entities/channels.js index e8f194b65..05f27db29 100644 --- a/service/selectors/entities/channels.js +++ b/service/selectors/entities/channels.js @@ -9,6 +9,10 @@ function getAllChannels(state) { return state.entities.channels.channels; } +function getAllChannelStats(state) { + return state.entities.channels.stats; +} + export function getCurrentChannelId(state) { return state.entities.channels.currentId; } @@ -21,6 +25,14 @@ export const getCurrentChannel = createSelector( } ); +export const getCurrentChannelStats = createSelector( + getAllChannelStats, + getCurrentChannelId, + (allChannelStats, currentChannelId) => { + return allChannelStats[currentChannelId]; + } +); + export const getChannelsOnCurrentTeam = createSelector( getAllChannels, getCurrentTeamId, diff --git a/service/selectors/entities/general.js b/service/selectors/entities/general.js new file mode 100644 index 000000000..e1342d66e --- /dev/null +++ b/service/selectors/entities/general.js @@ -0,0 +1,6 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +export function getCurrentUrl(state) { + return state.entities.general.credentials.url; +} diff --git a/service/selectors/entities/teams.js b/service/selectors/entities/teams.js index c8a29d3d3..4812ac19c 100644 --- a/service/selectors/entities/teams.js +++ b/service/selectors/entities/teams.js @@ -3,6 +3,8 @@ import {createSelector} from 'reselect'; +import {getCurrentUrl} from './general'; + export function getCurrentTeamId(state) { return state.entities.teams.currentId; } @@ -19,3 +21,10 @@ export const getCurrentTeam = createSelector( } ); +export const getCurrentTeamUrl = createSelector( + getCurrentUrl, + getCurrentTeam, + (currentUrl, currentTeam) => { + return `${currentUrl}/${currentTeam.name}`; + } +);