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}`;
+ }
+);