[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:
Gerzon Z 2021-05-25 14:04:05 -04:00 committed by GitHub
parent 8373571a07
commit 62336c6d3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1974 additions and 743 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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 = {

View File

@ -1,2 +1,3 @@
export const PADDING_HORIZONTAL = 12;
export const BASE_HEIGHT = 46;
export const ICON_SIZE = 20;

View File

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

View File

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

View File

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

View File

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

View File

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

146
app/views/SelectListView.js Normal file
View File

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

View File

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