[NEW] Leave Teams (#3116)
* Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Minor tweak * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Lint Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
8373571a07
commit
62336c6d3a
File diff suppressed because it is too large
Load Diff
|
@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
|
|||
import { themes } from '../../constants/colors';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { withTheme } from '../../theme';
|
||||
import { ICON_SIZE } from './constants';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
icon: {
|
||||
|
@ -23,7 +24,7 @@ const ListIcon = React.memo(({
|
|||
<CustomIcon
|
||||
name={name}
|
||||
color={color ?? themes[theme].auxiliaryText}
|
||||
size={20}
|
||||
size={ICON_SIZE}
|
||||
/>
|
||||
</View>
|
||||
));
|
||||
|
|
|
@ -10,8 +10,9 @@ import sharedStyles from '../../views/Styles';
|
|||
import { withTheme } from '../../theme';
|
||||
import I18n from '../../i18n';
|
||||
import { Icon } from '.';
|
||||
import { BASE_HEIGHT, PADDING_HORIZONTAL } from './constants';
|
||||
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
|
||||
import { withDimensions } from '../../dimensions';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -34,7 +35,15 @@ const styles = StyleSheet.create({
|
|||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
textAlertContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
alertIcon: {
|
||||
paddingLeft: 4
|
||||
},
|
||||
title: {
|
||||
flexShrink: 1,
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
|
@ -50,7 +59,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
const Content = React.memo(({
|
||||
title, subtitle, disabled, testID, left, right, color, theme, translateTitle, translateSubtitle, showActionIndicator, fontScale
|
||||
title, subtitle, disabled, testID, left, right, color, theme, translateTitle, translateSubtitle, showActionIndicator, fontScale, alert
|
||||
}) => (
|
||||
<View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]} testID={testID}>
|
||||
{left
|
||||
|
@ -61,7 +70,12 @@ const Content = React.memo(({
|
|||
)
|
||||
: null}
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text>
|
||||
<View style={styles.textAlertContainer}>
|
||||
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text>
|
||||
{alert ? (
|
||||
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
|
||||
) : null}
|
||||
</View>
|
||||
{subtitle
|
||||
? <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{translateSubtitle ? I18n.t(subtitle) : subtitle}</Text>
|
||||
: null
|
||||
|
@ -123,7 +137,8 @@ Content.propTypes = {
|
|||
translateTitle: PropTypes.bool,
|
||||
translateSubtitle: PropTypes.bool,
|
||||
showActionIndicator: PropTypes.bool,
|
||||
fontScale: PropTypes.number
|
||||
fontScale: PropTypes.number,
|
||||
alert: PropTypes.bool
|
||||
};
|
||||
|
||||
Content.defaultProps = {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export const PADDING_HORIZONTAL = 12;
|
||||
export const BASE_HEIGHT = 46;
|
||||
export const ICON_SIZE = 20;
|
||||
|
|
|
@ -290,6 +290,7 @@
|
|||
"last_message": "last message",
|
||||
"Leave_channel": "Leave channel",
|
||||
"leaving_room": "leaving room",
|
||||
"Leave": "Leave",
|
||||
"leave": "leave",
|
||||
"Legal": "Legal",
|
||||
"Light": "Light",
|
||||
|
@ -726,5 +727,13 @@
|
|||
"Auto-join": "Auto-join",
|
||||
"Delete_Team_Room_Warning": "Woud you like to remove this channel from the team? The channel will be moved back to the workspace",
|
||||
"Confirmation": "Confirmation",
|
||||
"invalid-room": "Invalid room"
|
||||
"invalid-room": "Invalid room",
|
||||
"You_are_leaving_the_team": "You are leaving the team '{{team}}'",
|
||||
"Leave_Team": "Leave Team",
|
||||
"Select_Team_Channels": "Select the Team's channels you would like to leave.",
|
||||
"Cannot_leave": "Cannot leave",
|
||||
"Last_owner_team_room": "You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.",
|
||||
"last-owner-can-not-be-removed": "Last owner cannot be removed",
|
||||
"leaving_team": "leaving team",
|
||||
"member-does-not-exist": "Member does not exist"
|
||||
}
|
||||
|
|
|
@ -769,6 +769,10 @@ const RocketChat = {
|
|||
// RC 3.13.0
|
||||
return this.post('teams.removeRoom', { roomId, teamId });
|
||||
},
|
||||
leaveTeam({ teamName, rooms }) {
|
||||
// RC 3.13.0
|
||||
return this.post('teams.leave', { teamName, rooms });
|
||||
},
|
||||
updateTeamRoom({ roomId, isDefault }) {
|
||||
// RC 3.13.0
|
||||
return this.post('teams.updateRoom', { roomId, isDefault });
|
||||
|
|
|
@ -73,6 +73,7 @@ import CreateDiscussionView from '../views/CreateDiscussionView';
|
|||
import QueueListView from '../ee/omnichannel/views/QueueListView';
|
||||
import AddChannelTeamView from '../views/AddChannelTeamView';
|
||||
import AddExistingChannelView from '../views/AddExistingChannelView';
|
||||
import SelectListView from '../views/SelectListView';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator();
|
||||
|
@ -93,6 +94,11 @@ const ChatsStackNavigator = () => {
|
|||
component={RoomActionsView}
|
||||
options={RoomActionsView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='SelectListView'
|
||||
component={SelectListView}
|
||||
options={SelectListView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='RoomInfoView'
|
||||
component={RoomInfoView}
|
||||
|
|
|
@ -63,6 +63,7 @@ import ShareView from '../../views/ShareView';
|
|||
import QueueListView from '../../ee/omnichannel/views/QueueListView';
|
||||
import AddChannelTeamView from '../../views/AddChannelTeamView';
|
||||
import AddExistingChannelView from '../../views/AddExistingChannelView';
|
||||
import SelectListView from '../../views/SelectListView';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator();
|
||||
|
@ -119,6 +120,11 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
|
|||
component={RoomInfoView}
|
||||
options={RoomInfoView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='SelectListView'
|
||||
component={SelectListView}
|
||||
options={SelectListView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='RoomInfoEditView'
|
||||
component={RoomInfoEditView}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View, Text, Alert, Share, Switch
|
||||
View, Text, Share, Switch
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { compareServerVersion, methods } from '../../lib/utils';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
|
@ -53,6 +54,7 @@ class RoomActionsView extends React.Component {
|
|||
theme: PropTypes.string,
|
||||
fontScale: PropTypes.number,
|
||||
serverVersion: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
addUserToJoinedRoomPermission: PropTypes.array,
|
||||
addUserToAnyCRoomPermission: PropTypes.array,
|
||||
addUserToAnyPRoomPermission: PropTypes.array,
|
||||
|
@ -395,21 +397,67 @@ class RoomActionsView extends React.Component {
|
|||
const { room } = this.state;
|
||||
const { leaveRoom } = this.props;
|
||||
|
||||
Alert.alert(
|
||||
I18n.t('Are_you_sure_question_mark'),
|
||||
I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }),
|
||||
[
|
||||
{
|
||||
text: I18n.t('Cancel'),
|
||||
style: 'cancel'
|
||||
},
|
||||
{
|
||||
text: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||
style: 'destructive',
|
||||
onPress: () => leaveRoom(room.rid, room.t)
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }),
|
||||
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||
onPress: () => leaveRoom(room.rid, room.t)
|
||||
});
|
||||
}
|
||||
|
||||
handleLeaveTeam = async(selected) => {
|
||||
try {
|
||||
const { room } = this.state;
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
const result = await RocketChat.leaveTeam({ teamName: room.name, ...(selected && { rooms: selected }) });
|
||||
|
||||
if (result.success) {
|
||||
if (isMasterDetail) {
|
||||
navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
navigation.navigate('RoomsListView');
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
showErrorAlert(
|
||||
e.data.error
|
||||
? I18n.t(e.data.error)
|
||||
: I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_team') }),
|
||||
I18n.t('Cannot_leave')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
leaveTeam = async() => {
|
||||
const { room } = this.state;
|
||||
const { navigation } = this.props;
|
||||
|
||||
try {
|
||||
const db = database.active;
|
||||
const subCollection = db.get('subscriptions');
|
||||
const teamChannels = await subCollection.query(
|
||||
Q.where('team_id', room.teamId),
|
||||
Q.where('team_main', null)
|
||||
);
|
||||
|
||||
if (teamChannels.length) {
|
||||
navigation.navigate('SelectListView', {
|
||||
title: 'Leave_Team',
|
||||
data: teamChannels,
|
||||
infoText: 'Select_Team_Channels',
|
||||
nextAction: data => this.handleLeaveTeam(data),
|
||||
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_leave'))
|
||||
});
|
||||
} else {
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
|
||||
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||
onPress: () => this.handleLeaveTeam()
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
||||
renderRoomInfo = () => {
|
||||
|
@ -568,9 +616,9 @@ class RoomActionsView extends React.Component {
|
|||
<List.Section>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Leave_channel'
|
||||
title='Leave'
|
||||
onPress={() => this.onPressTouchable({
|
||||
event: this.leaveChannel
|
||||
event: room.teamMain ? this.leaveTeam : this.leaveChannel
|
||||
})}
|
||||
testID='room-actions-leave-channel'
|
||||
left={() => <List.Icon name='logout' color={themes[theme].dangerColor} />}
|
||||
|
@ -880,6 +928,7 @@ const mapStateToProps = state => ({
|
|||
jitsiEnabled: state.settings.Jitsi_Enabled || false,
|
||||
encryptionEnabled: state.encryption.enabled,
|
||||
serverVersion: state.server.version,
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'],
|
||||
addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'],
|
||||
addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'],
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View, StyleSheet, FlatList, Text
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import * as List from '../containers/List';
|
||||
import sharedStyles from './Styles';
|
||||
import I18n from '../i18n';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import { animateNextTransition } from '../utils/layoutAnimation';
|
||||
import Loading from '../containers/Loading';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonText: {
|
||||
fontSize: 16,
|
||||
margin: 16,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
||||
|
||||
class SelectListView extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const data = props.route?.params?.data;
|
||||
this.title = props.route?.params?.title;
|
||||
this.infoText = props.route?.params?.infoText;
|
||||
this.nextAction = props.route?.params?.nextAction;
|
||||
this.showAlert = props.route?.params?.showAlert;
|
||||
this.state = {
|
||||
data,
|
||||
selected: [],
|
||||
loading: false
|
||||
};
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
const { selected } = this.state;
|
||||
|
||||
const options = {
|
||||
headerTitle: I18n.t(this.title)
|
||||
};
|
||||
|
||||
if (isMasterDetail) {
|
||||
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
|
||||
}
|
||||
|
||||
options.headerRight = () => (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item title={I18n.t('Next')} onPress={() => this.nextAction(selected)} testID='select-list-view-submit' />
|
||||
</HeaderButton.Container>
|
||||
);
|
||||
|
||||
navigation.setOptions(options);
|
||||
}
|
||||
|
||||
renderInfoText = () => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
<Text style={[styles.buttonText, { color: themes[theme].bodyText }]}>{I18n.t(this.infoText)}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
isChecked = (rid) => {
|
||||
const { selected } = this.state;
|
||||
return selected.includes(rid);
|
||||
}
|
||||
|
||||
toggleItem = (rid) => {
|
||||
const { selected } = this.state;
|
||||
|
||||
animateNextTransition();
|
||||
if (!this.isChecked(rid)) {
|
||||
this.setState({ selected: [...selected, rid] }, () => this.setHeader());
|
||||
} else {
|
||||
const filterSelected = selected.filter(el => el !== rid);
|
||||
this.setState({ selected: filterSelected }, () => this.setHeader());
|
||||
}
|
||||
}
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const { theme } = this.props;
|
||||
const alert = item.roles.length;
|
||||
|
||||
const icon = item.t === 'p' ? 'channel-private' : 'channel-public';
|
||||
const checked = this.isChecked(item.rid, item.roles) ? 'check' : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title={item.name}
|
||||
translateTitle={false}
|
||||
testID={`select-list-view-item-${ item.name }`}
|
||||
onPress={() => (alert ? this.showAlert() : this.toggleItem(item.rid))}
|
||||
alert={alert}
|
||||
left={() => <List.Icon name={icon} color={themes[theme].controlText} />}
|
||||
right={() => (checked ? <List.Icon name={checked} color={themes[theme].actionTintColor} /> : null)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, data } = this.state;
|
||||
const { theme } = this.props;
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='select-list-view'>
|
||||
<StatusBar />
|
||||
<FlatList
|
||||
data={data}
|
||||
extraData={this.state}
|
||||
keyExtractor={item => item.rid}
|
||||
renderItem={this.renderItem}
|
||||
ListHeaderComponent={this.renderInfoText}
|
||||
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
<Loading visible={loading} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(SelectListView));
|
|
@ -23,6 +23,20 @@ stories.add('title and subtitle', () => (
|
|||
</List.Container>
|
||||
));
|
||||
|
||||
stories.add('alert', () => (
|
||||
<List.Container>
|
||||
<List.Separator />
|
||||
<List.Item title='Chats' alert />
|
||||
<List.Separator />
|
||||
<List.Item title={longText} translateTitle={false} translateSubtitle={false} alert />
|
||||
<List.Separator />
|
||||
<List.Item title='Chats' right={() => <List.Icon name='emoji' />} alert />
|
||||
<List.Separator />
|
||||
<List.Item title={longText} translateTitle={false} translateSubtitle={false} right={() => <List.Icon name='emoji' />} alert />
|
||||
<List.Separator />
|
||||
</List.Container>
|
||||
));
|
||||
|
||||
stories.add('pressable', () => (
|
||||
<List.Container>
|
||||
<List.Separator />
|
||||
|
|
Loading…
Reference in New Issue