import React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { StackNavigationProp } from '@react-navigation/stack'; import { RouteProp } from '@react-navigation/native'; import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } from 'react-native'; import { dequal } from 'dequal'; import * as List from '../containers/List'; import TextInput from '../presentation/TextInput'; import Loading from '../containers/Loading'; import { createChannelRequest as createChannelRequestAction } from '../actions/createChannel'; import { removeUser as removeUserAction } from '../actions/selectedUsers'; import KeyboardView from '../presentation/KeyboardView'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import I18n from '../i18n'; import UserItem from '../presentation/UserItem'; import * as HeaderButton from '../containers/HeaderButton'; import StatusBar from '../containers/StatusBar'; import { SWITCH_TRACK_COLOR, themes } from '../constants/colors'; import { withTheme } from '../theme'; import { Review } from '../utils/review'; import { getUserSelector } from '../selectors/login'; import { events, logEvent } from '../utils/log'; import SafeAreaView from '../containers/SafeAreaView'; import RocketChat from '../lib/rocketchat'; import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { flex: 1 }, list: { width: '100%' }, input: { height: 54, paddingHorizontal: 18, fontSize: 17, ...sharedStyles.textRegular }, switchContainer: { height: 54, alignItems: 'center', justifyContent: 'space-between', flexDirection: 'row', paddingHorizontal: 18 }, label: { fontSize: 17, ...sharedStyles.textMedium }, invitedHeader: { marginTop: 18, marginHorizontal: 15, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, invitedTitle: { fontSize: 18, ...sharedStyles.textSemibold, lineHeight: 41 }, invitedCount: { fontSize: 14, ...sharedStyles.textRegular } }); interface IOtherUser { _id: string; name: string; fname: string; } interface ICreateFunction extends Omit { name: string; users: string[]; teamId: string; } interface ICreateChannelViewState { channelName: string; type: boolean; readOnly: boolean; encrypted: boolean; broadcast: boolean; isTeam: boolean; permissions: boolean[]; } interface ICreateChannelViewProps { navigation: StackNavigationProp; route: RouteProp<{ CreateChannelView: { isTeam: boolean; teamId: string } }, 'CreateChannelView'>; baseUrl: string; create: (data: ICreateFunction) => void; removeUser: (user: IOtherUser) => void; error: object; failure: boolean; isFetching: boolean; encryptionEnabled: boolean; users: IOtherUser[]; user: { id: string; token: string; roles: string[]; }; theme: string; teamId: string; createPublicChannelPermission: string[]; createPrivateChannelPermission: string[]; } interface ISwitch extends SwitchProps { id: string; label: string; } class CreateChannelView extends React.Component { private teamId: string; constructor(props: ICreateChannelViewProps) { super(props); const { route } = this.props; const isTeam = route?.params?.isTeam || false; this.teamId = route?.params?.teamId; this.state = { channelName: '', type: true, readOnly: false, encrypted: false, broadcast: false, isTeam, permissions: [] }; this.setHeader(); } componentDidMount() { this.handleHasPermission(); } shouldComponentUpdate(nextProps: ICreateChannelViewProps, nextState: ICreateChannelViewState) { const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state; const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } = this.props; if (nextProps.theme !== theme) { return true; } if (nextState.channelName !== channelName) { return true; } if (nextState.type !== type) { return true; } if (nextState.readOnly !== readOnly) { return true; } if (nextState.encrypted !== encrypted) { return true; } if (nextState.broadcast !== broadcast) { return true; } if (nextState.permissions !== permissions) { return true; } if (nextProps.isFetching !== isFetching) { return true; } if (nextProps.encryptionEnabled !== encryptionEnabled) { return true; } if (!dequal(nextProps.createPublicChannelPermission, createPublicChannelPermission)) { return true; } if (!dequal(nextProps.createPrivateChannelPermission, createPrivateChannelPermission)) { return true; } if (!dequal(nextProps.users, users)) { return true; } return false; } componentDidUpdate(prevProps: ICreateChannelViewProps) { const { createPublicChannelPermission, createPrivateChannelPermission } = this.props; if ( !dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) || !dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) ) { this.handleHasPermission(); } } setHeader = () => { const { navigation } = this.props; const { isTeam } = this.state; navigation.setOptions({ title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel') }); }; toggleRightButton = (channelName: string) => { const { navigation } = this.props; navigation.setOptions({ headerRight: () => channelName.trim().length > 0 && ( ) }); }; onChangeText = (channelName: string) => { this.toggleRightButton(channelName); this.setState({ channelName }); }; submit = () => { const { channelName, type, readOnly, broadcast, encrypted, isTeam } = this.state; const { users: usersProps, isFetching, create } = this.props; if (!channelName.trim() || isFetching) { return; } // transform users object into array of usernames const users = usersProps.map(user => user.name); // create channel or team create({ name: channelName, users, type, readOnly, broadcast, encrypted, isTeam, teamId: this.teamId }); Review.pushPositiveEvent(); }; removeUser = (user: IOtherUser) => { logEvent(events.CR_REMOVE_USER); const { removeUser } = this.props; removeUser(user); }; renderSwitch = ({ id, value, label, onValueChange, disabled = false }: ISwitch) => { const { theme } = this.props; return ( {I18n.t(label)} ); }; handleHasPermission = async () => { const { createPublicChannelPermission, createPrivateChannelPermission } = this.props; const permissions = [createPublicChannelPermission, createPrivateChannelPermission]; const permissionsToCreate = await RocketChat.hasPermission(permissions); this.setState({ permissions: permissionsToCreate }); }; renderType() { const { type, isTeam, permissions } = this.state; const isDisabled = permissions.filter(r => r === true).length <= 1; return this.renderSwitch({ id: 'type', value: permissions[1] ? type : false, disabled: isDisabled, label: isTeam ? 'Private_Team' : 'Private_Channel', onValueChange: (value: boolean) => { logEvent(events.CR_TOGGLE_TYPE); // If we set the channel as public, encrypted status should be false this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted })); } }); } renderReadOnly() { const { readOnly, broadcast, isTeam } = this.state; return this.renderSwitch({ id: 'readonly', value: readOnly, label: isTeam ? 'Read_Only_Team' : 'Read_Only_Channel', onValueChange: value => { logEvent(events.CR_TOGGLE_READ_ONLY); this.setState({ readOnly: value }); }, disabled: broadcast }); } renderEncrypted() { const { type, encrypted } = this.state; const { encryptionEnabled } = this.props; if (!encryptionEnabled) { return null; } return this.renderSwitch({ id: 'encrypted', value: encrypted, label: 'Encrypted', onValueChange: value => { logEvent(events.CR_TOGGLE_ENCRYPTED); this.setState({ encrypted: value }); }, disabled: !type }); } renderBroadcast() { const { broadcast, readOnly, isTeam } = this.state; return this.renderSwitch({ id: 'broadcast', value: broadcast, label: isTeam ? 'Broadcast_Team' : 'Broadcast_Channel', onValueChange: value => { logEvent(events.CR_TOGGLE_BROADCAST); this.setState({ broadcast: value, readOnly: value ? true : readOnly }); } }); } renderItem = ({ item }: { item: IOtherUser }) => { const { theme } = this.props; return ( this.removeUser(item)} testID={`create-channel-view-item-${item.name}`} icon='check' theme={theme} /> ); }; renderInvitedList = () => { const { users, theme } = this.props; return ( item._id} style={[ styles.list, sharedStyles.separatorVertical, { backgroundColor: themes[theme].focusedBackground, borderColor: themes[theme].separatorColor } ]} renderItem={this.renderItem} ItemSeparatorComponent={List.Separator} keyboardShouldPersistTaps='always' /> ); }; render() { const { channelName, isTeam } = this.state; const { users, isFetching, theme } = this.props; const userCount = users.length; return ( {this.renderType()} {this.renderReadOnly()} {this.renderEncrypted()} {this.renderBroadcast()} {I18n.t('Invite')} {userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })} {this.renderInvitedList()} ); } } const mapStateToProps = (state: any) => ({ baseUrl: state.server.server, isFetching: state.createChannel.isFetching, encryptionEnabled: state.encryption.enabled, users: state.selectedUsers.users, user: getUserSelector(state), createPublicChannelPermission: state.permissions['create-c'], createPrivateChannelPermission: state.permissions['create-p'] }); const mapDispatchToProps = (dispatch: Dispatch) => ({ create: (data: ICreateFunction) => dispatch(createChannelRequestAction(data)), removeUser: (user: IOtherUser) => dispatch(removeUserAction(user)) }); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(CreateChannelView));