RN-146 Join open teams (#590)

This commit is contained in:
enahum 2017-05-31 10:07:13 -04:00 committed by GitHub
parent 813d2cb334
commit 2309c91f88
9 changed files with 296 additions and 97 deletions

View file

@ -179,6 +179,7 @@ export default class ChannelDrawer extends PureComponent {
<ChannelDrawerTeams
closeChannelDrawer={this.closeChannelDrawer}
myTeamMembers={myTeamMembers}
navigator={navigator}
/>
</View>
);

View file

@ -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,

View file

@ -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 = (
<TouchableHighlight
style={styles.moreActionContainer}
onPress={() => preventDoubleTap(this.goToSelectTeam)}
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
>
<Text
style={styles.moreAction}
>
{'+'}
</Text>
</TouchableHighlight>
);
}
return (
<View style={styles.container}>
@ -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);

View file

@ -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)
};
}

View file

@ -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)
};

View file

@ -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 (
<Button
key={team.id}
onPress={() => this.onSelectTeam(team)}
style={GlobalStyles.buttonListItemText}
containerStyle={GlobalStyles.buttonListItem}
disabled={this.state.disableButtons}
>
{team.display_name}
<Icon
name='keyboard-arrow-right'
size={24}
color='#777'
/>
</Button>
);
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 (
<View style={GlobalStyles.container}>
<ScrollView
style={{flex: 1}}
contentContainerStyle={{alignItems: 'center', paddingVertical: 64}}
showsVerticalScrollIndicator={false}
<View style={styles.teamWrapper}>
<TouchableOpacity
onPress={() => preventDoubleTap(this.onSelectTeam, this, item)}
>
<Image
style={GlobalStyles.logo}
source={logo}
/>
<Text style={GlobalStyles.header}>
{this.props.config.SiteName}
</Text>
<FormattedText
style={GlobalStyles.subheader}
id='web.root.signup_info'
defaultMessage='All team communication in one place, searchable and accessible anywhere'
/>
<FormattedText
style={GlobalStyles.subheader}
id='mobile.select_team.choose'
defaultMessage='Your teams:'
/>
{content}
</ScrollView>
<View style={styles.teamContainer}>
<View style={styles.teamIconContainer}>
<Text style={styles.teamIcon}>
{item.display_name.substr(0, 2).toUpperCase()}
</Text>
</View>
<View style={styles.teamNameContainer}>
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={styles.teamName}
>
{item.display_name}
</Text>
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={styles.teamUrl}
>
{`${currentUrl}/${item.name}`}
</Text>
</View>
</View>
</TouchableOpacity>
</View>
);
};
render() {
const {teams, theme} = this.props;
const styles = getStyleSheet(theme);
if (this.state.joining) {
return <Loading/>;
}
return (
<View style={styles.container}>
<View style={styles.headingContainer}>
<View style={styles.headingWrapper}>
<FormattedText
id='mobile.select_team.join_open'
defaultMessage='Open teams you can join'
style={styles.heading}
/>
</View>
<View style={styles.line}/>
</View>
<FlatList
data={teams}
renderItem={this.renderItem}
keyExtractor={(item) => item.id}
viewabilityConfig={{
viewAreaCoveragePercentThreshold: 3,
waitForInteraction: false
}}
/>
</View>
);
}
}
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
}
});
});

View file

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

View file

@ -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;
}

View file

@ -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"