diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index 71efe029e..3138eb2dc 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -179,6 +179,7 @@ export default class ChannelDrawer extends PureComponent { ); diff --git a/app/components/channel_drawer_list/channel_drawer_list.js b/app/components/channel_drawer_list/channel_drawer_list.js index 30c6a9aec..e8108231a 100644 --- a/app/components/channel_drawer_list/channel_drawer_list.js +++ b/app/components/channel_drawer_list/channel_drawer_list.js @@ -567,9 +567,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, settingsContainer: { alignItems: 'center', - height: 44, justifyContent: 'center', - width: 50 + width: 50, + ...Platform.select({ + android: { + height: 46 + }, + ios: { + height: 44 + } + }) }, settings: { color: theme.sidebarHeaderTextColor, diff --git a/app/components/channel_drawer_teams/channel_drawer_teams.js b/app/components/channel_drawer_teams/channel_drawer_teams.js index 3513c1dbd..dbc94fa84 100644 --- a/app/components/channel_drawer_teams/channel_drawer_teams.js +++ b/app/components/channel_drawer_teams/channel_drawer_teams.js @@ -12,33 +12,56 @@ import { TouchableHighlight, View } from 'react-native'; +import {injectIntl, intlShape} from 'react-intl'; import IonIcon from 'react-native-vector-icons/Ionicons'; +import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import Badge from 'app/components/badge'; import FormattedText from 'app/components/formatted_text'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -export default class ChannelDrawerTeams extends PureComponent { +class ChannelDrawerTeams extends PureComponent { static propTypes = { actions: PropTypes.shape({ - handleTeamChange: PropTypes.func.isRequired + getTeams: PropTypes.func.isRequired, + handleTeamChange: PropTypes.func.isRequired, + markChannelAsRead: PropTypes.func.isRequired }).isRequired, canCreateTeams: PropTypes.bool.isRequired, - canJoinTeams: PropTypes.bool.isRequired, closeChannelDrawer: PropTypes.func.isRequired, + currentChannelId: PropTypes.string, currentTeamId: PropTypes.string.isRequired, currentUrl: PropTypes.string.isRequired, + intl: intlShape.isRequired, + joinableTeams: PropTypes.object.isRequired, myTeamMembers: PropTypes.object.isRequired, + navigator: PropTypes.object.isRequired, teams: PropTypes.array.isRequired, theme: PropTypes.object.isRequired }; + constructor(props) { + super(props); + + MaterialIcon.getImageSource('close', 20, props.theme.sidebarHeaderTextColor). + then((source) => { + this.closeButton = source; + }); + } + + componentWillMount() { + this.props.actions.getTeams(); + } + selectTeam = (team) => { - const {actions, closeChannelDrawer, currentTeamId} = this.props; + const {actions, closeChannelDrawer, currentChannelId, currentTeamId} = this.props; if (team.id === currentTeamId) { closeChannelDrawer(); } else { + if (currentChannelId) { + actions.markChannelAsRead(currentChannelId); + } closeChannelDrawer(); InteractionManager.runAfterInteractions(() => { actions.handleTeamChange(team); @@ -46,6 +69,34 @@ export default class ChannelDrawerTeams extends PureComponent { } }; + goToSelectTeam = () => { + const {currentUrl, intl, navigator, theme} = this.props; + + navigator.showModal({ + screen: 'SelectTeam', + title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}), + animationType: 'slide-up', + animated: true, + backButtonTitle: '', + navigatorStyle: { + navBarTextColor: theme.sidebarHeaderTextColor, + navBarBackgroundColor: theme.sidebarHeaderBg, + navBarButtonColor: theme.sidebarHeaderTextColor, + screenBackgroundColor: theme.centerChannelBg + }, + navigatorButtons: { + leftButtons: [{ + id: 'close-teams', + icon: this.closeButton + }] + }, + passProps: { + currentUrl, + theme + } + }); + }; + renderItem = ({item}) => { const {currentTeamId, currentUrl, myTeamMembers, theme} = this.props; const styles = getStyleSheet(theme); @@ -121,10 +172,25 @@ export default class ChannelDrawerTeams extends PureComponent { }; render() { - const {teams, theme} = this.props; + const {joinableTeams, teams, theme} = this.props; const styles = getStyleSheet(theme); let moreAction; + if (Object.keys(joinableTeams).length) { + moreAction = ( + preventDoubleTap(this.goToSelectTeam)} + underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)} + > + + {'+'} + + + ); + } return ( @@ -188,6 +254,23 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { fontSize: 17, fontWeight: 'normal' }, + moreActionContainer: { + alignItems: 'center', + justifyContent: 'center', + width: 50, + ...Platform.select({ + android: { + height: 46 + }, + ios: { + height: 44 + } + }) + }, + moreAction: { + color: theme.sidebarHeaderTextColor, + fontSize: 30 + }, teamWrapper: { marginTop: 20 }, @@ -250,3 +333,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { } }); }); + +export default injectIntl(ChannelDrawerTeams); diff --git a/app/components/channel_drawer_teams/index.js b/app/components/channel_drawer_teams/index.js index 051576fa4..2a93f156a 100644 --- a/app/components/channel_drawer_teams/index.js +++ b/app/components/channel_drawer_teams/index.js @@ -4,8 +4,11 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import {markChannelAsRead} from 'mattermost-redux/actions/channels'; +import {getTeams} from 'mattermost-redux/actions/teams'; +import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general'; -import {getCurrentTeamId, getMyTeams} from 'mattermost-redux/selectors/entities/teams'; +import {getCurrentTeamId, getJoinableTeams, getMyTeams} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; import {handleTeamChange} from 'app/actions/views/select_team'; @@ -27,7 +30,8 @@ function mapStateToProps(state, ownProps) { return { canCreateTeams: false, - canJoinTeams: false, + joinableTeams: getJoinableTeams(state), + currentChannelId: getCurrentChannelId(state), currentTeamId: getCurrentTeamId(state), currentUrl: removeProtocol(getCurrentUrl(state)), teams: getMyTeams(state).sort(sortTeams.bind(null, (user.locale))), @@ -39,7 +43,9 @@ function mapStateToProps(state, ownProps) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - handleTeamChange + getTeams, + handleTeamChange, + markChannelAsRead }, dispatch) }; } diff --git a/app/screens/select_team/index.js b/app/screens/select_team/index.js index abba16342..d93a9cc82 100644 --- a/app/screens/select_team/index.js +++ b/app/screens/select_team/index.js @@ -7,36 +7,34 @@ import {connect} from 'react-redux'; import {handleTeamChange} from 'app/actions/views/select_team'; import {markChannelAsRead} from 'mattermost-redux/actions/channels'; +import {getTeams, joinTeam} from 'mattermost-redux/actions/teams'; import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; -import {getCurrentTeam, getTeams, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams'; +import {getJoinableTeams} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; import SelectTeam from './select_team.js'; function mapStateToProps(state, ownProps) { - const allTeams = Object.values(getTeams(state)); - const myMembers = getTeamMemberships(state); const user = getCurrentUser(state); - const myTeams = allTeams.filter((team) => myMembers.hasOwnProperty(team.id)); function sortTeams(a, b) { return a.display_name.localeCompare(b.display_name, user.locale, {numeric: true}); } return { - ...ownProps, - config: state.entities.general.config, - teamsRequest: state.requests.teams.allTeams, - teams: myTeams.sort(sortTeams), - currentTeam: getCurrentTeam(state), - currentChannelId: getCurrentChannelId(state) + teamsRequest: state.requests.teams.getTeams, + teams: Object.values(getJoinableTeams(state)).sort(sortTeams), + currentChannelId: getCurrentChannelId(state), + ...ownProps }; } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ + getTeams, handleTeamChange, + joinTeam, markChannelAsRead }, dispatch) }; diff --git a/app/screens/select_team/select_team.js b/app/screens/select_team/select_team.js index 3de46f462..c1204d7cc 100644 --- a/app/screens/select_team/select_team.js +++ b/app/screens/select_team/select_team.js @@ -4,107 +4,209 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { - Image, + FlatList, InteractionManager, - ScrollView, + StyleSheet, Text, + TouchableOpacity, View } from 'react-native'; -import Button from 'react-native-button'; -import Icon from 'react-native-vector-icons/MaterialIcons'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; -import {GlobalStyles} from 'app/styles'; import FormattedText from 'app/components/formatted_text'; - -import logo from 'assets/images/logo.png'; +import Loading from 'app/components/loading'; +import {preventDoubleTap} from 'app/utils/tap'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; export default class SelectTeam extends PureComponent { static propTypes = { - config: PropTypes.object.isRequired, + actions: PropTypes.shape({ + getTeams: PropTypes.func.isRequired, + handleTeamChange: PropTypes.func.isRequired, + joinTeam: PropTypes.func.isRequired, + markChannelAsRead: PropTypes.func.isRequired + }).isRequired, currentChannelId: PropTypes.string, + currentUrl: PropTypes.string.isRequired, navigator: PropTypes.object, teams: PropTypes.array.isRequired, - actions: PropTypes.shape({ - handleTeamChange: PropTypes.func.isRequired, - markChannelAsRead: PropTypes.func.isRequired - }).isRequired + theme: PropTypes.object }; - state = { - disableButtons: false + constructor(props) { + super(props); + props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); + + this.state = { + joining: false + }; + } + + close = () => { + this.props.navigator.dismissModal({ + animationType: 'slide-down' + }); }; - onSelectTeam = async (team) => { - if (!this.state.disableButtons) { - this.setState({disableButtons: true}); - const { - currentChannelId, - handleTeamChange, - markChannelAsRead - } = this.props.actions; - - if (currentChannelId) { - markChannelAsRead(currentChannelId); + onNavigatorEvent = (event) => { + if (event.type === 'NavBarButtonPress') { + if (event.id === 'close-teams') { + this.close(); } - handleTeamChange(team); - EventEmitter.emit('close_channel_drawer'); - InteractionManager.runAfterInteractions(() => { - this.props.navigator.dismissModal({ - animationType: 'slide-down' - }); - }); } }; - render() { - const content = this.props.teams.map((team) => { - return ( - - ); + onSelectTeam = async (team) => { + this.setState({joining: true}); + const {currentChannelId} = this.props; + const { + joinTeam, + handleTeamChange, + markChannelAsRead + } = this.props.actions; + + if (currentChannelId) { + markChannelAsRead(currentChannelId); + } + await joinTeam(team.invite_id, team.id); + handleTeamChange(team); + EventEmitter.emit('close_channel_drawer'); + InteractionManager.runAfterInteractions(() => { + this.close(); }); + }; + + renderItem = ({item}) => { + const {currentUrl, theme} = this.props; + const styles = getStyleSheet(theme); return ( - - + preventDoubleTap(this.onSelectTeam, this, item)} > - - - {this.props.config.SiteName} - - - - {content} - + + + + {item.display_name.substr(0, 2).toUpperCase()} + + + + + {item.display_name} + + + {`${currentUrl}/${item.name}`} + + + + + + ); + }; + + render() { + const {teams, theme} = this.props; + const styles = getStyleSheet(theme); + + if (this.state.joining) { + return ; + } + + return ( + + + + + + + + item.id} + viewabilityConfig={{ + viewAreaCoveragePercentThreshold: 3, + waitForInteraction: false + }} + /> ); } } + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return StyleSheet.create({ + container: { + backgroundColor: theme.centerChannelBg, + flex: 1 + }, + headingContainer: { + alignItems: 'center', + flexDirection: 'row', + marginHorizontal: 16, + marginTop: 20 + }, + headingWrapper: { + marginRight: 15 + }, + heading: { + color: changeOpacity(theme.centerChannelColor, 0.5), + fontSize: 13 + }, + line: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.2), + width: '100%', + height: StyleSheet.hairlineWidth + }, + teamWrapper: { + marginTop: 20 + }, + teamContainer: { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + marginHorizontal: 16 + }, + teamIconContainer: { + alignItems: 'center', + backgroundColor: theme.buttonBg, + borderRadius: 2, + height: 40, + justifyContent: 'center', + width: 40 + }, + teamIcon: { + color: theme.buttonColor, + fontFamily: 'OpenSans', + fontSize: 18, + fontWeight: '600' + }, + teamNameContainer: { + flex: 1, + flexDirection: 'column', + marginLeft: 10 + }, + teamName: { + color: theme.centerChannelColor, + fontSize: 18 + }, + teamUrl: { + color: changeOpacity(theme.centerChannelColor, 0.5), + fontSize: 12 + } + }); +}); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index cbca4aa22..733667e35 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1755,7 +1755,7 @@ "mobile.routes.thread_dm": "Direct Message Thread", "mobile.routes.user_profile": "Profile", "mobile.routes.user_profile.send_message": "Send Message", - "mobile.select_team.choose": "Your teams:", + "mobile.select_team.join_open": "Open teams you can join", "mobile.server_ping_failed": "Cannot connect to the server. Please check your server URL and internet connection.", "mobile.server_url.invalid_format": "URL must start with http:// or https://", "mobile.session_expired": "Session Expired: Please log in to continue receiving notifications.", diff --git a/ios/Mattermost/AppDelegate.m b/ios/Mattermost/AppDelegate.m index d19c371ff..bca1affdc 100644 --- a/ios/Mattermost/AppDelegate.m +++ b/ios/Mattermost/AppDelegate.m @@ -38,7 +38,7 @@ self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.backgroundColor = [UIColor whiteColor]; - [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation]; + [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions]; return YES; } diff --git a/yarn.lock b/yarn.lock index 8dacf229e..c933480d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3645,7 +3645,7 @@ makeerror@1.0.x: mattermost-redux@mattermost/mattermost-redux#master: version "0.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/17b85854e5fda76d4e91b1e28d1e26b4ad57394d" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/67a40ea6c89a5065b751f434094183956817b1d3" dependencies: deep-equal "1.0.1" harmony-reflect "1.5.1"